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