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