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