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