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