gqview.h -> main.h
[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                 printf("xmp count %d\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                         printf("sidecar xmp count %d\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 }
320
321 char *exif_item_get_description(ExifItem *item)
322 {
323         try {
324                 if (!item) return NULL;
325                 return g_strdup(((Exiv2::Metadatum *)item)->tagLabel().c_str());
326         }
327         catch (std::exception& e) {
328 //              std::cout << "Caught Exiv2 exception '" << e << "'\n";
329                 return NULL;
330         }
331 }
332
333 /*
334 invalidTypeId, unsignedByte, asciiString, unsignedShort,
335   unsignedLong, unsignedRational, signedByte, undefined,
336   signedShort, signedLong, signedRational, string,
337   date, time, comment, directory,
338   xmpText, xmpAlt, xmpBag, xmpSeq,
339   langAlt, lastTypeId 
340 */
341
342 static guint format_id_trans_tbl [] = {
343         EXIF_FORMAT_UNKNOWN,
344         EXIF_FORMAT_BYTE_UNSIGNED,
345         EXIF_FORMAT_STRING,
346         EXIF_FORMAT_SHORT_UNSIGNED,
347         EXIF_FORMAT_LONG_UNSIGNED,
348         EXIF_FORMAT_RATIONAL_UNSIGNED,
349         EXIF_FORMAT_BYTE,
350         EXIF_FORMAT_UNDEFINED,
351         EXIF_FORMAT_SHORT,
352         EXIF_FORMAT_LONG,
353         EXIF_FORMAT_RATIONAL,
354         EXIF_FORMAT_STRING,
355         EXIF_FORMAT_STRING,
356         EXIF_FORMAT_STRING,
357         EXIF_FORMAT_UNDEFINED,
358         EXIF_FORMAT_STRING,
359         EXIF_FORMAT_STRING,
360         EXIF_FORMAT_STRING,
361         EXIF_FORMAT_STRING
362         };
363         
364         
365
366 guint exif_item_get_format_id(ExifItem *item)
367 {
368         try {
369                 if (!item) return EXIF_FORMAT_UNKNOWN;
370                 guint id = ((Exiv2::Metadatum *)item)->typeId();
371                 if (id >= (sizeof(format_id_trans_tbl) / sizeof(format_id_trans_tbl[0])) ) return EXIF_FORMAT_UNKNOWN;
372                 return format_id_trans_tbl[id];
373         }
374         catch (Exiv2::AnyError& e) {
375                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
376                 return EXIF_FORMAT_UNKNOWN;
377         }
378 }
379
380 const char *exif_item_get_format_name(ExifItem *item, gint brief)
381 {
382         try {
383                 if (!item) return NULL;
384                 return ((Exiv2::Metadatum *)item)->typeName();
385         }
386         catch (Exiv2::AnyError& e) {
387                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
388                 return NULL;
389         }
390 }
391
392
393 gchar *exif_item_get_data_as_text(ExifItem *item)
394 {
395         try {
396                 if (!item) return NULL;
397 //              std::stringstream str;  // does not work with Exiv2::Metadatum because operator<< is not virtual
398 //              str << *((Exiv2::Metadatum *)item);
399 //              return g_strdup(str.str().c_str());
400                 return g_strdup(((Exiv2::Metadatum *)item)->toString().c_str());
401         }
402         catch (Exiv2::AnyError& e) {
403                 return NULL;
404         }
405 }
406
407 gchar *exif_item_get_string(ExifItem *item, int idx)
408 {
409         try {
410                 if (!item) return NULL;
411                 Exiv2::Metadatum *em = (Exiv2::Metadatum *)item;
412 #if EXIV2_TEST_VERSION(0,16,0)
413                 std::string str = em->toString(idx);
414 #else
415                 std::string str = em->toString(); // FIXME
416 #endif
417                 if (idx == 0 && str == "") str = em->toString();
418                 if (str.length() > 5 && str.substr(0, 5) == "lang=") 
419                         {
420                         std::string::size_type pos = str.find_first_of(' ');
421                         if (pos != std::string::npos) str = str.substr(pos+1);
422                         }
423
424                 return g_strdup(str.c_str());
425         }
426         catch (Exiv2::AnyError& e) {
427                 return NULL;
428         }
429 }
430
431
432 gint exif_item_get_integer(ExifItem *item, gint *value)
433 {
434         try {
435                 if (!item) return 0;
436                 *value = ((Exiv2::Metadatum *)item)->toLong();
437                 return 1;
438         }
439         catch (Exiv2::AnyError& e) {
440                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
441                 return 0;
442         }
443 }
444
445 ExifRational *exif_item_get_rational(ExifItem *item, gint *sign)
446 {
447         try {
448                 if (!item) return NULL;
449                 Exiv2::Rational v = ((Exiv2::Metadatum *)item)->toRational();
450                 static ExifRational ret;
451                 ret.num = v.first;
452                 ret.den = v.second;
453                 return &ret;
454         }
455         catch (Exiv2::AnyError& e) {
456                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
457                 return NULL;
458         }
459 }
460
461 const gchar *exif_get_tag_description_by_key(const gchar *key)
462 {
463         try {
464                 Exiv2::ExifKey ekey(key);
465                 return Exiv2::ExifTags::tagLabel(ekey.tag(), ekey.ifdId ());
466         }
467         catch (Exiv2::AnyError& e) {
468                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
469                 return NULL;
470         }
471 }
472
473 int exif_item_set_string(ExifItem *item, const char *str)
474 {
475         try {
476                 if (!item) return 0;
477                 ((Exiv2::Metadatum *)item)->setValue(std::string(str));
478                 return 1;
479         }
480         catch (Exiv2::AnyError& e) {
481                 return 0;
482         }
483 }
484
485 int exif_item_delete(ExifData *exif, ExifItem *item)
486 {
487         try {
488                 if (!item) return 0;
489                 for (Exiv2::ExifData::iterator i = exif->exifData().begin(); i != exif->exifData().end(); ++i) {
490                         if (((Exiv2::Metadatum *)item) == &*i) {
491                                 i = exif->exifData().erase(i);
492                                 return 1;
493                         }
494                 }
495                 for (Exiv2::IptcData::iterator i = exif->iptcData().begin(); i != exif->iptcData().end(); ++i) {
496                         if (((Exiv2::Metadatum *)item) == &*i) {
497                                 i = exif->iptcData().erase(i);
498                                 return 1;
499                         }
500                 }
501 #if EXIV2_TEST_VERSION(0,16,0)
502                 for (Exiv2::XmpData::iterator i = exif->xmpData().begin(); i != exif->xmpData().end(); ++i) {
503                         if (((Exiv2::Metadatum *)item) == &*i) {
504                                 i = exif->xmpData().erase(i);
505                                 return 1;
506                         }
507                 }
508 #endif          
509                 return 0;
510         }
511         catch (Exiv2::AnyError& e) {
512                 return 0;
513         }
514 }
515
516
517
518 }
519
520 /* This is a dirty hack to support raw file preview, bassed on 
521 tiffparse.cpp from Exiv2 examples */
522
523 class RawFile {
524         public:
525     
526         RawFile(int fd);
527         ~RawFile();
528     
529         const Exiv2::Value *find(uint16_t tag, uint16_t group);
530     
531         unsigned long preview_offset();
532     
533         private:
534         int type;
535         Exiv2::TiffComponent::AutoPtr rootDir;
536         Exiv2::byte *map_data;
537         size_t map_len;
538         unsigned long offset;
539 };
540
541 using namespace Exiv2;
542
543 RawFile::RawFile(int fd) : map_data(NULL), map_len(0), offset(0)
544 {
545         struct stat st;
546         if (fstat(fd, &st) == -1)
547                 {
548                 throw Error(14);
549                 }
550         map_len = st.st_size;
551         map_data = (Exiv2::byte *) mmap(0, map_len, PROT_READ, MAP_PRIVATE, fd, 0);
552         if (map_data == MAP_FAILED)
553                 {
554                 throw Error(14);
555                 }
556         type = Exiv2::ImageFactory::getType(map_data, map_len);
557
558 #if EXIV2_TEST_VERSION(0,16,0)
559         TiffHeaderBase *tiffHeader = NULL;
560 #else
561         TiffHeade2 *tiffHeader = NULL;
562 #endif
563         Cr2Header *cr2Header = NULL;
564
565         switch (type) {
566                 case Exiv2::ImageType::tiff:
567                         tiffHeader = new TiffHeade2();
568                         break;
569                 case Exiv2::ImageType::cr2:
570                         cr2Header = new Cr2Header();
571                         break;
572 #if EXIV2_TEST_VERSION(0,16,0)
573                 case Exiv2::ImageType::orf:
574                         tiffHeader = new OrfHeader();
575                         break;
576 #endif
577 #if EXIV2_TEST_VERSION(0,13,0)
578                 case Exiv2::ImageType::raf:
579                         if (map_len < 84 + 4) throw Error(14);
580                         offset = getULong(map_data + 84, bigEndian);
581                         return;
582 #endif
583                 case Exiv2::ImageType::crw:
584                         {
585                         // Parse the image, starting with a CIFF header component
586                         Exiv2::CiffHeader::AutoPtr parseTree(new Exiv2::CiffHeader);
587                         parseTree->read(map_data, map_len);
588                         CiffComponent *entry = parseTree->findComponent(0x2007, 0); 
589                         if (entry) offset =  entry->pData() - map_data;
590                         return;
591                         }
592
593                 default:
594                         throw Error(3, "RAW");
595         }
596
597         // process tiff-like formats
598
599         TiffCompFactoryFct createFct = TiffCreator::create;
600
601         rootDir = createFct(Tag::root, Group::none);
602         if (0 == rootDir.get()) {
603                 throw Error(1, "No root element defined in TIFF structure");
604         }
605         
606         if (tiffHeader)
607                 {
608                 if (!tiffHeader->read(map_data, map_len)) throw Error(3, "TIFF");
609 #if EXIV2_TEST_VERSION(0,16,0)
610                 rootDir->setStart(map_data + tiffHeader->offset());
611 #else
612                 rootDir->setStart(map_data + tiffHeader->ifdOffset());
613 #endif
614                 }
615                 
616         if (cr2Header)
617                 {
618                 rootDir->setStart(map_data + cr2Header->offset());
619                 }
620         
621         TiffRwState::AutoPtr state(new TiffRwState(tiffHeader ? tiffHeader->byteOrder() : littleEndian, 0, createFct));
622
623         TiffReader reader(map_data,
624                       map_len,
625                       rootDir.get(),
626                       state);
627
628         rootDir->accept(reader);
629         
630         if (tiffHeader) 
631                 delete tiffHeader;
632         if (cr2Header) 
633                 delete cr2Header;
634 }
635
636 RawFile::~RawFile()
637 {
638         if (map_data && munmap(map_data, map_len) == -1)
639                 {
640                 printf("Failed to unmap file \n");
641                 }
642 }
643
644 const Value * RawFile::find(uint16_t tag, uint16_t group)
645 {
646         TiffFinder finder(tag, group);
647         rootDir->accept(finder);
648         TiffEntryBase* te = dynamic_cast<TiffEntryBase*>(finder.result());
649         if (te)
650                 {
651                 if (debug) printf("(tag: %04x %04x) ", tag, group);
652                 return te->pValue();
653                 }
654         else
655                 return NULL;
656 }
657
658 unsigned long RawFile::preview_offset()
659 {
660         const Value *val;
661         if (offset) return offset;
662         
663         if (type == Exiv2::ImageType::cr2)
664                 {
665                 val = find(0x111, Group::ifd0);
666                 if (val) return val->toLong();
667     
668                 return 0;
669                 }
670         
671         val = find(0x201, Group::sub0_0);
672         if (val) return val->toLong();
673
674         val = find(0x201, Group::ifd0);
675         if (val) return val->toLong();
676     
677         val = find(0x201, Group::ignr); // for PEF files, originally it was probably ifd2
678         if (val) return val->toLong();
679
680         val = find(0x111, Group::sub0_1); // dng
681         if (val) return val->toLong();
682
683         return 0;
684 }
685
686
687 extern "C" gint format_raw_img_exif_offsets_fd(int fd, const gchar *path,
688                                     unsigned char *header_data, const guint header_len,
689                                     guint *image_offset, guint *exif_offset)
690 {
691         int success;
692         unsigned long offset;
693
694         /* given image pathname, first do simple (and fast) file extension test */
695         if (!filter_file_class(path, FORMAT_CLASS_RAWIMAGE)) return 0;
696
697         try {
698                 RawFile rf(fd);
699                 if (debug) printf("%s: offset ", path);
700                 offset = rf.preview_offset();
701                 if (debug) printf("%d\n", offset);
702         }
703         catch (Exiv2::AnyError& e) {
704                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
705                 return 0;
706         }
707
708         if (image_offset && offset > 0)
709                 {
710                 *image_offset = offset;
711                 if (lseek(fd, *image_offset, SEEK_SET) != *image_offset)
712                         {
713                         printf("Failed to seek to embedded image\n");
714
715                         *image_offset = 0;
716                         if (*exif_offset) *exif_offset = 0;
717                         success = FALSE;
718                         }
719                 }
720
721         return offset > 0;
722 }
723
724
725 #endif 
726 /* HAVE_EXIV2 */