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