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