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