a hack to read raw previews with exiv2 0.16, however it should be fixed
[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/orfimage.hpp>
24 #include <exiv2/rafimage.hpp>
25 #include <exiv2/futils.hpp>
26
27
28
29 extern "C" {
30 #include <glib.h> 
31 #include "exif.h"
32
33 }
34
35 struct _ExifData
36 {
37         Exiv2::ExifData exifData;
38         Exiv2::ExifData::const_iterator exifIter; /* for exif_get_next_item */
39         Exiv2::IptcData iptcData;
40         Exiv2::IptcData::const_iterator iptcIter; /* for exif_get_next_item */
41         Exiv2::XmpData xmpData;
42         Exiv2::XmpData::const_iterator xmpIter; /* for exif_get_next_item */
43
44         _ExifData(gchar *path, gint parse_color_profile)
45         {
46                 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path);
47                 g_assert (image.get() != 0);
48                 image->readMetadata();
49                 exifData = image->exifData();
50                 iptcData = image->iptcData();
51                 xmpData = image->xmpData();
52         }
53
54 };
55
56 extern "C" {
57
58 ExifData *exif_read(gchar *path, gint parse_color_profile)
59 {
60         printf("exif %s\n", path);
61         try {
62                 return new ExifData(path, parse_color_profile);
63         }
64         catch (Exiv2::AnyError& e) {
65                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
66                 return 0;
67         }
68         
69 }
70
71 void exif_free(ExifData *exif)
72 {
73         
74         delete exif;
75 }
76
77 ExifItem *exif_get_item(ExifData *exif, const gchar *key)
78 {
79         try {
80                 Exiv2::Metadatum *item;
81                 try {
82                         Exiv2::ExifKey ekey(key);
83                         Exiv2::ExifData::iterator pos = exif->exifData.findKey(ekey);
84                         if (pos == exif->exifData.end()) return NULL;
85                         item = &*pos;
86                 }
87                 catch (Exiv2::AnyError& e) {
88                         try {
89                                 Exiv2::IptcKey ekey(key);
90                                 Exiv2::IptcData::iterator pos = exif->iptcData.findKey(ekey);
91                                 if (pos == exif->iptcData.end()) return NULL;
92                                 item = &*pos;
93                         }
94                         catch (Exiv2::AnyError& e) {
95                                 Exiv2::XmpKey ekey(key);
96                                 Exiv2::XmpData::iterator pos = exif->xmpData.findKey(ekey);
97                                 if (pos == exif->xmpData.end()) return NULL;
98                                 item = &*pos;
99                         }
100                 }
101                 return (ExifItem *)item;
102         }
103         catch (Exiv2::AnyError& e) {
104                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
105                 return NULL;
106         }
107 }
108
109
110 ExifItem *exif_get_first_item(ExifData *exif)
111 {
112         try {
113                 exif->exifIter = exif->exifData.begin();
114                 exif->iptcIter = exif->iptcData.begin();
115                 exif->xmpIter = exif->xmpData.begin();
116                 if (exif->exifIter != exif->exifData.end()) 
117                         {
118                         const Exiv2::Metadatum *item = &*exif->exifIter;
119                         exif->exifIter++;
120                         return (ExifItem *)item;
121                         }
122                 if (exif->iptcIter != exif->iptcData.end()) 
123                         {
124                         const Exiv2::Metadatum *item = &*exif->iptcIter;
125                         exif->iptcIter++;
126                         return (ExifItem *)item;
127                         }
128                 if (exif->xmpIter != exif->xmpData.end()) 
129                         {
130                         const Exiv2::Metadatum *item = &*exif->xmpIter;
131                         exif->xmpIter++;
132                         return (ExifItem *)item;
133                         }
134                 return NULL;
135                         
136         }
137         catch (Exiv2::AnyError& e) {
138                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
139                 return NULL;
140         }
141 }
142
143 ExifItem *exif_get_next_item(ExifData *exif)
144 {
145         try {
146                 if (exif->exifIter != exif->exifData.end())
147                         {
148                         const Exiv2::Metadatum *item = &*exif->exifIter;
149                         exif->exifIter++;
150                         return (ExifItem *)item;
151                 }
152                 if (exif->iptcIter != exif->iptcData.end())
153                         {
154                         const Exiv2::Metadatum *item = &*exif->iptcIter;
155                         exif->iptcIter++;
156                         return (ExifItem *)item;
157                 }
158                 if (exif->xmpIter != exif->xmpData.end())
159                         {
160                         const Exiv2::Metadatum *item = &*exif->xmpIter;
161                         exif->xmpIter++;
162                         return (ExifItem *)item;
163                 }
164                 return NULL;
165         }
166         catch (Exiv2::AnyError& e) {
167                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
168                 return NULL;
169         }
170 }
171
172 char *exif_item_get_tag_name(ExifItem *item)
173 {
174         try {
175                 if (!item) return NULL;
176                 return g_strdup(((Exiv2::Metadatum *)item)->key().c_str());
177         }
178         catch (Exiv2::AnyError& e) {
179                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
180                 return NULL;
181         }
182 }
183
184 guint exif_item_get_tag_id(ExifItem *item)
185 {
186         try {
187                 if (!item) return 0;
188                 return ((Exiv2::Metadatum *)item)->tag();
189         }
190         catch (Exiv2::AnyError& e) {
191                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
192                 return 0;
193         }
194 }
195
196 guint exif_item_get_elements(ExifItem *item)
197 {
198         try {
199                 if (!item) return 0;
200                 return ((Exiv2::Metadatum *)item)->count();
201         }
202         catch (Exiv2::AnyError& e) {
203                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
204                 return 0;
205         }
206 }
207
208 char *exif_item_get_data(ExifItem *item, guint *data_len)
209 {
210 }
211
212 char *exif_item_get_description(ExifItem *item)
213 {
214         try {
215                 if (!item) return NULL;
216                 return g_strdup(((Exiv2::Metadatum *)item)->tagLabel().c_str());
217         }
218         catch (std::exception& e) {
219 //              std::cout << "Caught Exiv2 exception '" << e << "'\n";
220                 return NULL;
221         }
222 }
223
224 /*
225 invalidTypeId, unsignedByte, asciiString, unsignedShort,
226   unsignedLong, unsignedRational, signedByte, undefined,
227   signedShort, signedLong, signedRational, string,
228   date, time, comment, directory,
229   xmpText, xmpAlt, xmpBag, xmpSeq,
230   langAlt, lastTypeId 
231 */
232
233 static guint format_id_trans_tbl [] = {
234         EXIF_FORMAT_UNKNOWN,
235         EXIF_FORMAT_BYTE_UNSIGNED,
236         EXIF_FORMAT_STRING,
237         EXIF_FORMAT_SHORT_UNSIGNED,
238         EXIF_FORMAT_LONG_UNSIGNED,
239         EXIF_FORMAT_RATIONAL_UNSIGNED,
240         EXIF_FORMAT_BYTE,
241         EXIF_FORMAT_UNDEFINED,
242         EXIF_FORMAT_SHORT,
243         EXIF_FORMAT_LONG,
244         EXIF_FORMAT_RATIONAL,
245         EXIF_FORMAT_STRING,
246         EXIF_FORMAT_STRING,
247         EXIF_FORMAT_STRING,
248         EXIF_FORMAT_UNDEFINED,
249         EXIF_FORMAT_STRING,
250         EXIF_FORMAT_STRING,
251         EXIF_FORMAT_STRING,
252         EXIF_FORMAT_STRING
253         };
254         
255         
256
257 guint exif_item_get_format_id(ExifItem *item)
258 {
259         try {
260                 if (!item) return EXIF_FORMAT_UNKNOWN;
261                 guint id = ((Exiv2::Metadatum *)item)->typeId();
262                 if (id >= (sizeof(format_id_trans_tbl) / sizeof(format_id_trans_tbl[0])) ) return EXIF_FORMAT_UNKNOWN;
263                 return format_id_trans_tbl[id];
264         }
265         catch (Exiv2::AnyError& e) {
266                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
267                 return EXIF_FORMAT_UNKNOWN;
268         }
269 }
270
271 const char *exif_item_get_format_name(ExifItem *item, gint brief)
272 {
273         try {
274                 if (!item) return NULL;
275                 return ((Exiv2::Metadatum *)item)->typeName();
276         }
277         catch (Exiv2::AnyError& e) {
278                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
279                 return NULL;
280         }
281 }
282
283
284 gchar *exif_item_get_data_as_text(ExifItem *item)
285 {
286         try {
287                 if (!item) return NULL;
288 //              std::stringstream str;  // does not work with Exiv2::Metadatum because operator<< is not virtual
289 //              str << *((Exiv2::Metadatum *)item);
290 //              return g_strdup(str.str().c_str());
291                 return g_strdup(((Exiv2::Metadatum *)item)->toString().c_str());
292         }
293         catch (Exiv2::AnyError& e) {
294                 return NULL;
295         }
296 }
297
298
299 gint exif_item_get_integer(ExifItem *item, gint *value)
300 {
301         try {
302                 if (!item) return 0;
303                 *value = ((Exiv2::Metadatum *)item)->toLong();
304                 return 1;
305         }
306         catch (Exiv2::AnyError& e) {
307                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
308                 return 0;
309         }
310 }
311
312 ExifRational *exif_item_get_rational(ExifItem *item, gint *sign)
313 {
314         try {
315                 if (!item) return NULL;
316                 Exiv2::Rational v = ((Exiv2::Metadatum *)item)->toRational();
317                 static ExifRational ret;
318                 ret.num = v.first;
319                 ret.den = v.second;
320                 return &ret;
321         }
322         catch (Exiv2::AnyError& e) {
323                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
324                 return NULL;
325         }
326 }
327
328 const gchar *exif_get_tag_description_by_key(const gchar *key)
329 {
330         try {
331                 Exiv2::ExifKey ekey(key);
332                 return Exiv2::ExifTags::tagLabel(ekey.tag(), ekey.ifdId ());
333         }
334         catch (Exiv2::AnyError& e) {
335                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
336                 return NULL;
337         }
338 }
339
340
341 }
342
343 /* This is a dirty hack to support raw file preview, bassed on 
344 tiffparse.cpp from Exiv2 examples */
345
346 class RawFile {
347         public:
348     
349         RawFile(int fd);
350         ~RawFile();
351     
352         const Exiv2::Value *find(uint16_t tag, uint16_t group);
353     
354         unsigned long preview_offset();
355     
356         private:
357         int type;
358         Exiv2::TiffComponent::AutoPtr rootDir;
359         Exiv2::byte *map_data;
360         size_t map_len;
361         unsigned long offset;
362 };
363
364 using namespace Exiv2;
365
366 RawFile::RawFile(int fd) : map_data(NULL), map_len(0), offset(0)
367 {
368         struct stat st;
369         if (fstat(fd, &st) == -1)
370                 {
371                 throw Error(14);
372                 }
373         map_len = st.st_size;
374         map_data = (Exiv2::byte *) mmap(0, map_len, PROT_READ, MAP_PRIVATE, fd, 0);
375         if (map_data == MAP_FAILED)
376                 {
377                 throw Error(14);
378                 }
379         type = Exiv2::ImageFactory::getType(map_data, map_len);
380
381         TiffHeaderBase *tiffHeader;
382         switch (type) {
383                 case Exiv2::ImageType::tiff:
384                         tiffHeader = new TiffHeade2();
385                         break;
386                 case Exiv2::ImageType::cr2:
387                         tiffHeader = new Cr2Header();
388                         break;
389                 case Exiv2::ImageType::orf:
390                         tiffHeader = new OrfHeader();
391                         break;
392                 case Exiv2::ImageType::raf:
393                         if (map_len < 84 + 4) throw Error(14);
394                         offset = getULong(map_data + 84, bigEndian);
395                         return;
396
397                 default:
398                         throw Error(3, "RAW");
399         }
400
401         // process tiff-like formats
402         if (!tiffHeader->read(map_data, map_len)) throw Error(3, "TIFF");
403
404         TiffCompFactoryFct createFct = TiffCreator::create;
405
406         rootDir = createFct(Tag::root, Group::none);
407         if (0 == rootDir.get()) {
408                 throw Error(1, "No root element defined in TIFF structure");
409         }
410         rootDir->setStart(map_data + tiffHeader->offset());
411
412         TiffRwState::AutoPtr state(
413                 new TiffRwState(tiffHeader->byteOrder(), 0, createFct));
414
415         TiffReader reader(map_data,
416                       map_len,
417                       rootDir.get(),
418                       state);
419
420         rootDir->accept(reader);
421         
422         delete tiffHeader;
423 }
424
425 RawFile::~RawFile()
426 {
427         if (map_data && munmap(map_data, map_len) == -1)
428                 {
429                 printf("Failed to unmap file \n");
430                 }
431 }
432
433 const Value * RawFile::find(uint16_t tag, uint16_t group)
434 {
435         printf("%04x %04x\n", tag, group);
436         TiffFinder finder(tag, group);
437         rootDir->accept(finder);
438         TiffEntryBase* te = dynamic_cast<TiffEntryBase*>(finder.result());
439         if (te)
440                 return te->pValue();
441         else
442                 return NULL;
443 }
444
445 unsigned long RawFile::preview_offset()
446 {
447         const Value *val;
448         if (offset) return offset;
449         
450         if (type == Exiv2::ImageType::cr2)
451                 {
452                 val = find(0x111, Group::ifd0);
453                 if (val) return val->toLong();
454     
455                 return 0;
456                 }
457         
458         val = find(0x201, Group::sub0_0);
459         if (val) return val->toLong();
460
461         val = find(0x201, Group::ifd0);
462         if (val) return val->toLong();
463     
464         val = find(0x201, Group::ignr); // for PEF files, originally it was probably ifd2
465         if (val) return val->toLong();
466
467         val = find(0x111, Group::sub0_1); // dng
468         if (val) return val->toLong();
469
470         return 0;
471 }
472
473
474 const static char *raw_ext_list[] = { ".cr2", ".nef", ".pef", ".arw", NULL };
475
476 extern "C" gint format_raw_img_exif_offsets_fd(int fd, const gchar *path,
477                                     unsigned char *header_data, const guint header_len,
478                                     guint *image_offset, guint *exif_offset)
479 {
480         int success;
481         unsigned long offset;
482
483         /* given image pathname, first do simple (and fast) file extension test */
484 /*      if (path)
485                 {
486                 const gchar *ext;
487                 gint match = FALSE;
488                 gint i;
489
490                 ext = strrchr(path, '.');
491                 if (!ext) return FALSE;
492                 
493                 for (i = 0; raw_ext_list[i]; i++) 
494                         {
495                         if (strcasecmp(raw_ext_list[i], ext) == 0)
496                                 {
497                                 match = TRUE;
498                                 break;
499                                 }
500                         }
501
502                 if (!match) return FALSE;
503
504                 }
505 */
506         try {
507                 RawFile rf(fd);
508                 offset = rf.preview_offset();
509         }
510         catch (Exiv2::AnyError& e) {
511                 std::cout << "Caught Exiv2 exception '" << e << "'\n";
512                 return 0;
513         }
514
515         if (image_offset)
516                 {
517                 *image_offset = offset;
518                 if (lseek(fd, *image_offset, SEEK_SET) != *image_offset)
519                         {
520                         printf("Failed to seek to embedded image\n");
521
522                         *image_offset = 0;
523                         if (*exif_offset) *exif_offset = 0;
524                         success = FALSE;
525                         }
526                 }
527
528         return offset > 0;
529 }
530
531
532 #endif 
533 /* HAVE_EXIV2 */