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