1 /* -*- Mode: CPP; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /*
4 * GThumb
5 *
6 * Copyright (C) 2008-2009 Free Software Foundation, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <config.h>
23 #include <glib.h>
24 #define GDK_PIXBUF_ENABLE_BACKEND
25 #include <gdk-pixbuf/gdk-pixbuf.h>
26 #include <exiv2/basicio.hpp>
27 #include <exiv2/error.hpp>
28 #include <exiv2/image.hpp>
29 #include <exiv2/exif.hpp>
30 #include <iostream>
31 #include <string>
32 #include <sstream>
33 #include <vector>
34 #include <iomanip>
35 #include <exiv2/exiv2.hpp>
36 #if EXIV2_TEST_VERSION(0, 27, 0)
37 #include <exiv2/xmp_exiv2.hpp>
38 #else
39 #include <exiv2/xmp.hpp>
40 #endif
41 #include <gthumb.h>
42 #include "exiv2-utils.h"
43
44 using namespace std;
45
46
47 #define INVALID_VALUE N_("(invalid value)")
48
49
50 /* Some bits of information may be contained in more than one metadata tag.
51 The arrays below define the valid tags for a particular piece of
52 information, in decreasing order of preference (best one first) */
53
54 const char *_DATE_TAG_NAMES[] = {
55 "Exif::Image::DateTime",
56 "Xmp::exif::DateTime",
57 "Exif::Photo::DateTimeOriginal",
58 "Xmp::exif::DateTimeOriginal",
59 "Exif::Photo::DateTimeDigitized",
60 "Xmp::exif::DateTimeDigitized",
61 "Xmp::xmp::CreateDate",
62 "Xmp::photoshop::DateCreated",
63 "Xmp::xmp::ModifyDate",
64 "Xmp::xmp::MetadataDate",
65 NULL
66 };
67
68 const char *_LAST_DATE_TAG_NAMES[] = {
69 "Exif::Image::DateTime",
70 "Xmp::exif::DateTime",
71 "Xmp::xmp::ModifyDate",
72 "Xmp::xmp::MetadataDate",
73 NULL
74 };
75
76 const char *_ORIGINAL_DATE_TAG_NAMES[] = {
77 "Exif::Photo::DateTimeOriginal",
78 "Xmp::exif::DateTimeOriginal",
79 "Exif::Photo::DateTimeDigitized",
80 "Xmp::exif::DateTimeDigitized",
81 "Xmp::xmp::CreateDate",
82 "Xmp::photoshop::DateCreated",
83 NULL
84 };
85
86 const char *_EXPOSURE_TIME_TAG_NAMES[] = {
87 "Exif::Photo::ExposureTime",
88 "Xmp::exif::ExposureTime",
89 "Exif::Photo::ShutterSpeedValue",
90 "Xmp::exif::ShutterSpeedValue",
91 NULL
92 };
93
94 const char *_EXPOSURE_MODE_TAG_NAMES[] = {
95 "Exif::Photo::ExposureMode",
96 "Xmp::exif::ExposureMode",
97 NULL
98 };
99
100 const char *_ISOSPEED_TAG_NAMES[] = {
101 "Exif::Photo::ISOSpeedRatings",
102 "Xmp::exif::ISOSpeedRatings",
103 NULL
104 };
105
106 const char *_APERTURE_TAG_NAMES[] = {
107 "Exif::Photo::ApertureValue",
108 "Xmp::exif::ApertureValue",
109 "Exif::Photo::FNumber",
110 "Xmp::exif::FNumber",
111 NULL
112 };
113
114 const char *_FOCAL_LENGTH_TAG_NAMES[] = {
115 "Exif::Photo::FocalLength",
116 "Xmp::exif::FocalLength",
117 NULL
118 };
119
120 const char *_SHUTTER_SPEED_TAG_NAMES[] = {
121 "Exif::Photo::ShutterSpeedValue",
122 "Xmp::exif::ShutterSpeedValue",
123 NULL
124 };
125
126 const char *_MAKE_TAG_NAMES[] = {
127 "Exif::Image::Make",
128 "Xmp::tiff::Make",
129 NULL
130 };
131
132 const char *_MODEL_TAG_NAMES[] = {
133 "Exif::Image::Model",
134 "Xmp::tiff::Model",
135 NULL
136 };
137
138 const char *_FLASH_TAG_NAMES[] = {
139 "Exif::Photo::Flash",
140 "Xmp::exif::Flash",
141 NULL
142 };
143
144 const char *_ORIENTATION_TAG_NAMES[] = {
145 "Exif::Image::Orientation",
146 "Xmp::tiff::Orientation",
147 NULL
148 };
149
150 const char *_DESCRIPTION_TAG_NAMES[] = {
151 "Iptc::Application2::Caption",
152 "Xmp::dc::description",
153 "Exif::Photo::UserComment",
154 "Exif::Image::ImageDescription",
155 "Xmp::tiff::ImageDescription",
156 "Iptc::Application2::Headline",
157 NULL
158 };
159
160 const char *_TITLE_TAG_NAMES[] = {
161 "Xmp::dc::title",
162 NULL
163 };
164
165 const char *_LOCATION_TAG_NAMES[] = {
166 "Xmp::iptc::Location",
167 "Iptc::Application2::LocationName",
168 NULL
169 };
170
171 const char *_KEYWORDS_TAG_NAMES[] = {
172 "Iptc::Application2::Keywords",
173 "Xmp::iptc::Keywords",
174 "Xmp::dc::subject",
175 NULL
176 };
177
178 const char *_RATING_TAG_NAMES[] = {
179 "Xmp::xmp::Rating",
180 NULL
181 };
182
183 const char *_AUTHOR_TAG_NAMES[] = {
184 "Exif::Image::Artist",
185 "Iptc::Application2::Byline",
186 "Xmp::dc::creator",
187 "Xmp::xmpDM::artist",
188 "Xmp::tiff::Artist",
189 "Xmp::plus::ImageCreator",
190 "Xmp::plus::ImageCreatorName",
191 NULL
192 };
193
194 const char *_COPYRIGHT_TAG_NAMES[] = {
195 "Exif::Image::Copyright",
196 "Iptc::Application2::Copyright",
197 "Xmp::dc::rights",
198 "Xmp::xmpDM::copyright",
199 "Xmp::tiff::Copyright",
200 "Xmp::plus::CopyrightOwner",
201 "Xmp::plus::CopyrightOwnerName",
202 NULL
203 };
204
205 /* Some evil camera fill in the ImageDescription or UserComment fields
206 with useless fluff. Try to filter these out, so they do not show up
207 as comments */
208 const char *stupid_comment_filter[] = {
209 "OLYMPUS DIGITAL CAMERA",
210 "SONY DSC",
211 "KONICA MINOLTA DIGITAL CAMERA",
212 "MINOLTA DIGITAL CAMERA",
213 NULL };
214
215
216 inline static char *
exiv2_key_from_attribute(const char * attribute)217 exiv2_key_from_attribute (const char *attribute)
218 {
219 return _g_utf8_replace_str (attribute, "::", ".");
220 }
221
222
223 inline static char *
exiv2_key_to_attribute(const char * key)224 exiv2_key_to_attribute (const char *key)
225 {
226 return _g_utf8_replace_str (key, ".", "::");
227 }
228
229
230 static gboolean
attribute_is_date(const char * key)231 attribute_is_date (const char *key)
232 {
233 int i;
234
235 for (i = 0; _DATE_TAG_NAMES[i] != NULL; i++) {
236 if (strcmp (_DATE_TAG_NAMES[i], key) == 0)
237 return TRUE;
238 }
239
240 return FALSE;
241 }
242
243
244 static GthMetadata *
create_metadata(const char * key,const char * description,const char * formatted_value,const char * raw_value,const char * category,const char * type_name)245 create_metadata (const char *key,
246 const char *description,
247 const char *formatted_value,
248 const char *raw_value,
249 const char *category,
250 const char *type_name)
251 {
252 char *formatted_value_utf8;
253 char *attribute;
254 GthMetadataInfo *metadata_info;
255 GthMetadata *metadata;
256 char *description_utf8;
257
258 formatted_value_utf8 = _g_utf8_from_any (formatted_value);
259 if (_g_utf8_all_spaces (formatted_value_utf8))
260 return NULL;
261
262 description_utf8 = _g_utf8_from_any (description);
263
264 attribute = exiv2_key_to_attribute (key);
265 if (attribute_is_date (attribute)) {
266 GTimeVal time_;
267
268 g_free (formatted_value_utf8);
269 formatted_value_utf8 = NULL;
270
271 if (_g_time_val_from_exif_date (raw_value, &time_))
272 formatted_value_utf8 = _g_time_val_strftime (&time_, "%x %X");
273 else
274 formatted_value_utf8 = g_locale_to_utf8 (formatted_value, -1, NULL, NULL, NULL);
275 }
276 else if (_g_utf8_has_prefix (formatted_value_utf8, "lang=")) {
277 const char *after_space;
278 char *formatted_clean;
279
280 after_space = _g_utf8_after_ascii_space (formatted_value_utf8);
281 formatted_clean = g_strdup (after_space);
282 g_free (formatted_value_utf8);
283 formatted_value_utf8 = formatted_clean;
284 }
285
286 if (formatted_value_utf8 == NULL)
287 formatted_value_utf8 = g_strdup (INVALID_VALUE);
288
289 metadata_info = gth_main_get_metadata_info (attribute);
290 if ((metadata_info == NULL) && (category != NULL)) {
291 GthMetadataInfo info;
292
293 info.id = attribute;
294 info.type = (type_name != NULL) ? g_strdup (type_name) : NULL;
295 info.display_name = description_utf8;
296 info.category = category;
297 info.sort_order = 500;
298 info.flags = GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW;
299 metadata_info = gth_main_register_metadata_info (&info);
300 }
301
302 if ((metadata_info != NULL) && (metadata_info->type == NULL) && (type_name != NULL))
303 metadata_info->type = g_strdup (type_name);
304
305 if ((metadata_info != NULL) && (metadata_info->display_name == NULL) && (description_utf8 != NULL))
306 metadata_info->display_name = g_strdup (description_utf8);
307
308 metadata = gth_metadata_new ();
309 g_object_set (metadata,
310 "id", key,
311 "description", description_utf8,
312 "formatted", formatted_value_utf8,
313 "raw", raw_value,
314 "value-type", type_name,
315 NULL);
316
317 g_free (formatted_value_utf8);
318 g_free (description_utf8);
319 g_free (attribute);
320
321 return metadata;
322 }
323
324
325 static void
add_string_list_to_metadata(GthMetadata * metadata,const Exiv2::Metadatum & value)326 add_string_list_to_metadata (GthMetadata *metadata,
327 const Exiv2::Metadatum &value)
328 {
329 GList *list;
330 GthStringList *string_list;
331
332 list = NULL;
333 for (int i = 0; i < value.count(); i++)
334 list = g_list_prepend (list, g_strdup (value.toString(i).c_str()));
335 string_list = gth_string_list_new (g_list_reverse (list));
336 g_object_set (metadata, "string-list", string_list, NULL);
337
338 g_object_unref (string_list);
339 _g_string_list_free (list);
340 }
341
342
343 static void
set_file_info(GFileInfo * info,const char * key,const char * description,const char * formatted_value,const char * raw_value,const char * category,const char * type_name)344 set_file_info (GFileInfo *info,
345 const char *key,
346 const char *description,
347 const char *formatted_value,
348 const char *raw_value,
349 const char *category,
350 const char *type_name)
351 {
352 char *attribute;
353 GthMetadata *metadata;
354
355 attribute = exiv2_key_to_attribute (key);
356 metadata = create_metadata (key, description, formatted_value, raw_value, category, type_name);
357 if (metadata != NULL) {
358 g_file_info_set_attribute_object (info, attribute, G_OBJECT (metadata));
359 g_object_unref (metadata);
360 }
361
362 g_free (attribute);
363 }
364
365
366 GHashTable *
create_metadata_hash(void)367 create_metadata_hash (void)
368 {
369 return g_hash_table_new_full (g_str_hash,
370 g_str_equal,
371 g_free,
372 g_object_unref);
373 }
374
375
376 static void
add_metadata_to_hash(GHashTable * table,GthMetadata * metadata)377 add_metadata_to_hash (GHashTable *table,
378 GthMetadata *metadata)
379 {
380 char *key;
381 gpointer object;
382
383 if (metadata == NULL)
384 return;
385
386 key = exiv2_key_to_attribute (gth_metadata_get_id (metadata));
387 object = g_hash_table_lookup (table, key);
388 if (object != NULL) {
389 GthStringList *string_list;
390 GList *list;
391
392 string_list = NULL;
393 switch (gth_metadata_get_data_type (GTH_METADATA (object))) {
394 case GTH_METADATA_TYPE_STRING:
395 string_list = gth_string_list_new (NULL);
396 list = g_list_append (NULL, g_strdup (gth_metadata_get_formatted (GTH_METADATA (object))));
397 gth_string_list_set_list (string_list, list);
398 break;
399
400 case GTH_METADATA_TYPE_STRING_LIST:
401 string_list = (GthStringList *) g_object_ref (gth_metadata_get_string_list (GTH_METADATA (object)));
402 break;
403 }
404
405 if (string_list == NULL) {
406 g_hash_table_insert (table,
407 g_strdup (key),
408 g_object_ref (metadata));
409 return;
410 }
411
412 switch (gth_metadata_get_data_type (metadata)) {
413 case GTH_METADATA_TYPE_STRING:
414 list = gth_string_list_get_list (string_list);
415 list = g_list_append (list, g_strdup (gth_metadata_get_formatted (metadata)));
416 gth_string_list_set_list (string_list, list);
417 break;
418
419 case GTH_METADATA_TYPE_STRING_LIST:
420 gth_string_list_concat (string_list, gth_metadata_get_string_list (metadata));
421 break;
422 }
423
424 g_object_set (metadata, "string-list", string_list, NULL);
425 g_hash_table_replace (table,
426 g_strdup (key),
427 g_object_ref (metadata));
428
429 g_object_unref (string_list);
430 }
431 else
432 g_hash_table_insert (table,
433 g_strdup (key),
434 g_object_ref (metadata));
435
436 g_free (key);
437 }
438
439
440 static void
set_file_info_from_hash(GFileInfo * info,GHashTable * table)441 set_file_info_from_hash (GFileInfo *info,
442 GHashTable *table)
443 {
444 GHashTableIter iter;
445 gpointer key;
446 gpointer value;
447
448 g_hash_table_iter_init (&iter, table);
449 while (g_hash_table_iter_next (&iter, &key, &value))
450 g_file_info_set_attribute_object (info, (char *)key, G_OBJECT (value));
451 }
452
453
454 static void
set_attribute_from_metadata(GFileInfo * info,const char * attribute,GObject * metadata)455 set_attribute_from_metadata (GFileInfo *info,
456 const char *attribute,
457 GObject *metadata)
458 {
459 char *description;
460 char *formatted_value;
461 char *raw_value;
462 char *type_name;
463
464 if (metadata == NULL)
465 return;
466
467 g_object_get (metadata,
468 "description", &description,
469 "formatted", &formatted_value,
470 "raw", &raw_value,
471 "value-type", &type_name,
472 NULL);
473
474 set_file_info (info,
475 attribute,
476 description,
477 formatted_value,
478 raw_value,
479 NULL,
480 type_name);
481
482 g_free (description);
483 g_free (formatted_value);
484 g_free (raw_value);
485 g_free (type_name);
486 }
487
488
489 static GObject *
get_attribute_from_tagset(GFileInfo * info,const char * tagset[])490 get_attribute_from_tagset (GFileInfo *info,
491 const char *tagset[])
492 {
493 GObject *metadata;
494 int i;
495
496 metadata = NULL;
497 for (i = 0; tagset[i] != NULL; i++) {
498 metadata = g_file_info_get_attribute_object (info, tagset[i]);
499 if (metadata != NULL)
500 return metadata;
501 }
502
503 return NULL;
504 }
505
506
507 static void
set_attribute_from_tagset(GFileInfo * info,const char * attribute,const char * tagset[])508 set_attribute_from_tagset (GFileInfo *info,
509 const char *attribute,
510 const char *tagset[])
511 {
512 GObject *metadata;
513
514 metadata = get_attribute_from_tagset (info, tagset);
515 if (metadata != NULL)
516 set_attribute_from_metadata (info, attribute, metadata);
517 }
518
519
520 static void
set_string_list_attribute_from_tagset(GFileInfo * info,const char * attribute,const char * tagset[])521 set_string_list_attribute_from_tagset (GFileInfo *info,
522 const char *attribute,
523 const char *tagset[])
524 {
525 GObject *metadata;
526 int i;
527
528 metadata = NULL;
529 for (i = 0; tagset[i] != NULL; i++) {
530 metadata = g_file_info_get_attribute_object (info, tagset[i]);
531 if (metadata != NULL)
532 break;
533 }
534
535 if (metadata == NULL)
536 return;
537
538 if (GTH_IS_METADATA (metadata) && (gth_metadata_get_data_type (GTH_METADATA (metadata)) != GTH_METADATA_TYPE_STRING_LIST)) {
539 char *raw;
540 char *utf8_raw;
541 char **keywords;
542 GthStringList *string_list;
543
544 g_object_get (metadata, "raw", &raw, NULL);
545 utf8_raw = _g_utf8_try_from_any (raw);
546 if (utf8_raw == NULL)
547 return;
548
549 keywords = g_strsplit (utf8_raw, ",", -1);
550 string_list = gth_string_list_new_from_strv (keywords);
551 metadata = (GObject *) gth_metadata_new_for_string_list (string_list);
552 g_file_info_set_attribute_object (info, attribute, metadata);
553
554 g_object_unref (metadata);
555 g_object_unref (string_list);
556 g_strfreev (keywords);
557 g_free (raw);
558 g_free (utf8_raw);
559 }
560 else
561 g_file_info_set_attribute_object (info, attribute, metadata);
562 }
563
564
565 static void
clear_studip_comments_from_tagset(GFileInfo * info,const char * tagset[])566 clear_studip_comments_from_tagset (GFileInfo *info,
567 const char *tagset[])
568 {
569 int i;
570
571 for (i = 0; tagset[i] != NULL; i++) {
572 GObject *metadata;
573 const char *value;
574 int j;
575
576 metadata = g_file_info_get_attribute_object (info, tagset[i]);
577 if ((metadata == NULL) || ! GTH_IS_METADATA (metadata))
578 continue;
579
580 value = gth_metadata_get_formatted (GTH_METADATA (metadata));
581 for (j = 0; stupid_comment_filter[j] != NULL; j++) {
582 if (strstr (value, stupid_comment_filter[j]) == value) {
583 g_file_info_remove_attribute (info, tagset[i]);
584 break;
585 }
586 }
587 }
588 }
589
590
591 extern "C"
592 void
exiv2_update_general_attributes(GFileInfo * info)593 exiv2_update_general_attributes (GFileInfo *info)
594 {
595 set_attribute_from_tagset (info, "general::datetime", _ORIGINAL_DATE_TAG_NAMES);
596 set_attribute_from_tagset (info, "general::description", _DESCRIPTION_TAG_NAMES);
597 set_attribute_from_tagset (info, "general::title", _TITLE_TAG_NAMES);
598
599 /* if iptc::caption and iptc::headline are different use iptc::headline
600 * to set general::title, if not already set. */
601
602 if (g_file_info_get_attribute_object (info, "general::title") == NULL) {
603 GObject *iptc_caption;
604 GObject *iptc_headline;
605
606 iptc_caption = g_file_info_get_attribute_object (info, "Iptc::Application2::Caption");
607 iptc_headline = g_file_info_get_attribute_object (info, "Iptc::Application2::Headline");
608
609 if ((iptc_caption != NULL)
610 && (iptc_headline != NULL)
611 && (g_strcmp0 (gth_metadata_get_raw (GTH_METADATA (iptc_caption)),
612 gth_metadata_get_raw (GTH_METADATA (iptc_headline))) != 0))
613 {
614 set_attribute_from_metadata (info, "general::title", iptc_headline);
615 }
616 }
617
618 set_attribute_from_tagset (info, "general::location", _LOCATION_TAG_NAMES);
619 set_string_list_attribute_from_tagset (info, "general::tags", _KEYWORDS_TAG_NAMES);
620 set_attribute_from_tagset (info, "general::rating", _RATING_TAG_NAMES);
621 }
622
623
624 #define EXPOSURE_SEPARATOR " · "
625
626
627 static void
set_attributes_from_tagsets(GFileInfo * info,gboolean update_general_attributes)628 set_attributes_from_tagsets (GFileInfo *info,
629 gboolean update_general_attributes)
630 {
631 clear_studip_comments_from_tagset (info, _DESCRIPTION_TAG_NAMES);
632 clear_studip_comments_from_tagset (info, _TITLE_TAG_NAMES);
633
634 if (update_general_attributes)
635 exiv2_update_general_attributes (info);
636
637 set_attribute_from_tagset (info, "Embedded::Photo::DateTimeOriginal", _ORIGINAL_DATE_TAG_NAMES);
638 set_attribute_from_tagset (info, "Embedded::Image::Orientation", _ORIENTATION_TAG_NAMES);
639
640 set_attribute_from_tagset (info, "Embedded::Photo::Aperture", _APERTURE_TAG_NAMES);
641 set_attribute_from_tagset (info, "Embedded::Photo::ISOSpeed", _ISOSPEED_TAG_NAMES);
642 set_attribute_from_tagset (info, "Embedded::Photo::ExposureTime", _EXPOSURE_TIME_TAG_NAMES);
643 set_attribute_from_tagset (info, "Embedded::Photo::ShutterSpeed", _SHUTTER_SPEED_TAG_NAMES);
644 set_attribute_from_tagset (info, "Embedded::Photo::FocalLength", _FOCAL_LENGTH_TAG_NAMES);
645 set_attribute_from_tagset (info, "Embedded::Photo::Flash", _FLASH_TAG_NAMES);
646 set_attribute_from_tagset (info, "Embedded::Photo::CameraModel", _MODEL_TAG_NAMES);
647 set_attribute_from_tagset (info, "Embedded::Photo::Author", _AUTHOR_TAG_NAMES);
648 set_attribute_from_tagset (info, "Embedded::Photo::Copyright", _COPYRIGHT_TAG_NAMES);
649
650 /* Embedded::Photo::Exposure */
651
652 GObject *aperture;
653 GObject *iso_speed;
654 GObject *shutter_speed;
655 GObject *exposure_time;
656 GString *exposure;
657
658 aperture = get_attribute_from_tagset (info, _APERTURE_TAG_NAMES);
659 iso_speed = get_attribute_from_tagset (info, _ISOSPEED_TAG_NAMES);
660 shutter_speed = get_attribute_from_tagset (info, _SHUTTER_SPEED_TAG_NAMES);
661 exposure_time = get_attribute_from_tagset (info, _EXPOSURE_TIME_TAG_NAMES);
662
663 exposure = g_string_new ("");
664
665 if (aperture != NULL) {
666 char *formatted_value;
667
668 g_object_get (aperture, "formatted", &formatted_value, NULL);
669 if (formatted_value != NULL) {
670 g_string_append (exposure, formatted_value);
671 g_free (formatted_value);
672 }
673 }
674
675 if (iso_speed != NULL) {
676 char *formatted_value;
677
678 g_object_get (iso_speed, "formatted", &formatted_value, NULL);
679 if (formatted_value != NULL) {
680 if (exposure->len > 0)
681 g_string_append (exposure, EXPOSURE_SEPARATOR);
682 g_string_append (exposure, "ISO ");
683 g_string_append (exposure, formatted_value);
684 g_free (formatted_value);
685 }
686 }
687
688 if (shutter_speed != NULL) {
689 char *formatted_value;
690
691 g_object_get (shutter_speed, "formatted", &formatted_value, NULL);
692 if (formatted_value != NULL) {
693 if (exposure->len > 0)
694 g_string_append (exposure, EXPOSURE_SEPARATOR);
695 g_string_append (exposure, formatted_value);
696 g_free (formatted_value);
697 }
698 }
699 else if (exposure_time != NULL) {
700 char *formatted_value;
701
702 g_object_get (exposure_time, "formatted", &formatted_value, NULL);
703 if (formatted_value != NULL) {
704 if (exposure->len > 0)
705 g_string_append (exposure, EXPOSURE_SEPARATOR);
706 g_string_append (exposure, formatted_value);
707 g_free (formatted_value);
708 }
709 }
710
711 set_file_info (info,
712 "Embedded::Photo::Exposure",
713 _("Exposure"),
714 exposure->str,
715 NULL,
716 NULL,
717 NULL);
718
719 g_string_free (exposure, TRUE);
720 }
721
722
723 static const char *
get_exif_default_category(const Exiv2::Exifdatum & md)724 get_exif_default_category (const Exiv2::Exifdatum &md)
725 {
726 #if EXIV2_TEST_VERSION(0, 21, 0)
727 if (Exiv2::ExifTags::isMakerGroup(md.groupName()))
728 #else
729 if (Exiv2::ExifTags::isMakerIfd(md.ifdId()))
730 #endif
731 return "Exif::MakerNotes";
732
733 if (md.groupName().compare("Thumbnail") == 0)
734 return "Exif::Thumbnail";
735 else if (md.groupName().compare("GPSInfo") == 0)
736 return "Exif::GPS";
737 else if (md.groupName().compare("Iop") == 0)
738 return "Exif::Versions";
739
740 return "Exif::Other";
741 }
742
743
744 static void
exiv2_read_metadata(Exiv2::Image::AutoPtr image,GFileInfo * info,gboolean update_general_attributes)745 exiv2_read_metadata (Exiv2::Image::AutoPtr image,
746 GFileInfo *info,
747 gboolean update_general_attributes)
748 {
749 image->readMetadata();
750
751 Exiv2::ExifData &exifData = image->exifData();
752 if (! exifData.empty()) {
753 Exiv2::ExifData::const_iterator end = exifData.end();
754 for (Exiv2::ExifData::const_iterator md = exifData.begin(); md != end; ++md) {
755 stringstream raw_value;
756 raw_value << md->value();
757
758 stringstream description;
759 if (! md->tagLabel().empty())
760 description << md->tagLabel();
761 #if EXIV2_TEST_VERSION(0, 21, 0)
762 else if (Exiv2::ExifTags::isMakerGroup(md->groupName()))
763 #else
764 else if (Exiv2::ExifTags::isMakerIfd(md->ifdId()))
765 #endif
766 // Must be a MakerNote - include group name
767 description << md->groupName() << "." << md->tagName();
768 else
769 // Normal exif tag - just use tag name
770 description << md->tagName();
771
772 set_file_info (info,
773 md->key().c_str(),
774 description.str().c_str(),
775 md->print(&exifData).c_str(),
776 raw_value.str().c_str(),
777 get_exif_default_category (*md),
778 md->typeName());
779 }
780 }
781
782 Exiv2::IptcData &iptcData = image->iptcData();
783 if (! iptcData.empty()) {
784 GHashTable *table = create_metadata_hash ();
785
786 Exiv2::IptcData::iterator end = iptcData.end();
787 for (Exiv2::IptcData::iterator md = iptcData.begin(); md != end; ++md) {
788 stringstream raw_value;
789 raw_value << md->value();
790
791 stringstream description;
792 if (! md->tagLabel().empty())
793 description << md->tagLabel();
794 else
795 description << md->tagName();
796
797 GthMetadata *metadata;
798 metadata = create_metadata (md->key().c_str(),
799 description.str().c_str(),
800 md->print().c_str(),
801 raw_value.str().c_str(),
802 "Iptc",
803 md->typeName());
804 if (metadata != NULL) {
805 add_metadata_to_hash (table, metadata);
806 g_object_unref (metadata);
807 }
808 }
809
810 set_file_info_from_hash (info, table);
811 g_hash_table_unref (table);
812 }
813
814 Exiv2::XmpData &xmpData = image->xmpData();
815 if (! xmpData.empty()) {
816 GHashTable *table = create_metadata_hash ();
817
818 Exiv2::XmpData::iterator end = xmpData.end();
819 for (Exiv2::XmpData::iterator md = xmpData.begin(); md != end; ++md) {
820 stringstream raw_value;
821 raw_value << md->value();
822
823 stringstream description;
824 if (! md->tagLabel().empty())
825 description << md->tagLabel();
826 else
827 description << md->groupName() << "." << md->tagName();
828
829 GthMetadata *metadata;
830 metadata = create_metadata (md->key().c_str(),
831 description.str().c_str(),
832 md->print().c_str(),
833 raw_value.str().c_str(),
834 "Xmp::Embedded",
835 md->typeName());
836 if (metadata != NULL) {
837 if ((g_strcmp0 (md->typeName(), "XmpBag") == 0)
838 || (g_strcmp0 (md->typeName(), "XmpSeq") == 0))
839 {
840 add_string_list_to_metadata (metadata, *md);
841 }
842
843 add_metadata_to_hash (table, metadata);
844 g_object_unref (metadata);
845 }
846 }
847
848 set_file_info_from_hash (info, table);
849 g_hash_table_unref (table);
850 }
851
852 set_attributes_from_tagsets (info, update_general_attributes);
853 }
854
855
856 /*
857 * exiv2_read_metadata_from_file
858 * reads metadata from image files
859 * code relies heavily on example1 from the exiv2 website
860 * http://www.exiv2.org/example1.html
861 */
862 extern "C"
863 gboolean
exiv2_read_metadata_from_file(GFile * file,GFileInfo * info,gboolean update_general_attributes,GCancellable * cancellable,GError ** error)864 exiv2_read_metadata_from_file (GFile *file,
865 GFileInfo *info,
866 gboolean update_general_attributes,
867 GCancellable *cancellable,
868 GError **error)
869 {
870 try {
871 char *path;
872
873 path = g_file_get_path (file);
874 if (path == NULL) {
875 if (error != NULL)
876 *error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
877 return FALSE;
878 }
879
880 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path);
881 g_free (path);
882
883 if (image.get() == 0) {
884 if (error != NULL)
885 *error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
886 return FALSE;
887 }
888 // Set the log level to only show errors (and suppress warnings, informational and debug messages)
889 Exiv2::LogMsg::setLevel(Exiv2::LogMsg::error);
890 exiv2_read_metadata (image, info, update_general_attributes);
891 }
892 catch (Exiv2::AnyError& e) {
893 if (error != NULL)
894 *error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, e.what());
895 return FALSE;
896 }
897
898 return TRUE;
899 }
900
901
902 extern "C"
903 gboolean
exiv2_read_metadata_from_buffer(void * buffer,gsize buffer_size,GFileInfo * info,gboolean update_general_attributes,GError ** error)904 exiv2_read_metadata_from_buffer (void *buffer,
905 gsize buffer_size,
906 GFileInfo *info,
907 gboolean update_general_attributes,
908 GError **error)
909 {
910 try {
911 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) buffer, buffer_size);
912
913 if (image.get() == 0) {
914 if (error != NULL)
915 *error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
916 return FALSE;
917 }
918
919 exiv2_read_metadata (image, info, update_general_attributes);
920 }
921 catch (Exiv2::AnyError& e) {
922 if (error != NULL)
923 *error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, e.what());
924 return FALSE;
925 }
926
927 return TRUE;
928 }
929
930
931 extern "C"
932 GFile *
exiv2_get_sidecar(GFile * file)933 exiv2_get_sidecar (GFile *file)
934 {
935 char *uri;
936 char *uri_wo_ext;
937 char *sidecar_uri;
938 GFile *sidecar;
939
940 uri = g_file_get_uri (file);
941 uri_wo_ext = _g_uri_remove_extension (uri);
942 sidecar_uri = g_strconcat (uri_wo_ext, ".xmp", NULL);
943 sidecar = g_file_new_for_uri (sidecar_uri);
944
945 g_free (sidecar_uri);
946 g_free (uri_wo_ext);
947 g_free (uri);
948
949 return sidecar;
950 }
951
952
953 extern "C"
954 gboolean
exiv2_read_sidecar(GFile * file,GFileInfo * info,gboolean update_general_attributes)955 exiv2_read_sidecar (GFile *file,
956 GFileInfo *info,
957 gboolean update_general_attributes)
958 {
959 try {
960 char *path;
961
962 path = g_file_get_path (file);
963 if (path == NULL)
964 return FALSE;
965
966 Exiv2::DataBuf buf = Exiv2::readFile(path);
967 g_free (path);
968
969 std::string xmpPacket;
970 xmpPacket.assign(reinterpret_cast<char*>(buf.pData_), buf.size_);
971 Exiv2::XmpData xmpData;
972
973 if (0 != Exiv2::XmpParser::decode(xmpData, xmpPacket))
974 return FALSE;
975
976 if (! xmpData.empty()) {
977 GHashTable *table = create_metadata_hash ();
978
979 Exiv2::XmpData::iterator end = xmpData.end();
980 for (Exiv2::XmpData::iterator md = xmpData.begin(); md != end; ++md) {
981 stringstream raw_value;
982 raw_value << md->value();
983
984 stringstream description;
985 if (! md->tagLabel().empty())
986 description << md->tagLabel();
987 else
988 description << md->groupName() << "." << md->tagName();
989
990 GthMetadata *metadata;
991 metadata = create_metadata (md->key().c_str(),
992 description.str().c_str(),
993 md->print().c_str(),
994 raw_value.str().c_str(),
995 "Xmp::Sidecar",
996 md->typeName());
997 if (metadata != NULL) {
998 if ((g_strcmp0 (md->typeName(), "XmpBag") == 0)
999 || (g_strcmp0 (md->typeName(), "XmpSeq") == 0))
1000 {
1001 add_string_list_to_metadata (metadata, *md);
1002 }
1003
1004 add_metadata_to_hash (table, metadata);
1005 g_object_unref (metadata);
1006 }
1007 }
1008
1009 set_file_info_from_hash (info, table);
1010 g_hash_table_unref (table);
1011 }
1012 Exiv2::XmpParser::terminate();
1013
1014 set_attributes_from_tagsets (info, update_general_attributes);
1015 }
1016 catch (Exiv2::AnyError& e) {
1017 std::cerr << "Caught Exiv2 exception '" << e << "'\n";
1018 return FALSE;
1019 }
1020
1021 return TRUE;
1022 }
1023
1024
1025 static void
mandatory_int(Exiv2::ExifData & checkdata,const char * tag,int value)1026 mandatory_int (Exiv2::ExifData &checkdata,
1027 const char *tag,
1028 int value)
1029 {
1030 Exiv2::ExifKey key = Exiv2::ExifKey(tag);
1031 if (checkdata.findKey(key) == checkdata.end())
1032 checkdata[tag] = value;
1033 }
1034
1035
1036 static void
mandatory_string(Exiv2::ExifData & checkdata,const char * tag,const char * value)1037 mandatory_string (Exiv2::ExifData &checkdata,
1038 const char *tag,
1039 const char *value)
1040 {
1041 Exiv2::ExifKey key = Exiv2::ExifKey(tag);
1042 if (checkdata.findKey(key) == checkdata.end())
1043 checkdata[tag] = value;
1044 }
1045
1046
1047 const char *
gth_main_get_metadata_type(gpointer metadata,const char * attribute)1048 gth_main_get_metadata_type (gpointer metadata,
1049 const char *attribute)
1050 {
1051 const char *value_type = NULL;
1052 GthMetadataInfo *metadatum_info;
1053
1054 if (GTH_IS_METADATA (metadata)) {
1055 value_type = gth_metadata_get_value_type (GTH_METADATA (metadata));
1056 if ((g_strcmp0 (value_type, "Undefined") == 0) || (g_strcmp0 (value_type, "") == 0))
1057 value_type = NULL;
1058
1059 if (value_type != NULL)
1060 return value_type;
1061 }
1062
1063 metadatum_info = gth_main_get_metadata_info (attribute);
1064 if (metadatum_info != NULL)
1065 value_type = metadatum_info->type;
1066
1067 return value_type;
1068 }
1069
1070
1071 static void
dump_exif_data(Exiv2::ExifData & exifData,const char * prefix)1072 dump_exif_data (Exiv2::ExifData &exifData,
1073 const char *prefix)
1074 {
1075 std::cout << prefix << "\n";
1076
1077 try {
1078 if (exifData.empty()) {
1079 #if EXIV2_TEST_VERSION(0, 27, 0)
1080 throw Exiv2::Error(Exiv2::kerErrorMessage, " No Exif data found in the file");
1081 #else
1082 throw Exiv2::Error(1, " No Exif data found in the file");
1083 #endif
1084 }
1085 Exiv2::ExifData::const_iterator end = exifData.end();
1086 for (Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) {
1087 const char* tn = i->typeName();
1088 std::cout << std::setw(44) << std::setfill(' ') << std::left
1089 << i->key() << " "
1090 << "0x" << std::setw(4) << std::setfill('0') << std::right
1091 << std::hex << i->tag() << " "
1092 << std::setw(9) << std::setfill(' ') << std::left
1093 << (tn ? tn : "Unknown") << " "
1094 << std::dec << std::setw(3)
1095 << std::setfill(' ') << std::right
1096 << i->count() << " "
1097 << std::dec << i->value()
1098 << "\n";
1099 }
1100 std::cout << "\n";
1101 }
1102 catch (Exiv2::Error& e) {
1103 std::cout << "Caught Exiv2 exception '" << e.what() << "'\n";
1104 return;
1105 }
1106 }
1107
1108
1109 static Exiv2::DataBuf
exiv2_write_metadata_private(Exiv2::Image::AutoPtr image,GFileInfo * info,GthImage * image_data)1110 exiv2_write_metadata_private (Exiv2::Image::AutoPtr image,
1111 GFileInfo *info,
1112 GthImage *image_data)
1113 {
1114 static char *software_name = NULL;
1115 char **attributes;
1116 int i;
1117
1118 image->clearMetadata();
1119
1120 // EXIF Data
1121
1122 Exiv2::ExifData ed;
1123 attributes = g_file_info_list_attributes (info, "Exif");
1124 for (i = 0; attributes[i] != NULL; i++) {
1125 GthMetadata *metadatum;
1126 char *key;
1127
1128 metadatum = (GthMetadata *) g_file_info_get_attribute_object (info, attributes[i]);
1129 key = exiv2_key_from_attribute (attributes[i]);
1130
1131 try {
1132 /* If the metadatum has no value yet, a new empty value
1133 * is created. The type is taken from Exiv2's tag
1134 * lookup tables. If the tag is not found in the table,
1135 * the type defaults to ASCII.
1136 * We always create the metadatum explicitly if the
1137 * type is available to avoid type errors.
1138 * See bug #610389 for more details. The original
1139 * explanation is here:
1140 * http://uk.groups.yahoo.com/group/exiv2/message/1472
1141 */
1142
1143 const char *raw_value = gth_metadata_get_raw (metadatum);
1144 const char *value_type = gth_main_get_metadata_type (metadatum, attributes[i]);
1145
1146 if ((raw_value != NULL) && (strcmp (raw_value, "") != 0) && (value_type != NULL)) {
1147 Exiv2::Value::AutoPtr value = Exiv2::Value::create (Exiv2::TypeInfo::typeId (value_type));
1148 value->read (raw_value);
1149 Exiv2::ExifKey exif_key(key);
1150 ed.add (exif_key, value.get());
1151 }
1152 }
1153 catch (Exiv2::AnyError& e) {
1154 /* we don't care about invalid key errors */
1155 g_warning ("%s", e.what());
1156 }
1157
1158 g_free (key);
1159 }
1160 g_strfreev (attributes);
1161
1162 // Mandatory tags - add if not already present
1163
1164 mandatory_int (ed, "Exif.Image.XResolution", 72);
1165 mandatory_int (ed, "Exif.Image.YResolution", 72);
1166 mandatory_int (ed, "Exif.Image.ResolutionUnit", 2);
1167 mandatory_int (ed, "Exif.Image.YCbCrPositioning", 1);
1168 mandatory_int (ed, "Exif.Photo.ColorSpace", 1);
1169 mandatory_string (ed, "Exif.Photo.ExifVersion", "48 50 50 49");
1170 mandatory_string (ed, "Exif.Photo.ComponentsConfiguration", "1 2 3 0");
1171 mandatory_string (ed, "Exif.Photo.FlashpixVersion", "48 49 48 48");
1172
1173 // Overwrite the software tag if the image content was modified
1174
1175 if (g_file_info_get_attribute_boolean (info, "gth::file::image-changed")) {
1176 if (software_name == NULL)
1177 software_name = g_strconcat (g_get_application_name (), " ", PACKAGE_VERSION, NULL);
1178 ed["Exif.Image.ProcessingSoftware"] = software_name;
1179 }
1180
1181 // Update the dimension tags with actual image values
1182
1183 cairo_surface_t *surface = NULL;
1184 int width = 0;
1185 int height = 0;
1186
1187 if (image_data != NULL)
1188 surface = gth_image_get_cairo_surface (image_data);
1189
1190 if (surface != NULL) {
1191 width = cairo_image_surface_get_width (surface);
1192 if (width > 0) {
1193 ed["Exif.Photo.PixelXDimension"] = width;
1194 ed["Exif.Image.ImageWidth"] = width;
1195 }
1196
1197 height = cairo_image_surface_get_height (surface);
1198 if (height > 0) {
1199 ed["Exif.Photo.PixelYDimension"] = height;
1200 ed["Exif.Image.ImageLength"] = height;
1201 }
1202
1203 ed["Exif.Image.Orientation"] = 1;
1204 }
1205
1206 // Update the thumbnail
1207
1208 Exiv2::ExifThumb thumb(ed);
1209 if ((surface != NULL) && (width > 0) && (height > 0)) {
1210 cairo_surface_t *thumbnail;
1211 GthImage *thumbnail_data;
1212 char *buffer;
1213 gsize buffer_size;
1214
1215 scale_keeping_ratio (&width, &height, 128, 128, FALSE);
1216 thumbnail = _cairo_image_surface_scale (surface, width, height, SCALE_FILTER_BEST, NULL);
1217 thumbnail_data = gth_image_new_for_surface (thumbnail);
1218 if (gth_image_save_to_buffer (thumbnail_data,
1219 "image/jpeg",
1220 NULL,
1221 &buffer,
1222 &buffer_size,
1223 NULL,
1224 NULL))
1225 {
1226 thumb.setJpegThumbnail ((Exiv2::byte *) buffer, buffer_size);
1227 ed["Exif.Thumbnail.XResolution"] = 72;
1228 ed["Exif.Thumbnail.YResolution"] = 72;
1229 ed["Exif.Thumbnail.ResolutionUnit"] = 2;
1230 g_free (buffer);
1231 }
1232 else
1233 thumb.erase();
1234
1235 g_object_unref (thumbnail_data);
1236 cairo_surface_destroy (thumbnail);
1237 }
1238 else
1239 thumb.erase();
1240
1241 if (surface != NULL)
1242 cairo_surface_destroy (surface);
1243
1244 // Update the DateTime tag
1245
1246 if (g_file_info_get_attribute_object (info, "Exif::Image::DateTime") == NULL) {
1247 GTimeVal current_time;
1248 g_get_current_time (¤t_time);
1249 char *date_time = _g_time_val_to_exif_date (¤t_time);
1250 ed["Exif.Image.DateTime"] = date_time;
1251 g_free (date_time);
1252 }
1253 ed.sortByKey();
1254
1255 // IPTC Data
1256
1257 Exiv2::IptcData id;
1258 attributes = g_file_info_list_attributes (info, "Iptc");
1259 for (i = 0; attributes[i] != NULL; i++) {
1260 gpointer metadatum = (GthMetadata *) g_file_info_get_attribute_object (info, attributes[i]);
1261 char *key = exiv2_key_from_attribute (attributes[i]);
1262
1263 try {
1264 const char *value_type;
1265
1266 value_type = gth_main_get_metadata_type (metadatum, attributes[i]);
1267 if (value_type != NULL) {
1268 /* See the exif data code above for an explanation. */
1269 Exiv2::Value::AutoPtr value = Exiv2::Value::create (Exiv2::TypeInfo::typeId (value_type));
1270 Exiv2::IptcKey iptc_key(key);
1271
1272 const char *raw_value;
1273
1274 switch (gth_metadata_get_data_type (GTH_METADATA (metadatum))) {
1275 case GTH_METADATA_TYPE_STRING:
1276 raw_value = gth_metadata_get_raw (GTH_METADATA (metadatum));
1277 if ((raw_value != NULL) && (strcmp (raw_value, "") != 0)) {
1278 value->read (raw_value);
1279 id.add (iptc_key, value.get());
1280 }
1281 break;
1282
1283 case GTH_METADATA_TYPE_STRING_LIST:
1284 GthStringList *string_list = gth_metadata_get_string_list (GTH_METADATA (metadatum));
1285 for (GList *scan = gth_string_list_get_list (string_list); scan; scan = scan->next) {
1286 char *single_value = (char *) scan->data;
1287
1288 value->read (single_value);
1289 id.add (iptc_key, value.get());
1290 }
1291 break;
1292 }
1293 }
1294 }
1295 catch (Exiv2::AnyError& e) {
1296 /* we don't care about invalid key errors */
1297 g_warning ("%s", e.what());
1298 }
1299
1300 g_free (key);
1301 }
1302 id.sortByKey();
1303 g_strfreev (attributes);
1304
1305 // XMP Data
1306
1307 Exiv2::XmpData xd;
1308 attributes = g_file_info_list_attributes (info, "Xmp");
1309 for (i = 0; attributes[i] != NULL; i++) {
1310 gpointer metadatum = (GthMetadata *) g_file_info_get_attribute_object (info, attributes[i]);
1311 char *key = exiv2_key_from_attribute (attributes[i]);
1312
1313 try {
1314 const char *value_type;
1315
1316 value_type = gth_main_get_metadata_type (metadatum, attributes[i]);
1317 if (value_type != NULL) {
1318 /* See the exif data code above for an explanation. */
1319 Exiv2::Value::AutoPtr value = Exiv2::Value::create (Exiv2::TypeInfo::typeId (value_type));
1320 Exiv2::XmpKey xmp_key(key);
1321
1322 const char *raw_value;
1323
1324 switch (gth_metadata_get_data_type (GTH_METADATA (metadatum))) {
1325 case GTH_METADATA_TYPE_STRING:
1326 raw_value = gth_metadata_get_raw (GTH_METADATA (metadatum));
1327 if ((raw_value != NULL) && (strcmp (raw_value, "") != 0)) {
1328 value->read (raw_value);
1329 xd.add (xmp_key, value.get());
1330 }
1331 break;
1332
1333 case GTH_METADATA_TYPE_STRING_LIST:
1334 GthStringList *string_list = gth_metadata_get_string_list (GTH_METADATA (metadatum));
1335 for (GList *scan = gth_string_list_get_list (string_list); scan; scan = scan->next) {
1336 char *single_value = (char *) scan->data;
1337
1338 value->read (single_value);
1339 xd.add (xmp_key, value.get());
1340 }
1341 break;
1342 }
1343 }
1344 }
1345 catch (Exiv2::AnyError& e) {
1346 /* we don't care about invalid key errors */
1347 g_warning ("%s", e.what());
1348 }
1349
1350 g_free (key);
1351 }
1352 xd.sortByKey();
1353 g_strfreev (attributes);
1354
1355 try {
1356 image->setExifData(ed);
1357 image->setIptcData(id);
1358 image->setXmpData(xd);
1359 image->writeMetadata();
1360 }
1361 catch (Exiv2::AnyError& e) {
1362 g_warning ("%s", e.what());
1363 }
1364
1365 Exiv2::BasicIo &io = image->io();
1366 io.open();
1367
1368 return io.read(io.size());
1369 }
1370
1371
1372 extern "C"
1373 gboolean
exiv2_supports_writes(const char * mime_type)1374 exiv2_supports_writes (const char *mime_type)
1375 {
1376 return (g_content_type_equals (mime_type, "image/jpeg")
1377 || g_content_type_equals (mime_type, "image/tiff")
1378 || g_content_type_equals (mime_type, "image/png"));
1379 }
1380
1381
1382 extern "C"
1383 gboolean
exiv2_write_metadata(GthImageSaveData * data)1384 exiv2_write_metadata (GthImageSaveData *data)
1385 {
1386 if (exiv2_supports_writes (data->mime_type) && (data->file_data != NULL)) {
1387 try {
1388 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) data->buffer, data->buffer_size);
1389 g_assert (image.get() != 0);
1390
1391 Exiv2::DataBuf buf = exiv2_write_metadata_private (image, data->file_data->info, data->image);
1392
1393 g_free (data->buffer);
1394 data->buffer = g_memdup (buf.pData_, buf.size_);
1395 data->buffer_size = buf.size_;
1396 }
1397 catch (Exiv2::AnyError& e) {
1398 if (data->error != NULL)
1399 *data->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, e.what());
1400 g_warning ("%s\n", e.what());
1401 return FALSE;
1402 }
1403 }
1404
1405 return TRUE;
1406 }
1407
1408
1409 extern "C"
1410 gboolean
exiv2_write_metadata_to_buffer(void ** buffer,gsize * buffer_size,GFileInfo * info,GthImage * image_data,GError ** error)1411 exiv2_write_metadata_to_buffer (void **buffer,
1412 gsize *buffer_size,
1413 GFileInfo *info,
1414 GthImage *image_data,
1415 GError **error)
1416 {
1417 try {
1418 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) *buffer, *buffer_size);
1419 g_assert (image.get() != 0);
1420
1421 Exiv2::DataBuf buf = exiv2_write_metadata_private (image, info, image_data);
1422
1423 g_free (*buffer);
1424 *buffer = g_memdup (buf.pData_, buf.size_);
1425 *buffer_size = buf.size_;
1426 }
1427 catch (Exiv2::AnyError& e) {
1428 if (error != NULL)
1429 *error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, e.what());
1430 return FALSE;
1431 }
1432
1433 return TRUE;
1434 }
1435
1436
1437 extern "C"
1438 gboolean
exiv2_clear_metadata(void ** buffer,gsize * buffer_size,GError ** error)1439 exiv2_clear_metadata (void **buffer,
1440 gsize *buffer_size,
1441 GError **error)
1442 {
1443 try {
1444 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) *buffer, *buffer_size);
1445
1446 if (image.get() == 0) {
1447 if (error != NULL)
1448 *error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
1449 return FALSE;
1450 }
1451
1452 try {
1453 image->clearMetadata();
1454 image->writeMetadata();
1455 }
1456 catch (Exiv2::AnyError& e) {
1457 g_warning ("%s", e.what());
1458 }
1459
1460 Exiv2::BasicIo &io = image->io();
1461 io.open();
1462 Exiv2::DataBuf buf = io.read(io.size());
1463
1464 g_free (*buffer);
1465 *buffer = g_memdup (buf.pData_, buf.size_);
1466 *buffer_size = buf.size_;
1467 }
1468 catch (Exiv2::AnyError& e) {
1469 if (error != NULL)
1470 *error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, e.what());
1471 return FALSE;
1472 }
1473
1474 return TRUE;
1475 }
1476
1477
1478 #define MAX_RATIO_ERROR_TOLERANCE 0.01
1479
1480
1481 GdkPixbuf *
exiv2_generate_thumbnail(const char * uri,const char * mime_type,int requested_size)1482 exiv2_generate_thumbnail (const char *uri,
1483 const char *mime_type,
1484 int requested_size)
1485 {
1486 GdkPixbuf *pixbuf = NULL;
1487
1488 if (! _g_content_type_is_a (mime_type, "image/jpeg")
1489 && ! _g_content_type_is_a (mime_type, "image/tiff"))
1490 {
1491 return NULL;
1492 }
1493
1494 try {
1495 char *path;
1496
1497 path = g_filename_from_uri (uri, NULL, NULL);
1498 if (path == NULL)
1499 return NULL;
1500
1501 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open (path);
1502 image->readMetadata ();
1503 Exiv2::ExifThumbC exifThumb (image->exifData ());
1504 Exiv2::DataBuf thumb = exifThumb.copy ();
1505
1506 g_free (path);
1507
1508 if (thumb.pData_ == NULL)
1509 return NULL;
1510
1511 Exiv2::ExifData &ed = image->exifData();
1512
1513 long orientation = (ed["Exif.Image.Orientation"].count() > 0) ? ed["Exif.Image.Orientation"].toLong() : 1;
1514 long image_width = (ed["Exif.Photo.PixelXDimension"].count() > 0) ? ed["Exif.Photo.PixelXDimension"].toLong() : -1;
1515 long image_height = (ed["Exif.Photo.PixelYDimension"].count() > 0) ? ed["Exif.Photo.PixelYDimension"].toLong() : -1;
1516
1517 if ((orientation != 1) || (image_width <= 0) || (image_height <= 0))
1518 return NULL;
1519
1520 GInputStream *stream = g_memory_input_stream_new_from_data (thumb.pData_, thumb.size_, NULL);
1521 pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
1522 g_object_unref (stream);
1523
1524 if (pixbuf == NULL)
1525 return NULL;
1526
1527 /* Heuristic to find out-of-date thumbnails: the thumbnail and image aspect ratios must be equal */
1528
1529 int pixbuf_width = gdk_pixbuf_get_width (pixbuf);
1530 int pixbuf_height = gdk_pixbuf_get_height (pixbuf);
1531 double image_ratio = (((double) image_width) / image_height);
1532 double thumbnail_ratio = (((double) pixbuf_width) / pixbuf_height);
1533 double ratio_delta = (image_ratio > thumbnail_ratio) ? (image_ratio - thumbnail_ratio) : (thumbnail_ratio - image_ratio);
1534
1535 if ((ratio_delta > MAX_RATIO_ERROR_TOLERANCE) /* the tolerance is used because the reduced image can have a slightly different ratio due to rounding errors */
1536 || (MAX (pixbuf_width, pixbuf_height) < requested_size)) /* ignore the embedded image if it's too small compared to the requested size */
1537 {
1538 g_object_unref (pixbuf);
1539 return NULL;
1540 }
1541
1542 /* Scale the pixbuf to perfectly fit the requested size */
1543
1544 if (scale_keeping_ratio (&pixbuf_width,
1545 &pixbuf_height,
1546 requested_size,
1547 requested_size,
1548 TRUE))
1549 {
1550 GdkPixbuf *tmp = pixbuf;
1551 pixbuf = _gdk_pixbuf_scale_simple_safe (tmp, pixbuf_width, pixbuf_height, GDK_INTERP_BILINEAR);
1552 g_object_unref (tmp);
1553 }
1554
1555 /* Save the original image size in the pixbuf options */
1556
1557 char *s = g_strdup_printf ("%ld", image_width);
1558 gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Width", s);
1559 g_object_set_data (G_OBJECT (pixbuf), "gnome-original-width", GINT_TO_POINTER ((int) image_width));
1560 g_free (s);
1561
1562 s = g_strdup_printf ("%ld", image_height);
1563 gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Height", s);
1564 g_object_set_data (G_OBJECT (pixbuf), "gnome-original-height", GINT_TO_POINTER ((int) image_height));
1565 g_free (s);
1566
1567 /* Set the orientation option to correctly rotate the thumbnail
1568 * in gnome_desktop_thumbnail_factory_generate_thumbnail() */
1569
1570 char *orientation_s = g_strdup_printf ("%ld", orientation);
1571 gdk_pixbuf_set_option (pixbuf, "orientation", orientation_s);
1572 g_free (orientation_s);
1573 }
1574 catch (Exiv2::AnyError& e) {
1575 }
1576
1577 return pixbuf;
1578 }
1579