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