b0f6560151ad2d8eac7e6df42c1c675bf443ef8b
[geeqie.git] / src / exiv2.cc
1 /*
2  * Geeqie
3  * Copyright (C) 2008 - 2012 The Geeqie Team
4  *
5  * Author: Vladimir Nadvornik
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12 #include "config.h"
13
14 #ifdef HAVE_EXIV2
15
16 #include <exiv2/image.hpp>
17 #include <exiv2/exif.hpp>
18 #include <iostream>
19
20 // EXIV2_TEST_VERSION is defined in Exiv2 0.15 and newer.
21 #ifndef EXIV2_TEST_VERSION
22 # define EXIV2_TEST_VERSION(major,minor,patch) \
23         ( EXIV2_VERSION >= EXIV2_MAKE_VERSION(major,minor,patch) )
24 #endif
25
26
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <sys/mman.h>
32
33 #if !EXIV2_TEST_VERSION(0,17,90)
34 #include <exiv2/tiffparser.hpp>
35 #include <exiv2/tiffcomposite.hpp>
36 #include <exiv2/tiffvisitor.hpp>
37 #include <exiv2/tiffimage.hpp>
38 #include <exiv2/cr2image.hpp>
39 #include <exiv2/crwimage.hpp>
40 #if EXIV2_TEST_VERSION(0,16,0)
41 #include <exiv2/orfimage.hpp>
42 #endif
43 #if EXIV2_TEST_VERSION(0,13,0)
44 #include <exiv2/rafimage.hpp>
45 #endif
46 #include <exiv2/futils.hpp>
47 #else
48 #include <exiv2/preview.hpp>
49 #endif
50
51 #if EXIV2_TEST_VERSION(0,17,0)
52 #include <exiv2/convert.hpp>
53 #include <exiv2/xmpsidecar.hpp>
54 #endif
55
56 extern "C" {
57 #include <glib.h>
58
59 #include "main.h"
60 #include "exif.h"
61
62 #include "filefilter.h"
63 #include "ui_fileops.h"
64
65 #include "misc.h"
66 }
67
68 typedef struct _AltKey AltKey;
69
70 struct _AltKey
71 {
72         const gchar *xmp_key;
73         const gchar *exif_key;
74         const gchar *iptc_key;
75 };
76
77 /* this is a list of keys that should be converted, even with the older Exiv2 which does not support it directly */
78 static const AltKey alt_keys[] = {
79         {"Xmp.tiff.Orientation",                "Exif.Image.Orientation",       NULL},
80         {"Xmp.dc.title",                        NULL,                           "Iptc.Application2.ObjectName"          },
81         {"Xmp.photoshop.Urgency",               NULL,                           "Iptc.Application2.Urgency"             },
82         {"Xmp.photoshop.Category",              NULL,                           "Iptc.Application2.Category"            },
83         {"Xmp.photoshop.SupplementalCategory",  NULL,                           "Iptc.Application2.SuppCategory"        },
84         {"Xmp.dc.subject",                      NULL,                           "Iptc.Application2.Keywords"            },
85         {"Xmp.iptc.Location",                   NULL,                           "Iptc.Application2.LocationName"        },
86         {"Xmp.photoshop.Instruction",           NULL,                           "Iptc.Application2.SpecialInstructions" },
87         {"Xmp.photoshop.DateCreated",           NULL,                           "Iptc.Application2.DateCreated"         },
88         {"Xmp.dc.creator",                      NULL,                           "Iptc.Application2.Byline"              },
89         {"Xmp.photoshop.AuthorsPosition",       NULL,                           "Iptc.Application2.BylineTitle"         },
90         {"Xmp.photoshop.City",                  NULL,                           "Iptc.Application2.City"                },
91         {"Xmp.photoshop.State",                 NULL,                           "Iptc.Application2.ProvinceState"       },
92         {"Xmp.iptc.CountryCode",                NULL,                           "Iptc.Application2.CountryCode"         },
93         {"Xmp.photoshop.Country",               NULL,                           "Iptc.Application2.CountryName"         },
94         {"Xmp.photoshop.TransmissionReference", NULL,                           "Iptc.Application2.TransmissionReference"},
95         {"Xmp.photoshop.Headline",              NULL,                           "Iptc.Application2.Headline"            },
96         {"Xmp.photoshop.Credit",                NULL,                           "Iptc.Application2.Credit"              },
97         {"Xmp.photoshop.Source",                NULL,                           "Iptc.Application2.Source"              },
98         {"Xmp.dc.rights",                       NULL,                           "Iptc.Application2.Copyright"           },
99         {"Xmp.dc.description",                  NULL,                           "Iptc.Application2.Caption"             },
100         {"Xmp.photoshop.CaptionWriter",         NULL,                           "Iptc.Application2.Writer"              },
101         {NULL, NULL, NULL}
102         };
103
104 static void debug_exception(Exiv2::AnyError& e)
105 {
106         gchar *str = g_locale_from_utf8(e.what(), -1, NULL, NULL, NULL);
107         DEBUG_1("Exiv2: %s", str);
108         g_free(str);
109 }
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 #if EXIV2_TEST_VERSION(0,16,0)
116         Exiv2::XmpData::const_iterator xmpIter; /* for exif_get_next_item */
117 #endif
118
119         virtual ~_ExifData()
120         {
121         }
122         
123         virtual void writeMetadata(gchar *path = NULL)
124         {
125                 g_critical("Unsupported method of writing metadata");
126         }
127
128         virtual ExifData *original()
129         {
130                 return NULL;
131         }
132
133         virtual Exiv2::Image *image() = 0;
134         
135         virtual Exiv2::ExifData &exifData() = 0;
136
137         virtual Exiv2::IptcData &iptcData() = 0;
138
139 #if EXIV2_TEST_VERSION(0,16,0)
140         virtual Exiv2::XmpData &xmpData() = 0;
141 #endif
142
143         virtual void add_jpeg_color_profile(unsigned char *cp_data, guint cp_length) = 0;
144
145         virtual guchar *get_jpeg_color_profile(guint *data_len) = 0;
146 };
147
148 // This allows read-only access to the original metadata
149 struct _ExifDataOriginal : public _ExifData
150 {
151 protected:
152         Exiv2::Image::AutoPtr image_;
153
154         /* the icc profile in jpeg is not technically exif - store it here */
155         unsigned char *cp_data_;
156         guint cp_length_;
157         gboolean valid_;
158
159         Exiv2::ExifData emptyExifData_;
160         Exiv2::IptcData emptyIptcData_;
161 #if EXIV2_TEST_VERSION(0,16,0)
162         Exiv2::XmpData emptyXmpData_;
163 #endif
164
165 public:
166         _ExifDataOriginal(Exiv2::Image::AutoPtr image)
167         {
168                 cp_data_ = NULL;
169                 cp_length_ = 0;
170                 image_ = image;
171                 valid_ = TRUE;
172         }
173
174         _ExifDataOriginal(gchar *path)
175         {
176                 cp_data_ = NULL;
177                 cp_length_ = 0;
178                 valid_ = TRUE;
179                 
180                 gchar *pathl = path_from_utf8(path);
181                 try 
182                         {
183                         image_ = Exiv2::ImageFactory::open(pathl);
184 //                      g_assert (image.get() != 0);
185                         image_->readMetadata();
186
187 #if EXIV2_TEST_VERSION(0,16,0)
188                         if (image_->mimeType() == "application/rdf+xml")
189                                 {
190                                 //Exiv2 sidecar converts xmp to exif and iptc, we don't want it.
191                                 image_->clearExifData();
192                                 image_->clearIptcData();
193                                 }
194 #endif
195
196 #if EXIV2_TEST_VERSION(0,14,0)
197                         if (image_->mimeType() == "image/jpeg")
198                                 {
199                                 /* try to get jpeg color profile */
200                                 Exiv2::BasicIo &io = image_->io();
201                                 gint open = io.isopen();
202                                 if (!open) io.open();
203                                 if (io.isopen())
204                                         {
205                                         unsigned char *mapped = (unsigned char*)io.mmap();
206                                         if (mapped) exif_jpeg_parse_color(this, mapped, io.size());
207                                         io.munmap();
208                                         }
209                                 if (!open) io.close();
210                                 }
211 #endif
212                         }
213                 catch (Exiv2::AnyError& e) 
214                         {
215                         valid_ = FALSE;
216                         }
217                 g_free(pathl);
218         }
219         
220         virtual ~_ExifDataOriginal()
221         {
222                 if (cp_data_) g_free(cp_data_);
223         }
224         
225         virtual Exiv2::Image *image()
226         {
227                 if (!valid_) return NULL;
228                 return image_.get();
229         }
230         
231         virtual Exiv2::ExifData &exifData ()
232         {
233                 if (!valid_) return emptyExifData_;
234                 return image_->exifData();
235         }
236
237         virtual Exiv2::IptcData &iptcData ()
238         {
239                 if (!valid_) return emptyIptcData_;
240                 return image_->iptcData();
241         }
242
243 #if EXIV2_TEST_VERSION(0,16,0)
244         virtual Exiv2::XmpData &xmpData ()
245         {
246                 if (!valid_) return emptyXmpData_;
247                 return image_->xmpData();
248         }
249 #endif
250
251         virtual void add_jpeg_color_profile(unsigned char *cp_data, guint cp_length)
252         {
253                 if (cp_data_) g_free(cp_data_);
254                 cp_data_ = cp_data;
255                 cp_length_ = cp_length;
256         }
257
258         virtual guchar *get_jpeg_color_profile(guint *data_len)
259         {
260                 if (cp_data_)
261                 {
262                         if (data_len) *data_len = cp_length_;
263                         return (unsigned char *) g_memdup(cp_data_, cp_length_);
264                 }
265                 return NULL;
266         }
267 };
268
269 extern "C" {
270 static void _ExifDataProcessed_update_xmp(gpointer key, gpointer value, gpointer data);
271 }
272
273 // This allows read-write access to the metadata
274 struct _ExifDataProcessed : public _ExifData
275 {
276 protected:
277         _ExifDataOriginal *imageData_;
278         _ExifDataOriginal *sidecarData_;
279
280         Exiv2::ExifData exifData_;
281         Exiv2::IptcData iptcData_;
282 #if EXIV2_TEST_VERSION(0,16,0)
283         Exiv2::XmpData xmpData_;
284 #endif
285
286 public:
287         _ExifDataProcessed(gchar *path, gchar *sidecar_path, GHashTable *modified_xmp)
288         {
289                 imageData_ = new _ExifDataOriginal(path);
290                 sidecarData_ = NULL;
291 #if EXIV2_TEST_VERSION(0,16,0)
292                 if (sidecar_path)
293                         {
294                         sidecarData_ = new _ExifDataOriginal(sidecar_path);
295                         xmpData_ = sidecarData_->xmpData();
296                         }
297                 else
298                         {
299                         xmpData_ = imageData_->xmpData();
300                         }
301
302 #endif
303                 exifData_ = imageData_->exifData();
304                 iptcData_ = imageData_->iptcData();
305 #if EXIV2_TEST_VERSION(0,17,0)
306                 try
307                         {
308                         syncExifWithXmp(exifData_, xmpData_);
309                         }
310                 catch (...)
311                         {
312                         DEBUG_1("Exiv2: Catching bug\n");
313                         }
314 #endif
315                 if (modified_xmp)
316                         {
317                         g_hash_table_foreach(modified_xmp, _ExifDataProcessed_update_xmp, this);
318                         }
319         }
320
321         virtual ~_ExifDataProcessed()
322         {
323                 if (imageData_) delete imageData_;
324                 if (sidecarData_) delete sidecarData_;
325         }
326
327         virtual ExifData *original()
328         {
329                 return imageData_;
330         }
331
332         virtual void writeMetadata(gchar *path = NULL)
333         {
334                 if (!path)
335                         {
336 #if EXIV2_TEST_VERSION(0,17,0)
337                         if (options->metadata.save_legacy_IPTC) 
338                                 copyXmpToIptc(xmpData_, iptcData_);
339                         else
340                                 iptcData_.clear();
341
342                         copyXmpToExif(xmpData_, exifData_);
343 #endif
344                         Exiv2::Image *image = imageData_->image();
345                         
346                         if (!image) Exiv2::Error(21);
347                         image->setExifData(exifData_);
348                         image->setIptcData(iptcData_);
349 #if EXIV2_TEST_VERSION(0,16,0)
350                         image->setXmpData(xmpData_);
351 #endif
352                         image->writeMetadata();
353                         } 
354                 else
355                         {
356 #if EXIV2_TEST_VERSION(0,17,0)
357                         gchar *pathl = path_from_utf8(path);;
358
359                         Exiv2::Image::AutoPtr sidecar = Exiv2::ImageFactory::create(Exiv2::ImageType::xmp, pathl);
360                                 
361                         g_free(pathl);
362
363                         sidecar->setXmpData(xmpData_);
364                         sidecar->writeMetadata();
365 #else
366                         throw Exiv2::Error(3, "xmp");
367 #endif
368                         }
369         }
370         
371         virtual Exiv2::Image *image()
372         {
373                 return imageData_->image();
374         }
375         
376         virtual Exiv2::ExifData &exifData ()
377         {
378                 return exifData_;
379         }
380
381         virtual Exiv2::IptcData &iptcData ()
382         {
383                 return iptcData_;
384         }
385
386 #if EXIV2_TEST_VERSION(0,16,0)
387         virtual Exiv2::XmpData &xmpData ()
388         {
389                 return xmpData_;
390         }
391 #endif
392
393         virtual void add_jpeg_color_profile(unsigned char *cp_data, guint cp_length)
394         {
395                 imageData_->add_jpeg_color_profile(cp_data, cp_length);
396         }
397
398         virtual guchar *get_jpeg_color_profile(guint *data_len)
399         {
400                 return imageData_->get_jpeg_color_profile(data_len);
401         }
402 };
403
404
405
406
407 extern "C" {
408
409
410 void exif_init(void)
411 {
412 #ifdef EXV_ENABLE_NLS
413         bind_textdomain_codeset (EXV_PACKAGE, "UTF-8");
414 #endif
415 }
416
417
418
419 static void _ExifDataProcessed_update_xmp(gpointer key, gpointer value, gpointer data)
420 {
421         exif_update_metadata((ExifData *)data, (gchar *)key, (GList *)value);
422 }
423
424 ExifData *exif_read(gchar *path, gchar *sidecar_path, GHashTable *modified_xmp)
425 {
426         DEBUG_1("exif read %s, sidecar: %s", path, sidecar_path ? sidecar_path : "-");
427         try {
428                 return new _ExifDataProcessed(path, sidecar_path, modified_xmp);
429         }
430         catch (Exiv2::AnyError& e) {
431                 debug_exception(e);
432                 return NULL;
433         }
434         
435 }
436
437 gboolean exif_write(ExifData *exif)
438 {
439         try {
440                 exif->writeMetadata();
441                 return TRUE;
442         }
443         catch (Exiv2::AnyError& e) {
444                 debug_exception(e);
445                 return FALSE;
446         }
447 }
448
449 gboolean exif_write_sidecar(ExifData *exif, gchar *path)
450 {
451         try {
452                 exif->writeMetadata(path);
453                 return TRUE;
454         }
455         catch (Exiv2::AnyError& e) {
456                 debug_exception(e);
457                 return FALSE;
458         }
459         
460 }
461
462
463 void exif_free(ExifData *exif)
464 {
465         if (!exif) return;
466         g_assert(dynamic_cast<_ExifDataProcessed *>(exif)); // this should not be called on ExifDataOriginal
467         delete exif;
468 }
469
470 ExifData *exif_get_original(ExifData *exif)
471 {
472         return exif->original();
473 }
474
475
476 ExifItem *exif_get_item(ExifData *exif, const gchar *key)
477 {
478         try {
479                 Exiv2::Metadatum *item = NULL;
480                 try {
481                         Exiv2::ExifKey ekey(key);
482                         Exiv2::ExifData::iterator pos = exif->exifData().findKey(ekey);
483                         if (pos == exif->exifData().end()) return NULL;
484                         item = &*pos;
485                 }
486                 catch (Exiv2::AnyError& e) {
487                         try {
488                                 Exiv2::IptcKey ekey(key);
489                                 Exiv2::IptcData::iterator pos = exif->iptcData().findKey(ekey);
490                                 if (pos == exif->iptcData().end()) return NULL;
491                                 item = &*pos;
492                         }
493                         catch (Exiv2::AnyError& e) {
494 #if EXIV2_TEST_VERSION(0,16,0)
495                                 Exiv2::XmpKey ekey(key);
496                                 Exiv2::XmpData::iterator pos = exif->xmpData().findKey(ekey);
497                                 if (pos == exif->xmpData().end()) return NULL;
498                                 item = &*pos;
499 #endif
500                         }
501                 }
502                 return (ExifItem *)item;
503         }
504         catch (Exiv2::AnyError& e) {
505                 debug_exception(e);
506                 return NULL;
507         }
508 }
509
510 ExifItem *exif_add_item(ExifData *exif, const gchar *key)
511 {
512         try {
513                 Exiv2::Metadatum *item = NULL;
514                 try {
515                         Exiv2::ExifKey ekey(key);
516                         exif->exifData().add(ekey, NULL);
517                         Exiv2::ExifData::iterator 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, NULL);
525                                 Exiv2::IptcData::iterator pos = exif->iptcData().end();
526                                 pos--;
527                                 item = &*pos;
528                         }
529                         catch (Exiv2::AnyError& e) {
530 #if EXIV2_TEST_VERSION(0,16,0)
531                                 Exiv2::XmpKey ekey(key);
532                                 exif->xmpData().add(ekey, NULL);
533                                 Exiv2::XmpData::iterator pos = exif->xmpData().end();
534                                 pos--;
535                                 item = &*pos;
536 #endif
537                         }
538                 }
539                 return (ExifItem *)item;
540         }
541         catch (Exiv2::AnyError& e) {
542                 debug_exception(e);
543                 return NULL;
544         }
545 }
546
547
548 ExifItem *exif_get_first_item(ExifData *exif)
549 {
550         try {
551                 exif->exifIter = exif->exifData().begin();
552                 exif->iptcIter = exif->iptcData().begin();
553 #if EXIV2_TEST_VERSION(0,16,0)
554                 exif->xmpIter = exif->xmpData().begin();
555 #endif
556                 if (exif->exifIter != exif->exifData().end())
557                         {
558                         const Exiv2::Metadatum *item = &*exif->exifIter;
559                         exif->exifIter++;
560                         return (ExifItem *)item;
561                         }
562                 if (exif->iptcIter != exif->iptcData().end())
563                         {
564                         const Exiv2::Metadatum *item = &*exif->iptcIter;
565                         exif->iptcIter++;
566                         return (ExifItem *)item;
567                         }
568 #if EXIV2_TEST_VERSION(0,16,0)
569                 if (exif->xmpIter != exif->xmpData().end())
570                         {
571                         const Exiv2::Metadatum *item = &*exif->xmpIter;
572                         exif->xmpIter++;
573                         return (ExifItem *)item;
574                         }
575 #endif
576                 return NULL;
577                         
578         }
579         catch (Exiv2::AnyError& e) {
580                 debug_exception(e);
581                 return NULL;
582         }
583 }
584
585 ExifItem *exif_get_next_item(ExifData *exif)
586 {
587         try {
588                 if (exif->exifIter != exif->exifData().end())
589                         {
590                         const Exiv2::Metadatum *item = &*exif->exifIter;
591                         exif->exifIter++;
592                         return (ExifItem *)item;
593                 }
594                 if (exif->iptcIter != exif->iptcData().end())
595                         {
596                         const Exiv2::Metadatum *item = &*exif->iptcIter;
597                         exif->iptcIter++;
598                         return (ExifItem *)item;
599                 }
600 #if EXIV2_TEST_VERSION(0,16,0)
601                 if (exif->xmpIter != exif->xmpData().end())
602                         {
603                         const Exiv2::Metadatum *item = &*exif->xmpIter;
604                         exif->xmpIter++;
605                         return (ExifItem *)item;
606                 }
607 #endif
608                 return NULL;
609         }
610         catch (Exiv2::AnyError& e) {
611                 debug_exception(e);
612                 return NULL;
613         }
614 }
615
616 char *exif_item_get_tag_name(ExifItem *item)
617 {
618         try {
619                 if (!item) return NULL;
620                 return g_strdup(((Exiv2::Metadatum *)item)->key().c_str());
621         }
622         catch (Exiv2::AnyError& e) {
623                 debug_exception(e);
624                 return NULL;
625         }
626 }
627
628 guint exif_item_get_tag_id(ExifItem *item)
629 {
630         try {
631                 if (!item) return 0;
632                 return ((Exiv2::Metadatum *)item)->tag();
633         }
634         catch (Exiv2::AnyError& e) {
635                 debug_exception(e);
636                 return 0;
637         }
638 }
639
640 guint exif_item_get_elements(ExifItem *item)
641 {
642         try {
643                 if (!item) return 0;
644                 return ((Exiv2::Metadatum *)item)->count();
645         }
646         catch (Exiv2::AnyError& e) {
647                 debug_exception(e);
648                 return 0;
649         }
650 }
651
652 char *exif_item_get_data(ExifItem *item, guint *data_len)
653 {
654         try {
655                 if (!item) return 0;
656                 Exiv2::Metadatum *md = (Exiv2::Metadatum *)item;
657                 if (data_len) *data_len = md->size();
658                 char *data = (char *)g_malloc(md->size());
659                 long res = md->copy((Exiv2::byte *)data, Exiv2::littleEndian /* should not matter */);
660                 g_assert(res == md->size());
661                 return data;
662         }
663         catch (Exiv2::AnyError& e) {
664                 debug_exception(e);
665                 return NULL;
666         }
667 }
668
669 char *exif_item_get_description(ExifItem *item)
670 {
671         try {
672                 if (!item) return NULL;
673                 return utf8_validate_or_convert(((Exiv2::Metadatum *)item)->tagLabel().c_str());
674         }
675         catch (std::exception& e) {
676 //              debug_exception(e);
677                 return NULL;
678         }
679 }
680
681 /*
682 invalidTypeId, unsignedByte, asciiString, unsignedShort,
683   unsignedLong, unsignedRational, signedByte, undefined,
684   signedShort, signedLong, signedRational, string,
685   date, time, comment, directory,
686   xmpText, xmpAlt, xmpBag, xmpSeq,
687   langAlt, lastTypeId
688 */
689
690 static guint format_id_trans_tbl [] = {
691         EXIF_FORMAT_UNKNOWN,
692         EXIF_FORMAT_BYTE_UNSIGNED,
693         EXIF_FORMAT_STRING,
694         EXIF_FORMAT_SHORT_UNSIGNED,
695         EXIF_FORMAT_LONG_UNSIGNED,
696         EXIF_FORMAT_RATIONAL_UNSIGNED,
697         EXIF_FORMAT_BYTE,
698         EXIF_FORMAT_UNDEFINED,
699         EXIF_FORMAT_SHORT,
700         EXIF_FORMAT_LONG,
701         EXIF_FORMAT_RATIONAL,
702         EXIF_FORMAT_STRING,
703         EXIF_FORMAT_STRING,
704         EXIF_FORMAT_STRING,
705         EXIF_FORMAT_UNDEFINED,
706         EXIF_FORMAT_STRING,
707         EXIF_FORMAT_STRING,
708         EXIF_FORMAT_STRING,
709         EXIF_FORMAT_STRING
710         };
711         
712         
713
714 guint exif_item_get_format_id(ExifItem *item)
715 {
716         try {
717                 if (!item) return EXIF_FORMAT_UNKNOWN;
718                 guint id = ((Exiv2::Metadatum *)item)->typeId();
719                 if (id >= (sizeof(format_id_trans_tbl) / sizeof(format_id_trans_tbl[0])) ) return EXIF_FORMAT_UNKNOWN;
720                 return format_id_trans_tbl[id];
721         }
722         catch (Exiv2::AnyError& e) {
723                 debug_exception(e);
724                 return EXIF_FORMAT_UNKNOWN;
725         }
726 }
727
728 const char *exif_item_get_format_name(ExifItem *item, gboolean brief)
729 {
730         try {
731                 if (!item) return NULL;
732                 return ((Exiv2::Metadatum *)item)->typeName();
733         }
734         catch (Exiv2::AnyError& e) {
735                 debug_exception(e);
736                 return NULL;
737         }
738 }
739
740
741 gchar *exif_item_get_data_as_text(ExifItem *item)
742 {
743         try {
744                 if (!item) return NULL;
745                 Exiv2::Metadatum *metadatum = (Exiv2::Metadatum *)item;
746 #if EXIV2_TEST_VERSION(0,17,0)
747                 return utf8_validate_or_convert(metadatum->print().c_str());
748 #else
749                 std::stringstream str;
750                 Exiv2::Exifdatum *exifdatum;
751                 Exiv2::Iptcdatum *iptcdatum;
752 #if EXIV2_TEST_VERSION(0,16,0)
753                 Exiv2::Xmpdatum *xmpdatum;
754 #endif
755                 if ((exifdatum = dynamic_cast<Exiv2::Exifdatum *>(metadatum)))
756                         str << *exifdatum;
757                 else if ((iptcdatum = dynamic_cast<Exiv2::Iptcdatum *>(metadatum)))
758                         str << *iptcdatum;
759 #if EXIV2_TEST_VERSION(0,16,0)
760                 else if ((xmpdatum = dynamic_cast<Exiv2::Xmpdatum *>(metadatum)))
761                         str << *xmpdatum;
762 #endif
763
764                 return utf8_validate_or_convert(str.str().c_str());
765 #endif
766         }
767         catch (Exiv2::AnyError& e) {
768                 return NULL;
769         }
770 }
771
772 gchar *exif_item_get_string(ExifItem *item, int idx)
773 {
774         try {
775                 if (!item) return NULL;
776                 Exiv2::Metadatum *em = (Exiv2::Metadatum *)item;
777 #if EXIV2_TEST_VERSION(0,16,0)
778                 std::string str = em->toString(idx);
779 #else
780                 std::string str = em->toString(); // FIXME
781 #endif
782                 if (idx == 0 && str == "") str = em->toString();
783                 if (str.length() > 5 && str.substr(0, 5) == "lang=")
784                         {
785                         std::string::size_type pos = str.find_first_of(' ');
786                         if (pos != std::string::npos) str = str.substr(pos+1);
787                         }
788
789                 return utf8_validate_or_convert(str.c_str());
790         }
791         catch (Exiv2::AnyError& e) {
792                 return NULL;
793         }
794 }
795
796
797 gint exif_item_get_integer(ExifItem *item, gint *value)
798 {
799         try {
800                 if (!item || exif_item_get_elements(item) == 0) return 0;
801                 *value = ((Exiv2::Metadatum *)item)->toLong();
802                 return 1;
803         }
804         catch (Exiv2::AnyError& e) {
805                 debug_exception(e);
806                 return 0;
807         }
808 }
809
810 ExifRational *exif_item_get_rational(ExifItem *item, gint *sign, guint n)
811 {
812         try {
813                 if (!item) return NULL;
814                 if (n >= exif_item_get_elements(item)) return NULL;
815                 Exiv2::Rational v = ((Exiv2::Metadatum *)item)->toRational(n);
816                 static ExifRational ret;
817                 ret.num = v.first;
818                 ret.den = v.second;
819                 if (sign) *sign = (((Exiv2::Metadatum *)item)->typeId() == Exiv2::signedRational);
820                 return &ret;
821         }
822         catch (Exiv2::AnyError& e) {
823                 debug_exception(e);
824                 return NULL;
825         }
826 }
827
828 gchar *exif_get_tag_description_by_key(const gchar *key)
829 {
830         try {
831                 Exiv2::ExifKey ekey(key);
832                 return utf8_validate_or_convert(ekey.tagLabel().c_str());
833         }
834         catch (Exiv2::AnyError& e) {
835                 try {
836                         Exiv2::IptcKey ikey(key);
837                         return utf8_validate_or_convert(ikey.tagLabel().c_str());
838                 }
839                 catch (Exiv2::AnyError& e) {
840                         try {
841 #if EXIV2_TEST_VERSION(0,16,0)
842                                 Exiv2::XmpKey xkey(key);
843                                 return utf8_validate_or_convert(xkey.tagLabel().c_str());
844 #endif
845                         }
846                         catch (Exiv2::AnyError& e) {
847                                 debug_exception(e);
848                                 return NULL;
849                         }
850                 }
851         }
852         return NULL;
853 }
854
855 static const AltKey *find_alt_key(const gchar *xmp_key)
856 {
857         gint i = 0;
858         
859         while (alt_keys[i].xmp_key)
860                 {
861                 if (strcmp(xmp_key, alt_keys[i].xmp_key) == 0) return &alt_keys[i];
862                 i++;
863                 }
864         return NULL;
865 }
866
867 static gint exif_update_metadata_simple(ExifData *exif, const gchar *key, const GList *values)
868 {
869         try {
870                 const GList *work = values;
871
872                 try {
873                         Exiv2::ExifKey ekey(key);
874                         
875                         Exiv2::ExifData::iterator pos = exif->exifData().findKey(ekey);
876                         while (pos != exif->exifData().end())
877                                 {
878                                 exif->exifData().erase(pos);
879                                 pos = exif->exifData().findKey(ekey);
880                                 }
881
882                         while (work)
883                                 {
884                                 exif->exifData()[key] = (gchar *)work->data;
885                                 work = work->next;
886                                 }
887                 }
888                 catch (Exiv2::AnyError& e) {
889 #if EXIV2_TEST_VERSION(0,16,0)
890                         try 
891 #endif
892                         {
893                                 Exiv2::IptcKey ekey(key);
894                                 Exiv2::IptcData::iterator pos = exif->iptcData().findKey(ekey);
895                                 while (pos != exif->iptcData().end())
896                                         {
897                                         exif->iptcData().erase(pos);
898                                         pos = exif->iptcData().findKey(ekey);
899                                         }
900
901                                 while (work)
902                                         {
903                                         exif->iptcData()[key] = (gchar *)work->data;
904                                         work = work->next;
905                                         }
906                         }
907 #if EXIV2_TEST_VERSION(0,16,0)
908                         catch (Exiv2::AnyError& e) {
909                                 Exiv2::XmpKey ekey(key);
910                                 Exiv2::XmpData::iterator pos = exif->xmpData().findKey(ekey);
911                                 while (pos != exif->xmpData().end())
912                                         {
913                                         exif->xmpData().erase(pos);
914                                         pos = exif->xmpData().findKey(ekey);
915                                         }
916
917                                 while (work)
918                                         {
919                                         exif->xmpData()[key] = (gchar *)work->data;
920                                         work = work->next;
921                                         }
922                         }
923 #endif
924                 }
925                 return 1;
926         }
927         catch (Exiv2::AnyError& e) {
928                 debug_exception(e);
929                 return 0;
930         }
931 }
932
933 gint exif_update_metadata(ExifData *exif, const gchar *key, const GList *values)
934 {
935         gint ret = exif_update_metadata_simple(exif, key, values);
936         
937         if (
938 #if !EXIV2_TEST_VERSION(0,17,0)
939             TRUE || /* no conversion support */
940 #endif
941             !values || /* deleting item */
942             !ret  /* writing to the explicitely given xmp tag failed */
943             )
944                 {
945                 /* deleted xmp metadatum can't be converted, we have to delete also the corresponding legacy tag */
946                 /* if we can't write xmp, update at least the legacy tag */
947                 const AltKey *alt_key = find_alt_key(key);
948                 if (alt_key && alt_key->iptc_key)
949                         ret = exif_update_metadata_simple(exif, alt_key->iptc_key, values);
950
951                 if (alt_key && alt_key->exif_key)
952                         ret = exif_update_metadata_simple(exif, alt_key->exif_key, values);
953                 }
954         return ret;
955 }
956
957
958 static GList *exif_add_value_to_glist(GList *list, Exiv2::Metadatum &item, MetadataFormat format, const Exiv2::ExifData *metadata)
959 {
960 #if EXIV2_TEST_VERSION(0,16,0)
961         Exiv2::TypeId id = item.typeId();
962         if (format == METADATA_FORMATTED ||
963             id == Exiv2::asciiString ||
964             id == Exiv2::undefined ||
965             id == Exiv2::string ||
966             id == Exiv2::date ||
967             id == Exiv2::time ||
968             id == Exiv2::xmpText ||
969             id == Exiv2::langAlt ||
970             id == Exiv2::comment
971             )
972                 {
973 #endif 
974                 /* read as a single entry */
975                 std::string str;
976                 
977                 if (format == METADATA_FORMATTED)
978                         {
979 #if EXIV2_TEST_VERSION(0,17,0)
980                         str = item.print(
981 #if EXIV2_TEST_VERSION(0,18,0)
982                                         metadata
983 #endif 
984                                         );
985 #else
986                         std::stringstream stream;
987                         Exiv2::Exifdatum *exifdatum;
988                         Exiv2::Iptcdatum *iptcdatum;
989 #if EXIV2_TEST_VERSION(0,16,0)
990                         Exiv2::Xmpdatum *xmpdatum;
991 #endif
992                         if ((exifdatum = dynamic_cast<Exiv2::Exifdatum *>(&item)))
993                                 stream << *exifdatum;
994                         else if ((iptcdatum = dynamic_cast<Exiv2::Iptcdatum *>(&item)))
995                                 stream << *iptcdatum;
996 #if EXIV2_TEST_VERSION(0,16,0)
997                         else if ((xmpdatum = dynamic_cast<Exiv2::Xmpdatum *>(&item)))
998                                 stream << *xmpdatum;
999 #endif
1000                         str = stream.str();
1001 #endif
1002                         if (str.length() > 1024)
1003                                 {
1004                                 /* truncate very long strings, they cause problems in gui */
1005                                 str.erase(1024);
1006                                 str.append("...");
1007                                 }
1008                         }
1009                 else
1010                         {
1011                         str = item.toString();
1012                         }
1013                 if (str.length() > 5 && str.substr(0, 5) == "lang=")
1014                         {
1015                         std::string::size_type pos = str.find_first_of(' ');
1016                         if (pos != std::string::npos) str = str.substr(pos+1);
1017                         }
1018                 list = g_list_append(list, utf8_validate_or_convert(str.c_str())); 
1019 #if EXIV2_TEST_VERSION(0,16,0)
1020                 }
1021         else
1022                 {
1023                 /* read as a list */
1024                 gint i;
1025                 for (i = 0; i < item.count(); i++)
1026                         list = g_list_append(list, utf8_validate_or_convert(item.toString(i).c_str())); 
1027                 }
1028 #endif 
1029         return list;
1030 }
1031
1032 static GList *exif_get_metadata_simple(ExifData *exif, const gchar *key, MetadataFormat format)
1033 {
1034         GList *list = NULL;
1035         try {
1036                 try {
1037                         Exiv2::ExifKey ekey(key);
1038                         Exiv2::ExifData::iterator pos = exif->exifData().findKey(ekey);
1039                         if (pos != exif->exifData().end())
1040                                 list = exif_add_value_to_glist(list, *pos, format, &exif->exifData());
1041
1042                 }
1043                 catch (Exiv2::AnyError& e) {
1044                         try {
1045                                 Exiv2::IptcKey ekey(key);
1046                                 Exiv2::IptcData::iterator pos = exif->iptcData().begin();
1047                                 while (pos != exif->iptcData().end())
1048                                         {
1049                                         if (pos->key() == key)
1050                                                 list = exif_add_value_to_glist(list, *pos, format, NULL);
1051                                         ++pos;
1052                                         }
1053
1054                         }
1055                         catch (Exiv2::AnyError& e) {
1056 #if EXIV2_TEST_VERSION(0,16,0)
1057                                 Exiv2::XmpKey ekey(key);
1058                                 Exiv2::XmpData::iterator pos = exif->xmpData().findKey(ekey);
1059                                 if (pos != exif->xmpData().end())
1060                                         list = exif_add_value_to_glist(list, *pos, format, NULL);
1061 #endif
1062                         }
1063                 }
1064         }
1065         catch (Exiv2::AnyError& e) {
1066                 debug_exception(e);
1067         }
1068         return list;
1069 }
1070
1071 GList *exif_get_metadata(ExifData *exif, const gchar *key, MetadataFormat format)
1072 {
1073         GList *list = NULL;
1074
1075         if (!key) return NULL;
1076
1077         if (format == METADATA_FORMATTED)
1078                 {
1079                 gchar *text;
1080                 gint key_valid;
1081                 text = exif_get_formatted_by_key(exif, key, &key_valid);
1082                 if (key_valid) return g_list_append(NULL, text);
1083                 }
1084                 
1085         list = exif_get_metadata_simple(exif, key, format);
1086         
1087         /* the following code can be ifdefed out as soon as Exiv2 supports it */
1088         if (!list)
1089                 {
1090                 const AltKey *alt_key = find_alt_key(key);
1091                 if (alt_key && alt_key->iptc_key)
1092                         list = exif_get_metadata_simple(exif, alt_key->iptc_key, format);
1093
1094 #if !EXIV2_TEST_VERSION(0,17,0) 
1095                 /* with older Exiv2 versions exif is not synced */
1096                 if (!list && alt_key && alt_key->exif_key)
1097                         list = exif_get_metadata_simple(exif, alt_key->exif_key, format);
1098 #endif
1099                 }
1100         return list;
1101 }
1102
1103
1104 void exif_add_jpeg_color_profile(ExifData *exif, unsigned char *cp_data, guint cp_length)
1105 {
1106         exif->add_jpeg_color_profile(cp_data, cp_length);
1107 }
1108
1109 guchar *exif_get_color_profile(ExifData *exif, guint *data_len)
1110 {
1111         guchar *ret = exif->get_jpeg_color_profile(data_len);
1112         if (ret) return ret;
1113
1114         ExifItem *prof_item = exif_get_item(exif, "Exif.Image.InterColorProfile");
1115         if (prof_item && exif_item_get_format_id(prof_item) == EXIF_FORMAT_UNDEFINED)
1116                 ret = (guchar *)exif_item_get_data(prof_item, data_len);
1117         return ret;
1118 }
1119
1120 #if EXIV2_TEST_VERSION(0,17,90)
1121
1122 guchar *exif_get_preview(ExifData *exif, guint *data_len, gint requested_width, gint requested_height)
1123 {
1124         if (!exif) return NULL;
1125
1126         if (!exif->image()) return NULL;
1127         
1128         const char* path = exif->image()->io().path().c_str();
1129         /* given image pathname, first do simple (and fast) file extension test */
1130         gboolean is_raw = filter_file_class(path, FORMAT_CLASS_RAWIMAGE);
1131         
1132         if (!is_raw && requested_width == 0) return NULL;
1133
1134         try {
1135
1136                 Exiv2::PreviewManager pm(*exif->image());
1137
1138                 Exiv2::PreviewPropertiesList list = pm.getPreviewProperties();
1139
1140                 if (!list.empty())
1141                         {
1142                         Exiv2::PreviewPropertiesList::iterator pos;
1143                         Exiv2::PreviewPropertiesList::iterator last = --list.end();
1144                         
1145                         if (requested_width == 0)
1146                                 {
1147                                 pos = last; // the largest
1148                                 }
1149                         else
1150                                 {
1151                                 pos = list.begin();
1152                                 while (pos != last)
1153                                         {
1154                                         if (pos->width_ >= (uint32_t)requested_width &&
1155                                             pos->height_ >= (uint32_t)requested_height) break;
1156                                         ++pos;
1157                                         }
1158                                 
1159                                 // we are not interested in smaller thumbnails in normal image formats - we can use full image instead
1160                                 if (!is_raw) 
1161                                         {
1162                                         if (pos->width_ < (uint32_t)requested_width || pos->height_ < (uint32_t)requested_height) return NULL; 
1163                                         }
1164                                 }
1165
1166                         Exiv2::PreviewImage image = pm.getPreviewImage(*pos);
1167
1168                         Exiv2::DataBuf buf = image.copy();
1169                         std::pair<Exiv2::byte*, long> p = buf.release();
1170
1171                         *data_len = p.second;
1172                         return p.first;
1173                         }
1174                 return NULL;
1175         }
1176         catch (Exiv2::AnyError& e) {
1177                 debug_exception(e);
1178                 return NULL;
1179         }
1180 }
1181
1182 void exif_free_preview(guchar *buf)
1183 {
1184         delete[] (Exiv2::byte*)buf;
1185 }
1186 #endif
1187
1188 }
1189 #if !EXIV2_TEST_VERSION(0,17,90)
1190
1191 /* This is a dirty hack to support raw file preview, bassed on
1192 tiffparse.cpp from Exiv2 examples */
1193
1194 class RawFile {
1195         public:
1196
1197         RawFile(Exiv2::BasicIo &io);
1198         ~RawFile();
1199
1200         const Exiv2::Value *find(uint16_t tag, uint16_t group);
1201
1202         unsigned long preview_offset();
1203
1204         private:
1205         int type;
1206         Exiv2::TiffComponent::AutoPtr rootDir;
1207         Exiv2::BasicIo &io_;
1208         const Exiv2::byte *map_data;
1209         size_t map_len;
1210         unsigned long offset;
1211 };
1212
1213 typedef struct _UnmapData UnmapData;
1214 struct _UnmapData
1215 {
1216         guchar *ptr;
1217         guchar *map_data;
1218         size_t map_len;
1219 };
1220
1221 static GList *exif_unmap_list = 0;
1222
1223 extern "C" guchar *exif_get_preview(ExifData *exif, guint *data_len, gint requested_width, gint requested_height)
1224 {
1225         unsigned long offset;
1226
1227         if (!exif) return NULL;
1228         if (!exif->image()) return NULL;
1229
1230         const char* path = exif->image()->io().path().c_str();
1231
1232         /* given image pathname, first do simple (and fast) file extension test */
1233         if (!filter_file_class(path, FORMAT_CLASS_RAWIMAGE)) return NULL;
1234
1235         try {
1236                 struct stat st;
1237                 guchar *map_data;
1238                 size_t map_len;
1239                 UnmapData *ud;
1240                 int fd;
1241                 
1242                 RawFile rf(exif->image()->io());
1243                 offset = rf.preview_offset();
1244                 DEBUG_1("%s: offset %lu", path, offset);
1245                 
1246                 fd = open(path, O_RDONLY);
1247                 if (fd == -1)
1248                         {
1249                         return NULL;
1250                         }
1251
1252                 if (fstat(fd, &st) == -1)
1253                         {
1254                         close(fd);
1255                         return NULL;
1256                         }
1257                 map_len = st.st_size;
1258                 map_data = (guchar *) mmap(0, map_len, PROT_READ, MAP_PRIVATE, fd, 0);
1259                 close(fd);
1260                 if (map_data == MAP_FAILED)
1261                         {
1262                         return NULL;
1263                         }
1264                 *data_len = map_len - offset;
1265                 ud = g_new(UnmapData, 1);
1266                 ud->ptr = map_data + offset;
1267                 ud->map_data = map_data;
1268                 ud->map_len = map_len;
1269                 
1270                 exif_unmap_list = g_list_prepend(exif_unmap_list, ud);
1271                 return ud->ptr;
1272                 
1273         }
1274         catch (Exiv2::AnyError& e) {
1275                 debug_exception(e);
1276         }
1277         return NULL;
1278
1279 }
1280
1281 void exif_free_preview(guchar *buf)
1282 {
1283         GList *work = exif_unmap_list;
1284         
1285         while (work)
1286                 {
1287                 UnmapData *ud = (UnmapData *)work->data;
1288                 if (ud->ptr == buf)
1289                         {
1290                         munmap(ud->map_data, ud->map_len);
1291                         exif_unmap_list = g_list_remove_link(exif_unmap_list, work);
1292                         g_free(ud);
1293                         return;
1294                         }
1295                 work = work->next;
1296                 }
1297         g_assert_not_reached();
1298 }
1299
1300 using namespace Exiv2;
1301
1302 RawFile::RawFile(BasicIo &io) : io_(io), map_data(NULL), map_len(0), offset(0)
1303 {
1304 /*
1305         struct stat st;
1306         if (fstat(fd, &st) == -1)
1307                 {
1308                 throw Error(14);
1309                 }
1310         map_len = st.st_size;
1311         map_data = (Exiv2::byte *) mmap(0, map_len, PROT_READ, MAP_PRIVATE, fd, 0);
1312         if (map_data == MAP_FAILED)
1313                 {
1314                 throw Error(14);
1315                 }
1316 */
1317         if (io.open() != 0) {
1318             throw Error(9, io.path(), strError());
1319         }
1320         
1321         map_data = io.mmap();
1322         map_len = io.size();
1323
1324
1325         type = Exiv2::ImageFactory::getType(map_data, map_len);
1326
1327 #if EXIV2_TEST_VERSION(0,16,0)
1328         TiffHeaderBase *tiffHeader = NULL;
1329 #else
1330         TiffHeade2 *tiffHeader = NULL;
1331 #endif
1332         Cr2Header *cr2Header = NULL;
1333
1334         switch (type) {
1335                 case Exiv2::ImageType::tiff:
1336                         tiffHeader = new TiffHeade2();
1337                         break;
1338                 case Exiv2::ImageType::cr2:
1339                         cr2Header = new Cr2Header();
1340                         break;
1341 #if EXIV2_TEST_VERSION(0,16,0)
1342                 case Exiv2::ImageType::orf:
1343                         tiffHeader = new OrfHeader();
1344                         break;
1345 #endif
1346 #if EXIV2_TEST_VERSION(0,13,0)
1347                 case Exiv2::ImageType::raf:
1348                         if (map_len < 84 + 4) throw Error(14);
1349                         offset = getULong(map_data + 84, bigEndian);
1350                         return;
1351 #endif
1352                 case Exiv2::ImageType::crw:
1353                         {
1354                         // Parse the image, starting with a CIFF header component
1355                         Exiv2::CiffHeader::AutoPtr parseTree(new Exiv2::CiffHeader);
1356                         parseTree->read(map_data, map_len);
1357                         CiffComponent *entry = parseTree->findComponent(0x2007, 0);
1358                         if (entry) offset =  entry->pData() - map_data;
1359                         return;
1360                         }
1361
1362                 default:
1363                         throw Error(3, "RAW");
1364         }
1365
1366         // process tiff-like formats
1367
1368         TiffCompFactoryFct createFct = TiffCreator::create;
1369
1370         rootDir = createFct(Tag::root, Group::none);
1371         if (0 == rootDir.get()) {
1372                 throw Error(1, "No root element defined in TIFF structure");
1373         }
1374         
1375         if (tiffHeader)
1376                 {
1377                 if (!tiffHeader->read(map_data, map_len)) throw Error(3, "TIFF");
1378 #if EXIV2_TEST_VERSION(0,16,0)
1379                 rootDir->setStart(map_data + tiffHeader->offset());
1380 #else
1381                 rootDir->setStart(map_data + tiffHeader->ifdOffset());
1382 #endif
1383                 }
1384                 
1385         if (cr2Header)
1386                 {
1387                 rootDir->setStart(map_data + cr2Header->offset());
1388                 }
1389         
1390         TiffRwState::AutoPtr state(new TiffRwState(tiffHeader ? tiffHeader->byteOrder() : littleEndian, 0, createFct));
1391
1392         TiffReader reader(map_data,
1393                           map_len,
1394                           rootDir.get(),
1395                           state);
1396
1397         rootDir->accept(reader);
1398         
1399         if (tiffHeader)
1400                 delete tiffHeader;
1401         if (cr2Header)
1402                 delete cr2Header;
1403 }
1404
1405 RawFile::~RawFile(void)
1406 {
1407         io_.munmap();
1408         io_.close();
1409 }
1410
1411 const Value * RawFile::find(uint16_t tag, uint16_t group)
1412 {
1413         TiffFinder finder(tag, group);
1414         rootDir->accept(finder);
1415         TiffEntryBase* te = dynamic_cast<TiffEntryBase*>(finder.result());
1416         if (te)
1417                 {
1418                 DEBUG_1("(tag: %04x %04x) ", tag, group);
1419                 return te->pValue();
1420                 }
1421         else
1422                 return NULL;
1423 }
1424
1425 unsigned long RawFile::preview_offset(void)
1426 {
1427         const Value *val;
1428         if (offset) return offset;
1429         
1430         if (type == Exiv2::ImageType::cr2)
1431                 {
1432                 val = find(0x111, Group::ifd0);
1433                 if (val) return val->toLong();
1434
1435                 return 0;
1436                 }
1437         
1438         val = find(0x201, Group::sub0_0);
1439         if (val) return val->toLong();
1440
1441         val = find(0x201, Group::ifd0);
1442         if (val) return val->toLong();
1443
1444         val = find(0x201, Group::ignr); // for PEF files, originally it was probably ifd2
1445         if (val) return val->toLong();
1446
1447         val = find(0x111, Group::sub0_1); // dng
1448         if (val) return val->toLong();
1449
1450         return 0;
1451 }
1452
1453
1454 #endif
1455
1456
1457 #endif
1458 /* HAVE_EXIV2 */
1459 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */