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