2aab78239460cdf29e3ce023ac02f00c1c922363
[geeqie.git] / src / exiv2.cc
1 /*
2  * Copyright (C) 2008 - 2016 The Geeqie Team
3  *
4  * Author: Vladimir Nadvornik
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include <config.h>
22
23 #if HAVE_EXIV2
24
25 #include "exif.h"
26
27 #include <algorithm>
28 #include <cstdint>
29 #include <cstring>
30 #include <exception>
31 #include <list>
32 #include <memory>
33 #include <string>
34 #include <utility>
35 #include <vector>
36
37 #include <exiv2/exiv2.hpp>
38 #include <glib.h>
39 #ifdef ENABLE_NLS
40 #  include <libintl.h>
41 #endif
42
43 #include "debug.h"
44 #include "filedata.h"
45 #include "filefilter.h"
46 #include "misc.h"
47 #include "options.h"
48 #include "typedefs.h"
49 #include "ui-fileops.h"
50
51 struct ExifItem;
52
53 #if EXIV2_TEST_VERSION(0,27,0)
54 #define HAVE_EXIV2_ERROR_CODE
55 #endif
56
57 #if EXIV2_TEST_VERSION(0,27,0)
58 #define EXV_PACKAGE "exiv2"
59 #endif
60
61 #if EXIV2_TEST_VERSION(0,28,0)
62 #define AnyError Error
63 #define AutoPtr UniquePtr
64 #endif
65
66 struct AltKey
67 {
68         const gchar *xmp_key;
69         const gchar *exif_key;
70         const gchar *iptc_key;
71 };
72
73 /* this is a list of keys that should be converted, even with the older Exiv2 which does not support it directly */
74 static constexpr AltKey alt_keys[] = {
75         {"Xmp.tiff.Orientation",                "Exif.Image.Orientation",       nullptr},
76         {"Xmp.dc.title",                        nullptr,                                "Iptc.Application2.ObjectName"          },
77         {"Xmp.photoshop.Urgency",               nullptr,                                "Iptc.Application2.Urgency"             },
78         {"Xmp.photoshop.Category",              nullptr,                                "Iptc.Application2.Category"            },
79         {"Xmp.photoshop.SupplementalCategory",  nullptr,                                "Iptc.Application2.SuppCategory"        },
80         {"Xmp.dc.subject",                      nullptr,                                "Iptc.Application2.Keywords"            },
81         {"Xmp.iptc.Location",                   nullptr,                                "Iptc.Application2.LocationName"        },
82         {"Xmp.photoshop.Instruction",           nullptr,                                "Iptc.Application2.SpecialInstructions" },
83         {"Xmp.photoshop.DateCreated",           nullptr,                                "Iptc.Application2.DateCreated"         },
84         {"Xmp.dc.creator",                      nullptr,                                "Iptc.Application2.Byline"              },
85         {"Xmp.photoshop.AuthorsPosition",       nullptr,                                "Iptc.Application2.BylineTitle"         },
86         {"Xmp.photoshop.City",                  nullptr,                                "Iptc.Application2.City"                },
87         {"Xmp.photoshop.State",                 nullptr,                                "Iptc.Application2.ProvinceState"       },
88         {"Xmp.iptc.CountryCode",                nullptr,                                "Iptc.Application2.CountryCode"         },
89         {"Xmp.photoshop.Country",               nullptr,                                "Iptc.Application2.CountryName"         },
90         {"Xmp.photoshop.TransmissionReference", nullptr,                                "Iptc.Application2.TransmissionReference"},
91         {"Xmp.photoshop.Headline",              nullptr,                                "Iptc.Application2.Headline"            },
92         {"Xmp.photoshop.Credit",                nullptr,                                "Iptc.Application2.Credit"              },
93         {"Xmp.photoshop.Source",                nullptr,                                "Iptc.Application2.Source"              },
94         {"Xmp.dc.rights",                       nullptr,                                "Iptc.Application2.Copyright"           },
95         {"Xmp.dc.description",                  nullptr,                                "Iptc.Application2.Caption"             },
96         {"Xmp.photoshop.CaptionWriter",         nullptr,                                "Iptc.Application2.Writer"              },
97         };
98
99 static void _debug_exception(const char* file,
100                              int line,
101                              const char* func,
102                              Exiv2::AnyError& e)
103 {
104         gchar *str = g_locale_from_utf8(e.what(), -1, nullptr, nullptr, nullptr);
105         DEBUG_1("%s:%d:%s:Exiv2: %s", file, line, func, str);
106         g_free(str);
107 }
108
109 #define debug_exception(e) _debug_exception(__FILE__, __LINE__, __func__, e)
110
111 struct ExifData
112 {
113         Exiv2::ExifData::const_iterator exifIter; /* for exif_get_next_item */
114         Exiv2::IptcData::const_iterator iptcIter; /* for exif_get_next_item */
115         Exiv2::XmpData::const_iterator xmpIter; /* for exif_get_next_item */
116
117         virtual ~ExifData() = default;
118
119         virtual void writeMetadata(gchar * = nullptr)
120         {
121                 g_critical("Unsupported method of writing metadata");
122         }
123
124         virtual ExifData *original()
125         {
126                 return nullptr;
127         }
128
129         virtual Exiv2::Image *image() = 0;
130
131         virtual Exiv2::ExifData &exifData() = 0;
132
133         virtual Exiv2::IptcData &iptcData() = 0;
134
135         virtual Exiv2::XmpData &xmpData() = 0;
136
137         virtual void add_jpeg_color_profile(unsigned char *cp_data, guint cp_length) = 0;
138
139         virtual guchar *get_jpeg_color_profile(guint *data_len) = 0;
140
141         virtual std::string image_comment() const = 0;
142
143         virtual void set_image_comment(const std::string& comment) = 0;
144 };
145
146 // This allows read-only access to the original metadata
147 struct ExifDataOriginal : public ExifData
148 {
149 protected:
150         Exiv2::Image::AutoPtr image_;
151
152         /* the icc profile in jpeg is not technically exif - store it here */
153         unsigned char *cp_data_;
154         guint cp_length_;
155         gboolean valid_;
156         gchar *pathl_;
157
158         Exiv2::ExifData emptyExifData_;
159         Exiv2::IptcData emptyIptcData_;
160         Exiv2::XmpData emptyXmpData_;
161
162 public:
163         ExifDataOriginal(Exiv2::Image::AutoPtr image)
164         {
165                 cp_data_ = nullptr;
166                 cp_length_ = 0;
167                 image_ = std::move(image);
168                 valid_ = TRUE;
169         }
170
171         ExifDataOriginal(gchar *path)
172         {
173                 cp_data_ = nullptr;
174                 cp_length_ = 0;
175                 valid_ = TRUE;
176
177                 pathl_ = path_from_utf8(path);
178                 try
179                         {
180                         image_ = Exiv2::ImageFactory::open(pathl_);
181                         image_->readMetadata();
182
183                         if (image_->mimeType() == "application/rdf+xml")
184                                 {
185                                 //Exiv2 sidecar converts xmp to exif and iptc, we don't want it.
186                                 image_->clearExifData();
187                                 image_->clearIptcData();
188                                 }
189
190                         if (image_->mimeType() == "image/jpeg")
191                                 {
192                                 /* try to get jpeg color profile */
193                                 Exiv2::BasicIo &io = image_->io();
194                                 gint open = io.isopen();
195                                 if (!open) io.open();
196                                 if (io.isopen())
197                                         {
198                                         auto mapped = static_cast<unsigned char*>(io.mmap());
199                                         if (mapped) exif_jpeg_parse_color(this, mapped, io.size());
200                                         io.munmap();
201                                         }
202                                 if (!open) io.close();
203                                 }
204                         }
205                 catch (Exiv2::AnyError& e)
206                         {
207                         valid_ = FALSE;
208                         }
209         }
210
211         ~ExifDataOriginal() override
212         {
213                 if (cp_data_) g_free(cp_data_);
214                 if (pathl_) g_free(pathl_);
215         }
216
217         Exiv2::Image *image() override
218         {
219                 if (!valid_) return nullptr;
220                 return image_.get();
221         }
222
223         Exiv2::ExifData &exifData () override
224         {
225                 if (!valid_) return emptyExifData_;
226                 return image_->exifData();
227         }
228
229         Exiv2::IptcData &iptcData () override
230         {
231                 if (!valid_) return emptyIptcData_;
232                 return image_->iptcData();
233         }
234
235         Exiv2::XmpData &xmpData () override
236         {
237                 if (!valid_) return emptyXmpData_;
238                 return image_->xmpData();
239         }
240
241         void add_jpeg_color_profile(unsigned char *cp_data, guint cp_length) override
242         {
243                 if (cp_data_) g_free(cp_data_);
244                 cp_data_ = cp_data;
245                 cp_length_ = cp_length;
246         }
247
248         guchar *get_jpeg_color_profile(guint *data_len) override
249         {
250                 if (cp_data_)
251                 {
252                         if (data_len) *data_len = cp_length_;
253 #if GLIB_CHECK_VERSION(2,68,0)
254                         return static_cast<unsigned char *>(g_memdup2(cp_data_, cp_length_));
255 #else
256                         return static_cast<unsigned char *>(g_memdup(cp_data_, cp_length_));
257 #endif
258                 }
259                 return nullptr;
260         }
261
262         std::string image_comment() const override
263         {
264                 return image_.get() ? image_->comment() : "";
265         }
266
267         void set_image_comment(const std::string& comment) override
268         {
269                 if (image_.get())
270                         image_->setComment(comment);
271         }
272 };
273
274 static void _ExifDataProcessed_update_xmp(gpointer key, gpointer value, gpointer data);
275
276 // This allows read-write access to the metadata
277 struct ExifDataProcessed : public ExifData
278 {
279 protected:
280         std::unique_ptr<ExifDataOriginal> imageData_;
281         std::unique_ptr<ExifDataOriginal> sidecarData_;
282
283         Exiv2::ExifData exifData_;
284         Exiv2::IptcData iptcData_;
285         Exiv2::XmpData xmpData_;
286
287 public:
288         ExifDataProcessed(gchar *path, gchar *sidecar_path, GHashTable *modified_xmp)
289         {
290                 imageData_ = std::make_unique<ExifDataOriginal>(path);
291                 sidecarData_ = nullptr;
292                 if (sidecar_path)
293                         {
294                         sidecarData_ = std::make_unique<ExifDataOriginal>(sidecar_path);
295                         xmpData_ = sidecarData_->xmpData();
296                         }
297                 else
298                         {
299                         xmpData_ = imageData_->xmpData();
300                         }
301
302                 exifData_ = imageData_->exifData();
303                 iptcData_ = imageData_->iptcData();
304                 try
305                         {
306                         syncExifWithXmp(exifData_, xmpData_);
307                         }
308                 catch (...)
309                         {
310                         DEBUG_1("Exiv2: Catching bug\n");
311                         }
312                 if (modified_xmp)
313                         {
314                         g_hash_table_foreach(modified_xmp, _ExifDataProcessed_update_xmp, this);
315                         }
316         }
317
318         ExifData *original() override
319         {
320                 return imageData_.get();
321         }
322
323         void writeMetadata(gchar *path = nullptr) override
324         {
325                 if (!path)
326                         {
327                         if (options->metadata.save_legacy_IPTC)
328                                 copyXmpToIptc(xmpData_, iptcData_);
329                         else
330                                 iptcData_.clear();
331
332                         copyXmpToExif(xmpData_, exifData_);
333                         Exiv2::Image *image = imageData_->image();
334
335                         if (!image)
336 #ifdef HAVE_EXIV2_ERROR_CODE
337 #  if EXIV2_TEST_VERSION(0,28,0)
338                                 throw Exiv2::Error(Exiv2::ErrorCode::kerInputDataReadFailed);
339 #  else
340                                 throw Exiv2::Error(Exiv2::kerInputDataReadFailed);
341 #  endif
342 #else
343                                 throw Exiv2::Error(21);
344 #endif
345                         image->setExifData(exifData_);
346                         image->setIptcData(iptcData_);
347                         image->setXmpData(xmpData_);
348                         image->writeMetadata();
349                         }
350                 else
351                         {
352                         gchar *pathl = path_from_utf8(path);;
353
354                         auto sidecar = Exiv2::ImageFactory::create(Exiv2::ImageType::xmp, pathl);
355
356                         g_free(pathl);
357
358                         sidecar->setXmpData(xmpData_);
359                         sidecar->writeMetadata();
360                         }
361         }
362
363         Exiv2::Image *image() override
364         {
365                 return imageData_->image();
366         }
367
368         Exiv2::ExifData &exifData () override
369         {
370                 return exifData_;
371         }
372
373         Exiv2::IptcData &iptcData () override
374         {
375                 return iptcData_;
376         }
377
378         Exiv2::XmpData &xmpData () override
379         {
380                 return xmpData_;
381         }
382
383         void add_jpeg_color_profile(unsigned char *cp_data, guint cp_length) override
384         {
385                 imageData_->add_jpeg_color_profile(cp_data, cp_length);
386         }
387
388         guchar *get_jpeg_color_profile(guint *data_len) override
389         {
390                 return imageData_->get_jpeg_color_profile(data_len);
391         }
392
393         std::string image_comment() const override
394         {
395                 return imageData_->image_comment();
396         }
397
398         void set_image_comment(const std::string& comment) override
399         {
400                 imageData_->set_image_comment(comment);
401         }
402 };
403
404
405
406
407
408
409 void exif_init()
410 {
411 #ifdef EXV_ENABLE_NLS
412         bind_textdomain_codeset (EXV_PACKAGE, "UTF-8");
413 #endif
414
415 #ifdef EXV_ENABLE_BMFF
416         Exiv2::enableBMFF(TRUE);
417 #endif
418 }
419
420
421
422 static void _ExifDataProcessed_update_xmp(gpointer key, gpointer value, gpointer data)
423 {
424         exif_update_metadata(static_cast<ExifData *>(data), static_cast<gchar *>(key), static_cast<GList *>(value));
425 }
426
427 ExifData *exif_read(gchar *path, gchar *sidecar_path, GHashTable *modified_xmp)
428 {
429         DEBUG_1("exif read %s, sidecar: %s", path, sidecar_path ? sidecar_path : "-");
430         try {
431                 return new ExifDataProcessed(path, sidecar_path, modified_xmp);
432         }
433         catch (Exiv2::AnyError& e) {
434                 debug_exception(e);
435                 return nullptr;
436         }
437
438 }
439
440 gboolean exif_write(ExifData *exif)
441 {
442         try {
443                 exif->writeMetadata();
444                 return TRUE;
445         }
446         catch (Exiv2::AnyError& e) {
447                 debug_exception(e);
448                 return FALSE;
449         }
450 }
451
452 gboolean exif_write_sidecar(ExifData *exif, gchar *path)
453 {
454         try {
455                 exif->writeMetadata(path);
456                 return TRUE;
457         }
458         catch (Exiv2::AnyError& e) {
459                 debug_exception(e);
460                 return FALSE;
461         }
462
463 }
464
465
466 void exif_free(ExifData *exif)
467 {
468         if (!exif) return;
469         g_assert(dynamic_cast<ExifDataProcessed *>(exif)); // this should not be called on ExifDataOriginal
470         delete exif;
471 }
472
473 ExifData *exif_get_original(ExifData *exif)
474 {
475         return exif->original();
476 }
477
478
479 ExifItem *exif_get_item(ExifData *exif, const gchar *key)
480 {
481         try {
482                 Exiv2::Metadatum *item = nullptr;
483                 try {
484                         Exiv2::ExifKey ekey(key);
485                         auto pos = exif->exifData().findKey(ekey);
486                         if (pos == exif->exifData().end()) return nullptr;
487                         item = &*pos;
488                 }
489                 catch (Exiv2::AnyError& e) {
490                         try {
491                                 Exiv2::IptcKey ekey(key);
492                                 auto pos = exif->iptcData().findKey(ekey);
493                                 if (pos == exif->iptcData().end()) return nullptr;
494                                 item = &*pos;
495                         }
496                         catch (Exiv2::AnyError& e) {
497                                 Exiv2::XmpKey ekey(key);
498                                 auto pos = exif->xmpData().findKey(ekey);
499                                 if (pos == exif->xmpData().end()) return nullptr;
500                                 item = &*pos;
501                         }
502                 }
503                 return reinterpret_cast<ExifItem *>(item);
504         }
505         catch (Exiv2::AnyError& e) {
506                 debug_exception(e);
507                 return nullptr;
508         }
509 }
510
511 ExifItem *exif_add_item(ExifData *exif, const gchar *key)
512 {
513         try {
514                 Exiv2::Metadatum *item = nullptr;
515                 try {
516                         Exiv2::ExifKey ekey(key);
517                         exif->exifData().add(ekey, nullptr);
518                         auto pos = exif->exifData().end(); // a hack, there should be a better way to get the currently added item
519                         pos--;
520                         item = &*pos;
521                 }
522                 catch (Exiv2::AnyError& e) {
523                         try {
524                                 Exiv2::IptcKey ekey(key);
525                                 exif->iptcData().add(ekey, nullptr);
526                                 auto pos = exif->iptcData().end();
527                                 pos--;
528                                 item = &*pos;
529                         }
530                         catch (Exiv2::AnyError& e) {
531                                 Exiv2::XmpKey ekey(key);
532                                 exif->xmpData().add(ekey, nullptr);
533                                 auto pos = exif->xmpData().end();
534                                 pos--;
535                                 item = &*pos;
536                         }
537                 }
538                 return reinterpret_cast<ExifItem *>(item);
539         }
540         catch (Exiv2::AnyError& e) {
541                 debug_exception(e);
542                 return nullptr;
543         }
544 }
545
546
547 ExifItem *exif_get_first_item(ExifData *exif)
548 {
549         try {
550                 exif->exifIter = exif->exifData().begin();
551                 exif->iptcIter = exif->iptcData().begin();
552                 exif->xmpIter = exif->xmpData().begin();
553                 if (exif->exifIter != exif->exifData().end())
554                         {
555                         const Exiv2::Metadatum *item = &*exif->exifIter;
556                         exif->exifIter++;
557                         return (ExifItem *)item;
558                         }
559                 if (exif->iptcIter != exif->iptcData().end())
560                         {
561                         const Exiv2::Metadatum *item = &*exif->iptcIter;
562                         exif->iptcIter++;
563                         return (ExifItem *)item;
564                         }
565                 if (exif->xmpIter != exif->xmpData().end())
566                         {
567                         const Exiv2::Metadatum *item = &*exif->xmpIter;
568                         exif->xmpIter++;
569                         return (ExifItem *)item;
570                         }
571                 return nullptr;
572
573         }
574         catch (Exiv2::AnyError& e) {
575                 debug_exception(e);
576                 return nullptr;
577         }
578 }
579
580 ExifItem *exif_get_next_item(ExifData *exif)
581 {
582         try {
583                 if (exif->exifIter != exif->exifData().end())
584                         {
585                         const Exiv2::Metadatum *item = &*exif->exifIter;
586                         exif->exifIter++;
587                         return (ExifItem *)item;
588                 }
589                 if (exif->iptcIter != exif->iptcData().end())
590                         {
591                         const Exiv2::Metadatum *item = &*exif->iptcIter;
592                         exif->iptcIter++;
593                         return (ExifItem *)item;
594                 }
595                 if (exif->xmpIter != exif->xmpData().end())
596                         {
597                         const Exiv2::Metadatum *item = &*exif->xmpIter;
598                         exif->xmpIter++;
599                         return (ExifItem *)item;
600                 }
601                 return nullptr;
602         }
603         catch (Exiv2::AnyError& e) {
604                 debug_exception(e);
605                 return nullptr;
606         }
607 }
608
609 char *exif_item_get_tag_name(ExifItem *item)
610 {
611         try {
612                 if (!item) return nullptr;
613                 return g_strdup((reinterpret_cast<Exiv2::Metadatum *>(item))->key().c_str());
614         }
615         catch (Exiv2::AnyError& e) {
616                 debug_exception(e);
617                 return nullptr;
618         }
619 }
620
621 guint exif_item_get_tag_id(ExifItem *item)
622 {
623         try {
624                 if (!item) return 0;
625                 return (reinterpret_cast<Exiv2::Metadatum *>(item))->tag();
626         }
627         catch (Exiv2::AnyError& e) {
628                 debug_exception(e);
629                 return 0;
630         }
631 }
632
633 guint exif_item_get_elements(ExifItem *item)
634 {
635         try {
636                 if (!item) return 0;
637                 return (reinterpret_cast<Exiv2::Metadatum *>(item))->count();
638         }
639         catch (Exiv2::AnyError& e) {
640                 debug_exception(e);
641                 return 0;
642         }
643 }
644
645 char *exif_item_get_data(ExifItem *item, guint *data_len)
646 {
647         try {
648                 if (!item) return nullptr;
649                 auto md = reinterpret_cast<Exiv2::Metadatum *>(item);
650                 if (data_len) *data_len = md->size();
651                 auto data = static_cast<char *>(g_malloc(md->size()));
652                 auto res = md->copy(reinterpret_cast<Exiv2::byte *>(data), Exiv2::littleEndian /* should not matter */);
653                 g_assert(res == md->size());
654                 return data;
655         }
656         catch (Exiv2::AnyError& e) {
657                 debug_exception(e);
658                 return nullptr;
659         }
660 }
661
662 char *exif_item_get_description(ExifItem *item)
663 {
664         try {
665                 if (!item) return nullptr;
666                 return utf8_validate_or_convert((reinterpret_cast<Exiv2::Metadatum *>(item))->tagLabel().c_str());
667         }
668         catch (std::exception& e) {
669                 return nullptr;
670         }
671 }
672
673 /*
674 invalidTypeId, unsignedByte, asciiString, unsignedShort,
675   unsignedLong, unsignedRational, signedByte, undefined,
676   signedShort, signedLong, signedRational, string,
677   date, time, comment, directory,
678   xmpText, xmpAlt, xmpBag, xmpSeq,
679   langAlt, lastTypeId
680 */
681
682 static guint format_id_trans_tbl [] = {
683         EXIF_FORMAT_UNKNOWN,
684         EXIF_FORMAT_BYTE_UNSIGNED,
685         EXIF_FORMAT_STRING,
686         EXIF_FORMAT_SHORT_UNSIGNED,
687         EXIF_FORMAT_LONG_UNSIGNED,
688         EXIF_FORMAT_RATIONAL_UNSIGNED,
689         EXIF_FORMAT_BYTE,
690         EXIF_FORMAT_UNDEFINED,
691         EXIF_FORMAT_SHORT,
692         EXIF_FORMAT_LONG,
693         EXIF_FORMAT_RATIONAL,
694         EXIF_FORMAT_STRING,
695         EXIF_FORMAT_STRING,
696         EXIF_FORMAT_STRING,
697         EXIF_FORMAT_UNDEFINED,
698         EXIF_FORMAT_STRING,
699         EXIF_FORMAT_STRING,
700         EXIF_FORMAT_STRING,
701         EXIF_FORMAT_STRING
702         };
703
704
705
706 guint exif_item_get_format_id(ExifItem *item)
707 {
708         try {
709                 if (!item) return EXIF_FORMAT_UNKNOWN;
710                 guint id = (reinterpret_cast<Exiv2::Metadatum *>(item))->typeId();
711                 if (id >= (sizeof(format_id_trans_tbl) / sizeof(format_id_trans_tbl[0])) ) return EXIF_FORMAT_UNKNOWN;
712                 return format_id_trans_tbl[id];
713         }
714         catch (Exiv2::AnyError& e) {
715                 debug_exception(e);
716                 return EXIF_FORMAT_UNKNOWN;
717         }
718 }
719
720 const char *exif_item_get_format_name(ExifItem *item, gboolean)
721 {
722         try {
723                 if (!item) return nullptr;
724                 return (reinterpret_cast<Exiv2::Metadatum *>(item))->typeName();
725         }
726         catch (Exiv2::AnyError& e) {
727                 debug_exception(e);
728                 return nullptr;
729         }
730 }
731
732
733 gchar *exif_item_get_data_as_text(ExifItem *item, ExifData *exif)
734 {
735         try {
736                 if (!item) return nullptr;
737                 auto metadatum = reinterpret_cast<Exiv2::Metadatum *>(item);
738                 return utf8_validate_or_convert(metadatum->print(&exif->exifData()).c_str());
739         }
740         catch (Exiv2::AnyError& e) {
741                 return nullptr;
742         }
743 }
744
745 gchar *exif_item_get_string(ExifItem *item, int idx)
746 {
747         try {
748                 if (!item) return nullptr;
749                 auto em = reinterpret_cast<Exiv2::Metadatum *>(item);
750                 std::string str = em->toString(idx);
751                 if (idx == 0 && str.empty()) str = em->toString();
752                 if (str.length() > 5 && str.substr(0, 5) == "lang=")
753                         {
754                         std::string::size_type pos = str.find_first_of(' ');
755                         if (pos != std::string::npos) str = str.substr(pos+1);
756                         }
757
758                 return utf8_validate_or_convert(str.c_str());
759         }
760         catch (Exiv2::AnyError& e) {
761                 return nullptr;
762         }
763 }
764
765
766 gint exif_item_get_integer(ExifItem *item, gint *value)
767 {
768         try {
769                 if (!item || exif_item_get_elements(item) == 0) return 0;
770
771 #if EXIV2_TEST_VERSION(0,28,0)
772         *value = ((Exiv2::Metadatum *)item)->toInt64();
773 #else
774                 *value = (reinterpret_cast<Exiv2::Metadatum *>(item))->toLong();
775 #endif
776                 return 1;
777         }
778         catch (Exiv2::AnyError& e) {
779                 debug_exception(e);
780                 return 0;
781         }
782 }
783
784 ExifRational *exif_item_get_rational(ExifItem *item, gint *sign, guint n)
785 {
786         try {
787                 if (!item) return nullptr;
788                 if (n >= exif_item_get_elements(item)) return nullptr;
789                 Exiv2::Rational v = (reinterpret_cast<Exiv2::Metadatum *>(item))->toRational(n);
790                 static ExifRational ret;
791                 ret.num = v.first;
792                 ret.den = v.second;
793                 if (sign) *sign = ((reinterpret_cast<Exiv2::Metadatum *>(item))->typeId() == Exiv2::signedRational);
794                 return &ret;
795         }
796         catch (Exiv2::AnyError& e) {
797                 debug_exception(e);
798                 return nullptr;
799         }
800 }
801
802 gchar *exif_get_tag_description_by_key(const gchar *key)
803 {
804         try {
805                 Exiv2::ExifKey ekey(key);
806                 return utf8_validate_or_convert(ekey.tagLabel().c_str());
807         }
808         catch (Exiv2::AnyError& e) {
809                 try {
810                         Exiv2::IptcKey ikey(key);
811                         return utf8_validate_or_convert(ikey.tagLabel().c_str());
812                 }
813                 catch (Exiv2::AnyError& e) {
814                         try {
815                                 Exiv2::XmpKey xkey(key);
816                                 return utf8_validate_or_convert(xkey.tagLabel().c_str());
817                         }
818                         catch (Exiv2::AnyError& e) {
819                                 debug_exception(e);
820                                 return nullptr;
821                         }
822                 }
823         }
824         return nullptr;
825 }
826
827 static const AltKey *find_alt_key(const gchar *xmp_key)
828 {
829         for (const auto& k : alt_keys)
830                 if (strcmp(xmp_key, k.xmp_key) == 0) return &k;
831         return nullptr;
832 }
833
834 static gint exif_update_metadata_simple(ExifData *exif, const gchar *key, const GList *values)
835 {
836         try {
837                 const GList *work = values;
838
839                 try {
840                         Exiv2::ExifKey ekey(key);
841
842                         auto pos = exif->exifData().findKey(ekey);
843                         while (pos != exif->exifData().end())
844                                 {
845                                 exif->exifData().erase(pos);
846                                 pos = exif->exifData().findKey(ekey);
847                                 }
848
849                         while (work)
850                                 {
851                                 exif->exifData()[key] = static_cast<gchar *>(work->data);
852                                 work = work->next;
853                                 }
854                 }
855                 catch (Exiv2::AnyError& e) {
856                         try
857                         {
858                                 Exiv2::IptcKey ekey(key);
859                                 auto pos = exif->iptcData().findKey(ekey);
860                                 while (pos != exif->iptcData().end())
861                                         {
862                                         exif->iptcData().erase(pos);
863                                         pos = exif->iptcData().findKey(ekey);
864                                         }
865
866                                 while (work)
867                                         {
868                                         exif->iptcData()[key] = static_cast<gchar *>(work->data);
869                                         work = work->next;
870                                         }
871                         }
872                         catch (Exiv2::AnyError& e) {
873                                 Exiv2::XmpKey ekey(key);
874                                 auto pos = exif->xmpData().findKey(ekey);
875                                 while (pos != exif->xmpData().end())
876                                         {
877                                         exif->xmpData().erase(pos);
878                                         pos = exif->xmpData().findKey(ekey);
879                                         }
880
881                                 while (work)
882                                         {
883                                         exif->xmpData()[key] = static_cast<gchar *>(work->data);
884                                         work = work->next;
885                                         }
886                         }
887                 }
888                 return 1;
889         }
890         catch (Exiv2::AnyError& e) {
891                 debug_exception(e);
892                 return 0;
893         }
894 }
895
896 gint exif_update_metadata(ExifData *exif, const gchar *key, const GList *values)
897 {
898         gint ret = exif_update_metadata_simple(exif, key, values);
899
900         if (
901             !values || /* deleting item */
902             !ret  /* writing to the explicitly given xmp tag failed */
903             )
904                 {
905                 /* deleted xmp metadatum can't be converted, we have to delete also the corresponding legacy tag */
906                 /* if we can't write xmp, update at least the legacy tag */
907                 const AltKey *alt_key = find_alt_key(key);
908                 if (alt_key && alt_key->iptc_key)
909                         ret = exif_update_metadata_simple(exif, alt_key->iptc_key, values);
910
911                 if (alt_key && alt_key->exif_key)
912                         ret = exif_update_metadata_simple(exif, alt_key->exif_key, values);
913                 }
914         return ret;
915 }
916
917
918 static GList *exif_add_value_to_glist(GList *list, Exiv2::Metadatum &item, MetadataFormat format, const Exiv2::ExifData *metadata)
919 {
920         Exiv2::TypeId id = item.typeId();
921         if (format == METADATA_FORMATTED ||
922             id == Exiv2::asciiString ||
923             id == Exiv2::undefined ||
924             id == Exiv2::string ||
925             id == Exiv2::date ||
926             id == Exiv2::time ||
927             id == Exiv2::xmpText ||
928             id == Exiv2::langAlt ||
929             id == Exiv2::comment
930             )
931                 {
932                 /* read as a single entry */
933                 std::string str;
934
935                 if (format == METADATA_FORMATTED)
936                         {
937                         str = item.print(metadata);
938                         if (str.length() > 1024)
939                                 {
940                                 /* truncate very long strings, they cause problems in gui */
941                                 str.erase(1024);
942                                 str.append("...");
943                                 }
944                         }
945                 else
946                         {
947                         str = item.toString();
948                         }
949                 if (str.length() > 5 && str.substr(0, 5) == "lang=")
950                         {
951                         std::string::size_type pos = str.find_first_of(' ');
952                         if (pos != std::string::npos) str = str.substr(pos+1);
953                         }
954                 list = g_list_append(list, utf8_validate_or_convert(str.c_str()));
955                 }
956         else
957                 {
958                 /* read as a list */
959 #if EXIV2_TEST_VERSION(0,28,0)
960                 size_t i;
961 #else
962                 long i;
963 #endif
964                 for (i = 0; i < item.count(); i++)
965                         list = g_list_append(list, utf8_validate_or_convert(item.toString(i).c_str()));
966                 }
967         return list;
968 }
969
970 static GList *exif_get_metadata_simple(ExifData *exif, const gchar *key, MetadataFormat format)
971 {
972         GList *list = nullptr;
973         try {
974                 try {
975                         Exiv2::ExifKey ekey(key);
976                         auto pos = exif->exifData().findKey(ekey);
977                         if (pos != exif->exifData().end())
978                                 list = exif_add_value_to_glist(list, *pos, format, &exif->exifData());
979
980                 }
981                 catch (Exiv2::AnyError& e) {
982                         try {
983                                 Exiv2::IptcKey ekey(key);
984                                 auto pos = exif->iptcData().begin();
985                                 while (pos != exif->iptcData().end())
986                                         {
987                                         if (pos->key() == key)
988                                                 list = exif_add_value_to_glist(list, *pos, format, nullptr);
989                                         ++pos;
990                                         }
991
992                         }
993                         catch (Exiv2::AnyError& e) {
994                                 Exiv2::XmpKey ekey(key);
995                                 auto pos = exif->xmpData().findKey(ekey);
996                                 if (pos != exif->xmpData().end())
997                                         list = exif_add_value_to_glist(list, *pos, format, nullptr);
998                         }
999                 }
1000         }
1001         catch (Exiv2::AnyError& e) {
1002                 debug_exception(e);
1003         }
1004         return list;
1005 }
1006
1007 GList *exif_get_metadata(ExifData *exif, const gchar *key, MetadataFormat format)
1008 {
1009         GList *list = nullptr;
1010
1011         if (!key) return nullptr;
1012
1013         if (format == METADATA_FORMATTED)
1014                 {
1015                 gchar *text;
1016                 gint key_valid;
1017                 text = exif_get_formatted_by_key(exif, key, &key_valid);
1018                 if (key_valid) return g_list_append(nullptr, text);
1019                 }
1020
1021         list = exif_get_metadata_simple(exif, key, format);
1022
1023         /* the following code can be ifdefed out as soon as Exiv2 supports it */
1024         if (!list)
1025                 {
1026                 const AltKey *alt_key = find_alt_key(key);
1027                 if (alt_key && alt_key->iptc_key)
1028                         list = exif_get_metadata_simple(exif, alt_key->iptc_key, format);
1029                 }
1030         return list;
1031 }
1032
1033
1034 void exif_add_jpeg_color_profile(ExifData *exif, unsigned char *cp_data, guint cp_length)
1035 {
1036         exif->add_jpeg_color_profile(cp_data, cp_length);
1037 }
1038
1039 guchar *exif_get_color_profile(ExifData *exif, guint *data_len)
1040 {
1041         guchar *ret = exif->get_jpeg_color_profile(data_len);
1042         if (ret) return ret;
1043
1044         ExifItem *prof_item = exif_get_item(exif, "Exif.Image.InterColorProfile");
1045         if (prof_item && exif_item_get_format_id(prof_item) == EXIF_FORMAT_UNDEFINED)
1046                 ret = reinterpret_cast<guchar *>(exif_item_get_data(prof_item, data_len));
1047         return ret;
1048 }
1049
1050 gchar* exif_get_image_comment(FileData* fd)
1051 {
1052         if (!fd || !fd->exif)
1053                 return g_strdup("");
1054
1055         return g_strdup(fd->exif->image_comment().c_str());
1056 }
1057
1058 void exif_set_image_comment(FileData* fd, const gchar* comment)
1059 {
1060         if (!fd || !fd->exif)
1061                 return;
1062
1063         fd->exif->set_image_comment(comment ? comment : "");
1064 }
1065
1066
1067 guchar *exif_get_preview(ExifData *exif, guint *data_len, gint requested_width, gint requested_height)
1068 {
1069         if (!exif) return nullptr;
1070
1071         if (!exif->image()) return nullptr;
1072
1073         std::string const path = exif->image()->io().path();
1074         /* given image pathname, first do simple (and fast) file extension test */
1075         gboolean is_raw = filter_file_class(path.c_str(), FORMAT_CLASS_RAWIMAGE);
1076
1077         if (!is_raw && requested_width == 0) return nullptr;
1078
1079         try {
1080
1081                 Exiv2::PreviewManager pm(*exif->image());
1082
1083                 Exiv2::PreviewPropertiesList list = pm.getPreviewProperties();
1084
1085                 if (!list.empty())
1086                         {
1087                         Exiv2::PreviewPropertiesList::iterator pos;
1088                         auto last = --list.end();
1089
1090                         if (requested_width == 0)
1091                                 {
1092                                 pos = last; // the largest
1093                                 }
1094                         else
1095                                 {
1096                                 pos = list.begin();
1097                                 while (pos != last)
1098                                         {
1099                                         if (pos->width_ >= static_cast<uint32_t>(requested_width) &&
1100                                             pos->height_ >= static_cast<uint32_t>(requested_height)) break;
1101                                         ++pos;
1102                                         }
1103
1104                                 // we are not interested in smaller thumbnails in normal image formats - we can use full image instead
1105                                 if (!is_raw)
1106                                         {
1107                                         if (pos->width_ < static_cast<uint32_t>(requested_width) || pos->height_ < static_cast<uint32_t>(requested_height)) return nullptr;
1108                                         }
1109                                 }
1110
1111                         Exiv2::PreviewImage image = pm.getPreviewImage(*pos);
1112
1113                         // Let's not touch data_len until we finish copy.
1114                         // Just in case we run into OOM.
1115                         size_t img_sz = image.size();
1116                         auto* b = new Exiv2::byte[img_sz];
1117                         std::copy_n(image.pData(), img_sz, b);
1118                         *data_len = img_sz;
1119                         return b;
1120                         }
1121                 return nullptr;
1122         }
1123         catch (Exiv2::AnyError& e) {
1124                 debug_exception(e);
1125                 return nullptr;
1126         }
1127 }
1128
1129 void exif_free_preview(const guchar *buf)
1130 {
1131         delete[] static_cast<const Exiv2::byte*>(buf);
1132 }
1133
1134 #endif /* HAVE_EXIV2 */
1135 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */