Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
xmp_sidecar.cc
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2014, 2016 johannes hanika.
4 Copyright (C) 2010-2012 Henrik Andersson.
5 Copyright (C) 2010, 2012-2015 Pascal de Bruijn.
6 Copyright (C) 2010-2018 Tobias Ellinghaus.
7 Copyright (C) 2011-2012, 2018 Edouard Gomez.
8 Copyright (C) 2011 Jochen Schroeder.
9 Copyright (C) 2011 Johanes Schneider.
10 Copyright (C) 2011-2012 Jérémy Rosen.
11 Copyright (C) 2011 Kanstantsin Shautsou.
12 Copyright (C) 2011 Robert Bieber.
13 Copyright (C) 2011, 2013 Simon Spannagel.
14 Copyright (C) 2011 Uli Scholler.
15 Copyright (C) 2012-2013, 2020 Aldric Renaudin.
16 Copyright (C) 2012 Alexander Clausen.
17 Copyright (C) 2012 James C. McPherson.
18 Copyright (C) 2012-2013 José Carlos García Sogo.
19 Copyright (C) 2012 Richard Wonka.
20 Copyright (C) 2012 Sergey Pavlov.
21 Copyright (C) 2012-2014, 2016 Ulrich Pegelow.
22 Copyright (C) 2012 Wolfgang Kühnel.
23 Copyright (C) 2013 Dennis Gnad.
24 Copyright (C) 2013 Jacek Naglak.
25 Copyright (C) 2013-2015, 2018-2022 Pascal Obry.
26 Copyright (C) 2013-2017 Roman Lebedev.
27 Copyright (C) 2014-2015 Jan Niklas Fingerle.
28 Copyright (C) 2014 Michael Neumann.
29 Copyright (C) 2014-2016 Pedro Côrte-Real.
30 Copyright (C) 2015 Jake Probst.
31 Copyright (C) 2015, 2018 Marcel Müller.
32 Copyright (C) 2015 Tom Vijlbrief.
33 Copyright (C) 2015 Torsten Bronger.
34 Copyright (C) 2016 Matthieu Volat.
35 Copyright (C) 2017, 2020 David-Tillmann Schaefer.
36 Copyright (C) 2017, 2019, 2021 luzpaz.
37 Copyright (C) 2018-2019 Andreas Schneider.
38 Copyright (C) 2018-2019 Edgardo Hoszowski.
39 Copyright (C) 2018 Fabian Wenzel.
40 Copyright (C) 2019 codingdave.
41 Copyright (C) 2019-2021 Hanno Schwalm.
42 Copyright (C) 2019-2022 Philippe Weyland.
43 Copyright (C) 2020 Chris Elston.
44 Copyright (C) 2020-2021 Hubert Kowalski.
45 Copyright (C) 2020 JP Verrue.
46 Copyright (C) 2020 Matt Maguire.
47 Copyright (C) 2020-2022 Miloš Komarčević.
48 Copyright (C) 2020-2021 Ralf Brown.
49 Copyright (C) 2021, 2023, 2025-2026 Aurélien PIERRE.
50 Copyright (C) 2021 Daniel Vogelbacher.
51 Copyright (C) 2021 Victor Forsiuk.
52 Copyright (C) 2022 gi-man.
53 Copyright (C) 2022 Martin Bařinka.
54 Copyright (C) 2022 paolodepetrillo.
55 Copyright (C) 2023, 2025 Alynx Zhou.
56 Copyright (C) 2023 Ricky Moon.
57 Copyright (C) 2025-2026 Guillaume Stutin.
58
59 darktable is free software: you can redistribute it and/or modify
60 it under the terms of the GNU General Public License as published by
61 the Free Software Foundation, either version 3 of the License, or
62 (at your option) any later version.
63
64 darktable is distributed in the hope that it will be useful,
65 but WITHOUT ANY WARRANTY; without even the implied warranty of
66 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
67 GNU General Public License for more details.
68
69 You should have received a copy of the GNU General Public License
70 along with darktable. If not, see <http://www.gnu.org/licenses/>.
71 */
72
73#define __STDC_FORMAT_MACROS
74
85#ifdef HAVE_CONFIG_H
86#include "config.h"
87#endif
88
89#include <errno.h>
90#include <glib.h>
91#include <glib/gstdio.h>
92#include <time.h>
93
94#include <cmath>
95#include <cstdlib>
96#include <cstring>
97#include <fstream>
98#include <iostream>
99#include <sstream>
100#include <string>
101
102#include <exiv2/exiv2.hpp>
103
104#if defined(_WIN32) && defined(EXV_UNICODE_PATH)
105 #define WIDEN(s) pugi::as_wide(s)
106#else
107 #define WIDEN(s) (s)
108#endif
109
110#include <pugixml.hpp>
111
112#include "common/xmp_sidecar.h"
113#include "metadata/exif.h"
115
116#include "caches/image_cache.h"
117#include "common/conf.h"
118#include "common/datetime.h"
119#include "common/deprecations.h"
120#include "history/history.h"
121#include "common/utility.h"
122#include "common/variables.h"
124#include "database/database.h"
128#include "develop/blend.h"
129#include "develop/iop_order.h"
130#include "develop/masks.h"
131#include "imageio/imageio_core.h"
132#include "metadata/metadata.h"
133#include "metadata/notify.h"
134#include "metadata/tags.h"
135#include "system/macros.h"
136#include "system/mem_alloc.h"
137
138using namespace std;
139
140#define DT_XMP_EXIF_VERSION 5
141
142#if EXIV2_TEST_VERSION(0,28,0)
143#define AnyError Error
144#define toLong toInt64
145#endif
146
147static const char *_exif_get_exiv2_tag_type(const char *tagname)
148{
149 if(IS_NULL_PTR(tagname)) return NULL;
150
151 const size_t tagname_len = strlen(tagname);
152
153 // the list lives in metadata/exif.cc and is built on first use; it was a file-static
154 // shared with this code when the two were one translation unit
155 for(const GList *tag = dt_exif_get_exiv2_taglist(); tag; tag = g_list_next(tag))
156 {
157 const char *t = (const char *)tag->data;
158 if(IS_NULL_PTR(t)) continue; // was tested AFTER t had already been dereferenced
159
160 // each entry is "<tagname>,<type>". The prefix comparison having succeeded is what
161 // makes t[tagname_len] in bounds: it is either the ',' or the terminating NUL.
162 if(strncmp(t, tagname, tagname_len) == 0 && t[tagname_len] == ',')
163 return t + tagname_len + 1;
164 }
165 return NULL;
166}
167
168static void read_xmp_timestamps(Exiv2::XmpData &xmpData, dt_image_t *img, const int xmp_version);
169
170// this array should contain all XmpBag and XmpSeq keys used by dt
171const char *dt_xmp_keys[]
172 = { "Xmp.dc.subject", "Xmp.lr.hierarchicalSubject", "Xmp.darktable.colorlabels", "Xmp.darktable.history",
173 "Xmp.darktable.history_modversion", "Xmp.darktable.history_enabled", "Xmp.darktable.history_end",
174 "Xmp.darktable.iop_order_version", "Xmp.darktable.iop_order_list",
175 "Xmp.darktable.history_operation", "Xmp.darktable.history_params", "Xmp.darktable.blendop_params",
176 "Xmp.darktable.blendop_version", "Xmp.darktable.multi_priority", "Xmp.darktable.multi_name",
177 "Xmp.darktable.iop_order",
178 "Xmp.darktable.xmp_version", "Xmp.darktable.raw_params", "Xmp.darktable.auto_presets_applied",
179 "Xmp.darktable.mask_id", "Xmp.darktable.mask_type", "Xmp.darktable.mask_name",
180 "Xmp.darktable.masks_history", "Xmp.darktable.mask_num", "Xmp.darktable.mask_points",
181 "Xmp.darktable.mask_version", "Xmp.darktable.mask", "Xmp.darktable.mask_nb", "Xmp.darktable.mask_src",
182 "Xmp.darktable.history_basic_hash", "Xmp.darktable.history_auto_hash", "Xmp.darktable.history_current_hash",
183 "Xmp.darktable.import_timestamp", "Xmp.darktable.change_timestamp",
184 "Xmp.darktable.export_timestamp", "Xmp.darktable.print_timestamp",
185 "Xmp.acdsee.notes", "Xmp.darktable.version_name",
186 "Xmp.dc.creator", "Xmp.dc.publisher", "Xmp.dc.title", "Xmp.dc.description", "Xmp.dc.rights",
187 "Xmp.xmpMM.DerivedFrom" };
188
189static const guint dt_xmp_keys_n = G_N_ELEMENTS(dt_xmp_keys); // the number of XmpBag XmpSeq keys that dt uses
190
191// function to remove known dt keys and subtrees from xmpdata, so not to append them twice
192// this should work because dt first reads all known keys
193static void dt_remove_known_keys(Exiv2::XmpData &xmp)
194{
195 xmp.sortByKey();
196 for(unsigned int i = 0; i < dt_xmp_keys_n; i++)
197 {
198 Exiv2::XmpData::iterator pos = xmp.findKey(Exiv2::XmpKey(dt_xmp_keys[i]));
199
200 while(pos != xmp.end())
201 {
202 std::string key = pos->key();
203 const char *ckey = key.c_str();
204 size_t len = key.size();
205 // stop iterating once the key no longer matches what we are trying to delete. this assumes sorted input
206 if(!(g_str_has_prefix(ckey, dt_xmp_keys[i]) && (ckey[len] == '[' || ckey[len] == '\0')))
207 break;
208 pos = xmp.erase(pos);
209 }
210 }
211}
212
213static void dt_remove_xmp_keys(Exiv2::XmpData &xmp, const char *keys[], unsigned int n_keys)
214{
215 for(unsigned int i = 0; i < n_keys; i++)
216 {
217 try
218 {
219 Exiv2::XmpData::iterator pos;
220 while((pos = xmp.findKey(Exiv2::XmpKey(keys[i]))) != xmp.end())
221 xmp.erase(pos);
222 }
223 catch(const std::exception &e)
224 {
225 // the only exception we may get is "invalid" tag, which is not
226 // important enough to either stop the function, or even display
227 // a message (it's probably the tag is not implemented in the
228 // exiv2 version used)
229 }
230 }
231}
232
233static void dt_remove_exif_geotag(Exiv2::ExifData &exifData)
234{
235 static const char *keys[] =
236 {
237 "Exif.GPSInfo.GPSLatitude",
238 "Exif.GPSInfo.GPSLongitude",
239 "Exif.GPSInfo.GPSAltitude",
240 "Exif.GPSInfo.GPSLatitudeRef",
241 "Exif.GPSInfo.GPSLongitudeRef",
242 "Exif.GPSInfo.GPSAltitudeRef",
243 "Exif.GPSInfo.GPSVersionID"
244 };
245 static const guint n_keys = G_N_ELEMENTS(keys);
247}
248
249int dt_exif_read_blob(uint8_t **buf, const char *path, const int32_t imgid, const int sRGB, const int out_width,
250 const int out_height, const int dng_mode)
251{
252 *buf = NULL;
253 try
254 {
255 std::unique_ptr<Exiv2::Image> image(Exiv2::ImageFactory::open(WIDEN(path)));
256 if(!image.get()) return 1;
258 Exiv2::ExifData &exifData = image->exifData();
259
260 // get rid of thumbnails
261 Exiv2::ExifThumb(exifData).erase();
262 Exiv2::ExifData::const_iterator pos;
263
264 {
265 static const char *keys[] = {
266 "Exif.Image.ImageWidth",
267 "Exif.Image.ImageLength",
268 "Exif.Image.BitsPerSample",
269 "Exif.Image.Compression",
270 "Exif.Image.PhotometricInterpretation",
271 "Exif.Image.FillOrder",
272 "Exif.Image.SamplesPerPixel",
273 "Exif.Image.StripOffsets",
274 "Exif.Image.RowsPerStrip",
275 "Exif.Image.StripByteCounts",
276 "Exif.Image.TileWidth",
277 "Exif.Image.TileLength",
278 "Exif.Image.TileOffsets",
279 "Exif.Image.TileByteCounts",
280 "Exif.Image.PlanarConfiguration"
281 };
282 static const guint n_keys = G_N_ELEMENTS(keys);
284 }
285
286 /* Many tags should be removed in all cases as they are simply wrong also for dng files */
287
288 // remove subimage* trees, related to thumbnails or HDR usually; also UserCrop
289 for(Exiv2::ExifData::iterator i = exifData.begin(); i != exifData.end();)
290 {
291 static const std::string needle = "Exif.SubImage";
292 if(i->key().compare(0, needle.length(), needle) == 0)
293 i = exifData.erase(i);
294 else
295 ++i;
296 }
297
298 {
299 static const char *keys[] = {
300 // Canon color space info
301 "Exif.Canon.ColorSpace",
302 "Exif.Canon.ColorData",
303
304 // Nikon thumbnail data
305 "Exif.Nikon3.Preview",
306 "Exif.NikonPreview.JPEGInterchangeFormat",
307
308 // TIFF/EP & Exif stuff irrelevant after developing
309 "Exif.Image.CFARepeatPatternDim",
310 "Exif.Image.CFAPattern",
311 "Exif.Image.InterColorProfile",
312 "Exif.Image.SpectralSensitivity",
313 "Exif.Image.OECF",
314 "Exif.Image.SpatialFrequencyResponse",
315 "Exif.Image.Noise",
316 "Exif.Image.SensingMethod",
317 "Exif.Image.TIFFEPStandardID",
318 "Exif.Photo.SpectralSensitivity",
319 "Exif.Photo.OECF",
320 "Exif.Photo.SpatialFrequencyResponse",
321 "Exif.Photo.SensingMethod",
322 "Exif.Photo.CFAPattern",
323
324 // DNG stuff that is irrelevant or misleading
325 "Exif.Image.DNGVersion",
326 "Exif.Image.DNGBackwardVersion",
327 "Exif.Image.DNGPrivateData",
328 "Exif.Image.DefaultBlackRender",
329 "Exif.Image.DefaultCropOrigin",
330 "Exif.Image.DefaultCropSize",
331 "Exif.Image.RawDataUniqueID",
332 "Exif.Image.OriginalRawFileName",
333 "Exif.Image.OriginalRawFileData",
334 "Exif.Image.ActiveArea",
335 "Exif.Image.MaskedAreas",
336 "Exif.Image.AsShotICCProfile",
337 "Exif.Image.OpcodeList1",
338 "Exif.Image.OpcodeList2",
339 "Exif.Image.OpcodeList3",
340 "Exif.Image.AsShotNeutral",
341 "Exif.Image.AsShotWhiteXY",
342 "Exif.Image.BaselineExposure",
343 "Exif.Image.BaselineNoise",
344 "Exif.Image.BaselineSharpness",
345 "Exif.Image.LinearResponseLimit",
346 "Exif.Image.ShadowScale",
347 "Exif.Image.PreviewApplicationName",
348 "Exif.Image.PreviewApplicationVersion",
349 "Exif.Image.PreviewSettingsDigest",
350 "Exif.Image.PreviewColorSpace",
351 "Exif.Image.PreviewDateTime",
352 "Exif.Image.NoiseProfile",
353 "Exif.Image.NewRawImageDigest",
354 "Exif.Image.RawImageDigest",
355
356 "Exif.Photo.MakerNote",
357
358 // Pentax thumbnail data
359 "Exif.Pentax.PreviewResolution",
360 "Exif.Pentax.PreviewLength",
361 "Exif.Pentax.PreviewOffset",
362 "Exif.PentaxDng.PreviewResolution",
363 "Exif.PentaxDng.PreviewLength",
364 "Exif.PentaxDng.PreviewOffset",
365 // Pentax color info
366 "Exif.PentaxDng.ColorInfo",
367
368 // Minolta thumbnail data
369 "Exif.Minolta.Thumbnail",
370 "Exif.Minolta.ThumbnailOffset",
371 "Exif.Minolta.ThumbnailLength",
372
373 // Sony thumbnail data
374 "Exif.SonyMinolta.ThumbnailOffset",
375 "Exif.SonyMinolta.ThumbnailLength",
376
377 // Olympus thumbnail data
378 "Exif.Olympus.Thumbnail",
379 "Exif.Olympus.ThumbnailOffset",
380 "Exif.Olympus.ThumbnailLength",
381
382 "Exif.Image.BaselineExposureOffset",
383
384 // Samsung makernote cleanup, the entries below have no
385 // relevance for exported images
386 "Exif.Samsung2.SensorAreas",
387 "Exif.Samsung2.ColorSpace",
388 "Exif.Samsung2.EncryptionKey",
389 "Exif.Samsung2.WB_RGGBLevelsUncorrected",
390 "Exif.Samsung2.WB_RGGBLevelsAuto",
391 "Exif.Samsung2.WB_RGGBLevelsIlluminator1",
392 "Exif.Samsung2.WB_RGGBLevelsIlluminator2",
393 "Exif.Samsung2.WB_RGGBLevelsBlack",
394 "Exif.Samsung2.ColorMatrix",
395 "Exif.Samsung2.ColorMatrixSRGB",
396 "Exif.Samsung2.ColorMatrixAdobeRGB",
397 "Exif.Samsung2.ToneCurve1",
398 "Exif.Samsung2.ToneCurve2",
399 "Exif.Samsung2.ToneCurve3",
400 "Exif.Samsung2.ToneCurve4"
401 };
402 static const guint n_keys = G_N_ELEMENTS(keys);
404 }
405
406 static const char *dngkeys[] = {
407 // Embedded color profile info
408 "Exif.Image.ColorMatrix1",
409 "Exif.Image.ColorMatrix2",
410 "Exif.Image.CameraCalibration1",
411 "Exif.Image.CameraCalibration2",
412 "Exif.Image.ReductionMatrix1",
413 "Exif.Image.ReductionMatrix2",
414 "Exif.Image.AnalogBalance",
415 "Exif.Image.CalibrationIlluminant1",
416 "Exif.Image.CalibrationIlluminant2",
417 "Exif.Image.CameraCalibrationSignature",
418 "Exif.Image.ProfileCalibrationSignature",
419 "Exif.Image.ExtraCameraProfiles",
420 "Exif.Image.AsShotProfileName",
421 "Exif.Image.ProfileName",
422 "Exif.Image.ProfileHueSatMapDims",
423 "Exif.Image.ProfileHueSatMapData1",
424 "Exif.Image.ProfileHueSatMapData2",
425 "Exif.Image.ProfileToneCurve",
426 "Exif.Image.ProfileEmbedPolicy",
427 "Exif.Image.ProfileCopyright",
428 "Exif.Image.ForwardMatrix1",
429 "Exif.Image.ForwardMatrix2",
430 "Exif.Image.ProfileLookTableDims",
431 "Exif.Image.ProfileLookTableData",
432 "Exif.Image.ProfileLookTableEncoding",
433 "Exif.Image.ProfileHueSatMapEncoding"
434 };
435 static const guint n_dngkeys = G_N_ELEMENTS(dngkeys);
437
438 /* Write appropriate color space tag if using sRGB output */
439 if(sRGB)
440 exifData["Exif.Photo.ColorSpace"] = uint16_t(1); /* sRGB */
441 else
442 exifData["Exif.Photo.ColorSpace"] = uint16_t(0xFFFF); /* Uncalibrated */
443
444 // we don't write the orientation here for dng as it is set in dt_imageio_dng_write_tiff_header
445 // or might be defined in this blob.
446 if(!dng_mode) exifData["Exif.Image.Orientation"] = uint16_t(1);
447
448 /* Replace RAW dimension with output dimensions (for example after crop/scale, or orientation for dng
449 * mode) */
450 if(out_width > 0) exifData["Exif.Photo.PixelXDimension"] = (uint32_t)out_width;
451 if(out_height > 0) exifData["Exif.Photo.PixelYDimension"] = (uint32_t)out_height;
452
453 const int resolution = dt_conf_get_int("metadata/resolution");
454 exifData["Exif.Image.XResolution"] = Exiv2::Rational(resolution, 1);
455 exifData["Exif.Image.YResolution"] = Exiv2::Rational(resolution, 1);
456 exifData["Exif.Image.ResolutionUnit"] = uint16_t(2); /* inches */
457
458 exifData["Exif.Image.Software"] = darktable_package_string;
459
460 // TODO: find a nice place for the missing metadata (tags, publisher, colorlabels?). Additionally find out
461 // how to embed XMP data.
462 // And shall we add a description of the history stack to Exif.Image.ImageHistory?
463 if(imgid >= 0)
464 {
465 /* Delete metadata taken from the original file if it's fields we manage in dt, too */
466 static const char * keys[] = {
467 "Exif.Image.Artist",
468 "Exif.Image.ImageDescription",
469 "Exif.Photo.UserComment",
470 "Exif.Image.Copyright",
471 "Exif.Image.Rating",
472 "Exif.Image.RatingPercent",
473 "Exif.Photo.SubSecTimeOriginal",
474 "Exif.GPSInfo.GPSVersionID",
475 "Exif.GPSInfo.GPSLongitudeRef",
476 "Exif.GPSInfo.GPSLatitudeRef",
477 "Exif.GPSInfo.GPSLongitude",
478 "Exif.GPSInfo.GPSLatitude",
479 "Exif.GPSInfo.GPSAltitudeRef",
480 "Exif.GPSInfo.GPSAltitude"
481 };
482 static const guint n_keys = G_N_ELEMENTS(keys);
484
485 GList *res = dt_metadata_get(imgid, "Xmp.dc.creator", NULL);
486 if(!IS_NULL_PTR(res))
487 {
488 exifData["Exif.Image.Artist"] = (char *)res->data;
490 res = NULL;
491 }
492
493 res = dt_metadata_get(imgid, "Xmp.dc.description", NULL);
494 if(!IS_NULL_PTR(res))
495 {
496 char *desc = (char *)res->data;
497 if(g_str_is_ascii(desc))
498 exifData["Exif.Image.ImageDescription"] = desc;
499 else
500 exifData["Exif.Photo.UserComment"] = desc;
502 res = NULL;
503 }
504#if EXIV2_TEST_VERSION(0,27,4)
505 else
506 // mandatory tag for TIFF/EP and recommended for Exif, empty is ok (unknown)
507 // but correctly written only by exiv2 >= 0.27.4
508 exifData["Exif.Image.ImageDescription"] = "";
509#endif
510
511 res = dt_metadata_get(imgid, "Xmp.dc.rights", NULL);
512 if(!IS_NULL_PTR(res))
513 {
514 exifData["Exif.Image.Copyright"] = (char *)res->data;
516 res = NULL;
517 }
518#if EXIV2_TEST_VERSION(0,27,4)
519 else
520 // mandatory tag for TIFF/EP and optional for Exif, empty is ok (unknown)
521 // but correctly written only by exiv2 >= 0.27.4
522 exifData["Exif.Image.Copyright"] = "";
523#endif
524
525 res = dt_metadata_get(imgid, "Xmp.xmp.Rating", NULL);
526 if(!IS_NULL_PTR(res))
527 {
528 const int rating = GPOINTER_TO_INT(res->data) + 1;
529 exifData["Exif.Image.Rating"] = rating;
531 res = NULL;
532 }
533
534 // GPS data
536 const dt_image_t *cimg = dt_image_cache_get(imgid, 'r');
537 if(!isnan(cimg->geoloc.longitude) && !isnan(cimg->geoloc.latitude))
538 {
539 exifData["Exif.GPSInfo.GPSVersionID"] = "02 02 00 00";
540 exifData["Exif.GPSInfo.GPSLongitudeRef"] = (cimg->geoloc.longitude < 0) ? "W" : "E";
541 exifData["Exif.GPSInfo.GPSLatitudeRef"] = (cimg->geoloc.latitude < 0) ? "S" : "N";
542
543 long long_deg = (int)floor(fabs(cimg->geoloc.longitude));
544 long lat_deg = (int)floor(fabs(cimg->geoloc.latitude));
545 long long_min = (int)floor((fabs(cimg->geoloc.longitude) - floor(fabs(cimg->geoloc.longitude))) * 60000000);
546 long lat_min = (int)floor((fabs(cimg->geoloc.latitude) - floor(fabs(cimg->geoloc.latitude))) * 60000000);
547 gchar *long_str = g_strdup_printf("%ld/1 %ld/1000000 0/1", long_deg, long_min);
548 gchar *lat_str = g_strdup_printf("%ld/1 %ld/1000000 0/1", lat_deg, lat_min);
549 exifData["Exif.GPSInfo.GPSLongitude"] = long_str;
550 exifData["Exif.GPSInfo.GPSLatitude"] = lat_str;
553 }
554 if(!isnan(cimg->geoloc.elevation))
555 {
556 exifData["Exif.GPSInfo.GPSVersionID"] = "02 02 00 00";
557 exifData["Exif.GPSInfo.GPSAltitudeRef"] = (cimg->geoloc.elevation < 0) ? "1" : "0";
558
559 long ele_dm = (int)floor(fabs(10.0 * cimg->geoloc.elevation));
560 gchar *ele_str = g_strdup_printf("%ld/10", ele_dm);
561 exifData["Exif.GPSInfo.GPSAltitude"] = ele_str;
563 }
564
565 // According to the Exif specs DateTime is to be set to the last modification time while
566 // DateTimeOriginal is to be kept.
567 // For us "keeping" it means to write out what we have in DB to support people adding a time offset in
568 // the geotagging module.
571 exifData["Exif.Image.DateTime"] = new_datetime;
572 gchar datetime[DT_DATETIME_LENGTH];
573 dt_datetime_img_to_exif(datetime, sizeof(datetime), cimg);
574 datetime[DT_DATETIME_EXIF_LENGTH - 1] = '\0';
575 exifData["Exif.Image.DateTimeOriginal"] = datetime;
576 exifData["Exif.Photo.DateTimeOriginal"] = datetime;
577 if(g_strcmp0(&datetime[DT_DATETIME_EXIF_LENGTH], "000"))
578 exifData["Exif.Photo.SubSecTimeOriginal"] = &datetime[DT_DATETIME_EXIF_LENGTH];
579
581 }
582
583 Exiv2::Blob blob;
584 Exiv2::ExifParser::encode(blob, Exiv2::bigEndian, exifData);
585 const size_t length = blob.size();
586 *buf = (uint8_t *)malloc(length);
587 if (IS_NULL_PTR(*buf))
588 {
589 return 0;
590 }
591 memcpy(*buf, &(blob[0]), length);
592 return length;
593 }
594 catch(const std::exception &e)
595 {
596 // std::cerr.rdbuf(savecerr);
597 std::string s(e.what());
598 std::cerr << "[exiv2 dt_exif_read_blob] " << path << ": " << s << std::endl;
599 dt_free(*buf);
600 return 0;
601 }
602}
603
604typedef struct history_entry_t
605{
607 gboolean enabled;
609 unsigned char *params;
612 // int multi_name_hand_edited;
615 unsigned char *blendop_params;
617 int num;
618 double iop_order; // kept for compatibility with xmp version < 4
619
620 // sanity checking
623
624// used for a hash table that maps mask_id to the mask data
640
643{
644 if(IS_NULL_PTR(entry) || IS_NULL_PTR(entry->operation))
645 {
646 std::cout << "malformed entry" << std::endl;
647 return;
648 }
649
650 std::cout << entry->operation << std::endl;
651 std::cout << " modversion :" << entry->modversion << std::endl;
652 std::cout << " enabled :" << entry->enabled << std::endl;
653 std::cout << " params :" << (entry->params ? "<found>" : "<missing>") << std::endl;
654 std::cout << " multi_name :" << (entry->multi_name ? entry->multi_name : "<missing>") << std::endl;
655 std::cout << " multi_priority :" << entry->multi_priority << std::endl;
656 std::cout << " iop_order :" << entry->iop_order << std::endl;
657 std::cout << " blendop_version :" << entry->blendop_version << std::endl;
658 std::cout << " blendop_params :" << (entry->blendop_params ? "<found>" : "<missing>") << std::endl;
659 std::cout << std::endl;
660}
661
662static void free_history_entry(gpointer data)
663{
664 history_entry_t *entry = (history_entry_t *)data;
665 dt_free(entry->operation);
666 dt_free(entry->multi_name);
667 dt_free(entry->params);
668 dt_free(entry->blendop_params);
669 dt_free(entry);
670}
671
672// we have to use pugixml as the old format could contain empty rdf:li elements in the multi_name array
673// which causes problems when accessing it with libexiv2 :(
674// superold is a flag indicating that data is wrapped in <rdf:Bag> instead of <rdf:Seq>.
675static GList *read_history_v1(const std::string &xmpPacket, const char *filename, const int superold)
676{
678
679 pugi::xml_document doc;
680#if defined(PUGIXML_VERSION) && PUGIXML_VERSION >= 150
681 pugi::xml_parse_result result = doc.load_string(xmpPacket.c_str());
682#else
683 pugi::xml_parse_result result = doc.load(xmpPacket.c_str());
684#endif
685
686 if(!result)
687 {
688 std::cerr << "XML '" << filename << "' parsed with errors" << std::endl;
689 std::cerr << "Error description: " << result.description() << std::endl;
690 std::cerr << "Error offset: " << result.offset << std::endl;
691 return NULL;
692 }
693
694 // get the old elements
695 // select_single_node() is deprecated and just kept for old versions shipped in some distributions
696#if defined(PUGIXML_VERSION) && PUGIXML_VERSION >= 150
697 pugi::xpath_node modversion = superold ?
698 doc.select_node("//darktable:history_modversion/rdf:Bag"):
699 doc.select_node("//darktable:history_modversion/rdf:Seq");
700 pugi::xpath_node enabled = superold ?
701 doc.select_node("//darktable:history_enabled/rdf:Bag"):
702 doc.select_node("//darktable:history_enabled/rdf:Seq");
703 pugi::xpath_node operation = superold ?
704 doc.select_node("//darktable:history_operation/rdf:Bag"):
705 doc.select_node("//darktable:history_operation/rdf:Seq");
706 pugi::xpath_node params = superold ?
707 doc.select_node("//darktable:history_params/rdf:Bag"):
708 doc.select_node("//darktable:history_params/rdf:Seq");
709 pugi::xpath_node blendop_params = superold ?
710 doc.select_node("//darktable:blendop_params/rdf:Bag"):
711 doc.select_node("//darktable:blendop_params/rdf:Seq");
712 pugi::xpath_node blendop_version = superold ?
713 doc.select_node("//darktable:blendop_version/rdf:Bag"):
714 doc.select_node("//darktable:blendop_version/rdf:Seq");
715 pugi::xpath_node multi_priority = superold ?
716 doc.select_node("//darktable:multi_priority/rdf:Bag"):
717 doc.select_node("//darktable:multi_priority/rdf:Seq");
718 pugi::xpath_node multi_name = superold ?
719 doc.select_node("//darktable:multi_name/rdf:Bag"):
720 doc.select_node("//darktable:multi_name/rdf:Seq");
721#else
722 pugi::xpath_node modversion = superold ?
723 doc.select_single_node("//darktable:history_modversion/rdf:Bag"):
724 doc.select_single_node("//darktable:history_modversion/rdf:Seq");
725 pugi::xpath_node enabled = superold ?
726 doc.select_single_node("//darktable:history_enabled/rdf:Bag"):
727 doc.select_single_node("//darktable:history_enabled/rdf:Seq");
728 pugi::xpath_node operation = superold ?
729 doc.select_single_node("//darktable:history_operation/rdf:Bag"):
730 doc.select_single_node("//darktable:history_operation/rdf:Seq");
731 pugi::xpath_node params = superold ?
732 doc.select_single_node("//darktable:history_params/rdf:Bag"):
733 doc.select_single_node("//darktable:history_params/rdf:Seq");
734 pugi::xpath_node blendop_params = superold ?
735 doc.select_single_node("//darktable:blendop_params/rdf:Bag"):
736 doc.select_single_node("//darktable:blendop_params/rdf:Seq");
737 pugi::xpath_node blendop_version = superold ?
738 doc.select_single_node("//darktable:blendop_version/rdf:Bag"):
739 doc.select_single_node("//darktable:blendop_version/rdf:Seq");
740 pugi::xpath_node multi_priority = superold ?
741 doc.select_single_node("//darktable:multi_priority/rdf:Bag"):
742 doc.select_single_node("//darktable:multi_priority/rdf:Seq");
743 pugi::xpath_node multi_name = superold ?
744 doc.select_single_node("//darktable:multi_name/rdf:Bag"):
745 doc.select_single_node("//darktable:multi_name/rdf:Seq");
746#endif
747
748 // fill the list of history entries. we are iterating over history_operation as we know that it's there.
749 // the other iters are taken care of manually.
750 auto modversion_iter = modversion.node().children().begin();
751 auto enabled_iter = enabled.node().children().begin();
752 auto params_iter = params.node().children().begin();
753 auto blendop_params_iter = blendop_params.node().children().begin();
754 auto blendop_version_iter = blendop_version.node().children().begin();
755 auto multi_priority_iter = multi_priority.node().children().begin();
756 auto multi_name_iter = multi_name.node().children().begin();
757
758 for(pugi::xml_node operation_iter: operation.node().children())
759 {
761 current_entry->blendop_version = 1; // default version in case it's not specified
763
764 current_entry->operation = g_strdup(operation_iter.child_value());
765
766 current_entry->enabled = g_strcmp0(enabled_iter->child_value(), "0") != 0;
767
768 current_entry->modversion = atoi(modversion_iter->child_value());
769
770 const char *params_text = params_iter->child_value();
772 &current_entry->params_len);
773
774 if(multi_name && multi_name_iter != multi_name.node().children().end())
775 {
776 current_entry->multi_name = g_strdup(multi_name_iter->child_value());
778 }
779
780 if(multi_priority && multi_priority_iter != multi_priority.node().children().end())
781 {
782 current_entry->multi_priority = atoi(multi_priority_iter->child_value());
784 }
785
786 if(blendop_version && blendop_version_iter != blendop_version.node().children().end())
787 {
788 current_entry->blendop_version = atoi(blendop_version_iter->child_value());
790 }
791
792 if(blendop_params && blendop_params_iter != blendop_params.node().children().end())
793 {
794 const char *blendop_text = blendop_params_iter->child_value();
796 &current_entry->blendop_params_len);
798 }
799
800 current_entry->iop_order = -1.0;
801
803 enabled_iter++;
804 params_iter++;
805 }
806
807 return history_entries;
808}
809
810static GList *read_history_v2(Exiv2::XmpData &xmpData, const char *filename)
811{
814
815 for(auto history = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.history")); history != xmpData.end(); history++)
816 {
817 // TODO: support human readable params via introspection with something like this:
818 // XmpText: Xmp.darktable.history[1]/darktable:settings[1]/darktable:name = width
819 // XmpText: Xmp.darktable.history[1]/darktable:settings[1]/darktable:value = 23
820
821 std::string key_item = history->key();
822 char *key = g_strdup(key_item.c_str());
823 char *key_iter = key;
824 if(g_str_has_prefix(key, "Xmp.darktable.history["))
825 {
826 key_iter += strlen("Xmp.darktable.history[");
827 errno = 0;
828 unsigned int n = strtol(key_iter, &key_iter, 10);
829 if(errno)
830 {
831 std::cerr << "error reading history from '" << key << "' (" << filename << ")" << std::endl;
834 dt_free(key);
835 return NULL;
836 }
837
838 // skip everything that isn't part of the actual array
839 if(*(key_iter++) != ']')
840 {
841 std::cerr << "error reading history from '"
842 << key << "' (" << filename << ")" << std::endl;
845 dt_free(key);
846 return NULL;
847 }
848 if(*(key_iter++) != '/') goto skip;
849 if(*key_iter == '?') key_iter++;
850
851 // make sure we are filling in the details of the correct entry
852 unsigned int length = g_list_length(history_entries);
853 if(n > length)
854 {
856 current_entry->blendop_version = 1; // default version in case it's not specified
857 current_entry->iop_order = -1.0;
859 }
860 else if(n < length)
861 {
862 // AFAICT this can't happen with regular exiv2 parsed XMP data, but better safe than sorry.
863 // it can happen though when constructing things in a unusual order and then passing it to us without
864 // serializing it in between
865 current_entry = (history_entry_t *)g_list_nth_data(history_entries, n - 1); // XMP starts counting at 1!
866 }
867
868 // go on reading things into current_entry
869 if(g_str_has_prefix(key_iter, "darktable:operation"))
870 {
871 current_entry->have_operation = TRUE;
872 std::string value_item = history->toString();
873 current_entry->operation = g_strdup(value_item.c_str());
874 }
875 else if(g_str_has_prefix(key_iter, "darktable:num"))
876 {
877 current_entry->num = history->toLong();
878 }
879 else if(g_str_has_prefix(key_iter, "darktable:enabled"))
880 {
881 current_entry->enabled = history->toLong() == 1;
882 }
883 else if(g_str_has_prefix(key_iter, "darktable:modversion"))
884 {
885 current_entry->have_modversion = TRUE;
886 current_entry->modversion = history->toLong();
887 }
888 else if(g_str_has_prefix(key_iter, "darktable:params"))
889 {
890 current_entry->have_params = TRUE;
891 std::string value_item = history->toString();
893 history->size(),
894 &current_entry->params_len);
895 }
896 /*
897 else if(g_str_has_prefix(key_iter, "darktable:multi_name_hand_edited"))
898 {
899 current_entry->multi_name_hand_edited = history->toLong() == 1;
900 }
901 */
902 else if(g_str_has_prefix(key_iter, "darktable:multi_name"))
903 {
904 std::string value_item = history->toString();
905 current_entry->multi_name = g_strdup(value_item.c_str());
906 }
907 else if(g_str_has_prefix(key_iter, "darktable:multi_priority"))
908 {
909 current_entry->multi_priority = history->toLong();
910 }
911 else if(g_str_has_prefix(key_iter, "darktable:iop_order"))
912 {
913 // we ensure reading the iop_order as a high precision float
914 std::string value_item = history->toString();
915 string str = g_strdup(value_item.c_str());
916 static const std::locale& c_locale = std::locale("C");
917 std::istringstream istring(str);
918 istring.imbue(c_locale);
919 istring >> current_entry->iop_order;
920 }
921 else if(g_str_has_prefix(key_iter, "darktable:blendop_version"))
922 {
923 current_entry->blendop_version = history->toLong();
924 }
925 else if(g_str_has_prefix(key_iter, "darktable:blendop_params"))
926 {
927 std::string value_item = history->toString();
928 current_entry->blendop_params =
930 history->size(),
931 &current_entry->blendop_params_len);
932 }
933 }
934skip:
935 dt_free(key);
936 }
937
938 // a final sanity check
940 {
941 history_entry_t *entry = (history_entry_t *)iter->data;
942 if(!(entry->have_operation && entry->have_params && entry->have_modversion))
943 {
944 std::cerr << "[exif] error: reading history from '" << filename << "' failed due to missing tags" << std::endl;
947 break;
948 }
949 }
950
951 return history_entries;
952}
953
954void free_mask_entry(gpointer data)
955{
956 mask_entry_t *entry = (mask_entry_t *)data;
957 dt_free(entry->mask_name);
958 dt_free(entry->mask_points);
959 dt_free(entry->mask_src);
960 dt_free(entry);
961}
962
963static GHashTable *read_masks(Exiv2::XmpData &xmpData, const char *filename, const int version)
964{
966
967 // TODO: turn that into something like Xmp.darktable.history!
968 Exiv2::XmpData::iterator mask;
969 Exiv2::XmpData::iterator mask_name;
970 Exiv2::XmpData::iterator mask_type;
971 Exiv2::XmpData::iterator mask_version;
972 Exiv2::XmpData::iterator mask_id;
973 Exiv2::XmpData::iterator mask_nb;
974 Exiv2::XmpData::iterator mask_src;
975 if((mask = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.mask"))) != xmpData.end()
976 && (mask_src = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.mask_src"))) != xmpData.end()
977 && (mask_name = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.mask_name"))) != xmpData.end()
978 && (mask_type = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.mask_type"))) != xmpData.end()
979 && (mask_version = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.mask_version"))) != xmpData.end()
980 && (mask_id = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.mask_id"))) != xmpData.end()
981 && (mask_nb = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.mask_nb"))) != xmpData.end())
982 {
983 // fixes API change happened after exiv2 v0.27.2.1
984 const size_t cnt = (size_t)mask->count();
985 const size_t mask_src_cnt = (size_t)mask_src->count();
986 const size_t mask_name_cnt = (size_t)mask_name->count();
987 const size_t mask_type_cnt = (size_t)mask_type->count();
988 const size_t mask_version_cnt = (size_t)mask_version->count();
989 const size_t mask_id_cnt = (size_t)mask_id->count();
990 const size_t mask_nb_cnt = (size_t)mask_nb->count();
993 {
994 for(size_t i = 0; i < cnt; i++)
995 {
996 mask_entry_t *entry = (mask_entry_t *)calloc(1, sizeof(mask_entry_t));
997
998 entry->version = version;
999 entry->mask_id = mask_id->toLong(i);
1000 entry->mask_type = mask_type->toLong(i);
1001 std::string mask_name_str = mask_name->toString(i);
1002 if(mask_name_str.c_str() != NULL)
1003 entry->mask_name = g_strdup(mask_name_str.c_str());
1004 else
1005 entry->mask_name = g_strdup("form");
1006
1007 entry->mask_version = mask_version->toLong(i);
1008
1009 std::string mask_str = mask->toString(i);
1010 const char *mask_c = mask_str.c_str();
1011 const size_t mask_c_len = mask_str.size();
1013
1014 entry->mask_nb = mask_nb->toLong(i);
1015
1016 std::string mask_src_str = mask_src->toString(i);
1017 const char *mask_src_c = mask_src_str.c_str();
1018 const size_t mask_src_c_len = mask_src_str.size();
1020
1021 g_hash_table_insert(mask_entries, &entry->mask_id, (gpointer)entry);
1022 }
1023 }
1024 }
1025
1026 return mask_entries;
1027}
1028
1029static GList *read_masks_v3(Exiv2::XmpData &xmpData, const char *filename, const int version)
1030{
1033
1034 for(auto history = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.masks_history")); history != xmpData.end(); history++)
1035 {
1036 // TODO: support human readable params via introspection with something like this:
1037 // XmpText: Xmp.darktable.history[1]/darktable:settings[1]/darktable:name = width
1038 // XmpText: Xmp.darktable.history[1]/darktable:settings[1]/darktable:value = 23
1039
1040 std::string key_item = history->key();
1041 char *key = g_strdup(key_item.c_str());
1042 char *key_iter = key;
1043 if(g_str_has_prefix(key, "Xmp.darktable.masks_history["))
1044 {
1045 key_iter += strlen("Xmp.darktable.masks_history[");
1046 errno = 0;
1047 unsigned int n = strtol(key_iter, &key_iter, 10);
1048 if(errno)
1049 {
1050 std::cerr << "error reading masks history from '" << key << "' (" << filename << ")" << std::endl;
1053 dt_free(key);
1054 return NULL;
1055 }
1056
1057 // skip everything that isn't part of the actual array
1058 if(*(key_iter++) != ']')
1059 {
1060 std::cerr << "error reading masks history from '" << key << "' (" << filename << ")" << std::endl;
1063 dt_free(key);
1064 return NULL;
1065 }
1066 if(*(key_iter++) != '/') goto skip;
1067 if(*key_iter == '?') key_iter++;
1068
1069 // make sure we are filling in the details of the correct entry
1070 unsigned int length = g_list_length(history_entries);
1071 if(n > length)
1072 {
1074 current_entry->version = version;
1076 }
1077 else if(n < length)
1078 {
1079 // AFAICT this can't happen with regular exiv2 parsed XMP data, but better safe than sorry.
1080 // it can happen though when constructing things in a unusual order and then passing it to us without
1081 // serializing it in between
1082 current_entry = (mask_entry_t *)g_list_nth_data(history_entries, n - 1); // XMP starts counting at 1!
1083 }
1084
1085 // go on reading things into current_entry
1086 if(g_str_has_prefix(key_iter, "darktable:mask_num"))
1087 {
1088 current_entry->mask_num = history->toLong();
1089 }
1090 else if(g_str_has_prefix(key_iter, "darktable:mask_id"))
1091 {
1092 current_entry->mask_id = history->toLong();
1093 }
1094 else if(g_str_has_prefix(key_iter, "darktable:mask_type"))
1095 {
1096 current_entry->mask_type = history->toLong();
1097 }
1098 else if(g_str_has_prefix(key_iter, "darktable:mask_name"))
1099 {
1100 std::string value_item = history->toString();
1101 current_entry->mask_name = g_strdup(value_item.c_str());
1102 }
1103 else if(g_str_has_prefix(key_iter, "darktable:mask_version"))
1104 {
1105 current_entry->mask_version = history->toLong();
1106 }
1107 else if(g_str_has_prefix(key_iter, "darktable:mask_points"))
1108 {
1109 std::string value_item = history->toString();
1110 current_entry->mask_points = dt_exif_xmp_decode(value_item.c_str(),
1111 history->size(),
1112 &current_entry->mask_points_len);
1113 }
1114 else if(g_str_has_prefix(key_iter, "darktable:mask_nb"))
1115 {
1116 current_entry->mask_nb = history->toLong();
1117 }
1118 else if(g_str_has_prefix(key_iter, "darktable:mask_src"))
1119 {
1120 std::string value_item = history->toString();
1121 current_entry->mask_src = dt_exif_xmp_decode(value_item.c_str(),
1122 history->size(),
1123 &current_entry->mask_src_len);
1124 }
1125
1126 }
1127skip:
1128 dt_free(key);
1129 }
1130
1131 return history_entries;
1132}
1133
1134static void add_mask_entry_to_db(int32_t imgid, mask_entry_t *entry)
1135{
1136 // add the mask entry only once
1137 if(entry->already_added)
1138 return;
1139
1140 const int mask_num = 0;
1141
1142 const int mask_num_used = (entry->version < 3) ? mask_num : entry->mask_num;
1143 const gboolean inserted =
1145 entry->mask_name, entry->mask_version, entry->mask_points,
1146 entry->mask_points_len, entry->mask_nb, entry->mask_src,
1147 entry->mask_src_len);
1148
1149 // Mark entry to true only after confirmation the sql insert was successful.
1150 //
1151 // This used to compare sqlite3_step()'s result against SQLITE_OK. An INSERT that runs to
1152 // completion returns SQLITE_DONE, so the test was never true, the flag was never set, and the
1153 // guard at the top of this function never fired. For a legacy sidecar (xmp_version < 3) every
1154 // non-clone mask is reached TWICE -- once by the hash-table pass over every entry, and again
1155 // by the per-history-item recursion through its group -- and once more per extra module
1156 // sharing that group. Each of those wrote another row into main.masks_history.
1157 if(inserted)
1158 {
1159 entry->already_added = TRUE;
1160 }
1161}
1162
1163static void add_non_clone_mask_entries_to_db(gpointer key, gpointer value, gpointer user_data)
1164{
1165 int32_t imgid = *(int *)user_data;
1166 mask_entry_t *entry = (mask_entry_t *)value;
1167 if(!(entry->mask_type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE))) add_mask_entry_to_db(imgid, entry);
1168}
1169
1171{
1172 if(mask_id <= 0) return;
1173
1174 // look for mask_id in the hash table
1176
1177 if(IS_NULL_PTR(entry)) return;
1178
1179 // if it's a group: recurse into the children first
1180 if(entry->mask_type & DT_MASKS_GROUP)
1181 {
1183 if((int)(entry->mask_nb * sizeof(dt_masks_form_group_t)) != entry->mask_points_len)
1184 {
1185 fprintf(stderr, "[masks] error loading masks from xmp file, bad binary blob size.\n");
1186 return;
1187 }
1188 for(int i = 0; i < entry->mask_nb; i++)
1189 add_mask_entries_to_db(imgid, mask_entries, group[i].formid);
1190 }
1191
1192 add_mask_entry_to_db(imgid, entry);
1193}
1194
1195// get MAX multi_priority
1196int _get_max_multi_priority(GList *history, const char *operation)
1197{
1198 int max_prio = 0;
1199
1200 for(GList *iter = history; iter; iter = g_list_next(iter))
1201 {
1202 history_entry_t *entry = (history_entry_t *)iter->data;
1203
1204 if(!strcmp(entry->operation, operation))
1206 }
1207
1208 return max_prio;
1209}
1210
1211// need a write lock on *img (non-const) to write stars (and soon color labels).
1212int dt_exif_xmp_read(dt_image_t *img, const char *filename, const int history_only)
1213{
1214 // Neither argument was checked, and this is a public entry point: common/image.c passes
1215 // a dt_image_cache_get() result straight in, which is nullable everywhere else in the
1216 // tree. Non-zero is this function's existing "did not read it" answer, and every caller
1217 // already tests for it.
1218 if(IS_NULL_PTR(img) || IS_NULL_PTR(filename)) return 1;
1219
1220 // exclude pfm to avoid stupid errors on the console.
1221 // The length is checked BEFORE the pointer is formed: `filename + strlen(filename) - 4'
1222 // on a name shorter than four characters computes a pointer before the start of the
1223 // array, which is undefined behaviour -- only one-past-the-end is legal. The `c >=
1224 // filename' test that used to follow cannot rescue that; the pointer is already invalid.
1225 const size_t filename_len = strlen(filename);
1226 if(filename_len >= 4 && !strcmp(filename + filename_len - 4, ".pfm")) return 1;
1227 try
1228 {
1229 // read xmp sidecar
1230 std::unique_ptr<Exiv2::Image> image(Exiv2::ImageFactory::open(WIDEN(filename)));
1231 if(!image.get()) return 1;
1233 Exiv2::XmpData &xmpData = image->xmpData();
1234
1235 Exiv2::XmpData::iterator pos;
1236
1237 int xmp_version = 0;
1238 GList *iop_order_list = NULL;
1239 dt_iop_order_t iop_order_version = DT_IOP_ORDER_LEGACY;
1240
1241 int num_masks = 0;
1242 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.xmp_version"))) != xmpData.end())
1243 xmp_version = pos->toLong();
1244
1245 if(!history_only)
1246 {
1247 // otherwise we ignore title, description, ... from non-dt xmp files :(
1248 const size_t ns_pos = image->xmpPacket().find("xmlns:darktable=\"http://darktable.sf.net/\"");
1249 const bool is_a_dt_xmp = (ns_pos != std::string::npos);
1250 dt_exif_decode_xmp_data(img, xmpData, is_a_dt_xmp ? xmp_version : -1, false);
1251 }
1252
1253
1254 // convert legacy flip bits (will not be written anymore, convert to flip history item here):
1255 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.raw_params"))) != xmpData.end())
1256 {
1257 union {
1258 int32_t in;
1260 } raw_params;
1261 raw_params.in = pos->toLong();
1262 const int32_t user_flip = raw_params.out.user_flip;
1263 img->legacy_flip.user_flip = user_flip;
1264 img->legacy_flip.legacy = 0;
1265 }
1266
1267 int32_t preset_applied = 0;
1268
1269 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.auto_presets_applied"))) != xmpData.end())
1270 {
1271 preset_applied = pos->toLong();
1272
1273 // in any case, this is no legacy image.
1275 }
1276 else if(xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.xmp_version")) == xmpData.end())
1277 {
1278 // if there is no darktable xmp_version in the XMP, this XMP must have been generated by another
1279 // program; since this is the first time darktable sees it, there can't be legacy presets
1281 }
1282 else
1283 {
1284 // so we are legacy (thus have to clear the no-legacy flag)
1286 }
1287 // when we are reading the xmp data it doesn't make sense to flag the image as removed
1288 img->flags &= ~DT_IMAGE_REMOVE;
1289
1290 if(xmp_version == 4 || xmp_version == 5)
1291 {
1292 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.iop_order_version"))) != xmpData.end())
1293 {
1294 iop_order_version = (dt_iop_order_t)pos->toLong();
1295 }
1296
1297 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.iop_order_list"))) != xmpData.end())
1298 {
1299 iop_order_list = dt_ioppr_deserialize_text_iop_order_list(pos->toString().c_str());
1300 // insert modules created after this edit's order was serialized, so
1301 // their history entries land in the right pipeline position
1302 if(iop_order_list) iop_order_list = dt_ioppr_insert_missing_modules(iop_order_list);
1303 }
1304 else
1305 iop_order_list = dt_ioppr_get_iop_order_list_version(iop_order_version);
1306 }
1307 else if(xmp_version == 3)
1308 {
1309 iop_order_version = DT_IOP_ORDER_LEGACY;
1310
1311 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.iop_order_version"))) != xmpData.end())
1312 {
1313 // All iop-order version before 3 are legacy one. Starting with version 3 we have the first
1314 // attempts to propose the final v3 iop-order.
1315 iop_order_version = pos->toLong() < 3 ? DT_IOP_ORDER_LEGACY : DT_IOP_ORDER_ANSEL_RAW;
1316 iop_order_list = dt_ioppr_get_iop_order_list_version(iop_order_version);
1317 }
1318 else
1320 }
1321 else
1322 {
1323 iop_order_version = DT_IOP_ORDER_LEGACY;
1325 }
1326
1327 // masks
1330
1331 // clean all old masks for this image
1333
1334 // read the masks from the file first so we can add them to the db while reading history entries
1335 if(xmp_version < 3)
1336 mask_entries = read_masks(xmpData, filename, xmp_version);
1337 else
1338 mask_entries_v3 = read_masks_v3(xmpData, filename, xmp_version);
1339
1340 // now add all masks that are not used for cloning. keeping them might be useful.
1341 // TODO: make this configurable? or remove it altogether?
1343
1344 if(xmp_version < 3)
1345 {
1347 }
1348 else
1349 {
1351 {
1353
1355 }
1356 }
1357
1359
1360 // history
1361 int num = 0;
1362 gboolean all_ok = TRUE;
1364
1365 if(xmp_version < 2)
1366 {
1367 std::string &xmpPacket = image->xmpPacket();
1369 if(!history_entries) // didn't work? try super old version with rdf:Bag
1371 }
1372 else if(xmp_version == 2 || xmp_version == 3 || xmp_version == 4 || xmp_version == 5 )
1373 history_entries = read_history_v2(xmpData, filename);
1374 else
1375 {
1376 std::cerr << "error: Xmp schema version " << xmp_version << " in " << filename << " not supported" << std::endl;
1378 return 1;
1379 }
1380
1382
1384 {
1385 fprintf(stderr, "[exif] error deleting history for image %d\n", img->id);
1386 fprintf(stderr, "[exif] %s\n", dt_database_get_last_error());
1387 all_ok = FALSE;
1388 goto end;
1389 }
1390
1392 {
1393 history_entry_t *entry = (history_entry_t *)iter->data;
1394 const int db_num = (xmp_version < 3) ? num : entry->num;
1395
1396 if(entry->blendop_params)
1397 {
1398 if(xmp_version < 3)
1399 {
1400 // check what mask entries belong to this iop and add them to the db
1401 const dt_develop_blend_params_t *blendop_params = (dt_develop_blend_params_t *)entry->blendop_params;
1402 add_mask_entries_to_db(img->id, mask_entries, blendop_params->mask_id);
1403 }
1404 }
1405
1407 entry->params, entry->params_len,
1408 entry->modversion, entry->enabled != 0,
1409 entry->blendop_params, entry->blendop_params_len,
1410 entry->blendop_version, entry->multi_priority,
1411 entry->multi_name ? entry->multi_name : ""))
1412 {
1413 fprintf(stderr, "[exif] error adding history entry for image %d\n", img->id);
1414 fprintf(stderr, "[exif] %s\n", dt_database_get_last_error());
1415 all_ok = FALSE;
1416 goto end;
1417 }
1418
1419 num++;
1420 }
1421
1422 // we now need to create and store the proper iop-order taking into account all multi-instances
1423 // for previous xmp versions.
1424
1425 if(xmp_version < 4)
1426 {
1427 // in this version we had iop-order, use it
1428
1430 {
1431 history_entry_t *entry = (history_entry_t *)iter->data;
1432
1434 memcpy(e->operation, entry->operation, sizeof(e->operation));
1435 e->instance = entry->multi_priority;
1436
1437 if(xmp_version < 3)
1438 {
1439 // prior to v3 there was no iop-order, all multi instances where grouped, use the multièpriority
1440 // to restore the order.
1441 GList *base_order = dt_ioppr_get_iop_order_link(iop_order_list, entry->operation, -1);
1442
1443 if(base_order)
1444 e->o.iop_order_f = ((dt_iop_order_entry_t *)(base_order->data))->o.iop_order_f
1445 - entry->multi_priority / 100.0f;
1446 else if(!dt_deprecated(entry->operation))
1447 {
1449 "[exif] cannot get iop-order for module '%s', XMP may be corrupted\n",
1450 entry->operation);
1451 g_list_free_full(iop_order_list, dt_free_gpointer);
1452 iop_order_list = NULL;
1458 dt_free(e);
1459 return 1;
1460 }
1461 }
1462 else
1463 {
1464 // otherwise use the iop_order for the entry
1465 e->o.iop_order_f = entry->iop_order; // legacy iop-order is used to insert item at the right location
1466 }
1467
1468 // remove a current entry from the iop-order list if found as it will be replaced, possibly with another iop-order
1469 // with a new item in the history.
1470
1471 GList *link = dt_ioppr_get_iop_order_link(iop_order_list, e->operation, e->instance);
1472 if(link) iop_order_list = g_list_delete_link(iop_order_list, link);
1473
1474 iop_order_list = g_list_append(iop_order_list, e);
1475 }
1476
1477 // and finally reorder the full list based on the iop-order
1478
1479 iop_order_list = g_list_sort(iop_order_list, dt_sort_iop_list_by_order_f);
1480 }
1481
1482 // if masks have been read, create a mask manager entry in history
1483 if(xmp_version < 3)
1484 {
1486
1487 if(num_masks > 0)
1488 {
1489 // make room for mask_manager entry
1491 {
1492 fprintf(stderr, "[exif] error shifting history nums for image %d\n", img->id);
1493 all_ok = FALSE;
1494 goto end;
1495 }
1496 if(!dt_history_repository_write_item(img->id, 0, "mask_manager", NULL, 0, 1, 0, NULL, 0, 0, 0, ""))
1497 {
1498 fprintf(stderr, "[exif] error adding mask history entry for image %d\n", img->id);
1499 fprintf(stderr, "[exif] %s\n", dt_database_get_last_error());
1500 all_ok = FALSE;
1501 goto end;
1502 }
1503
1504 num++;
1505 }
1506 }
1507
1508 // we shouldn't change history_end when no history was read!
1509 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.history_end"))) != xmpData.end() && num > 0)
1510 {
1511 int history_end = MIN(pos->toLong(), num);
1512 if(num_masks > 0) history_end++;
1513 if((history_end < 1) && preset_applied) preset_applied = -1;
1514 if(!dt_history_repository_set_end(img->id, history_end))
1515 {
1516 fprintf(stderr, "[exif] error writing history_end for image %d\n", img->id);
1517 fprintf(stderr, "[exif] %s\n", dt_database_get_last_error());
1518 all_ok = FALSE;
1519 goto end;
1520 }
1521 }
1522 else
1523 {
1525 const int32_t history_end = dt_history_repository_get_next_num(img->id);
1526 if(!dt_history_repository_set_end(img->id, history_end))
1527 {
1528 fprintf(stderr, "[exif] error writing history_end for image %d\n", img->id);
1529 fprintf(stderr, "[exif] %s\n", dt_database_get_last_error());
1530 all_ok = FALSE;
1531 goto end;
1532 }
1533 }
1534 if(!dt_ioppr_write_iop_order_list(iop_order_list, img->id))
1535 {
1536 fprintf(stderr, "[exif] error writing iop_list for image %d\n", img->id);
1537 fprintf(stderr, "[exif] %s\n", dt_database_get_last_error());
1538 all_ok = FALSE;
1539 goto end;
1540 }
1541
1542 end:
1543
1544 read_xmp_timestamps(xmpData, img, xmp_version);
1545
1546 // set or clear bit in image struct. ONLY set if the Xmp.darktable.auto_presets_applied was 1
1547 // AND there was a history in xmp
1548 if(preset_applied > 0)
1549 {
1551 }
1552 else
1553 {
1554 // not found for old or buggy xmp where it was found but history was 0
1556
1557 if(preset_applied < 0)
1558 {
1559 fprintf(stderr,"[exif] dt_exif_xmp_read for %s, id %i found auto_presets_applied but there was no history\n",filename,img->id);
1560 }
1561 }
1562
1563 g_list_free_full(iop_order_list, dt_free_gpointer);
1564 iop_order_list = NULL;
1570
1571 if(all_ok)
1572 {
1574
1575 // history_hash (current only)
1576 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.history_current_hash"))) != xmpData.end())
1577 {
1578 int hash_len = 0;
1579 // one temporary, measured once: toString() called twice returns two distinct
1580 // strings, so the length came from a different object than the pointer did --
1581 // same contents, so it worked, but nothing said it had to
1582 const std::string hash_str = pos->toString();
1583 unsigned char *decoded = dt_exif_xmp_decode(hash_str.c_str(), hash_str.size(), &hash_len);
1584 if(decoded && hash_len == (int)sizeof(uint64_t))
1585 {
1586 uint64_t be_hash = 0;
1587 memcpy(&be_hash, decoded, sizeof(be_hash));
1589 }
1591 }
1592 }
1593 else
1594 {
1595 std::cerr << "[exif] error reading history from '" << filename << "'" << std::endl;
1597 return 1;
1598 }
1599
1600 }
1601 catch(const std::exception &e)
1602 {
1603 // actually nobody's interested in that if the file doesn't exist:
1604 // std::string s(e.what());
1605 // std::cerr << "[exiv2] " << filename << ": " << s << std::endl;
1606 return 1;
1607 }
1608 return 0;
1609}
1610
1611// add history metadata to XmpData
1612typedef struct _xmp_mask_ctx_t
1613{
1614 Exiv2::XmpData *xmpData;
1615 int num;
1617
1618static void _xmp_append_mask(void *user_data, const int mask_num, const int mask_id,
1619 const int mask_type, const char *mask_name, const int mask_version,
1620 const void *points, const int points_len, const int mask_nb,
1621 const void *source, const int source_len)
1622{
1623 _xmp_mask_ctx_t *ctx = (_xmp_mask_ctx_t *)user_data;
1624 Exiv2::XmpData &xmpData = *ctx->xmpData;
1625 char key[1024];
1626 const int num = ctx->num;
1627
1628 char *mask_d = dt_exif_xmp_encode((const unsigned char *)points, points_len, NULL);
1629 char *mask_src = dt_exif_xmp_encode((const unsigned char *)source, source_len, NULL);
1630
1631 snprintf(key, sizeof(key), "Xmp.darktable.masks_history[%d]/darktable:mask_num", num);
1632 xmpData[key] = mask_num;
1633 snprintf(key, sizeof(key), "Xmp.darktable.masks_history[%d]/darktable:mask_id", num);
1634 xmpData[key] = mask_id;
1635 snprintf(key, sizeof(key), "Xmp.darktable.masks_history[%d]/darktable:mask_type", num);
1636 xmpData[key] = mask_type;
1637 snprintf(key, sizeof(key), "Xmp.darktable.masks_history[%d]/darktable:mask_name", num);
1638 xmpData[key] = mask_name;
1639 snprintf(key, sizeof(key), "Xmp.darktable.masks_history[%d]/darktable:mask_version", num);
1640 xmpData[key] = mask_version;
1641 snprintf(key, sizeof(key), "Xmp.darktable.masks_history[%d]/darktable:mask_points", num);
1642 xmpData[key] = mask_d;
1643 snprintf(key, sizeof(key), "Xmp.darktable.masks_history[%d]/darktable:mask_nb", num);
1644 xmpData[key] = mask_nb;
1645 snprintf(key, sizeof(key), "Xmp.darktable.masks_history[%d]/darktable:mask_src", num);
1646 xmpData[key] = mask_src;
1647
1648 dt_free(mask_d);
1649 dt_free(mask_src);
1650
1651 ctx->num++;
1652}
1653
1655{
1656 Exiv2::XmpData *xmpData;
1657 int num;
1659
1660static void _xmp_append_history(void *user_data, const int32_t row_imgid, const int hist_num,
1661 const int modversion, const char *operation,
1662 const void *op_params, const int params_len,
1663 const gboolean enabled, const void *blendop_params_blob,
1664 const int blendop_params_len, const int blendop_version,
1665 const int multi_priority, const char *multi_name,
1666 const char *preset_name)
1667{
1669 Exiv2::XmpData &xmpData = *ctx->xmpData;
1670 char key[1024];
1671 const int num = ctx->num;
1672
1673 if(IS_NULL_PTR(operation)) return; // no op is fatal.
1674
1675 char *params = dt_exif_xmp_encode((const unsigned char *)op_params, params_len, NULL);
1676
1677 snprintf(key, sizeof(key), "Xmp.darktable.history[%d]/darktable:num", num);
1678 xmpData[key] = hist_num;
1679 snprintf(key, sizeof(key), "Xmp.darktable.history[%d]/darktable:operation", num);
1680 xmpData[key] = operation;
1681 snprintf(key, sizeof(key), "Xmp.darktable.history[%d]/darktable:enabled", num);
1682 xmpData[key] = enabled;
1683 snprintf(key, sizeof(key), "Xmp.darktable.history[%d]/darktable:modversion", num);
1684 xmpData[key] = modversion;
1685 snprintf(key, sizeof(key), "Xmp.darktable.history[%d]/darktable:params", num);
1686 xmpData[key] = params;
1687 snprintf(key, sizeof(key), "Xmp.darktable.history[%d]/darktable:multi_name", num);
1688 xmpData[key] = multi_name ? multi_name : "";
1689 snprintf(key, sizeof(key), "Xmp.darktable.history[%d]/darktable:multi_priority", num);
1690 xmpData[key] = multi_priority;
1691
1693 {
1694 // this shouldn't fail in general, but reading is robust enough to allow it,
1695 // and flipping images from LT will result in this being left out
1696 char *blendop_params = dt_exif_xmp_encode((const unsigned char *)blendop_params_blob, blendop_params_len, NULL);
1697 snprintf(key, sizeof(key), "Xmp.darktable.history[%d]/darktable:blendop_version", num);
1698 xmpData[key] = blendop_version;
1699 snprintf(key, sizeof(key), "Xmp.darktable.history[%d]/darktable:blendop_params", num);
1700 xmpData[key] = blendop_params;
1701 dt_free(blendop_params);
1702 }
1703
1704 dt_free(params);
1705
1706 ctx->num++;
1707}
1708
1709static void dt_set_xmp_dt_history(Exiv2::XmpData &xmpData, const int32_t imgid, int history_end)
1710{
1711 // masks history:
1712 int num = 1;
1713
1714 // create an array:
1715 Exiv2::XmpTextValue tvm("");
1716 tvm.setXmpArrayType(Exiv2::XmpValue::xaSeq);
1717 xmpData.add(Exiv2::XmpKey("Xmp.darktable.masks_history"), &tvm);
1718 _xmp_mask_ctx_t mask_ctx = { &xmpData, num };
1720 num = mask_ctx.num;
1721
1722 // history stack:
1723 num = 1;
1724
1725 // create an array:
1726 Exiv2::XmpTextValue tv("");
1727 tv.setXmpArrayType(Exiv2::XmpValue::xaSeq);
1728 xmpData.add(Exiv2::XmpKey("Xmp.darktable.history"), &tv);
1729 _xmp_history_ctx_t hist_ctx = { &xmpData, num };
1731 num = hist_ctx.num;
1732 if(history_end == -1) history_end = num - 1;
1733 else history_end = MIN(history_end, num - 1); // safeguard for some old buggy libraries
1734 xmpData["Xmp.darktable.history_end"] = history_end;
1735}
1736
1737// add timestamps to XmpData.
1738static void set_xmp_timestamps(Exiv2::XmpData &xmpData, const int32_t imgid)
1739{
1740 static const char *keys[] =
1741 {
1742 "Xmp.darktable.import_timestamp",
1743 "Xmp.darktable.change_timestamp",
1744 "Xmp.darktable.export_timestamp",
1745 "Xmp.darktable.print_timestamp"
1746 };
1747 static const guint n_keys = G_N_ELEMENTS(keys);
1748 dt_remove_xmp_keys(xmpData, keys, n_keys);
1749
1752 {
1753 if(ts.has_import) xmpData["Xmp.darktable.import_timestamp"] = ts.import_timestamp;
1754 if(ts.has_change) xmpData["Xmp.darktable.change_timestamp"] = ts.change_timestamp;
1755 if(ts.has_export) xmpData["Xmp.darktable.export_timestamp"] = ts.export_timestamp;
1756 if(ts.has_print) xmpData["Xmp.darktable.print_timestamp"] = ts.print_timestamp;
1757 }
1758}
1759
1760GTimeSpan _convert_unix_to_gtimespan(const time_t unix)
1761{
1763 if(gdt)
1764 {
1767 return gts;
1768 }
1769 return 0;
1770}
1771
1772// read timestamps from XmpData
1773void read_xmp_timestamps(Exiv2::XmpData &xmpData, dt_image_t *img, const int xmp_version)
1774{
1775 Exiv2::XmpData::iterator pos;
1776
1777 // Do not read for import_ts. It must be updated at each import.
1778 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.change_timestamp"))) != xmpData.end())
1779 {
1780 if(xmp_version > 5)
1781 img->change_timestamp = pos->toLong();
1782 else if(pos->toLong() >= 1)
1783 img->change_timestamp = _convert_unix_to_gtimespan(pos->toLong());
1784 }
1785 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.export_timestamp"))) != xmpData.end())
1786 {
1787 if(xmp_version > 5)
1788 img->export_timestamp = pos->toLong();
1789 else if(pos->toLong() >= 1)
1790 img->export_timestamp = _convert_unix_to_gtimespan(pos->toLong());
1791 }
1792 if((pos = xmpData.findKey(Exiv2::XmpKey("Xmp.darktable.print_timestamp"))) != xmpData.end())
1793 {
1794 if(xmp_version > 5)
1795 img->print_timestamp = pos->toLong();
1796 else if(pos->toLong() >= 1)
1797 img->print_timestamp = _convert_unix_to_gtimespan(pos->toLong());
1798 }
1799}
1800
1801static void dt_remove_xmp_exif_geotag(Exiv2::XmpData &xmpData)
1802{
1803 static const char *keys[] =
1804 {
1805 "Xmp.exif.GPSVersionID",
1806 "Xmp.exif.GPSLongitude",
1807 "Xmp.exif.GPSLatitude",
1808 "Xmp.exif.GPSAltitudeRef",
1809 "Xmp.exif.GPSAltitude"
1810 };
1811 static const guint n_keys = G_N_ELEMENTS(keys);
1812 dt_remove_xmp_keys(xmpData, keys, n_keys);
1813}
1814
1815static void dt_set_xmp_exif_geotag(Exiv2::XmpData &xmpData, double longitude, double latitude, double altitude)
1816{
1818 if(!isnan(longitude) && !isnan(latitude))
1819 {
1820 char long_dir = 'E', lat_dir = 'N';
1821 if(longitude < 0) long_dir = 'W';
1822 if(latitude < 0) lat_dir = 'S';
1823
1824 longitude = fabs(longitude);
1825 latitude = fabs(latitude);
1826
1827 int long_deg = (int)floor(longitude);
1828 int lat_deg = (int)floor(latitude);
1829 double long_min = (longitude - (double)long_deg) * 60.0;
1830 double lat_min = (latitude - (double)lat_deg) * 60.0;
1831
1832 char *str = (char *)g_malloc(G_ASCII_DTOSTR_BUF_SIZE);
1833
1835 gchar *long_str = g_strdup_printf("%d,%s%c", long_deg, str, long_dir);
1837 gchar *lat_str = g_strdup_printf("%d,%s%c", lat_deg, str, lat_dir);
1838
1839 xmpData["Xmp.exif.GPSVersionID"] = "2.2.0.0";
1840 xmpData["Xmp.exif.GPSLongitude"] = long_str;
1841 xmpData["Xmp.exif.GPSLatitude"] = lat_str;
1844 dt_free(str);
1845 }
1846 if(!isnan(altitude))
1847 {
1848 xmpData["Xmp.exif.GPSAltitudeRef"] = (altitude < 0) ? "1" : "0";
1849
1850 long ele_dm = (int)floor(fabs(10.0 * altitude));
1851 gchar *ele_str = g_strdup_printf("%ld/10", ele_dm);
1852 xmpData["Xmp.exif.GPSAltitude"] = ele_str;
1854 }
1855}
1856
1858{
1859 Exiv2::XmpData *xmpData;
1860 gboolean export_flag;
1862
1863static void _xmp_append_metadata(void *user_data, const int keyid, const char *value)
1864{
1866 Exiv2::XmpData &xmpData = *ctx->xmpData;
1867
1868 if(ctx->export_flag && (dt_metadata_get_type(keyid) != DT_METADATA_TYPE_INTERNAL))
1869 {
1870 const gchar *name = dt_metadata_get_name(keyid);
1871 gchar *setting = dt_util_dstrcat(NULL, "plugins/lighttable/metadata/%s_flag", name);
1872 const uint32_t flag = dt_conf_get_int(setting);
1875 xmpData[dt_metadata_get_key(keyid)] = value;
1876 }
1877 else
1878 xmpData[dt_metadata_get_key(keyid)] = value;
1879}
1880
1881static void _xmp_append_colorlabel(void *user_data, const int color)
1882{
1883 Exiv2::Value *v = (Exiv2::Value *)user_data;
1884 char val[2048];
1885 snprintf(val, sizeof(val), "%d", color);
1886 v->read(val);
1887}
1888
1889static void dt_set_xmp_dt_metadata(Exiv2::XmpData &xmpData, const int32_t imgid, const gboolean export_flag)
1890{
1891 // metadata
1892 _xmp_metadata_ctx_t meta_ctx = { &xmpData, export_flag };
1894
1895 // color labels
1896 std::unique_ptr<Exiv2::Value> v(Exiv2::Value::create(Exiv2::xmpSeq)); // or xmpBag or xmpAlt.
1897
1898 /* Already initialized v = Exiv2::Value::create(Exiv2::xmpSeq); // or xmpBag or xmpAlt.*/
1900 if(v->count() > 0) xmpData.add(Exiv2::XmpKey("Xmp.darktable.colorlabels"), v.get());
1901}
1902
1903// helper to create an xmp data thing. throws exiv2 exceptions if stuff goes wrong.
1904static void _exif_xmp_append_history_hash(Exiv2::XmpData &xmpData, const int32_t imgid,
1905 const dt_image_t *image)
1906{
1907 const dt_image_t *cached = image;
1908 if(IS_NULL_PTR(cached))
1909 cached = dt_image_cache_get(imgid, 'r');
1910
1911 if(cached)
1912 {
1913 if(cached->history_hash != UINT64_MAX)
1914 {
1915 const uint64_t be_hash = GUINT64_TO_BE(cached->history_hash);
1916 char *value = dt_exif_xmp_encode(reinterpret_cast<const unsigned char *>(&be_hash), sizeof(be_hash), NULL);
1917 if(value)
1918 {
1919 xmpData["Xmp.darktable.history_current_hash"] = value;
1920 dt_free(value);
1921 }
1922 }
1923 if(IS_NULL_PTR(image))
1925 }
1926}
1927
1928static void _exif_xmp_read_data(Exiv2::XmpData &xmpData, const int32_t imgid, const dt_image_t *image)
1929{
1930 const int xmp_version = DT_XMP_EXIF_VERSION;
1931 int stars = 1, raw_params = 0, history_end = -1;
1932 double longitude = NAN, latitude = NAN, altitude = NAN;
1933 gchar *filename = NULL;
1934 gchar *iop_order_list = NULL;
1935 GTimeSpan gts = 0;
1936
1937 // get stars and raw params from db
1940 {
1941 filename = row.filename;
1942 stars = row.flags;
1943 raw_params = row.raw_parameters;
1944 if(row.has_longitude) longitude = row.longitude;
1945 if(row.has_latitude) latitude = row.latitude;
1946 if(row.has_altitude) altitude = row.altitude;
1947 history_end = row.history_end;
1948 gts = row.datetime_taken;
1949 }
1950
1951 // The pipe order only matters when history entries are exported. After
1952 // deleting history, avoid rebuilding a default order just to write an empty
1953 // sidecar: the default will be selected again when the image is opened.
1954 dt_iop_order_t iop_order_version = DT_IOP_ORDER_ANSEL_RAW;
1955 if(history_end > 0)
1956 {
1957 iop_order_version = dt_ioppr_get_iop_order_version(imgid);
1958 GList *iop_list = dt_ioppr_get_iop_order_list(imgid, TRUE);
1959
1960 if(iop_order_version == DT_IOP_ORDER_CUSTOM || dt_ioppr_has_multiple_instances(iop_list))
1961 {
1962 iop_order_list = dt_ioppr_serialize_text_iop_order_list(iop_list);
1963 }
1965 iop_list = NULL;
1966 }
1967
1968 // Store datetime_taken as DateTimeOriginal to take into account the user's selected date/time
1971 xmpData["Xmp.exif.DateTimeOriginal"] = exif_datetime;
1972
1973 // We have to erase the old ratings first as exiv2 seems to not change it otherwise.
1974 Exiv2::XmpData::iterator pos = xmpData.findKey(Exiv2::XmpKey("Xmp.xmp.Rating"));
1975 if(pos != xmpData.end()) xmpData.erase(pos);
1976 xmpData["Xmp.xmp.Rating"] = dt_image_get_xmp_rating_from_flags(stars);
1977
1978 // The original file name
1979 if(filename) xmpData["Xmp.xmpMM.DerivedFrom"] = filename;
1980
1981 // timestamps
1982 set_xmp_timestamps(xmpData, imgid);
1983
1984 // GPS data
1985 dt_set_xmp_exif_geotag(xmpData, longitude, latitude, altitude);
1986
1987 // the meta data
1988 dt_set_xmp_dt_metadata(xmpData, imgid, FALSE);
1989
1990 // get tags from db, store in dublin core
1991 std::unique_ptr<Exiv2::Value> v1(Exiv2::Value::create(Exiv2::xmpBag));
1992
1993 std::unique_ptr<Exiv2::Value> v2(Exiv2::Value::create(Exiv2::xmpBag));
1994
1995 GList *tags = dt_tag_get_list(imgid);
1996 try
1997 {
1998 for(GList *tag = tags; tag; tag = g_list_next(tag))
1999 {
2000 v1->read((char *)tag->data);
2001 }
2002 }
2003 catch(...)
2004 {
2006 throw;
2007 }
2008 if(v1->count() > 0) xmpData.add(Exiv2::XmpKey("Xmp.dc.subject"), v1.get());
2010 tags = NULL;
2011
2013 try
2014 {
2016 {
2017 v2->read((char *)hier->data);
2018 }
2019 }
2020 catch(...)
2021 {
2023 throw;
2024 }
2025 if(v2->count() > 0) xmpData.add(Exiv2::XmpKey("Xmp.lr.hierarchicalSubject"), v2.get());
2028 /* TODO: Add tags to IPTC namespace as well */
2029
2030 xmpData["Xmp.darktable.xmp_version"] = xmp_version;
2031 xmpData["Xmp.darktable.raw_params"] = raw_params;
2033 xmpData["Xmp.darktable.auto_presets_applied"] = 1;
2034 else
2035 xmpData["Xmp.darktable.auto_presets_applied"] = 0;
2036 dt_set_xmp_dt_history(xmpData, imgid, history_end);
2037
2038 if(history_end > 0)
2039 {
2040 xmpData["Xmp.darktable.iop_order_version"] = iop_order_version;
2041 if(iop_order_list) xmpData["Xmp.darktable.iop_order_list"] = iop_order_list;
2042 }
2043
2044 _exif_xmp_append_history_hash(xmpData, imgid, image);
2045
2046 dt_free(iop_order_list);
2048}
2049
2050// helper to create an xmp data thing. throws exiv2 exceptions if stuff goes wrong.
2051static void _exif_xmp_read_data_export(Exiv2::XmpData &xmpData, const int32_t imgid, dt_export_metadata_t *metadata)
2052{
2053 const int xmp_version = DT_XMP_EXIF_VERSION;
2054 int stars = 1, raw_params = 0, history_end = -1;
2055 double longitude = NAN, latitude = NAN, altitude = NAN;
2056 gchar *filename = NULL;
2057 GTimeSpan gts = 0;
2058 gchar *iop_order_list = NULL;
2059
2060 // get stars and raw params from db
2063 {
2064 filename = row.filename;
2065 stars = row.flags;
2066 raw_params = row.raw_parameters;
2067 if(row.has_longitude) longitude = row.longitude;
2068 if(row.has_latitude) latitude = row.latitude;
2069 if(row.has_altitude) altitude = row.altitude;
2070 history_end = row.history_end;
2071 gts = row.datetime_taken;
2072 }
2073
2074 // The pipe order only matters when history entries are exported. After
2075 // deleting history, avoid rebuilding a default order just to write an empty
2076 // sidecar: the default will be selected again when the image is opened.
2077 dt_iop_order_t iop_order_version = DT_IOP_ORDER_ANSEL_RAW;
2078 if(history_end > 0)
2079 {
2080 iop_order_version = dt_ioppr_get_iop_order_version(imgid);
2081 GList *iop_list = dt_ioppr_get_iop_order_list(imgid, TRUE);
2082
2083 if(iop_order_version == DT_IOP_ORDER_CUSTOM || dt_ioppr_has_multiple_instances(iop_list))
2084 {
2085 iop_order_list = dt_ioppr_serialize_text_iop_order_list(iop_list);
2086 }
2088 iop_list = NULL;
2089 }
2090
2091 if(metadata->flags & DT_META_METADATA)
2092 {
2093 // Store datetime_taken as DateTimeOriginal to take into account the user's selected date/time
2094 if (!(metadata->flags & DT_META_EXIF))
2095 {
2098 xmpData["Xmp.exif.DateTimeOriginal"] = exif_datetime;
2099 }
2100 // We have to erase the old ratings first as exiv2 seems to not change it otherwise.
2101 Exiv2::XmpData::iterator pos = xmpData.findKey(Exiv2::XmpKey("Xmp.xmp.Rating"));
2102 if(pos != xmpData.end()) xmpData.erase(pos);
2103 xmpData["Xmp.xmp.Rating"] = dt_image_get_xmp_rating_from_flags(stars);
2104
2105 // The original file name
2106 if(filename) xmpData["Xmp.xmpMM.DerivedFrom"] = filename;
2107 }
2108
2109 // GPS data
2110 if (metadata->flags & DT_META_GEOTAG)
2111 dt_set_xmp_exif_geotag(xmpData, longitude, latitude, altitude);
2112 else
2114
2115
2116 // the meta data
2117 if (metadata->flags & DT_META_METADATA)
2118 dt_set_xmp_dt_metadata(xmpData, imgid, TRUE);
2119
2120 // tags
2121 if (metadata->flags & DT_META_TAG)
2122 {
2123 // get tags from db, store in dublin core
2124 std::unique_ptr<Exiv2::Value> v1(Exiv2::Value::create(Exiv2::xmpBag));
2125 GList *tags = dt_tag_get_list_export(imgid, metadata->flags);
2126 try
2127 {
2128 for(GList *tag = tags; tag; tag = g_list_next(tag))
2129 {
2130 v1->read((char *)tag->data);
2131 }
2132 }
2133 catch(...)
2134 {
2136 throw;
2137 }
2138 if(v1->count() > 0) xmpData.add(Exiv2::XmpKey("Xmp.dc.subject"), v1.get());
2140 tags = NULL;
2141 }
2142
2143 if (metadata->flags & DT_META_HIERARCHICAL_TAG)
2144 {
2145 std::unique_ptr<Exiv2::Value> v2(Exiv2::Value::create(Exiv2::xmpBag));
2147 try
2148 {
2150 {
2151 v2->read((char *)hier->data);
2152 }
2153 }
2154 catch(...)
2155 {
2157 throw;
2158 }
2159 if(v2->count() > 0) xmpData.add(Exiv2::XmpKey("Xmp.lr.hierarchicalSubject"), v2.get());
2162 }
2163
2164 if (metadata->flags & DT_META_DT_HISTORY)
2165 {
2166 xmpData["Xmp.darktable.xmp_version"] = xmp_version;
2167 xmpData["Xmp.darktable.raw_params"] = raw_params;
2169 xmpData["Xmp.darktable.auto_presets_applied"] = 1;
2170 else
2171 xmpData["Xmp.darktable.auto_presets_applied"] = 0;
2172 dt_set_xmp_dt_history(xmpData, imgid, history_end);
2173
2174 if(history_end > 0)
2175 {
2176 xmpData["Xmp.darktable.iop_order_version"] = iop_order_version;
2177 if(iop_order_list) xmpData["Xmp.darktable.iop_order_list"] = iop_order_list;
2178 }
2179 _exif_xmp_append_history_hash(xmpData, imgid, NULL);
2180 }
2181
2182 dt_free(iop_order_list);
2184}
2185
2186#if EXIV2_TEST_VERSION(0,27,0)
2187#define ERROR_CODE(a) (static_cast<Exiv2::ErrorCode>((a)))
2188#else
2189#define ERROR_CODE(a) (a)
2190#endif
2191
2192char *dt_exif_xmp_read_string(const int32_t imgid)
2193{
2194 try
2195 {
2196 // Serialize the non-thread-safe exiv2/XMP toolkit (XmpParser::decode()/encode() below) against
2197 // all other exiv2 work. Recursive mutex, so nested helpers re-lock harmlessly.
2198 Lock lock;
2199
2200 char input_filename[PATH_MAX] = { 0 };
2201 gboolean from_cache = FALSE;
2203
2204 // first take over the data from the source image
2205 Exiv2::XmpData xmpData;
2207 {
2208 std::string xmpPacket;
2209
2210 Exiv2::DataBuf buf = Exiv2::readFile(WIDEN(input_filename));
2211#if EXIV2_TEST_VERSION(0,28,0)
2212 xmpPacket.assign(buf.c_str(), buf.size());
2213#else
2214 xmpPacket.assign(reinterpret_cast<char *>(buf.pData_), buf.size_);
2215#endif
2216 Exiv2::XmpParser::decode(xmpData, xmpPacket);
2217 // because XmpSeq or XmpBag are added to the list, we first have
2218 // to remove these so that we don't end up with a string of duplicates
2219 dt_remove_known_keys(xmpData);
2220 }
2221
2222 // now add whatever we have in the sidecar XMP. this overwrites stuff from the source image
2224 g_strlcat(input_filename, ".xmp", sizeof(input_filename));
2226 {
2227 Exiv2::XmpData sidecarXmpData;
2228 std::string xmpPacket;
2229
2230 Exiv2::DataBuf buf = Exiv2::readFile(WIDEN(input_filename));
2231#if EXIV2_TEST_VERSION(0,28,0)
2232 xmpPacket.assign(buf.c_str(), buf.size());
2233#else
2234 xmpPacket.assign(reinterpret_cast<char *>(buf.pData_), buf.size_);
2235#endif
2236 Exiv2::XmpParser::decode(sidecarXmpData, xmpPacket);
2237
2238 for(Exiv2::XmpData::const_iterator it = sidecarXmpData.begin(); it != sidecarXmpData.end(); ++it)
2239 xmpData.add(*it);
2240 }
2241
2242 dt_remove_known_keys(xmpData); // is this needed?
2243
2244 // last but not least attach what we have in DB to the XMP. in theory that should be
2245 // the same as what we just copied over from the sidecar file, but you never know ...
2246 _exif_xmp_read_data(xmpData, imgid, NULL);
2247
2248 // serialize the xmp data and output the xmp packet
2249 std::string xmpPacket;
2250 if(Exiv2::XmpParser::encode(xmpPacket, xmpData,
2251 Exiv2::XmpParser::useCompactFormat | Exiv2::XmpParser::omitPacketWrapper) != 0)
2252 {
2253 throw Exiv2::Error(ERROR_CODE(1), "[xmp_write] failed to serialize xmp data");
2254 }
2255 return g_strdup(xmpPacket.c_str());
2256 }
2257 catch(const std::exception &e)
2258 {
2259 std::cerr << "[xmp_read_blob] caught exiv2 exception '" << e.what() << "'\n";
2260 return NULL;
2261 }
2262}
2263
2264static void dt_remove_xmp_key(Exiv2::XmpData &xmp, const char *key)
2265{
2266 try
2267 {
2268 Exiv2::XmpData::iterator pos = xmp.findKey(Exiv2::XmpKey(key));
2269 if (pos != xmp.end())
2270 xmp.erase(pos);
2271 }
2272 catch(const std::exception &e)
2273 {
2274 }
2275}
2276
2277static void _remove_xmp_keys(Exiv2::XmpData &xmpData, const char *key)
2278{
2279 try
2280 {
2281 const std::string needle = key;
2282 for(Exiv2::XmpData::iterator i = xmpData.begin(); i != xmpData.end();)
2283 {
2284 if(i->key().compare(0, needle.length(), needle) == 0)
2285 i = xmpData.erase(i);
2286 else
2287 ++i;
2288 }
2289 }
2290 catch(const std::exception &e)
2291 {
2292 }
2293}
2294
2295static void dt_remove_exif_key(Exiv2::ExifData &exif, const char *key)
2296{
2297 try
2298 {
2299 Exiv2::ExifData::iterator pos = exif.findKey(Exiv2::ExifKey(key));
2300 if (pos != exif.end())
2301 exif.erase(pos);
2302 }
2303 catch(const std::exception &e)
2304 {
2305 }
2306}
2307
2308static void dt_remove_iptc_key(Exiv2::IptcData &iptc, const char *key)
2309{
2310 try
2311 {
2312 Exiv2::IptcData::iterator pos;
2313 while((pos = iptc.findKey(Exiv2::IptcKey(key))) != iptc.end())
2314 iptc.erase(pos);
2315 }
2316 catch(const std::exception &e)
2317 {
2318 }
2319}
2320
2321int dt_exif_xmp_attach_export(const int32_t imgid, const char *filename, void *metadata)
2322{
2324 try
2325 {
2326 // Serialize the whole exiv2 region: this function mixes readMetadata(), XmpParser::decode() and
2327 // writeMetadata(), all of which touch the non-thread-safe exiv2/XMP toolkit and must not run
2328 // concurrently with other exiv2 work. The mutex is recursive, so the nested
2329 // read_metadata_threadsafe() calls (and any metadata read during variable expansion) re-lock
2330 // harmlessly.
2331 Lock lock;
2332
2333 char input_filename[PATH_MAX] = { 0 };
2334 gboolean from_cache = TRUE;
2336
2337 std::unique_ptr<Exiv2::Image> img(Exiv2::ImageFactory::open(WIDEN(filename)));
2338 // unfortunately it seems we have to read the metadata, to not erase the exif (which we just wrote).
2339 // will make export slightly slower, oh well.
2340 // img->clearXmpPacket();
2342
2343 try
2344 {
2345 // initialize XMP and IPTC data with the one from the original file
2346 std::unique_ptr<Exiv2::Image> input_image(Exiv2::ImageFactory::open(WIDEN(input_filename)));
2347 if(input_image.get() != 0)
2348 {
2350 img->setIptcData(input_image->iptcData());
2351 img->setXmpData(input_image->xmpData());
2352 }
2353 }
2354 catch(const std::exception &e)
2355 {
2356 std::cerr << "[xmp_attach] " << input_filename << ": caught exiv2 exception '" << e.what() << "'\n";
2357 }
2358
2359 Exiv2::XmpData &xmpData = img->xmpData();
2360
2361 // now add whatever we have in the sidecar XMP. this overwrites stuff from the source image
2363 g_strlcat(input_filename, ".xmp", sizeof(input_filename));
2365 {
2366 Exiv2::XmpData sidecarXmpData;
2367 std::string xmpPacket;
2368
2369 Exiv2::DataBuf buf = Exiv2::readFile(WIDEN(input_filename));
2370#if EXIV2_TEST_VERSION(0,28,0)
2371 xmpPacket.assign(buf.c_str(), buf.size());
2372#else
2373 xmpPacket.assign(reinterpret_cast<char *>(buf.pData_), buf.size_);
2374#endif
2375 Exiv2::XmpParser::decode(sidecarXmpData, xmpPacket);
2376
2377 for(Exiv2::XmpData::const_iterator it = sidecarXmpData.begin(); it != sidecarXmpData.end(); ++it)
2378 xmpData.add(*it);
2379 }
2380
2381 dt_remove_known_keys(xmpData); // is this needed?
2382
2383 {
2384 // We also want to make sure to not have some tags that might
2385 // have come in from XMP files created by digikam or similar
2386 static const char *keys[] = {
2387 "Xmp.tiff.Orientation"
2388 };
2389 static const guint n_keys = G_N_ELEMENTS(keys);
2390 dt_remove_xmp_keys(xmpData, keys, n_keys);
2391 }
2392
2393 // last but not least attach what we have in DB to the XMP. in theory that should be
2394 // the same as what we just copied over from the sidecar file, but you never know ...
2395 // make sure to remove all geotags if necessary
2396 if(m)
2397 {
2398 Exiv2::ExifData exifOldData;
2399 Exiv2::ExifData &exifData = img->exifData();
2400 if(!(m->flags & DT_META_EXIF))
2401 {
2402 for(Exiv2::ExifData::const_iterator i = exifData.begin(); i != exifData.end() ; ++i)
2403 {
2404 exifOldData[i->key()] = i->value();
2405 }
2406 img->clearExifData();
2407 }
2408
2409 _exif_xmp_read_data_export(xmpData, imgid, m);
2410
2411 Exiv2::IptcData &iptcData = img->iptcData();
2412
2413 if(!(m->flags & DT_META_GEOTAG))
2415 // calculated metadata
2416 dt_variables_params_t *params;
2417 dt_variables_params_init(&params);
2418 params->filename = input_filename;
2419 params->jobcode = "infos";
2420 params->sequence = 0;
2421 params->imgid = imgid;
2422
2423 dt_variables_set_tags_flags(params, m->flags);
2424 for (GList *tags = m->list; tags; tags = g_list_next(tags))
2425 {
2426 gchar *tagname = (gchar *)tags->data;
2427 tags = g_list_next(tags);
2428 if (IS_NULL_PTR(tags)) break;
2429 gchar *formula = (gchar *)tags->data;
2430 if (formula[0])
2431 {
2432 if(!(m->flags & DT_META_EXIF) && (formula[0] == '=') && g_str_has_prefix(tagname, "Exif."))
2433 {
2434 // remove this specific exif
2435 Exiv2::ExifData::const_iterator pos;
2436 if(dt_exif_read_exif_tag(exifOldData, &pos, tagname))
2437 {
2438 exifData[tagname] = pos->value();
2439 }
2440 }
2441 else
2442 {
2443 gchar *result = dt_variables_expand(params, formula, FALSE);
2444 if(result && result[0])
2445 {
2446 if(g_str_has_prefix(tagname, "Xmp."))
2447 {
2448 const char *type = _exif_get_exiv2_tag_type(tagname);
2449 // if xmpBag or xmpSeq, split the list when necessary
2450 // else provide the string as is (can be a list of strings)
2451 if(!g_strcmp0(type, "XmpBag") || !g_strcmp0(type, "XmpSeq"))
2452 {
2453 char *tuple = g_strrstr(result, ",");
2454 while(tuple)
2455 {
2456 tuple[0] = '\0';
2457 tuple++;
2458 xmpData[tagname] = tuple;
2459 tuple = g_strrstr(result, ",");
2460 }
2461 }
2462 xmpData[tagname] = result;
2463 }
2464 else if(g_str_has_prefix(tagname, "Iptc."))
2465 {
2466 const char *type = _exif_get_exiv2_tag_type(tagname);
2467 if(!g_strcmp0(type, "String-R"))
2468 {
2469 // clean up the original tags before giving new values
2470 dt_remove_iptc_key(iptcData, tagname);
2471 // convert the input list (separator ", ") into different tags
2472 // FIXME if an element of the list contains a ", " it is not correctly exported
2473 Exiv2::IptcKey key(tagname);
2474 Exiv2::Iptcdatum id(key);
2475 gchar **values = g_strsplit(result, ", ", 0);
2476 if(values)
2477 {
2478 gchar **entry = values;
2479 while (*entry)
2480 {
2481 char *e = g_strstrip(*entry);
2482 if(*e)
2483 {
2484 id.setValue(e);
2485 iptcData.add(id);
2486 }
2487 entry++;
2488 }
2489 }
2490 g_strfreev(values);
2491 }
2492 else iptcData[tagname] = result;
2493 }
2494 else if(g_str_has_prefix(tagname, "Exif."))
2495 {
2496 const char *type = _exif_get_exiv2_tag_type(tagname);
2497 if((!g_strcmp0(type, "Rational") || !g_strcmp0(type, "SRational")) &&
2498 (strstr(result, "/") == NULL))
2499 {
2500 float float_value = (float)std::atof(result);
2501 if(!isnan(float_value))
2502 {
2503 dt_free(result);
2504 int int_value = (int)float_value;
2505 int divisor = 1;
2506 while(fabs(float_value - int_value) > 0.000001)
2507 {
2508 divisor *= 10;
2509 float_value *= 10.0;
2510 int_value = (int)float_value;
2511 }
2512 result = g_strdup_printf("%d/%d", (int)float_value, divisor);
2513 }
2514 }
2515 exifData[tagname] = result;
2516 }
2517 }
2518 dt_free(result);
2519 }
2520 }
2521 else
2522 {
2523 if (g_str_has_prefix(tagname, "Xmp."))
2524 dt_remove_xmp_key(xmpData, tagname);
2525 else if (g_str_has_prefix(tagname, "Exif."))
2526 dt_remove_exif_key(exifData, tagname);
2527 else if (g_str_has_prefix(tagname, "Iptc."))
2528 dt_remove_iptc_key(iptcData, tagname);
2529 }
2530 }
2532 }
2533
2534 try
2535 {
2536 img->writeMetadata();
2537 }
2538 catch(Exiv2::AnyError &e)
2539 {
2540#if EXIV2_TEST_VERSION(0,27,0)
2541 if(e.code() == Exiv2::ErrorCode::kerTooLargeJpegSegment)
2542#else
2543 if(e.code() == 37)
2544#endif
2545 {
2546 _remove_xmp_keys(xmpData, "Xmp.darktable.history");
2547 _remove_xmp_keys(xmpData, "Xmp.darktable.masks_history");
2548 _remove_xmp_keys(xmpData, "Xmp.darktable.auto_presets_applied");
2549 _remove_xmp_keys(xmpData, "Xmp.darktable.iop_order");
2550 try
2551 {
2552 img->writeMetadata();
2553 }
2554 catch(const std::exception &e2)
2555 {
2556 std::cerr << "[dt_exif_xmp_attach_export] without history " << filename << ": caught exiv2 exception '" << e2.what() << "'\n";
2557 return -1;
2558 }
2559 }
2560 else
2561 throw;
2562 }
2563 catch(const std::exception &e)
2564 {
2565 std::cerr << "[dt_exif_xmp_attach_export] " << filename << ": caught exception '" << e.what() << "'\n";
2566 return -1;
2567 }
2568 return 0;
2569 }
2570 catch(const std::exception &e)
2571 {
2572 std::cerr << "[dt_exif_xmp_attach_export] " << filename << ": caught exiv2 exception '" << e.what() << "'\n";
2573 return -1;
2574 }
2575}
2576
2577// write xmp sidecar file:
2578int dt_exif_xmp_write_with_imgpath(const dt_image_t *image, const char *filename,
2579 const char *imgpath)
2580{
2581 // refuse to write sidecar for non-existent image:
2582 if(IS_NULL_PTR(image) || image->id <= 0) return 1;
2583 if(IS_NULL_PTR(imgpath) || !*imgpath) return 1;
2585 const int32_t imgid = image->id;
2586
2587 try
2588 {
2589 // The Adobe XMP toolkit behind Exiv2::XmpParser::decode()/encode() keeps process-global
2590 // state and is NOT thread-safe. Sidecar writes run on the worker thread pool, so several
2591 // imports/writes can hit dt_exif_xmp_write_with_imgpath() at once (and race the locked
2592 // readMetadata() in dt_exif_read()), corrupting the heap -> SIGABRT in free(). Serialize the
2593 // whole exiv2 region on the same (recursive) mutex read_metadata_threadsafe() uses.
2594 Lock lock;
2595
2596 Exiv2::XmpData xmpData;
2597 std::string xmpPacket;
2598 char *checksum_old = NULL;
2599 if(g_file_test(filename, G_FILE_TEST_EXISTS))
2600 {
2601 // we want to avoid writing the sidecar file if it didn't change to avoid issues when using the same images
2602 // from different computers. sample use case: images on NAS, several computers using them NOT AT THE SAME TIME and
2603 // the xmp crawler is used to find changed sidecars.
2604 errno = 0;
2605 size_t end;
2606 unsigned char *content = (unsigned char*)dt_read_file(filename, &end);
2607 if(content)
2608 {
2609 if(end > 1000000)
2610 {
2612 _("The XMP file \n'%s'\n weighs %.2f MB. Writing it will take some time."),
2613 filename, (float)end / 1000000);
2614 fprintf(stdout, "The XMP file '%s' weighs %.2f MB. Writing it will take some time.\n", filename, (float)end / 1000000);
2615 }
2616
2618 dt_free(content);
2619 }
2620 else
2621 {
2622 fprintf(stderr, "cannot read xmp file '%s': '%s'\n", filename, strerror(errno));
2623 dt_metadata_notify(DT_METADATA_NOTICE_MESSAGE, _("cannot read xmp file '%s': '%s'"),
2624 filename, strerror(errno));
2625 }
2626
2627 Exiv2::DataBuf buf = Exiv2::readFile(WIDEN(filename));
2628#if EXIV2_TEST_VERSION(0,28,0)
2629 xmpPacket.assign(buf.c_str(), buf.size());
2630#else
2631 xmpPacket.assign(reinterpret_cast<char *>(buf.pData_), buf.size_);
2632#endif
2633 Exiv2::XmpParser::decode(xmpData, xmpPacket);
2634 // because XmpSeq or XmpBag are added to the list, we first have
2635 // to remove these so that we don't end up with a string of duplicates
2636 dt_remove_known_keys(xmpData);
2637 }
2638
2639 // initialize xmp data:
2640 _exif_xmp_read_data(xmpData, imgid, image);
2641
2642 // serialize the xmp data and output the xmp packet
2643 if(Exiv2::XmpParser::encode(xmpPacket, xmpData,
2644 Exiv2::XmpParser::useCompactFormat | Exiv2::XmpParser::omitPacketWrapper) != 0)
2645 {
2646 throw Exiv2::Error(ERROR_CODE(1), "[xmp_write] failed to serialize xmp data");
2647 }
2648
2649 // hash the new data and compare it to the old hash (if applicable)
2650 const char *xml_header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
2651 gboolean write_sidecar = TRUE;
2652 if(checksum_old)
2653 {
2655 if(checksum)
2656 {
2657 g_checksum_update(checksum, (unsigned char*)xml_header, -1);
2658 g_checksum_update(checksum, (unsigned char*)xmpPacket.c_str(), -1);
2662 }
2664 }
2665
2666 if(write_sidecar)
2667 {
2668 // using std::ofstream isn't possible here -- on Windows it doesn't support Unicode filenames with mingw
2669 errno = 0;
2670 FILE *fout = g_fopen(filename, "wb");
2671 if(fout)
2672 {
2673 fprintf(fout, "%s", xml_header);
2674 fprintf(fout, "%s", xmpPacket.c_str());
2675 fclose(fout);
2676 }
2677 else
2678 {
2679 fprintf(stderr, "cannot write xmp file '%s': '%s'\n", filename, strerror(errno));
2680 dt_metadata_notify(DT_METADATA_NOTICE_MESSAGE, _("cannot write xmp file '%s': '%s'"),
2681 filename, strerror(errno));
2682 return -1;
2683 }
2684 }
2685
2686 return 0;
2687 }
2688 catch(const std::exception &e)
2689 {
2690 std::cerr << "[dt_exif_xmp_write] " << filename << ": caught exiv2 exception '" << e.what() << "'\n";
2691 return -1;
2692 }
2693}
2694
2695// clang-format off
2696// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2697// vim: shiftwidth=2 expandtab tabstop=2 cindent
2698// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2699// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:282
RAII guard over the process-wide exiv2 lock.
void dt_colorlabel_repository_foreach(const int32_t imgid, void(*cb)(void *, const int), void *user_data)
Every colour label of imgid, one call per row, in the order the rows come back.
main.color_labels: which of the five colour labels are set on an image.
const int t
const float v
static dt_aligned_pixel_t sRGB
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
int dt_conf_get_int(const char *name)
void dt_image_path_append_version(const int32_t imgid, char *pathname, size_t pathname_len)
int dt_image_get_xmp_rating_from_flags(const int flags)
void dt_image_full_path(const int32_t imgid, char *pathname, size_t pathname_len, gboolean *from_cache, const char *calling_func)
Get the full path of an image out of the database.
const char darktable_package_string[]
const char * dt_database_get_last_error(void)
Definition database.c:3747
void dt_database_rollback_transaction(void)
Definition database.c:4870
#define dt_database_start_transaction()
Definition database.h:290
#define dt_database_release_transaction()
Definition database.h:291
gboolean dt_datetime_img_to_exif(char *exif, const size_t exif_size, const dt_image_t *img)
Definition datetime.c:255
void dt_datetime_now_to_exif(char *exif)
Definition datetime.c:225
gboolean dt_datetime_gtimespan_to_exif(char *sdt, const size_t sdt_size, const GTimeSpan gts)
Definition datetime.c:405
GTimeSpan dt_datetime_gdatetime_to_gtimespan(GDateTime *gdt)
Definition datetime.c:459
#define DT_DATETIME_LENGTH
Definition datetime.h:38
#define DT_DATETIME_EXIF_LENGTH
Definition datetime.h:39
int dt_deprecated(const char *op)
Modules without a proper IOP order should throw errors and log, except if they are deprecated definit...
bool dt_exif_read_exif_tag(Exiv2::ExifData &exifData, Exiv2::ExifData::const_iterator *pos, string key)
Read one EXIF tag into pos, FALSE if the image does not carry it.
Definition exif.cc:662
unsigned char * dt_exif_xmp_decode(const char *input, const int len, int *output_len)
Definition exif.cc:1972
bool dt_exif_decode_xmp_data(dt_image_t *img, Exiv2::XmpData &xmpData, int version, bool exif_read)
Apply one XMP packet's metadata, tags and colour labels to img.
Definition exif.cc:414
char * dt_exif_xmp_encode(const unsigned char *input, const int len, int *output_len)
Definition exif.cc:1881
void dt_remove_exif_keys(Exiv2::ExifData &exif, const char *keys[], unsigned int n_keys)
Strip the given EXIF keys from exif, ignoring any that are absent.
Definition exif.cc:375
What a photograph says about itself: the EXIF, IPTC and XMP tags a camera and a cataloguer write,...
The seam between metadata/exif.cc and common/xmp_sidecar.cc.
#define read_metadata_threadsafe(image)
const GList * dt_exif_get_exiv2_taglist()
Definition exif.cc:347
GdkRGBA color[]
Definition geotagging.c:539
void dt_history_repository_foreach_mask_item(const int32_t imgid, dt_history_repository_mask_cb cb, void *user_data)
gboolean dt_history_repository_shift_nums(const int32_t imgid, const int delta)
gboolean dt_history_repository_write_item(const int32_t imgid, const int num, const char *operation, const void *op_params, const int op_params_size, const int module_version, const gboolean enabled, const void *blendop_params, const int blendop_params_size, const int blendop_version, const int multi_priority, const char *multi_name)
gboolean dt_history_repository_write_mask_item(const int32_t imgid, const int num, const int formid, const int form, const char *name, const int version, const void *points, const int points_len, const int points_count, const void *source, const int source_len)
int dt_history_repository_count_mask_items(const int32_t imgid)
gboolean dt_history_repository_delete_masks_history(const int32_t imgid)
gboolean dt_history_repository_delete_history(const int32_t imgid)
gboolean dt_history_repository_set_end(const int32_t imgid, const int32_t history_end)
int32_t dt_history_repository_get_next_num(const int32_t imgid)
void dt_history_repository_foreach_row(const int32_t imgid, dt_history_repository_row_cb cb, void *user_data)
const char flag
Definition image.h:285
@ DT_IMAGE_NO_LEGACY_PRESETS
Definition image.h:131
@ DT_IMAGE_AUTO_PRESETS_APPLIED
Definition image.h:129
dt_image_t * dt_image_cache_get(const int32_t imgid, char mode)
void dt_image_cache_read_release(const dt_image_t *img)
void dt_image_repository_xmp_row_cleanup(dt_image_xmp_row_t *row)
Release what dt_image_repository_get_xmp_row() allocated.
gboolean dt_image_repository_get_timestamps(const int32_t imgid, dt_image_timestamps_t *ts)
Read imgid's four timestamps. FALSE when there is no such image.
gboolean dt_image_repository_get_xmp_row(const int32_t imgid, dt_image_xmp_row_t *row)
Read imgid's sidecar fields. FALSE when there is no such image.
Reading and writing one dt_image_t to and from the library database. The SQL half of what used to be ...
GList * dt_ioppr_get_iop_order_list(int32_t imgid, gboolean sorted)
Load the order list for an image from the DB.
Definition iop_order.c:1100
gboolean dt_ioppr_has_multiple_instances(GList *iop_order_list)
Detect whether multiple instances are grouped for a non-custom order.
Definition iop_order.c:991
GList * dt_ioppr_get_iop_order_list_version(dt_iop_order_t version)
Return the built-in order list for a given version.
Definition iop_order.c:1066
gint dt_sort_iop_list_by_order_f(gconstpointer a, gconstpointer b)
Compare two list nodes holding modules by iop_order.
Definition iop_order.c:939
gboolean dt_ioppr_write_iop_order_list(GList *iop_order_list, const int32_t imgid)
Persist an order list to the DB for a given image.
Definition iop_order.c:1035
char * dt_ioppr_serialize_text_iop_order_list(GList *iop_order_list)
Serialize an order list to a text representation.
Definition iop_order.c:2507
GList * dt_ioppr_deserialize_text_iop_order_list(const char *buf)
Deserialize an order list from a text representation.
Definition iop_order.c:2562
GList * dt_ioppr_insert_missing_modules(GList *iop_order_list)
Insert modules that postdate a deserialized/custom order list.
Definition iop_order.c:773
dt_iop_order_t dt_ioppr_get_iop_order_version(const int32_t imgid)
Fetch the IOP order version stored for an image.
Definition iop_order.c:811
GList * dt_ioppr_get_iop_order_link(GList *iop_order_list, const char *op_name, const int multi_priority)
Find a list link matching an operation and instance.
Definition iop_order.c:875
dt_iop_order_t
Definition iop_order.h:149
@ DT_IOP_ORDER_ANSEL_RAW
Definition iop_order.h:154
@ DT_IOP_ORDER_LEGACY
Definition iop_order.h:151
@ DT_IOP_ORDER_CUSTOM
Definition iop_order.h:150
_lib_location_type_t type
Definition location.c:1
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:65
@ DT_MASKS_NON_CLONE
Definition masks.h:148
@ DT_MASKS_CLONE
Definition masks.h:144
@ DT_MASKS_GROUP
Definition masks.h:143
static void dt_free_gpointer(gpointer ptr)
Definition mem_alloc.h:104
#define dt_free(ptr)
Definition mem_alloc.h:97
char * key
const char * dt_metadata_get_key(const uint32_t keyid)
const char * dt_metadata_get_name(const uint32_t keyid)
GList * dt_metadata_get(const int id, const char *key, uint32_t *count)
int dt_metadata_get_type(const uint32_t keyid)
void dt_metadata_notify(const dt_metadata_notice_t kind, const char *format,...)
Everything this module says to the outside world: messages for the user, and the fact that the tag vo...
@ DT_METADATA_NOTICE_MESSAGE
@ DT_METADATA_FLAG_HIDDEN
Definition metadata.h:79
@ DT_METADATA_FLAG_PRIVATE
Definition metadata.h:80
@ DT_METADATA_TYPE_INTERNAL
Definition metadata.h:65
@ DT_META_HIERARCHICAL_TAG
@ DT_META_TAG
@ DT_META_EXIF
@ DT_META_METADATA
@ DT_META_DT_HISTORY
@ DT_META_GEOTAG
void dt_metadata_repository_foreach(const int32_t imgid, dt_metadata_repository_row_cb cb, void *user_data)
Every metadata row of imgid, in row order.
main.meta_data: the free-text image metadata (title, description, creator, publisher,...
#define PATH_MAX
Definition paths.h:45
const char * name
Definition pdf.h:90
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
char * dt_variables_expand(dt_variables_params_t *params, gchar *source, gboolean iterate)
void dt_variables_params_destroy(dt_variables_params_t *params)
void dt_variables_params_init(dt_variables_params_t **params)
void dt_variables_set_tags_flags(dt_variables_params_t *params, uint32_t flags)
unsigned __int64 uint64_t
Definition strptime.c:75
Exiv2::XmpData * xmpData
Exiv2::XmpData * xmpData
Exiv2::XmpData * xmpData
GTimeSpan export_timestamp
Definition image.h:391
uint64_t history_hash
Definition image.h:380
int32_t flags
Definition image.h:377
GTimeSpan change_timestamp
Definition image.h:391
GTimeSpan print_timestamp
Definition image.h:391
dt_image_raw_parameters_t legacy_flip
Definition image.h:407
int32_t id
Definition image.h:377
The four timestamps, each with whether it is set at all.
The main.images fields an XMP sidecar carries.
Definition iop_order.h:160
char operation[20]
Definition iop_order.h:166
double iop_order_f
Definition iop_order.h:162
union dt_iop_order_entry_t::@26 o
int32_t instance
Definition iop_order.h:167
unsigned char * blendop_params
int blendop_params_len
int blendop_version
int modversion
double iop_order
int multi_priority
gboolean have_operation
gboolean enabled
char * multi_name
int num
gboolean have_params
gboolean have_modversion
unsigned char * params
int params_len
char * operation
int mask_type
int mask_id
int mask_nb
int mask_version
int mask_num
unsigned char * mask_points
char * mask_name
int version
int mask_src_len
int mask_points_len
gboolean already_added
unsigned char * mask_src
dt_pthread_mutex_t lock
Definition supervisor.c:122
GList * dt_tag_get_list_export(int32_t imgid, int32_t flags)
Definition tags.c:720
GList * dt_tag_get_list(int32_t imgid)
Definition tags.c:623
GList * dt_tag_get_hierarchical_export(int32_t imgid, int32_t flags)
Definition tags.c:800
GList * dt_tag_get_hierarchical(int32_t imgid)
Definition tags.c:667
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static const int mask_id
Definition useless.c:214
static const char * mask_name
Definition useless.c:215
char * dt_read_file(const char *const filename, size_t *filesize)
Definition utility.c:895
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:98
static const char * _exif_get_exiv2_tag_type(const char *tagname)
int dt_exif_xmp_attach_export(const int32_t imgid, const char *filename, void *metadata)
static void dt_remove_xmp_key(Exiv2::XmpData &xmp, const char *key)
static void _xmp_append_metadata(void *user_data, const int keyid, const char *value)
static void dt_remove_known_keys(Exiv2::XmpData &xmp)
static void free_history_entry(gpointer data)
static void read_xmp_timestamps(Exiv2::XmpData &xmpData, dt_image_t *img, const int xmp_version)
static void dt_set_xmp_exif_geotag(Exiv2::XmpData &xmpData, double longitude, double latitude, double altitude)
static void dt_set_xmp_dt_history(Exiv2::XmpData &xmpData, const int32_t imgid, int history_end)
static void dt_remove_xmp_keys(Exiv2::XmpData &xmp, const char *keys[], unsigned int n_keys)
static void _xmp_append_colorlabel(void *user_data, const int color)
#define WIDEN(s)
static GList * read_masks_v3(Exiv2::XmpData &xmpData, const char *filename, const int version)
int dt_exif_xmp_write_with_imgpath(const dt_image_t *image, const char *filename, const char *imgpath)
GTimeSpan _convert_unix_to_gtimespan(const time_t unix)
static GList * read_history_v1(const std::string &xmpPacket, const char *filename, const int superold)
static void print_history_entry(history_entry_t *entry) __attribute__((unused))
int dt_exif_xmp_read(dt_image_t *img, const char *filename, const int history_only)
static void _xmp_append_history(void *user_data, const int32_t row_imgid, const int hist_num, const int modversion, const char *operation, const void *op_params, const int params_len, const gboolean enabled, const void *blendop_params_blob, const int blendop_params_len, const int blendop_version, const int multi_priority, const char *multi_name, const char *preset_name)
static void dt_remove_xmp_exif_geotag(Exiv2::XmpData &xmpData)
static void add_mask_entry_to_db(int32_t imgid, mask_entry_t *entry)
#define DT_XMP_EXIF_VERSION
static void _remove_xmp_keys(Exiv2::XmpData &xmpData, const char *key)
const char * dt_xmp_keys[]
int dt_exif_read_blob(uint8_t **buf, const char *path, const int32_t imgid, const int sRGB, const int out_width, const int out_height, const int dng_mode)
static void dt_remove_exif_key(Exiv2::ExifData &exif, const char *key)
char * dt_exif_xmp_read_string(const int32_t imgid)
static void _exif_xmp_read_data_export(Exiv2::XmpData &xmpData, const int32_t imgid, dt_export_metadata_t *metadata)
int _get_max_multi_priority(GList *history, const char *operation)
static void _exif_xmp_append_history_hash(Exiv2::XmpData &xmpData, const int32_t imgid, const dt_image_t *image)
static void dt_remove_iptc_key(Exiv2::IptcData &iptc, const char *key)
static GList * read_history_v2(Exiv2::XmpData &xmpData, const char *filename)
static GHashTable * read_masks(Exiv2::XmpData &xmpData, const char *filename, const int version)
static void add_non_clone_mask_entries_to_db(gpointer key, gpointer value, gpointer user_data)
#define ERROR_CODE(a)
static void _exif_xmp_read_data(Exiv2::XmpData &xmpData, const int32_t imgid, const dt_image_t *image)
void free_mask_entry(gpointer data)
static const guint dt_xmp_keys_n
static void _xmp_append_mask(void *user_data, const int mask_num, const int mask_id, const int mask_type, const char *mask_name, const int mask_version, const void *points, const int points_len, const int mask_nb, const void *source, const int source_len)
static void set_xmp_timestamps(Exiv2::XmpData &xmpData, const int32_t imgid)
static void dt_set_xmp_dt_metadata(Exiv2::XmpData &xmpData, const int32_t imgid, const gboolean export_flag)
static void add_mask_entries_to_db(int32_t imgid, GHashTable *mask_entries, int mask_id)
static void dt_remove_exif_geotag(Exiv2::ExifData &exifData)
The XMP document that carries an image's development: history stack, mask shapes, module order,...