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