exiv2 version checks
[geeqie.git] / src / exiv2.cc
1
2 #ifdef HAVE_CONFIG_H
3 #  include "config.h"
4 #endif
5
6 #ifdef HAVE_EXIV2
7
8 #include <exiv2/image.hpp>
9 #include <exiv2/exif.hpp>
10 #include <iostream>
11
12 // EXIV2_TEST_VERSION is defined in Exiv2 0.15 and newer.
13 #ifndef EXIV2_TEST_VERSION
14 # define EXIV2_TEST_VERSION(major,minor,patch) \
15         ( EXIV2_VERSION >= EXIV2_MAKE_VERSION(major,minor,patch) )
16 #endif
17
18
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22 #include <fcntl.h>
23 #include <sys/mman.h>
24
25 #include <exiv2/tiffparser.hpp>
26 #include <exiv2/tiffcomposite.hpp>
27 #include <exiv2/tiffvisitor.hpp>
28 #include <exiv2/tiffimage.hpp>
29 #include <exiv2/cr2image.hpp>
30 #include <exiv2/crwimage.hpp>
31 #if EXIV2_TEST_VERSION(0,16,0)
32 #include <exiv2/orfimage.hpp>
33 #endif
34 #if EXIV2_TEST_VERSION(0,13,0)
35 #include <exiv2/rafimage.hpp>
36 #endif
37 #include <exiv2/futils.hpp>
38
39
40
41 extern "C" {
42 #include <glib.h> 
43 #include "gqview.h"
44 #include "exif.h"
45
46 }
47
48 struct _ExifData
49 {
50         Exiv2::Image::AutoPtr image;
51         Exiv2::Image::AutoPtr sidecar;
52         Exiv2::ExifData::const_iterator exifIter; /* for exif_get_next_item */
53         Exiv2::IptcData::const_iterator iptcIter; /* for exif_get_next_item */
54 #if EXIV2_TEST_VERSION(0,16,0)
55         Exiv2::XmpData::const_iterator xmpIter; /* for exif_get_next_item */
56 #endif
57         bool have_sidecar;
58
59
60         _ExifData(gchar *path, gchar *sidecar_path, gint parse_color_profile)
61         {
62                 have_sidecar = false;
63                 image = Exiv2::ImageFactory::open(path);
64 //              g_assert (image.get() != 0);
65                 image->readMetadata();
66
67 #if EXIV2_TEST_VERSION(0,16,0)
68                 printf("xmp count %d\n", image->xmpData().count());
69                 if (sidecar_path && image->xmpData().empty())
70                         {
71                         sidecar = Exiv2::ImageFactory::open(sidecar_path);
72                         sidecar->readMetadata();
73                         have_sidecar = sidecar->good();
74                         printf("sidecar xmp count %d\n", sidecar->xmpData().count());
75                         }
76                 
77 #endif
78         }
79         
80         void writeMetadata()
81         {
82                 if (have_sidecar) sidecar->writeMetadata();
83                 image->writeMetadata();
84         }
85         
86         Exiv2::ExifData &exifData ()
87         {
88                 return image->exifData();
89         }
90
91         Exiv2::IptcData &iptcData ()
92         {
93                 return image->iptcData();
94         }
95
96 #if EXIV2_TEST_VERSION(0,16,0)
97         Exiv2::XmpData &xmpData ()
98         {
99                 return have_sidecar ? sidecar->xmpData() : image->xmpData();
100         }
101 #endif
102
103 };
104
105 extern "C" {
106
107 ExifData *exif_read(gchar *path, gchar *sidecar_path, gint parse_color_profile)
108 {
109         printf("exif %s %s\n", path, sidecar_path ? sidecar_path : "-");
110         try {
111                 return new ExifData(path, sidecar_path, parse_color_profile);
112         }
113         catch (Exiv2::AnyError& e) {
114                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
115                 return NULL;
116         }
117         
118 }
119
120 int exif_write(ExifData *exif)
121 {
122         try {
123                 exif->writeMetadata();
124                 return 1;
125         }
126         catch (Exiv2::AnyError& e) {
127                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
128                 return 0;
129         }
130         
131 }
132
133
134 void exif_free(ExifData *exif)
135 {
136         
137         delete exif;
138 }
139
140 ExifItem *exif_get_item(ExifData *exif, const gchar *key)
141 {
142         try {
143                 Exiv2::Metadatum *item;
144                 try {
145                         Exiv2::ExifKey ekey(key);
146                         Exiv2::ExifData::iterator pos = exif->exifData().findKey(ekey);
147                         if (pos == exif->exifData().end()) return NULL;
148                         item = &*pos;
149                 }
150                 catch (Exiv2::AnyError& e) {
151                         try {
152                                 Exiv2::IptcKey ekey(key);
153                                 Exiv2::IptcData::iterator pos = exif->iptcData().findKey(ekey);
154                                 if (pos == exif->iptcData().end()) return NULL;
155                                 item = &*pos;
156                         }
157                         catch (Exiv2::AnyError& e) {
158 #if EXIV2_TEST_VERSION(0,16,0)
159                                 Exiv2::XmpKey ekey(key);
160                                 Exiv2::XmpData::iterator pos = exif->xmpData().findKey(ekey);
161                                 if (pos == exif->xmpData().end()) return NULL;
162                                 item = &*pos;
163 #endif
164                         }
165                 }
166                 return (ExifItem *)item;
167         }
168         catch (Exiv2::AnyError& e) {
169                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
170                 return NULL;
171         }
172 }
173
174 ExifItem *exif_add_item(ExifData *exif, const gchar *key)
175 {
176         try {
177                 Exiv2::Metadatum *item;
178                 try {
179                         Exiv2::ExifKey ekey(key);
180                         exif->exifData().add(ekey, NULL);
181                         Exiv2::ExifData::iterator pos = exif->exifData().end(); // a hack, there should be a better way to get the currently added item
182                         pos--;
183                         item = &*pos;
184                 }
185                 catch (Exiv2::AnyError& e) {
186                         try {
187                                 Exiv2::IptcKey ekey(key);
188                                 exif->iptcData().add(ekey, NULL);
189                                 Exiv2::IptcData::iterator pos = exif->iptcData().end();
190                                 pos--;
191                                 item = &*pos;
192                         }
193                         catch (Exiv2::AnyError& e) {
194 #if EXIV2_TEST_VERSION(0,16,0)
195                                 Exiv2::XmpKey ekey(key);
196                                 exif->xmpData().add(ekey, NULL);
197                                 Exiv2::XmpData::iterator pos = exif->xmpData().end();
198                                 pos--;
199                                 item = &*pos;
200 #endif
201                         }
202                 }
203                 return (ExifItem *)item;
204         }
205         catch (Exiv2::AnyError& e) {
206                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
207                 return NULL;
208         }
209 }
210
211
212 ExifItem *exif_get_first_item(ExifData *exif)
213 {
214         try {
215                 exif->exifIter = exif->exifData().begin();
216                 exif->iptcIter = exif->iptcData().begin();
217 #if EXIV2_TEST_VERSION(0,16,0)
218                 exif->xmpIter = exif->xmpData().begin();
219 #endif
220                 if (exif->exifIter != exif->exifData().end()) 
221                         {
222                         const Exiv2::Metadatum *item = &*exif->exifIter;
223                         exif->exifIter++;
224                         return (ExifItem *)item;
225                         }
226                 if (exif->iptcIter != exif->iptcData().end()) 
227                         {
228                         const Exiv2::Metadatum *item = &*exif->iptcIter;
229                         exif->iptcIter++;
230                         return (ExifItem *)item;
231                         }
232 #if EXIV2_TEST_VERSION(0,16,0)
233                 if (exif->xmpIter != exif->xmpData().end()) 
234                         {
235                         const Exiv2::Metadatum *item = &*exif->xmpIter;
236                         exif->xmpIter++;
237                         return (ExifItem *)item;
238                         }
239 #endif
240                 return NULL;
241                         
242         }
243         catch (Exiv2::AnyError& e) {
244                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
245                 return NULL;
246         }
247 }
248
249 ExifItem *exif_get_next_item(ExifData *exif)
250 {
251         try {
252                 if (exif->exifIter != exif->exifData().end())
253                         {
254                         const Exiv2::Metadatum *item = &*exif->exifIter;
255                         exif->exifIter++;
256                         return (ExifItem *)item;
257                 }
258                 if (exif->iptcIter != exif->iptcData().end())
259                         {
260                         const Exiv2::Metadatum *item = &*exif->iptcIter;
261                         exif->iptcIter++;
262                         return (ExifItem *)item;
263                 }
264 #if EXIV2_TEST_VERSION(0,16,0)
265                 if (exif->xmpIter != exif->xmpData().end())
266                         {
267                         const Exiv2::Metadatum *item = &*exif->xmpIter;
268                         exif->xmpIter++;
269                         return (ExifItem *)item;
270                 }
271 #endif
272                 return NULL;
273         }
274         catch (Exiv2::AnyError& e) {
275                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
276                 return NULL;
277         }
278 }
279
280 char *exif_item_get_tag_name(ExifItem *item)
281 {
282         try {
283                 if (!item) return NULL;
284                 return g_strdup(((Exiv2::Metadatum *)item)->key().c_str());
285         }
286         catch (Exiv2::AnyError& e) {
287                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
288                 return NULL;
289         }
290 }
291
292 guint exif_item_get_tag_id(ExifItem *item)
293 {
294         try {
295                 if (!item) return 0;
296                 return ((Exiv2::Metadatum *)item)->tag();
297         }
298         catch (Exiv2::AnyError& e) {
299                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
300                 return 0;
301         }
302 }
303
304 guint exif_item_get_elements(ExifItem *item)
305 {
306         try {
307                 if (!item) return 0;
308                 return ((Exiv2::Metadatum *)item)->count();
309         }
310         catch (Exiv2::AnyError& e) {
311                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
312                 return 0;
313         }
314 }
315
316 char *exif_item_get_data(ExifItem *item, guint *data_len)
317 {
318 }
319
320 char *exif_item_get_description(ExifItem *item)
321 {
322         try {
323                 if (!item) return NULL;
324                 return g_strdup(((Exiv2::Metadatum *)item)->tagLabel().c_str());
325         }
326         catch (std::exception& e) {
327 //              std::cout << "Caught Exiv2 exception '" << e << "'\n";
328                 return NULL;
329         }
330 }
331
332 /*
333 invalidTypeId, unsignedByte, asciiString, unsignedShort,
334   unsignedLong, unsignedRational, signedByte, undefined,
335   signedShort, signedLong, signedRational, string,
336   date, time, comment, directory,
337   xmpText, xmpAlt, xmpBag, xmpSeq,
338   langAlt, lastTypeId 
339 */
340
341 static guint format_id_trans_tbl [] = {
342         EXIF_FORMAT_UNKNOWN,
343         EXIF_FORMAT_BYTE_UNSIGNED,
344         EXIF_FORMAT_STRING,
345         EXIF_FORMAT_SHORT_UNSIGNED,
346         EXIF_FORMAT_LONG_UNSIGNED,
347         EXIF_FORMAT_RATIONAL_UNSIGNED,
348         EXIF_FORMAT_BYTE,
349         EXIF_FORMAT_UNDEFINED,
350         EXIF_FORMAT_SHORT,
351         EXIF_FORMAT_LONG,
352         EXIF_FORMAT_RATIONAL,
353         EXIF_FORMAT_STRING,
354         EXIF_FORMAT_STRING,
355         EXIF_FORMAT_STRING,
356         EXIF_FORMAT_UNDEFINED,
357         EXIF_FORMAT_STRING,
358         EXIF_FORMAT_STRING,
359         EXIF_FORMAT_STRING,
360         EXIF_FORMAT_STRING
361         };
362         
363         
364
365 guint exif_item_get_format_id(ExifItem *item)
366 {
367         try {
368                 if (!item) return EXIF_FORMAT_UNKNOWN;
369                 guint id = ((Exiv2::Metadatum *)item)->typeId();
370                 if (id >= (sizeof(format_id_trans_tbl) / sizeof(format_id_trans_tbl[0])) ) return EXIF_FORMAT_UNKNOWN;
371                 return format_id_trans_tbl[id];
372         }
373         catch (Exiv2::AnyError& e) {
374                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
375                 return EXIF_FORMAT_UNKNOWN;
376         }
377 }
378
379 const char *exif_item_get_format_name(ExifItem *item, gint brief)
380 {
381         try {
382                 if (!item) return NULL;
383                 return ((Exiv2::Metadatum *)item)->typeName();
384         }
385         catch (Exiv2::AnyError& e) {
386                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
387                 return NULL;
388         }
389 }
390
391
392 gchar *exif_item_get_data_as_text(ExifItem *item)
393 {
394         try {
395                 if (!item) return NULL;
396 //              std::stringstream str;  // does not work with Exiv2::Metadatum because operator<< is not virtual
397 //              str << *((Exiv2::Metadatum *)item);
398 //              return g_strdup(str.str().c_str());
399                 return g_strdup(((Exiv2::Metadatum *)item)->toString().c_str());
400         }
401         catch (Exiv2::AnyError& e) {
402                 return NULL;
403         }
404 }
405
406 gchar *exif_item_get_string(ExifItem *item, int idx)
407 {
408         try {
409                 if (!item) return NULL;
410                 Exiv2::Metadatum *em = (Exiv2::Metadatum *)item;
411 #if EXIV2_TEST_VERSION(0,16,0)
412                 std::string str = em->toString(idx);
413 #else
414                 std::string str = em->toString(); // FIXME
415 #endif
416                 if (idx == 0 && str == "") str = em->toString();
417                 if (str.length() > 5 && str.substr(0, 5) == "lang=") 
418                         {
419                         std::string::size_type pos = str.find_first_of(' ');
420                         if (pos != std::string::npos) str = str.substr(pos+1);
421                         }
422
423                 return g_strdup(str.c_str());
424         }
425         catch (Exiv2::AnyError& e) {
426                 return NULL;
427         }
428 }
429
430
431 gint exif_item_get_integer(ExifItem *item, gint *value)
432 {
433         try {
434                 if (!item) return 0;
435                 *value = ((Exiv2::Metadatum *)item)->toLong();
436                 return 1;
437         }
438         catch (Exiv2::AnyError& e) {
439                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
440                 return 0;
441         }
442 }
443
444 ExifRational *exif_item_get_rational(ExifItem *item, gint *sign)
445 {
446         try {
447                 if (!item) return NULL;
448                 Exiv2::Rational v = ((Exiv2::Metadatum *)item)->toRational();
449                 static ExifRational ret;
450                 ret.num = v.first;
451                 ret.den = v.second;
452                 return &ret;
453         }
454         catch (Exiv2::AnyError& e) {
455                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
456                 return NULL;
457         }
458 }
459
460 const gchar *exif_get_tag_description_by_key(const gchar *key)
461 {
462         try {
463                 Exiv2::ExifKey ekey(key);
464                 return Exiv2::ExifTags::tagLabel(ekey.tag(), ekey.ifdId ());
465         }
466         catch (Exiv2::AnyError& e) {
467                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
468                 return NULL;
469         }
470 }
471
472 int exif_item_set_string(ExifItem *item, const char *str)
473 {
474         try {
475                 if (!item) return 0;
476                 ((Exiv2::Metadatum *)item)->setValue(std::string(str));
477                 return 1;
478         }
479         catch (Exiv2::AnyError& e) {
480                 return 0;
481         }
482 }
483
484 int exif_item_delete(ExifData *exif, ExifItem *item)
485 {
486         try {
487                 if (!item) return 0;
488                 for (Exiv2::ExifData::iterator i = exif->exifData().begin(); i != exif->exifData().end(); ++i) {
489                         if (((Exiv2::Metadatum *)item) == &*i) {
490                                 i = exif->exifData().erase(i);
491                                 return 1;
492                         }
493                 }
494                 for (Exiv2::IptcData::iterator i = exif->iptcData().begin(); i != exif->iptcData().end(); ++i) {
495                         if (((Exiv2::Metadatum *)item) == &*i) {
496                                 i = exif->iptcData().erase(i);
497                                 return 1;
498                         }
499                 }
500 #if EXIV2_TEST_VERSION(0,16,0)
501                 for (Exiv2::XmpData::iterator i = exif->xmpData().begin(); i != exif->xmpData().end(); ++i) {
502                         if (((Exiv2::Metadatum *)item) == &*i) {
503                                 i = exif->xmpData().erase(i);
504                                 return 1;
505                         }
506                 }
507 #endif          
508                 return 0;
509         }
510         catch (Exiv2::AnyError& e) {
511                 return 0;
512         }
513 }
514
515
516
517 }
518
519 /* This is a dirty hack to support raw file preview, bassed on 
520 tiffparse.cpp from Exiv2 examples */
521
522 class RawFile {
523         public:
524     
525         RawFile(int fd);
526         ~RawFile();
527     
528         const Exiv2::Value *find(uint16_t tag, uint16_t group);
529     
530         unsigned long preview_offset();
531     
532         private:
533         int type;
534         Exiv2::TiffComponent::AutoPtr rootDir;
535         Exiv2::byte *map_data;
536         size_t map_len;
537         unsigned long offset;
538 };
539
540 using namespace Exiv2;
541
542 RawFile::RawFile(int fd) : map_data(NULL), map_len(0), offset(0)
543 {
544         struct stat st;
545         if (fstat(fd, &st) == -1)
546                 {
547                 throw Error(14);
548                 }
549         map_len = st.st_size;
550         map_data = (Exiv2::byte *) mmap(0, map_len, PROT_READ, MAP_PRIVATE, fd, 0);
551         if (map_data == MAP_FAILED)
552                 {
553                 throw Error(14);
554                 }
555         type = Exiv2::ImageFactory::getType(map_data, map_len);
556
557 #if EXIV2_TEST_VERSION(0,16,0)
558         TiffHeaderBase *tiffHeader;
559 #else
560         TiffHeade2 *tiffHeader;
561 #endif
562         switch (type) {
563                 case Exiv2::ImageType::tiff:
564                         tiffHeader = new TiffHeade2();
565                         break;
566 #if EXIV2_TEST_VERSION(0,16,0)
567
568                 case Exiv2::ImageType::cr2:
569                         tiffHeader = new Cr2Header();
570                         break;
571                 case Exiv2::ImageType::orf:
572                         tiffHeader = new OrfHeader();
573                         break;
574 #endif
575 #if EXIV2_TEST_VERSION(0,13,0)
576                 case Exiv2::ImageType::raf:
577                         if (map_len < 84 + 4) throw Error(14);
578                         offset = getULong(map_data + 84, bigEndian);
579                         return;
580 #endif
581                 case Exiv2::ImageType::crw:
582                         {
583                         // Parse the image, starting with a CIFF header component
584                         Exiv2::CiffHeader::AutoPtr parseTree(new Exiv2::CiffHeader);
585                         parseTree->read(map_data, map_len);
586                         CiffComponent *entry = parseTree->findComponent(0x2007, 0); 
587                         if (entry) offset =  entry->pData() - map_data;
588                         return;
589                         }
590
591                 default:
592                         throw Error(3, "RAW");
593         }
594
595         // process tiff-like formats
596         if (!tiffHeader->read(map_data, map_len)) throw Error(3, "TIFF");
597
598         TiffCompFactoryFct createFct = TiffCreator::create;
599
600         rootDir = createFct(Tag::root, Group::none);
601         if (0 == rootDir.get()) {
602                 throw Error(1, "No root element defined in TIFF structure");
603         }
604 #if EXIV2_TEST_VERSION(0,16,0)
605         rootDir->setStart(map_data + tiffHeader->offset());
606 #else
607         rootDir->setStart(map_data + tiffHeader->ifdOffset());
608 #endif
609         TiffRwState::AutoPtr state(
610                 new TiffRwState(tiffHeader->byteOrder(), 0, createFct));
611
612         TiffReader reader(map_data,
613                       map_len,
614                       rootDir.get(),
615                       state);
616
617         rootDir->accept(reader);
618         
619         delete tiffHeader;
620 }
621
622 RawFile::~RawFile()
623 {
624         if (map_data && munmap(map_data, map_len) == -1)
625                 {
626                 printf("Failed to unmap file \n");
627                 }
628 }
629
630 const Value * RawFile::find(uint16_t tag, uint16_t group)
631 {
632         TiffFinder finder(tag, group);
633         rootDir->accept(finder);
634         TiffEntryBase* te = dynamic_cast<TiffEntryBase*>(finder.result());
635         if (te)
636                 {
637                 if (debug) printf("(tag: %04x %04x) ", tag, group);
638                 return te->pValue();
639                 }
640         else
641                 return NULL;
642 }
643
644 unsigned long RawFile::preview_offset()
645 {
646         const Value *val;
647         if (offset) return offset;
648         
649         if (type == Exiv2::ImageType::cr2)
650                 {
651                 val = find(0x111, Group::ifd0);
652                 if (val) return val->toLong();
653     
654                 return 0;
655                 }
656         
657         val = find(0x201, Group::sub0_0);
658         if (val) return val->toLong();
659
660         val = find(0x201, Group::ifd0);
661         if (val) return val->toLong();
662     
663         val = find(0x201, Group::ignr); // for PEF files, originally it was probably ifd2
664         if (val) return val->toLong();
665
666         val = find(0x111, Group::sub0_1); // dng
667         if (val) return val->toLong();
668
669         return 0;
670 }
671
672
673 const static char *raw_ext_list[] = { ".cr2", ".nef", ".pef", ".arw", NULL };
674
675 extern "C" gint format_raw_img_exif_offsets_fd(int fd, const gchar *path,
676                                     unsigned char *header_data, const guint header_len,
677                                     guint *image_offset, guint *exif_offset)
678 {
679         int success;
680         unsigned long offset;
681
682         /* given image pathname, first do simple (and fast) file extension test */
683 /*      if (path)
684                 {
685                 const gchar *ext;
686                 gint match = FALSE;
687                 gint i;
688
689                 ext = strrchr(path, '.');
690                 if (!ext) return FALSE;
691                 
692                 for (i = 0; raw_ext_list[i]; i++) 
693                         {
694                         if (strcasecmp(raw_ext_list[i], ext) == 0)
695                                 {
696                                 match = TRUE;
697                                 break;
698                                 }
699                         }
700
701                 if (!match) return FALSE;
702
703                 }
704 */
705         try {
706                 RawFile rf(fd);
707                 if (debug) printf("%s: offset ", path);
708                 offset = rf.preview_offset();
709                 if (debug) printf("%d\n", offset);
710         }
711         catch (Exiv2::AnyError& e) {
712                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
713                 return 0;
714         }
715
716         if (image_offset && offset > 0)
717                 {
718                 *image_offset = offset;
719                 if (lseek(fd, *image_offset, SEEK_SET) != *image_offset)
720                         {
721                         printf("Failed to seek to embedded image\n");
722
723                         *image_offset = 0;
724                         if (*exif_offset) *exif_offset = 0;
725                         success = FALSE;
726                         }
727                 }
728
729         return offset > 0;
730 }
731
732
733 #endif 
734 /* HAVE_EXIV2 */