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