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