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