Move third-party sources to separate sub-directory
[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 #ifdef HAVE_EXIV2_ERROR_CODE
336 #if EXIV2_TEST_VERSION(0,28,0)
337             if (!image) throw Exiv2::Error(Exiv2::ErrorCode::kerInputDataReadFailed);
338 #else
339                         if (!image) throw Exiv2::Error(Exiv2::kerInputDataReadFailed);
340 #endif
341 #else
342                         if (!image) throw Exiv2::Error(21);
343 #endif
344                         image->setExifData(exifData_);
345                         image->setIptcData(iptcData_);
346                         image->setXmpData(xmpData_);
347                         image->writeMetadata();
348                         }
349                 else
350                         {
351                         gchar *pathl = path_from_utf8(path);;
352
353                         auto sidecar = Exiv2::ImageFactory::create(Exiv2::ImageType::xmp, pathl);
354
355                         g_free(pathl);
356
357                         sidecar->setXmpData(xmpData_);
358                         sidecar->writeMetadata();
359                         }
360         }
361
362         Exiv2::Image *image() override
363         {
364                 return imageData_->image();
365         }
366
367         Exiv2::ExifData &exifData () override
368         {
369                 return exifData_;
370         }
371
372         Exiv2::IptcData &iptcData () override
373         {
374                 return iptcData_;
375         }
376
377         Exiv2::XmpData &xmpData () override
378         {
379                 return xmpData_;
380         }
381
382         void add_jpeg_color_profile(unsigned char *cp_data, guint cp_length) override
383         {
384                 imageData_->add_jpeg_color_profile(cp_data, cp_length);
385         }
386
387         guchar *get_jpeg_color_profile(guint *data_len) override
388         {
389                 return imageData_->get_jpeg_color_profile(data_len);
390         }
391
392         std::string image_comment() const override
393         {
394                 return imageData_->image_comment();
395         }
396
397         void set_image_comment(const std::string& comment) override
398         {
399                 imageData_->set_image_comment(comment);
400         }
401 };
402
403
404
405
406
407
408 void exif_init()
409 {
410 #ifdef EXV_ENABLE_NLS
411         bind_textdomain_codeset (EXV_PACKAGE, "UTF-8");
412 #endif
413
414 #ifdef EXV_ENABLE_BMFF
415         Exiv2::enableBMFF(TRUE);
416 #endif
417 }
418
419
420
421 static void _ExifDataProcessed_update_xmp(gpointer key, gpointer value, gpointer data)
422 {
423         exif_update_metadata(static_cast<ExifData *>(data), static_cast<gchar *>(key), static_cast<GList *>(value));
424 }
425
426 ExifData *exif_read(gchar *path, gchar *sidecar_path, GHashTable *modified_xmp)
427 {
428         DEBUG_1("exif read %s, sidecar: %s", path, sidecar_path ? sidecar_path : "-");
429         try {
430                 return new ExifDataProcessed(path, sidecar_path, modified_xmp);
431         }
432         catch (Exiv2::AnyError& e) {
433                 debug_exception(e);
434                 return nullptr;
435         }
436
437 }
438
439 gboolean exif_write(ExifData *exif)
440 {
441         try {
442                 exif->writeMetadata();
443                 return TRUE;
444         }
445         catch (Exiv2::AnyError& e) {
446                 debug_exception(e);
447                 return FALSE;
448         }
449 }
450
451 gboolean exif_write_sidecar(ExifData *exif, gchar *path)
452 {
453         try {
454                 exif->writeMetadata(path);
455                 return TRUE;
456         }
457         catch (Exiv2::AnyError& e) {
458                 debug_exception(e);
459                 return FALSE;
460         }
461
462 }
463
464
465 void exif_free(ExifData *exif)
466 {
467         if (!exif) return;
468         g_assert(dynamic_cast<ExifDataProcessed *>(exif)); // this should not be called on ExifDataOriginal
469         delete exif;
470 }
471
472 ExifData *exif_get_original(ExifData *exif)
473 {
474         return exif->original();
475 }
476
477
478 ExifItem *exif_get_item(ExifData *exif, const gchar *key)
479 {
480         try {
481                 Exiv2::Metadatum *item = nullptr;
482                 try {
483                         Exiv2::ExifKey ekey(key);
484                         auto pos = exif->exifData().findKey(ekey);
485                         if (pos == exif->exifData().end()) return nullptr;
486                         item = &*pos;
487                 }
488                 catch (Exiv2::AnyError& e) {
489                         try {
490                                 Exiv2::IptcKey ekey(key);
491                                 auto pos = exif->iptcData().findKey(ekey);
492                                 if (pos == exif->iptcData().end()) return nullptr;
493                                 item = &*pos;
494                         }
495                         catch (Exiv2::AnyError& e) {
496                                 Exiv2::XmpKey ekey(key);
497                                 auto pos = exif->xmpData().findKey(ekey);
498                                 if (pos == exif->xmpData().end()) return nullptr;
499                                 item = &*pos;
500                         }
501                 }
502                 return reinterpret_cast<ExifItem *>(item);
503         }
504         catch (Exiv2::AnyError& e) {
505                 debug_exception(e);
506                 return nullptr;
507         }
508 }
509
510 ExifItem *exif_add_item(ExifData *exif, const gchar *key)
511 {
512         try {
513                 Exiv2::Metadatum *item = nullptr;
514                 try {
515                         Exiv2::ExifKey ekey(key);
516                         exif->exifData().add(ekey, nullptr);
517                         auto pos = exif->exifData().end(); // a hack, there should be a better way to get the currently added item
518                         pos--;
519                         item = &*pos;
520                 }
521                 catch (Exiv2::AnyError& e) {
522                         try {
523                                 Exiv2::IptcKey ekey(key);
524                                 exif->iptcData().add(ekey, nullptr);
525                                 auto pos = exif->iptcData().end();
526                                 pos--;
527                                 item = &*pos;
528                         }
529                         catch (Exiv2::AnyError& e) {
530                                 Exiv2::XmpKey ekey(key);
531                                 exif->xmpData().add(ekey, nullptr);
532                                 auto pos = exif->xmpData().end();
533                                 pos--;
534                                 item = &*pos;
535                         }
536                 }
537                 return reinterpret_cast<ExifItem *>(item);
538         }
539         catch (Exiv2::AnyError& e) {
540                 debug_exception(e);
541                 return nullptr;
542         }
543 }
544
545
546 ExifItem *exif_get_first_item(ExifData *exif)
547 {
548         try {
549                 exif->exifIter = exif->exifData().begin();
550                 exif->iptcIter = exif->iptcData().begin();
551                 exif->xmpIter = exif->xmpData().begin();
552                 if (exif->exifIter != exif->exifData().end())
553                         {
554                         const Exiv2::Metadatum *item = &*exif->exifIter;
555                         exif->exifIter++;
556                         return (ExifItem *)item;
557                         }
558                 if (exif->iptcIter != exif->iptcData().end())
559                         {
560                         const Exiv2::Metadatum *item = &*exif->iptcIter;
561                         exif->iptcIter++;
562                         return (ExifItem *)item;
563                         }
564                 if (exif->xmpIter != exif->xmpData().end())
565                         {
566                         const Exiv2::Metadatum *item = &*exif->xmpIter;
567                         exif->xmpIter++;
568                         return (ExifItem *)item;
569                         }
570                 return nullptr;
571
572         }
573         catch (Exiv2::AnyError& e) {
574                 debug_exception(e);
575                 return nullptr;
576         }
577 }
578
579 ExifItem *exif_get_next_item(ExifData *exif)
580 {
581         try {
582                 if (exif->exifIter != exif->exifData().end())
583                         {
584                         const Exiv2::Metadatum *item = &*exif->exifIter;
585                         exif->exifIter++;
586                         return (ExifItem *)item;
587                 }
588                 if (exif->iptcIter != exif->iptcData().end())
589                         {
590                         const Exiv2::Metadatum *item = &*exif->iptcIter;
591                         exif->iptcIter++;
592                         return (ExifItem *)item;
593                 }
594                 if (exif->xmpIter != exif->xmpData().end())
595                         {
596                         const Exiv2::Metadatum *item = &*exif->xmpIter;
597                         exif->xmpIter++;
598                         return (ExifItem *)item;
599                 }
600                 return nullptr;
601         }
602         catch (Exiv2::AnyError& e) {
603                 debug_exception(e);
604                 return nullptr;
605         }
606 }
607
608 char *exif_item_get_tag_name(ExifItem *item)
609 {
610         try {
611                 if (!item) return nullptr;
612                 return g_strdup((reinterpret_cast<Exiv2::Metadatum *>(item))->key().c_str());
613         }
614         catch (Exiv2::AnyError& e) {
615                 debug_exception(e);
616                 return nullptr;
617         }
618 }
619
620 guint exif_item_get_tag_id(ExifItem *item)
621 {
622         try {
623                 if (!item) return 0;
624                 return (reinterpret_cast<Exiv2::Metadatum *>(item))->tag();
625         }
626         catch (Exiv2::AnyError& e) {
627                 debug_exception(e);
628                 return 0;
629         }
630 }
631
632 guint exif_item_get_elements(ExifItem *item)
633 {
634         try {
635                 if (!item) return 0;
636                 return (reinterpret_cast<Exiv2::Metadatum *>(item))->count();
637         }
638         catch (Exiv2::AnyError& e) {
639                 debug_exception(e);
640                 return 0;
641         }
642 }
643
644 char *exif_item_get_data(ExifItem *item, guint *data_len)
645 {
646         try {
647                 if (!item) return nullptr;
648                 auto md = reinterpret_cast<Exiv2::Metadatum *>(item);
649                 if (data_len) *data_len = md->size();
650                 auto data = static_cast<char *>(g_malloc(md->size()));
651                 long res = md->copy(reinterpret_cast<Exiv2::byte *>(data), Exiv2::littleEndian /* should not matter */);
652                 g_assert(res == md->size());
653                 return data;
654         }
655         catch (Exiv2::AnyError& e) {
656                 debug_exception(e);
657                 return nullptr;
658         }
659 }
660
661 char *exif_item_get_description(ExifItem *item)
662 {
663         try {
664                 if (!item) return nullptr;
665                 return utf8_validate_or_convert((reinterpret_cast<Exiv2::Metadatum *>(item))->tagLabel().c_str());
666         }
667         catch (std::exception& e) {
668                 return nullptr;
669         }
670 }
671
672 /*
673 invalidTypeId, unsignedByte, asciiString, unsignedShort,
674   unsignedLong, unsignedRational, signedByte, undefined,
675   signedShort, signedLong, signedRational, string,
676   date, time, comment, directory,
677   xmpText, xmpAlt, xmpBag, xmpSeq,
678   langAlt, lastTypeId
679 */
680
681 static guint format_id_trans_tbl [] = {
682         EXIF_FORMAT_UNKNOWN,
683         EXIF_FORMAT_BYTE_UNSIGNED,
684         EXIF_FORMAT_STRING,
685         EXIF_FORMAT_SHORT_UNSIGNED,
686         EXIF_FORMAT_LONG_UNSIGNED,
687         EXIF_FORMAT_RATIONAL_UNSIGNED,
688         EXIF_FORMAT_BYTE,
689         EXIF_FORMAT_UNDEFINED,
690         EXIF_FORMAT_SHORT,
691         EXIF_FORMAT_LONG,
692         EXIF_FORMAT_RATIONAL,
693         EXIF_FORMAT_STRING,
694         EXIF_FORMAT_STRING,
695         EXIF_FORMAT_STRING,
696         EXIF_FORMAT_UNDEFINED,
697         EXIF_FORMAT_STRING,
698         EXIF_FORMAT_STRING,
699         EXIF_FORMAT_STRING,
700         EXIF_FORMAT_STRING
701         };
702
703
704
705 guint exif_item_get_format_id(ExifItem *item)
706 {
707         try {
708                 if (!item) return EXIF_FORMAT_UNKNOWN;
709                 guint id = (reinterpret_cast<Exiv2::Metadatum *>(item))->typeId();
710                 if (id >= (sizeof(format_id_trans_tbl) / sizeof(format_id_trans_tbl[0])) ) return EXIF_FORMAT_UNKNOWN;
711                 return format_id_trans_tbl[id];
712         }
713         catch (Exiv2::AnyError& e) {
714                 debug_exception(e);
715                 return EXIF_FORMAT_UNKNOWN;
716         }
717 }
718
719 const char *exif_item_get_format_name(ExifItem *item, gboolean)
720 {
721         try {
722                 if (!item) return nullptr;
723                 return (reinterpret_cast<Exiv2::Metadatum *>(item))->typeName();
724         }
725         catch (Exiv2::AnyError& e) {
726                 debug_exception(e);
727                 return nullptr;
728         }
729 }
730
731
732 gchar *exif_item_get_data_as_text(ExifItem *item, ExifData *exif)
733 {
734         try {
735                 if (!item) return nullptr;
736                 auto metadatum = reinterpret_cast<Exiv2::Metadatum *>(item);
737                 return utf8_validate_or_convert(metadatum->print(&exif->exifData()).c_str());
738         }
739         catch (Exiv2::AnyError& e) {
740                 return nullptr;
741         }
742 }
743
744 gchar *exif_item_get_string(ExifItem *item, int idx)
745 {
746         try {
747                 if (!item) return nullptr;
748                 auto em = reinterpret_cast<Exiv2::Metadatum *>(item);
749                 std::string str = em->toString(idx);
750                 if (idx == 0 && str.empty()) str = em->toString();
751                 if (str.length() > 5 && str.substr(0, 5) == "lang=")
752                         {
753                         std::string::size_type pos = str.find_first_of(' ');
754                         if (pos != std::string::npos) str = str.substr(pos+1);
755                         }
756
757                 return utf8_validate_or_convert(str.c_str());
758         }
759         catch (Exiv2::AnyError& e) {
760                 return nullptr;
761         }
762 }
763
764
765 gint exif_item_get_integer(ExifItem *item, gint *value)
766 {
767         try {
768                 if (!item || exif_item_get_elements(item) == 0) return 0;
769
770 #if EXIV2_TEST_VERSION(0,28,0)
771         *value = ((Exiv2::Metadatum *)item)->toInt64();
772 #else
773                 *value = (reinterpret_cast<Exiv2::Metadatum *>(item))->toLong();
774 #endif
775                 return 1;
776         }
777         catch (Exiv2::AnyError& e) {
778                 debug_exception(e);
779                 return 0;
780         }
781 }
782
783 ExifRational *exif_item_get_rational(ExifItem *item, gint *sign, guint n)
784 {
785         try {
786                 if (!item) return nullptr;
787                 if (n >= exif_item_get_elements(item)) return nullptr;
788                 Exiv2::Rational v = (reinterpret_cast<Exiv2::Metadatum *>(item))->toRational(n);
789                 static ExifRational ret;
790                 ret.num = v.first;
791                 ret.den = v.second;
792                 if (sign) *sign = ((reinterpret_cast<Exiv2::Metadatum *>(item))->typeId() == Exiv2::signedRational);
793                 return &ret;
794         }
795         catch (Exiv2::AnyError& e) {
796                 debug_exception(e);
797                 return nullptr;
798         }
799 }
800
801 gchar *exif_get_tag_description_by_key(const gchar *key)
802 {
803         try {
804                 Exiv2::ExifKey ekey(key);
805                 return utf8_validate_or_convert(ekey.tagLabel().c_str());
806         }
807         catch (Exiv2::AnyError& e) {
808                 try {
809                         Exiv2::IptcKey ikey(key);
810                         return utf8_validate_or_convert(ikey.tagLabel().c_str());
811                 }
812                 catch (Exiv2::AnyError& e) {
813                         try {
814                                 Exiv2::XmpKey xkey(key);
815                                 return utf8_validate_or_convert(xkey.tagLabel().c_str());
816                         }
817                         catch (Exiv2::AnyError& e) {
818                                 debug_exception(e);
819                                 return nullptr;
820                         }
821                 }
822         }
823         return nullptr;
824 }
825
826 static const AltKey *find_alt_key(const gchar *xmp_key)
827 {
828         for (const auto& k : alt_keys)
829                 if (strcmp(xmp_key, k.xmp_key) == 0) return &k;
830         return nullptr;
831 }
832
833 static gint exif_update_metadata_simple(ExifData *exif, const gchar *key, const GList *values)
834 {
835         try {
836                 const GList *work = values;
837
838                 try {
839                         Exiv2::ExifKey ekey(key);
840
841                         auto pos = exif->exifData().findKey(ekey);
842                         while (pos != exif->exifData().end())
843                                 {
844                                 exif->exifData().erase(pos);
845                                 pos = exif->exifData().findKey(ekey);
846                                 }
847
848                         while (work)
849                                 {
850                                 exif->exifData()[key] = static_cast<gchar *>(work->data);
851                                 work = work->next;
852                                 }
853                 }
854                 catch (Exiv2::AnyError& e) {
855                         try
856                         {
857                                 Exiv2::IptcKey ekey(key);
858                                 auto pos = exif->iptcData().findKey(ekey);
859                                 while (pos != exif->iptcData().end())
860                                         {
861                                         exif->iptcData().erase(pos);
862                                         pos = exif->iptcData().findKey(ekey);
863                                         }
864
865                                 while (work)
866                                         {
867                                         exif->iptcData()[key] = static_cast<gchar *>(work->data);
868                                         work = work->next;
869                                         }
870                         }
871                         catch (Exiv2::AnyError& e) {
872                                 Exiv2::XmpKey ekey(key);
873                                 auto pos = exif->xmpData().findKey(ekey);
874                                 while (pos != exif->xmpData().end())
875                                         {
876                                         exif->xmpData().erase(pos);
877                                         pos = exif->xmpData().findKey(ekey);
878                                         }
879
880                                 while (work)
881                                         {
882                                         exif->xmpData()[key] = static_cast<gchar *>(work->data);
883                                         work = work->next;
884                                         }
885                         }
886                 }
887                 return 1;
888         }
889         catch (Exiv2::AnyError& e) {
890                 debug_exception(e);
891                 return 0;
892         }
893 }
894
895 gint exif_update_metadata(ExifData *exif, const gchar *key, const GList *values)
896 {
897         gint ret = exif_update_metadata_simple(exif, key, values);
898
899         if (
900             !values || /* deleting item */
901             !ret  /* writing to the explicitly given xmp tag failed */
902             )
903                 {
904                 /* deleted xmp metadatum can't be converted, we have to delete also the corresponding legacy tag */
905                 /* if we can't write xmp, update at least the legacy tag */
906                 const AltKey *alt_key = find_alt_key(key);
907                 if (alt_key && alt_key->iptc_key)
908                         ret = exif_update_metadata_simple(exif, alt_key->iptc_key, values);
909
910                 if (alt_key && alt_key->exif_key)
911                         ret = exif_update_metadata_simple(exif, alt_key->exif_key, values);
912                 }
913         return ret;
914 }
915
916
917 static GList *exif_add_value_to_glist(GList *list, Exiv2::Metadatum &item, MetadataFormat format, const Exiv2::ExifData *metadata)
918 {
919         Exiv2::TypeId id = item.typeId();
920         if (format == METADATA_FORMATTED ||
921             id == Exiv2::asciiString ||
922             id == Exiv2::undefined ||
923             id == Exiv2::string ||
924             id == Exiv2::date ||
925             id == Exiv2::time ||
926             id == Exiv2::xmpText ||
927             id == Exiv2::langAlt ||
928             id == Exiv2::comment
929             )
930                 {
931                 /* read as a single entry */
932                 std::string str;
933
934                 if (format == METADATA_FORMATTED)
935                         {
936                         str = item.print(metadata);
937                         if (str.length() > 1024)
938                                 {
939                                 /* truncate very long strings, they cause problems in gui */
940                                 str.erase(1024);
941                                 str.append("...");
942                                 }
943                         }
944                 else
945                         {
946                         str = item.toString();
947                         }
948                 if (str.length() > 5 && str.substr(0, 5) == "lang=")
949                         {
950                         std::string::size_type pos = str.find_first_of(' ');
951                         if (pos != std::string::npos) str = str.substr(pos+1);
952                         }
953                 list = g_list_append(list, utf8_validate_or_convert(str.c_str()));
954                 }
955         else
956                 {
957                 /* read as a list */
958                 gint i;
959                 for (i = 0; i < item.count(); i++)
960                         list = g_list_append(list, utf8_validate_or_convert(item.toString(i).c_str()));
961                 }
962         return list;
963 }
964
965 static GList *exif_get_metadata_simple(ExifData *exif, const gchar *key, MetadataFormat format)
966 {
967         GList *list = nullptr;
968         try {
969                 try {
970                         Exiv2::ExifKey ekey(key);
971                         auto pos = exif->exifData().findKey(ekey);
972                         if (pos != exif->exifData().end())
973                                 list = exif_add_value_to_glist(list, *pos, format, &exif->exifData());
974
975                 }
976                 catch (Exiv2::AnyError& e) {
977                         try {
978                                 Exiv2::IptcKey ekey(key);
979                                 auto pos = exif->iptcData().begin();
980                                 while (pos != exif->iptcData().end())
981                                         {
982                                         if (pos->key() == key)
983                                                 list = exif_add_value_to_glist(list, *pos, format, nullptr);
984                                         ++pos;
985                                         }
986
987                         }
988                         catch (Exiv2::AnyError& e) {
989                                 Exiv2::XmpKey ekey(key);
990                                 auto pos = exif->xmpData().findKey(ekey);
991                                 if (pos != exif->xmpData().end())
992                                         list = exif_add_value_to_glist(list, *pos, format, nullptr);
993                         }
994                 }
995         }
996         catch (Exiv2::AnyError& e) {
997                 debug_exception(e);
998         }
999         return list;
1000 }
1001
1002 GList *exif_get_metadata(ExifData *exif, const gchar *key, MetadataFormat format)
1003 {
1004         GList *list = nullptr;
1005
1006         if (!key) return nullptr;
1007
1008         if (format == METADATA_FORMATTED)
1009                 {
1010                 gchar *text;
1011                 gint key_valid;
1012                 text = exif_get_formatted_by_key(exif, key, &key_valid);
1013                 if (key_valid) return g_list_append(nullptr, text);
1014                 }
1015
1016         list = exif_get_metadata_simple(exif, key, format);
1017
1018         /* the following code can be ifdefed out as soon as Exiv2 supports it */
1019         if (!list)
1020                 {
1021                 const AltKey *alt_key = find_alt_key(key);
1022                 if (alt_key && alt_key->iptc_key)
1023                         list = exif_get_metadata_simple(exif, alt_key->iptc_key, format);
1024                 }
1025         return list;
1026 }
1027
1028
1029 void exif_add_jpeg_color_profile(ExifData *exif, unsigned char *cp_data, guint cp_length)
1030 {
1031         exif->add_jpeg_color_profile(cp_data, cp_length);
1032 }
1033
1034 guchar *exif_get_color_profile(ExifData *exif, guint *data_len)
1035 {
1036         guchar *ret = exif->get_jpeg_color_profile(data_len);
1037         if (ret) return ret;
1038
1039         ExifItem *prof_item = exif_get_item(exif, "Exif.Image.InterColorProfile");
1040         if (prof_item && exif_item_get_format_id(prof_item) == EXIF_FORMAT_UNDEFINED)
1041                 ret = reinterpret_cast<guchar *>(exif_item_get_data(prof_item, data_len));
1042         return ret;
1043 }
1044
1045 gchar* exif_get_image_comment(FileData* fd)
1046 {
1047         if (!fd || !fd->exif)
1048                 return g_strdup("");
1049
1050         return g_strdup(fd->exif->image_comment().c_str());
1051 }
1052
1053 void exif_set_image_comment(FileData* fd, const gchar* comment)
1054 {
1055         if (!fd || !fd->exif)
1056                 return;
1057
1058         fd->exif->set_image_comment(comment ? comment : "");
1059 }
1060
1061
1062 guchar *exif_get_preview(ExifData *exif, guint *data_len, gint requested_width, gint requested_height)
1063 {
1064         if (!exif) return nullptr;
1065
1066         if (!exif->image()) return nullptr;
1067
1068         std::string const path = exif->image()->io().path();
1069         /* given image pathname, first do simple (and fast) file extension test */
1070         gboolean is_raw = filter_file_class(path.c_str(), FORMAT_CLASS_RAWIMAGE);
1071
1072         if (!is_raw && requested_width == 0) return nullptr;
1073
1074         try {
1075
1076                 Exiv2::PreviewManager pm(*exif->image());
1077
1078                 Exiv2::PreviewPropertiesList list = pm.getPreviewProperties();
1079
1080                 if (!list.empty())
1081                         {
1082                         Exiv2::PreviewPropertiesList::iterator pos;
1083                         auto last = --list.end();
1084
1085                         if (requested_width == 0)
1086                                 {
1087                                 pos = last; // the largest
1088                                 }
1089                         else
1090                                 {
1091                                 pos = list.begin();
1092                                 while (pos != last)
1093                                         {
1094                                         if (pos->width_ >= static_cast<uint32_t>(requested_width) &&
1095                                             pos->height_ >= static_cast<uint32_t>(requested_height)) break;
1096                                         ++pos;
1097                                         }
1098
1099                                 // we are not interested in smaller thumbnails in normal image formats - we can use full image instead
1100                                 if (!is_raw)
1101                                         {
1102                                         if (pos->width_ < static_cast<uint32_t>(requested_width) || pos->height_ < static_cast<uint32_t>(requested_height)) return nullptr;
1103                                         }
1104                                 }
1105
1106                         Exiv2::PreviewImage image = pm.getPreviewImage(*pos);
1107
1108                         // Let's not touch data_len until we finish copy.
1109                         // Just in case we run into OOM.
1110                         size_t img_sz = image.size();
1111                         auto* b = new Exiv2::byte[img_sz];
1112                         std::copy_n(image.pData(), img_sz, b);
1113                         *data_len = img_sz;
1114                         return b;
1115                         }
1116                 return nullptr;
1117         }
1118         catch (Exiv2::AnyError& e) {
1119                 debug_exception(e);
1120                 return nullptr;
1121         }
1122 }
1123
1124 void exif_free_preview(const guchar *buf)
1125 {
1126         delete[] static_cast<const Exiv2::byte*>(buf);
1127 }
1128
1129 #endif /* HAVE_EXIV2 */
1130 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */