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