/* -*- Mode: CPP; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* GThumb
*
* Copyright (C) 2008-2009 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include
#include
#define GDK_PIXBUF_ENABLE_BACKEND
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if EXIV2_TEST_VERSION(0, 27, 0)
#include
#else
#include
#endif
#include
#include "exiv2-utils.h"
using namespace std;
#define INVALID_VALUE N_("(invalid value)")
/* Some bits of information may be contained in more than one metadata tag.
The arrays below define the valid tags for a particular piece of
information, in decreasing order of preference (best one first) */
const char *_DATE_TAG_NAMES[] = {
"Exif::Image::DateTime",
"Xmp::exif::DateTime",
"Exif::Photo::DateTimeOriginal",
"Xmp::exif::DateTimeOriginal",
"Exif::Photo::DateTimeDigitized",
"Xmp::exif::DateTimeDigitized",
"Xmp::xmp::CreateDate",
"Xmp::photoshop::DateCreated",
"Xmp::xmp::ModifyDate",
"Xmp::xmp::MetadataDate",
NULL
};
const char *_LAST_DATE_TAG_NAMES[] = {
"Exif::Image::DateTime",
"Xmp::exif::DateTime",
"Xmp::xmp::ModifyDate",
"Xmp::xmp::MetadataDate",
NULL
};
const char *_ORIGINAL_DATE_TAG_NAMES[] = {
"Exif::Photo::DateTimeOriginal",
"Xmp::exif::DateTimeOriginal",
"Exif::Photo::DateTimeDigitized",
"Xmp::exif::DateTimeDigitized",
"Xmp::xmp::CreateDate",
"Xmp::photoshop::DateCreated",
NULL
};
const char *_EXPOSURE_TIME_TAG_NAMES[] = {
"Exif::Photo::ExposureTime",
"Xmp::exif::ExposureTime",
"Exif::Photo::ShutterSpeedValue",
"Xmp::exif::ShutterSpeedValue",
NULL
};
const char *_EXPOSURE_MODE_TAG_NAMES[] = {
"Exif::Photo::ExposureMode",
"Xmp::exif::ExposureMode",
NULL
};
const char *_ISOSPEED_TAG_NAMES[] = {
"Exif::Photo::ISOSpeedRatings",
"Xmp::exif::ISOSpeedRatings",
NULL
};
const char *_APERTURE_TAG_NAMES[] = {
"Exif::Photo::ApertureValue",
"Xmp::exif::ApertureValue",
"Exif::Photo::FNumber",
"Xmp::exif::FNumber",
NULL
};
const char *_FOCAL_LENGTH_TAG_NAMES[] = {
"Exif::Photo::FocalLength",
"Xmp::exif::FocalLength",
NULL
};
const char *_SHUTTER_SPEED_TAG_NAMES[] = {
"Exif::Photo::ShutterSpeedValue",
"Xmp::exif::ShutterSpeedValue",
NULL
};
const char *_MAKE_TAG_NAMES[] = {
"Exif::Image::Make",
"Xmp::tiff::Make",
NULL
};
const char *_MODEL_TAG_NAMES[] = {
"Exif::Image::Model",
"Xmp::tiff::Model",
NULL
};
const char *_FLASH_TAG_NAMES[] = {
"Exif::Photo::Flash",
"Xmp::exif::Flash",
NULL
};
const char *_ORIENTATION_TAG_NAMES[] = {
"Exif::Image::Orientation",
"Xmp::tiff::Orientation",
NULL
};
const char *_DESCRIPTION_TAG_NAMES[] = {
"Iptc::Application2::Caption",
"Xmp::dc::description",
"Exif::Photo::UserComment",
"Exif::Image::ImageDescription",
"Xmp::tiff::ImageDescription",
"Iptc::Application2::Headline",
NULL
};
const char *_TITLE_TAG_NAMES[] = {
"Xmp::dc::title",
NULL
};
const char *_LOCATION_TAG_NAMES[] = {
"Xmp::iptc::Location",
"Iptc::Application2::LocationName",
NULL
};
const char *_KEYWORDS_TAG_NAMES[] = {
"Iptc::Application2::Keywords",
"Xmp::iptc::Keywords",
"Xmp::dc::subject",
NULL
};
const char *_RATING_TAG_NAMES[] = {
"Xmp::xmp::Rating",
NULL
};
const char *_AUTHOR_TAG_NAMES[] = {
"Exif::Image::Artist",
"Iptc::Application2::Byline",
"Xmp::dc::creator",
"Xmp::xmpDM::artist",
"Xmp::tiff::Artist",
"Xmp::plus::ImageCreator",
"Xmp::plus::ImageCreatorName",
NULL
};
const char *_COPYRIGHT_TAG_NAMES[] = {
"Exif::Image::Copyright",
"Iptc::Application2::Copyright",
"Xmp::dc::rights",
"Xmp::xmpDM::copyright",
"Xmp::tiff::Copyright",
"Xmp::plus::CopyrightOwner",
"Xmp::plus::CopyrightOwnerName",
NULL
};
/* Some evil camera fill in the ImageDescription or UserComment fields
with useless fluff. Try to filter these out, so they do not show up
as comments */
const char *stupid_comment_filter[] = {
"OLYMPUS DIGITAL CAMERA",
"SONY DSC",
"KONICA MINOLTA DIGITAL CAMERA",
"MINOLTA DIGITAL CAMERA",
NULL };
inline static char *
exiv2_key_from_attribute (const char *attribute)
{
return _g_utf8_replace_str (attribute, "::", ".");
}
inline static char *
exiv2_key_to_attribute (const char *key)
{
return _g_utf8_replace_str (key, ".", "::");
}
static gboolean
attribute_is_date (const char *key)
{
int i;
for (i = 0; _DATE_TAG_NAMES[i] != NULL; i++) {
if (strcmp (_DATE_TAG_NAMES[i], key) == 0)
return TRUE;
}
return FALSE;
}
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)
{
char *formatted_value_utf8;
char *attribute;
GthMetadataInfo *metadata_info;
GthMetadata *metadata;
char *description_utf8;
formatted_value_utf8 = _g_utf8_from_any (formatted_value);
if (_g_utf8_all_spaces (formatted_value_utf8))
return NULL;
description_utf8 = _g_utf8_from_any (description);
attribute = exiv2_key_to_attribute (key);
if (attribute_is_date (attribute)) {
GTimeVal time_;
g_free (formatted_value_utf8);
formatted_value_utf8 = NULL;
if (_g_time_val_from_exif_date (raw_value, &time_))
formatted_value_utf8 = _g_time_val_strftime (&time_, "%x %X");
else
formatted_value_utf8 = g_locale_to_utf8 (formatted_value, -1, NULL, NULL, NULL);
}
else {
char *tmp = _g_utf8_remove_string_properties (formatted_value_utf8);
g_free (formatted_value_utf8);
formatted_value_utf8 = tmp;
}
if (formatted_value_utf8 == NULL)
formatted_value_utf8 = g_strdup (INVALID_VALUE);
metadata_info = gth_main_get_metadata_info (attribute);
if ((metadata_info == NULL) && (category != NULL)) {
GthMetadataInfo info;
info.id = attribute;
info.type = (type_name != NULL) ? g_strdup (type_name) : NULL;
info.display_name = description_utf8;
info.category = category;
info.sort_order = 500;
info.flags = GTH_METADATA_ALLOW_IN_PROPERTIES_VIEW;
metadata_info = gth_main_register_metadata_info (&info);
}
if ((metadata_info != NULL) && (metadata_info->type == NULL) && (type_name != NULL))
metadata_info->type = g_strdup (type_name);
if ((metadata_info != NULL) && (metadata_info->display_name == NULL) && (description_utf8 != NULL))
metadata_info->display_name = g_strdup (description_utf8);
metadata = gth_metadata_new ();
g_object_set (metadata,
"id", key,
"description", description_utf8,
"formatted", formatted_value_utf8,
"raw", raw_value,
"value-type", type_name,
NULL);
g_free (formatted_value_utf8);
g_free (description_utf8);
g_free (attribute);
return metadata;
}
static void
add_string_list_to_metadata (GthMetadata *metadata,
const Exiv2::Metadatum &value)
{
GList *list;
GthStringList *string_list;
list = NULL;
for (int i = 0; i < value.count(); i++)
list = g_list_prepend (list, g_strdup (value.toString(i).c_str()));
string_list = gth_string_list_new (g_list_reverse (list));
g_object_set (metadata, "string-list", string_list, NULL);
g_object_unref (string_list);
_g_string_list_free (list);
}
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)
{
char *attribute;
GthMetadata *metadata;
attribute = exiv2_key_to_attribute (key);
metadata = create_metadata (key, description, formatted_value, raw_value, category, type_name);
if (metadata != NULL) {
g_file_info_set_attribute_object (info, attribute, G_OBJECT (metadata));
g_object_unref (metadata);
}
g_free (attribute);
}
GHashTable *
create_metadata_hash (void)
{
return g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
g_object_unref);
}
static void
add_metadata_to_hash (GHashTable *table,
GthMetadata *metadata)
{
char *key;
gpointer object;
if (metadata == NULL)
return;
key = exiv2_key_to_attribute (gth_metadata_get_id (metadata));
object = g_hash_table_lookup (table, key);
if (object != NULL) {
GthStringList *string_list;
GList *list;
string_list = NULL;
switch (gth_metadata_get_data_type (GTH_METADATA (object))) {
case GTH_METADATA_TYPE_STRING:
string_list = gth_string_list_new (NULL);
list = g_list_append (NULL, g_strdup (gth_metadata_get_formatted (GTH_METADATA (object))));
gth_string_list_set_list (string_list, list);
break;
case GTH_METADATA_TYPE_STRING_LIST:
string_list = (GthStringList *) g_object_ref (gth_metadata_get_string_list (GTH_METADATA (object)));
break;
}
if (string_list == NULL) {
g_hash_table_insert (table,
g_strdup (key),
g_object_ref (metadata));
return;
}
switch (gth_metadata_get_data_type (metadata)) {
case GTH_METADATA_TYPE_STRING:
list = gth_string_list_get_list (string_list);
list = g_list_append (list, g_strdup (gth_metadata_get_formatted (metadata)));
gth_string_list_set_list (string_list, list);
break;
case GTH_METADATA_TYPE_STRING_LIST:
gth_string_list_concat (string_list, gth_metadata_get_string_list (metadata));
break;
}
g_object_set (metadata, "string-list", string_list, NULL);
g_hash_table_replace (table,
g_strdup (key),
g_object_ref (metadata));
g_object_unref (string_list);
}
else
g_hash_table_insert (table,
g_strdup (key),
g_object_ref (metadata));
g_free (key);
}
static void
set_file_info_from_hash (GFileInfo *info,
GHashTable *table)
{
GHashTableIter iter;
gpointer key;
gpointer value;
g_hash_table_iter_init (&iter, table);
while (g_hash_table_iter_next (&iter, &key, &value))
g_file_info_set_attribute_object (info, (char *)key, G_OBJECT (value));
}
static void
set_attribute_from_metadata (GFileInfo *info,
const char *attribute,
GObject *metadata)
{
char *description;
char *formatted_value;
char *raw_value;
char *type_name;
if (metadata == NULL)
return;
g_object_get (metadata,
"description", &description,
"formatted", &formatted_value,
"raw", &raw_value,
"value-type", &type_name,
NULL);
set_file_info (info,
attribute,
description,
formatted_value,
raw_value,
NULL,
type_name);
g_free (description);
g_free (formatted_value);
g_free (raw_value);
g_free (type_name);
}
static GObject *
get_attribute_from_tagset (GFileInfo *info,
const char *tagset[])
{
GObject *metadata;
int i;
metadata = NULL;
for (i = 0; tagset[i] != NULL; i++) {
metadata = g_file_info_get_attribute_object (info, tagset[i]);
if (metadata != NULL)
return metadata;
}
return NULL;
}
static void
set_attribute_from_tagset (GFileInfo *info,
const char *attribute,
const char *tagset[])
{
GObject *metadata;
metadata = get_attribute_from_tagset (info, tagset);
if (metadata != NULL)
set_attribute_from_metadata (info, attribute, metadata);
}
static void
set_string_list_attribute_from_tagset (GFileInfo *info,
const char *attribute,
const char *tagset[])
{
GObject *metadata;
int i;
metadata = NULL;
for (i = 0; tagset[i] != NULL; i++) {
metadata = g_file_info_get_attribute_object (info, tagset[i]);
if (metadata != NULL)
break;
}
if (metadata == NULL)
return;
if (GTH_IS_METADATA (metadata) && (gth_metadata_get_data_type (GTH_METADATA (metadata)) != GTH_METADATA_TYPE_STRING_LIST)) {
char *raw;
char *utf8_raw;
char **keywords;
GthStringList *string_list;
g_object_get (metadata, "raw", &raw, NULL);
utf8_raw = _g_utf8_try_from_any (raw);
if (utf8_raw == NULL)
return;
keywords = g_strsplit (utf8_raw, ",", -1);
string_list = gth_string_list_new_from_strv (keywords);
metadata = (GObject *) gth_metadata_new_for_string_list (string_list);
g_file_info_set_attribute_object (info, attribute, metadata);
g_object_unref (metadata);
g_object_unref (string_list);
g_strfreev (keywords);
g_free (raw);
g_free (utf8_raw);
}
else
g_file_info_set_attribute_object (info, attribute, metadata);
}
static void
clear_studip_comments_from_tagset (GFileInfo *info,
const char *tagset[])
{
int i;
for (i = 0; tagset[i] != NULL; i++) {
GObject *metadata;
const char *value;
int j;
metadata = g_file_info_get_attribute_object (info, tagset[i]);
if ((metadata == NULL) || ! GTH_IS_METADATA (metadata))
continue;
value = gth_metadata_get_formatted (GTH_METADATA (metadata));
for (j = 0; stupid_comment_filter[j] != NULL; j++) {
if (strstr (value, stupid_comment_filter[j]) == value) {
g_file_info_remove_attribute (info, tagset[i]);
break;
}
}
}
}
extern "C"
void
exiv2_update_general_attributes (GFileInfo *info)
{
set_attribute_from_tagset (info, "general::datetime", _ORIGINAL_DATE_TAG_NAMES);
set_attribute_from_tagset (info, "general::description", _DESCRIPTION_TAG_NAMES);
set_attribute_from_tagset (info, "general::title", _TITLE_TAG_NAMES);
/* if iptc::caption and iptc::headline are different use iptc::headline
* to set general::title, if not already set. */
if (g_file_info_get_attribute_object (info, "general::title") == NULL) {
GObject *iptc_caption;
GObject *iptc_headline;
iptc_caption = g_file_info_get_attribute_object (info, "Iptc::Application2::Caption");
iptc_headline = g_file_info_get_attribute_object (info, "Iptc::Application2::Headline");
if ((iptc_caption != NULL)
&& (iptc_headline != NULL)
&& (g_strcmp0 (gth_metadata_get_raw (GTH_METADATA (iptc_caption)),
gth_metadata_get_raw (GTH_METADATA (iptc_headline))) != 0))
{
set_attribute_from_metadata (info, "general::title", iptc_headline);
}
}
set_attribute_from_tagset (info, "general::location", _LOCATION_TAG_NAMES);
set_string_list_attribute_from_tagset (info, "general::tags", _KEYWORDS_TAG_NAMES);
set_attribute_from_tagset (info, "general::rating", _RATING_TAG_NAMES);
}
#define EXPOSURE_SEPARATOR " ยท "
static void
set_attributes_from_tagsets (GFileInfo *info,
gboolean update_general_attributes)
{
clear_studip_comments_from_tagset (info, _DESCRIPTION_TAG_NAMES);
clear_studip_comments_from_tagset (info, _TITLE_TAG_NAMES);
if (update_general_attributes)
exiv2_update_general_attributes (info);
set_attribute_from_tagset (info, "Embedded::Photo::DateTimeOriginal", _ORIGINAL_DATE_TAG_NAMES);
set_attribute_from_tagset (info, "Embedded::Image::Orientation", _ORIENTATION_TAG_NAMES);
set_attribute_from_tagset (info, "Embedded::Photo::Aperture", _APERTURE_TAG_NAMES);
set_attribute_from_tagset (info, "Embedded::Photo::ISOSpeed", _ISOSPEED_TAG_NAMES);
set_attribute_from_tagset (info, "Embedded::Photo::ExposureTime", _EXPOSURE_TIME_TAG_NAMES);
set_attribute_from_tagset (info, "Embedded::Photo::ShutterSpeed", _SHUTTER_SPEED_TAG_NAMES);
set_attribute_from_tagset (info, "Embedded::Photo::FocalLength", _FOCAL_LENGTH_TAG_NAMES);
set_attribute_from_tagset (info, "Embedded::Photo::Flash", _FLASH_TAG_NAMES);
set_attribute_from_tagset (info, "Embedded::Photo::CameraModel", _MODEL_TAG_NAMES);
set_attribute_from_tagset (info, "Embedded::Photo::Author", _AUTHOR_TAG_NAMES);
set_attribute_from_tagset (info, "Embedded::Photo::Copyright", _COPYRIGHT_TAG_NAMES);
/* Embedded::Photo::Exposure */
GObject *aperture;
GObject *iso_speed;
GObject *shutter_speed;
GObject *exposure_time;
GString *exposure;
aperture = get_attribute_from_tagset (info, _APERTURE_TAG_NAMES);
iso_speed = get_attribute_from_tagset (info, _ISOSPEED_TAG_NAMES);
shutter_speed = get_attribute_from_tagset (info, _SHUTTER_SPEED_TAG_NAMES);
exposure_time = get_attribute_from_tagset (info, _EXPOSURE_TIME_TAG_NAMES);
exposure = g_string_new ("");
if (aperture != NULL) {
char *formatted_value;
g_object_get (aperture, "formatted", &formatted_value, NULL);
if (formatted_value != NULL) {
g_string_append (exposure, formatted_value);
g_free (formatted_value);
}
}
if (iso_speed != NULL) {
char *formatted_value;
g_object_get (iso_speed, "formatted", &formatted_value, NULL);
if (formatted_value != NULL) {
if (exposure->len > 0)
g_string_append (exposure, EXPOSURE_SEPARATOR);
g_string_append (exposure, "ISO ");
g_string_append (exposure, formatted_value);
g_free (formatted_value);
}
}
if (shutter_speed != NULL) {
char *formatted_value;
g_object_get (shutter_speed, "formatted", &formatted_value, NULL);
if (formatted_value != NULL) {
if (exposure->len > 0)
g_string_append (exposure, EXPOSURE_SEPARATOR);
g_string_append (exposure, formatted_value);
g_free (formatted_value);
}
}
else if (exposure_time != NULL) {
char *formatted_value;
g_object_get (exposure_time, "formatted", &formatted_value, NULL);
if (formatted_value != NULL) {
if (exposure->len > 0)
g_string_append (exposure, EXPOSURE_SEPARATOR);
g_string_append (exposure, formatted_value);
g_free (formatted_value);
}
}
set_file_info (info,
"Embedded::Photo::Exposure",
_("Exposure"),
exposure->str,
NULL,
NULL,
NULL);
g_string_free (exposure, TRUE);
}
static const char *
get_exif_default_category (const Exiv2::Exifdatum &md)
{
#if EXIV2_TEST_VERSION(0, 21, 0)
if (Exiv2::ExifTags::isMakerGroup(md.groupName()))
#else
if (Exiv2::ExifTags::isMakerIfd(md.ifdId()))
#endif
return "Exif::MakerNotes";
if (md.groupName().compare("Thumbnail") == 0)
return "Exif::Thumbnail";
else if (md.groupName().compare("GPSInfo") == 0)
return "Exif::GPS";
else if (md.groupName().compare("Iop") == 0)
return "Exif::Versions";
return "Exif::Other";
}
static void
exiv2_read_metadata (Exiv2::Image::AutoPtr image,
GFileInfo *info,
gboolean update_general_attributes)
{
image->readMetadata();
Exiv2::ExifData &exifData = image->exifData();
if (! exifData.empty()) {
Exiv2::ExifData::const_iterator end = exifData.end();
for (Exiv2::ExifData::const_iterator md = exifData.begin(); md != end; ++md) {
stringstream raw_value;
raw_value << md->value();
stringstream description;
if (! md->tagLabel().empty())
description << md->tagLabel();
#if EXIV2_TEST_VERSION(0, 21, 0)
else if (Exiv2::ExifTags::isMakerGroup(md->groupName()))
#else
else if (Exiv2::ExifTags::isMakerIfd(md->ifdId()))
#endif
// Must be a MakerNote - include group name
description << md->groupName() << "." << md->tagName();
else
// Normal exif tag - just use tag name
description << md->tagName();
set_file_info (info,
md->key().c_str(),
description.str().c_str(),
md->print(&exifData).c_str(),
raw_value.str().c_str(),
get_exif_default_category (*md),
md->typeName());
}
}
Exiv2::IptcData &iptcData = image->iptcData();
if (! iptcData.empty()) {
GHashTable *table = create_metadata_hash ();
Exiv2::IptcData::iterator end = iptcData.end();
for (Exiv2::IptcData::iterator md = iptcData.begin(); md != end; ++md) {
stringstream raw_value;
raw_value << md->value();
stringstream description;
if (! md->tagLabel().empty())
description << md->tagLabel();
else
description << md->tagName();
GthMetadata *metadata;
metadata = create_metadata (md->key().c_str(),
description.str().c_str(),
md->print().c_str(),
raw_value.str().c_str(),
"Iptc",
md->typeName());
if (metadata != NULL) {
add_metadata_to_hash (table, metadata);
g_object_unref (metadata);
}
}
set_file_info_from_hash (info, table);
g_hash_table_unref (table);
}
Exiv2::XmpData &xmpData = image->xmpData();
if (! xmpData.empty()) {
GHashTable *table = create_metadata_hash ();
Exiv2::XmpData::iterator end = xmpData.end();
for (Exiv2::XmpData::iterator md = xmpData.begin(); md != end; ++md) {
stringstream raw_value;
raw_value << md->value();
stringstream description;
if (! md->tagLabel().empty())
description << md->tagLabel();
else
description << md->groupName() << "." << md->tagName();
GthMetadata *metadata;
metadata = create_metadata (md->key().c_str(),
description.str().c_str(),
md->print().c_str(),
raw_value.str().c_str(),
"Xmp::Embedded",
md->typeName());
if (metadata != NULL) {
if ((g_strcmp0 (md->typeName(), "XmpBag") == 0)
|| (g_strcmp0 (md->typeName(), "XmpSeq") == 0))
{
add_string_list_to_metadata (metadata, *md);
}
add_metadata_to_hash (table, metadata);
g_object_unref (metadata);
}
}
set_file_info_from_hash (info, table);
g_hash_table_unref (table);
}
set_attributes_from_tagsets (info, update_general_attributes);
}
/*
* exiv2_read_metadata_from_file
* reads metadata from image files
* code relies heavily on example1 from the exiv2 website
* http://www.exiv2.org/example1.html
*/
extern "C"
gboolean
exiv2_read_metadata_from_file (GFile *file,
GFileInfo *info,
gboolean update_general_attributes,
GCancellable *cancellable,
GError **error)
{
try {
char *path;
path = g_file_get_path (file);
if (path == NULL) {
if (error != NULL)
*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
return FALSE;
}
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(path);
g_free (path);
if (image.get() == 0) {
if (error != NULL)
*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
return FALSE;
}
// Set the log level to only show errors (and suppress warnings, informational and debug messages)
Exiv2::LogMsg::setLevel(Exiv2::LogMsg::error);
exiv2_read_metadata (image, info, update_general_attributes);
}
catch (Exiv2::AnyError& e) {
if (error != NULL)
*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, e.what());
return FALSE;
}
return TRUE;
}
extern "C"
gboolean
exiv2_read_metadata_from_buffer (void *buffer,
gsize buffer_size,
GFileInfo *info,
gboolean update_general_attributes,
GError **error)
{
try {
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) buffer, buffer_size);
if (image.get() == 0) {
if (error != NULL)
*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
return FALSE;
}
exiv2_read_metadata (image, info, update_general_attributes);
}
catch (Exiv2::AnyError& e) {
if (error != NULL)
*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, e.what());
return FALSE;
}
return TRUE;
}
extern "C"
GFile *
exiv2_get_sidecar (GFile *file)
{
char *uri;
char *uri_wo_ext;
char *sidecar_uri;
GFile *sidecar;
uri = g_file_get_uri (file);
uri_wo_ext = _g_uri_remove_extension (uri);
sidecar_uri = g_strconcat (uri_wo_ext, ".xmp", NULL);
sidecar = g_file_new_for_uri (sidecar_uri);
g_free (sidecar_uri);
g_free (uri_wo_ext);
g_free (uri);
return sidecar;
}
extern "C"
gboolean
exiv2_read_sidecar (GFile *file,
GFileInfo *info,
gboolean update_general_attributes)
{
try {
char *path;
path = g_file_get_path (file);
if (path == NULL)
return FALSE;
Exiv2::DataBuf buf = Exiv2::readFile(path);
g_free (path);
std::string xmpPacket;
xmpPacket.assign(reinterpret_cast(buf.pData_), buf.size_);
Exiv2::XmpData xmpData;
if (0 != Exiv2::XmpParser::decode(xmpData, xmpPacket))
return FALSE;
if (! xmpData.empty()) {
GHashTable *table = create_metadata_hash ();
Exiv2::XmpData::iterator end = xmpData.end();
for (Exiv2::XmpData::iterator md = xmpData.begin(); md != end; ++md) {
stringstream raw_value;
raw_value << md->value();
stringstream description;
if (! md->tagLabel().empty())
description << md->tagLabel();
else
description << md->groupName() << "." << md->tagName();
GthMetadata *metadata;
metadata = create_metadata (md->key().c_str(),
description.str().c_str(),
md->print().c_str(),
raw_value.str().c_str(),
"Xmp::Sidecar",
md->typeName());
if (metadata != NULL) {
if ((g_strcmp0 (md->typeName(), "XmpBag") == 0)
|| (g_strcmp0 (md->typeName(), "XmpSeq") == 0))
{
add_string_list_to_metadata (metadata, *md);
}
add_metadata_to_hash (table, metadata);
g_object_unref (metadata);
}
}
set_file_info_from_hash (info, table);
g_hash_table_unref (table);
}
Exiv2::XmpParser::terminate();
set_attributes_from_tagsets (info, update_general_attributes);
}
catch (Exiv2::AnyError& e) {
std::cerr << "Caught Exiv2 exception '" << e << "'\n";
return FALSE;
}
return TRUE;
}
static void
mandatory_int (Exiv2::ExifData &checkdata,
const char *tag,
int value)
{
Exiv2::ExifKey key = Exiv2::ExifKey(tag);
if (checkdata.findKey(key) == checkdata.end())
checkdata[tag] = value;
}
static void
mandatory_string (Exiv2::ExifData &checkdata,
const char *tag,
const char *value)
{
Exiv2::ExifKey key = Exiv2::ExifKey(tag);
if (checkdata.findKey(key) == checkdata.end())
checkdata[tag] = value;
}
const char *
gth_main_get_metadata_type (gpointer metadata,
const char *attribute)
{
const char *value_type = NULL;
GthMetadataInfo *metadatum_info;
if (GTH_IS_METADATA (metadata)) {
value_type = gth_metadata_get_value_type (GTH_METADATA (metadata));
if ((g_strcmp0 (value_type, "Undefined") == 0) || (g_strcmp0 (value_type, "") == 0))
value_type = NULL;
if (value_type != NULL)
return value_type;
}
metadatum_info = gth_main_get_metadata_info (attribute);
if (metadatum_info != NULL)
value_type = metadatum_info->type;
return value_type;
}
static void
dump_exif_data (Exiv2::ExifData &exifData,
const char *prefix)
{
std::cout << prefix << "\n";
try {
if (exifData.empty()) {
#if EXIV2_TEST_VERSION(0, 27, 0)
throw Exiv2::Error(Exiv2::kerErrorMessage, " No Exif data found in the file");
#else
throw Exiv2::Error(1, " No Exif data found in the file");
#endif
}
Exiv2::ExifData::const_iterator end = exifData.end();
for (Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) {
const char* tn = i->typeName();
std::cout << std::setw(44) << std::setfill(' ') << std::left
<< i->key() << " "
<< "0x" << std::setw(4) << std::setfill('0') << std::right
<< std::hex << i->tag() << " "
<< std::setw(9) << std::setfill(' ') << std::left
<< (tn ? tn : "Unknown") << " "
<< std::dec << std::setw(3)
<< std::setfill(' ') << std::right
<< i->count() << " "
<< std::dec << i->value()
<< "\n";
}
std::cout << "\n";
}
catch (Exiv2::Error& e) {
std::cout << "Caught Exiv2 exception '" << e.what() << "'\n";
return;
}
}
static Exiv2::DataBuf
exiv2_write_metadata_private (Exiv2::Image::AutoPtr image,
GFileInfo *info,
GthImage *image_data)
{
static char *software_name = NULL;
char **attributes;
int i;
image->clearMetadata();
// EXIF Data
Exiv2::ExifData ed;
attributes = g_file_info_list_attributes (info, "Exif");
for (i = 0; attributes[i] != NULL; i++) {
GthMetadata *metadatum;
char *key;
metadatum = (GthMetadata *) g_file_info_get_attribute_object (info, attributes[i]);
key = exiv2_key_from_attribute (attributes[i]);
try {
/* If the metadatum has no value yet, a new empty value
* is created. The type is taken from Exiv2's tag
* lookup tables. If the tag is not found in the table,
* the type defaults to ASCII.
* We always create the metadatum explicitly if the
* type is available to avoid type errors.
* See bug #610389 for more details. The original
* explanation is here:
* http://uk.groups.yahoo.com/group/exiv2/message/1472
*/
const char *raw_value = gth_metadata_get_raw (metadatum);
const char *value_type = gth_main_get_metadata_type (metadatum, attributes[i]);
if ((raw_value != NULL) && (strcmp (raw_value, "") != 0) && (value_type != NULL)) {
Exiv2::Value::AutoPtr value = Exiv2::Value::create (Exiv2::TypeInfo::typeId (value_type));
value->read (raw_value);
Exiv2::ExifKey exif_key(key);
ed.add (exif_key, value.get());
}
}
catch (Exiv2::AnyError& e) {
/* we don't care about invalid key errors */
g_warning ("%s", e.what());
}
g_free (key);
}
g_strfreev (attributes);
// Mandatory tags - add if not already present
mandatory_int (ed, "Exif.Image.XResolution", 72);
mandatory_int (ed, "Exif.Image.YResolution", 72);
mandatory_int (ed, "Exif.Image.ResolutionUnit", 2);
mandatory_int (ed, "Exif.Image.YCbCrPositioning", 1);
mandatory_int (ed, "Exif.Photo.ColorSpace", 1);
mandatory_string (ed, "Exif.Photo.ExifVersion", "48 50 50 49");
mandatory_string (ed, "Exif.Photo.ComponentsConfiguration", "1 2 3 0");
mandatory_string (ed, "Exif.Photo.FlashpixVersion", "48 49 48 48");
// Overwrite the software tag if the image content was modified
if (g_file_info_get_attribute_boolean (info, "gth::file::image-changed")) {
if (software_name == NULL)
software_name = g_strconcat (g_get_application_name (), " ", PACKAGE_VERSION, NULL);
ed["Exif.Image.ProcessingSoftware"] = software_name;
}
// Update the dimension tags with actual image values
cairo_surface_t *surface = NULL;
int width = 0;
int height = 0;
if (image_data != NULL)
surface = gth_image_get_cairo_surface (image_data);
if (surface != NULL) {
width = cairo_image_surface_get_width (surface);
if (width > 0) {
ed["Exif.Photo.PixelXDimension"] = width;
ed["Exif.Image.ImageWidth"] = width;
}
height = cairo_image_surface_get_height (surface);
if (height > 0) {
ed["Exif.Photo.PixelYDimension"] = height;
ed["Exif.Image.ImageLength"] = height;
}
ed["Exif.Image.Orientation"] = 1;
}
// Update the thumbnail
Exiv2::ExifThumb thumb(ed);
if ((surface != NULL) && (width > 0) && (height > 0)) {
cairo_surface_t *thumbnail;
GthImage *thumbnail_data;
char *buffer;
gsize buffer_size;
scale_keeping_ratio (&width, &height, 128, 128, FALSE);
thumbnail = _cairo_image_surface_scale (surface, width, height, SCALE_FILTER_BEST, NULL);
thumbnail_data = gth_image_new_for_surface (thumbnail);
if (gth_image_save_to_buffer (thumbnail_data,
"image/jpeg",
NULL,
&buffer,
&buffer_size,
NULL,
NULL))
{
thumb.setJpegThumbnail ((Exiv2::byte *) buffer, buffer_size);
ed["Exif.Thumbnail.XResolution"] = 72;
ed["Exif.Thumbnail.YResolution"] = 72;
ed["Exif.Thumbnail.ResolutionUnit"] = 2;
g_free (buffer);
}
else
thumb.erase();
g_object_unref (thumbnail_data);
cairo_surface_destroy (thumbnail);
}
else
thumb.erase();
if (surface != NULL)
cairo_surface_destroy (surface);
// Update the DateTime tag
if (g_file_info_get_attribute_object (info, "Exif::Image::DateTime") == NULL) {
GTimeVal current_time;
g_get_current_time (¤t_time);
char *date_time = _g_time_val_to_exif_date (¤t_time);
ed["Exif.Image.DateTime"] = date_time;
g_free (date_time);
}
ed.sortByKey();
// IPTC Data
Exiv2::IptcData id;
attributes = g_file_info_list_attributes (info, "Iptc");
for (i = 0; attributes[i] != NULL; i++) {
gpointer metadatum = (GthMetadata *) g_file_info_get_attribute_object (info, attributes[i]);
char *key = exiv2_key_from_attribute (attributes[i]);
try {
const char *value_type;
value_type = gth_main_get_metadata_type (metadatum, attributes[i]);
if (value_type != NULL) {
/* See the exif data code above for an explanation. */
Exiv2::Value::AutoPtr value = Exiv2::Value::create (Exiv2::TypeInfo::typeId (value_type));
Exiv2::IptcKey iptc_key(key);
const char *raw_value;
switch (gth_metadata_get_data_type (GTH_METADATA (metadatum))) {
case GTH_METADATA_TYPE_STRING:
raw_value = gth_metadata_get_raw (GTH_METADATA (metadatum));
if ((raw_value != NULL) && (strcmp (raw_value, "") != 0)) {
value->read (raw_value);
id.add (iptc_key, value.get());
}
break;
case GTH_METADATA_TYPE_STRING_LIST:
GthStringList *string_list = gth_metadata_get_string_list (GTH_METADATA (metadatum));
for (GList *scan = gth_string_list_get_list (string_list); scan; scan = scan->next) {
char *single_value = (char *) scan->data;
value->read (single_value);
id.add (iptc_key, value.get());
}
break;
}
}
}
catch (Exiv2::AnyError& e) {
/* we don't care about invalid key errors */
g_warning ("%s", e.what());
}
g_free (key);
}
id.sortByKey();
g_strfreev (attributes);
// XMP Data
Exiv2::XmpData xd;
attributes = g_file_info_list_attributes (info, "Xmp");
for (i = 0; attributes[i] != NULL; i++) {
gpointer metadatum = (GthMetadata *) g_file_info_get_attribute_object (info, attributes[i]);
char *key = exiv2_key_from_attribute (attributes[i]);
try {
const char *value_type;
value_type = gth_main_get_metadata_type (metadatum, attributes[i]);
if (value_type != NULL) {
/* See the exif data code above for an explanation. */
Exiv2::Value::AutoPtr value = Exiv2::Value::create (Exiv2::TypeInfo::typeId (value_type));
Exiv2::XmpKey xmp_key(key);
const char *raw_value;
switch (gth_metadata_get_data_type (GTH_METADATA (metadatum))) {
case GTH_METADATA_TYPE_STRING:
raw_value = gth_metadata_get_raw (GTH_METADATA (metadatum));
if ((raw_value != NULL) && (strcmp (raw_value, "") != 0)) {
value->read (raw_value);
xd.add (xmp_key, value.get());
}
break;
case GTH_METADATA_TYPE_STRING_LIST:
GthStringList *string_list = gth_metadata_get_string_list (GTH_METADATA (metadatum));
for (GList *scan = gth_string_list_get_list (string_list); scan; scan = scan->next) {
char *single_value = (char *) scan->data;
value->read (single_value);
xd.add (xmp_key, value.get());
}
break;
}
}
}
catch (Exiv2::AnyError& e) {
/* we don't care about invalid key errors */
g_warning ("%s", e.what());
}
g_free (key);
}
xd.sortByKey();
g_strfreev (attributes);
try {
image->setExifData(ed);
image->setIptcData(id);
image->setXmpData(xd);
image->writeMetadata();
}
catch (Exiv2::AnyError& e) {
g_warning ("%s", e.what());
}
Exiv2::BasicIo &io = image->io();
io.open();
return io.read(io.size());
}
extern "C"
gboolean
exiv2_supports_writes (const char *mime_type)
{
return (g_content_type_equals (mime_type, "image/jpeg")
|| g_content_type_equals (mime_type, "image/tiff")
|| g_content_type_equals (mime_type, "image/png"));
}
extern "C"
gboolean
exiv2_write_metadata (GthImageSaveData *data)
{
if (exiv2_supports_writes (data->mime_type) && (data->file_data != NULL)) {
try {
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) data->buffer, data->buffer_size);
g_assert (image.get() != 0);
Exiv2::DataBuf buf = exiv2_write_metadata_private (image, data->file_data->info, data->image);
g_free (data->buffer);
data->buffer = g_memdup (buf.pData_, buf.size_);
data->buffer_size = buf.size_;
}
catch (Exiv2::AnyError& e) {
if (data->error != NULL)
*data->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, e.what());
g_warning ("%s\n", e.what());
return FALSE;
}
}
return TRUE;
}
extern "C"
gboolean
exiv2_write_metadata_to_buffer (void **buffer,
gsize *buffer_size,
GFileInfo *info,
GthImage *image_data,
GError **error)
{
try {
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) *buffer, *buffer_size);
g_assert (image.get() != 0);
Exiv2::DataBuf buf = exiv2_write_metadata_private (image, info, image_data);
g_free (*buffer);
*buffer = g_memdup (buf.pData_, buf.size_);
*buffer_size = buf.size_;
}
catch (Exiv2::AnyError& e) {
if (error != NULL)
*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, e.what());
return FALSE;
}
return TRUE;
}
extern "C"
gboolean
exiv2_clear_metadata (void **buffer,
gsize *buffer_size,
GError **error)
{
try {
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) *buffer, *buffer_size);
if (image.get() == 0) {
if (error != NULL)
*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
return FALSE;
}
try {
image->clearMetadata();
image->writeMetadata();
}
catch (Exiv2::AnyError& e) {
g_warning ("%s", e.what());
}
Exiv2::BasicIo &io = image->io();
io.open();
Exiv2::DataBuf buf = io.read(io.size());
g_free (*buffer);
*buffer = g_memdup (buf.pData_, buf.size_);
*buffer_size = buf.size_;
}
catch (Exiv2::AnyError& e) {
if (error != NULL)
*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, e.what());
return FALSE;
}
return TRUE;
}
#define MAX_RATIO_ERROR_TOLERANCE 0.01
GdkPixbuf *
exiv2_generate_thumbnail (const char *uri,
const char *mime_type,
int requested_size)
{
GdkPixbuf *pixbuf = NULL;
if (! _g_content_type_is_a (mime_type, "image/jpeg")
&& ! _g_content_type_is_a (mime_type, "image/tiff"))
{
return NULL;
}
try {
char *path;
path = g_filename_from_uri (uri, NULL, NULL);
if (path == NULL)
return NULL;
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open (path);
image->readMetadata ();
Exiv2::ExifThumbC exifThumb (image->exifData ());
Exiv2::DataBuf thumb = exifThumb.copy ();
g_free (path);
if (thumb.pData_ == NULL)
return NULL;
Exiv2::ExifData &ed = image->exifData();
long orientation = (ed["Exif.Image.Orientation"].count() > 0) ? ed["Exif.Image.Orientation"].toLong() : 1;
long image_width = (ed["Exif.Photo.PixelXDimension"].count() > 0) ? ed["Exif.Photo.PixelXDimension"].toLong() : -1;
long image_height = (ed["Exif.Photo.PixelYDimension"].count() > 0) ? ed["Exif.Photo.PixelYDimension"].toLong() : -1;
if ((orientation != 1) || (image_width <= 0) || (image_height <= 0))
return NULL;
GInputStream *stream = g_memory_input_stream_new_from_data (thumb.pData_, thumb.size_, NULL);
pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
g_object_unref (stream);
if (pixbuf == NULL)
return NULL;
/* Heuristic to find out-of-date thumbnails: the thumbnail and image aspect ratios must be equal */
int pixbuf_width = gdk_pixbuf_get_width (pixbuf);
int pixbuf_height = gdk_pixbuf_get_height (pixbuf);
double image_ratio = (((double) image_width) / image_height);
double thumbnail_ratio = (((double) pixbuf_width) / pixbuf_height);
double ratio_delta = (image_ratio > thumbnail_ratio) ? (image_ratio - thumbnail_ratio) : (thumbnail_ratio - image_ratio);
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 */
|| (MAX (pixbuf_width, pixbuf_height) < requested_size)) /* ignore the embedded image if it's too small compared to the requested size */
{
g_object_unref (pixbuf);
return NULL;
}
/* Scale the pixbuf to perfectly fit the requested size */
if (scale_keeping_ratio (&pixbuf_width,
&pixbuf_height,
requested_size,
requested_size,
TRUE))
{
GdkPixbuf *tmp = pixbuf;
pixbuf = _gdk_pixbuf_scale_simple_safe (tmp, pixbuf_width, pixbuf_height, GDK_INTERP_BILINEAR);
g_object_unref (tmp);
}
/* Save the original image size in the pixbuf options */
char *s = g_strdup_printf ("%ld", image_width);
gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Width", s);
g_object_set_data (G_OBJECT (pixbuf), "gnome-original-width", GINT_TO_POINTER ((int) image_width));
g_free (s);
s = g_strdup_printf ("%ld", image_height);
gdk_pixbuf_set_option (pixbuf, "tEXt::Thumb::Image::Height", s);
g_object_set_data (G_OBJECT (pixbuf), "gnome-original-height", GINT_TO_POINTER ((int) image_height));
g_free (s);
/* Set the orientation option to correctly rotate the thumbnail
* in gnome_desktop_thumbnail_factory_generate_thumbnail() */
char *orientation_s = g_strdup_printf ("%ld", orientation);
gdk_pixbuf_set_option (pixbuf, "orientation", orientation_s);
g_free (orientation_s);
}
catch (Exiv2::AnyError& e) {
}
return pixbuf;
}