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