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