diff options
| author | Tomas Bzatek <tbzatek@users.sourceforge.net> | 2019-05-13 22:16:28 +0200 |
|---|---|---|
| committer | Tomas Bzatek <tbzatek@users.sourceforge.net> | 2019-05-13 22:16:28 +0200 |
| commit | 0c3b218886342e44275b087c41faf3b6a2b7f664 (patch) | |
| tree | 35191c66c8f20e6f9b245d81cd79398a435370d9 | |
| parent | 86438b558aaeb758d8770144c9e2fe70fdc4ee8a (diff) | |
| download | cataract-0c3b218886342e44275b087c41faf3b6a2b7f664.tar.xz | |
jpeg-utils: Port to gexiv2
The gexiv2 library is just a GObject wrapper around exiv2 library. It's
a healthy project that continually keeps up with exiv2 API changes.
Adopted by e.g. GIMP there's certain guarantee of future maintenance.
This allows us to get rid of C++ code, making it more readable and
predictable.
| -rw-r--r-- | configure.ac | 48 | ||||
| -rw-r--r-- | src/Makefile.am | 6 | ||||
| -rw-r--r-- | src/jpeg-utils.c (renamed from src/jpeg-utils.cpp) | 759 | ||||
| -rw-r--r-- | src/jpeg-utils.h | 15 |
4 files changed, 345 insertions, 483 deletions
diff --git a/configure.ac b/configure.ac index adeac72..e99d6be 100644 --- a/configure.ac +++ b/configure.ac @@ -12,14 +12,10 @@ AM_INIT_AUTOMAKE([1.11.1 no-dist-gzip dist-xz tar-ustar]) AM_CONFIG_HEADER(config.h) GLIB_REQUIRED=2.16.0 -EXIV2_REQUIRED=0.17 AC_C_CONST AC_SEARCH_LIBS([strerror],[cposix]) AC_PROG_CC -AC_PROG_CPP -AC_PROG_CXX -AC_PROG_CXXCPP AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET @@ -67,39 +63,6 @@ PKG_CHECK_MODULES(LIBXML2, libxml-2.0) dnl ************************************************** -dnl *** Check for EXIV2 version *** -dnl ************************************************** -PKG_CHECK_MODULES(EXIV2, exiv2 >= $EXIV2_REQUIRED) -AC_MSG_CHECKING(for EXIV2 - version >= $EXIV2_REQUIRED) -exiv2_version=`$PKG_CONFIG --modversion exiv2` -AC_MSG_RESULT(yes (version $exiv2_version)) - -dnl Check for new exiv2 thumbnailing API -AC_DEFUN([EXIV2_HAVE_NEW_THUMBNAILING_API], -[AC_CACHE_CHECK(for new Exiv2::ExifThumb API, -ac_cv_exiv2_have_new_exifthumb, -[AC_LANG_PUSH([C++]) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <exiv2/image.hpp> -#include <exiv2/exif.hpp> - -void test () { - Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(""); - Exiv2::ExifData &exifData = image->exifData(); - Exiv2::ExifThumb exifThumb(image->exifData()); - exifThumb.erase(); -} -]], [[return 0;]])],[ac_cv_exiv2_have_new_exifthumb=yes],[ac_cv_exiv2_have_new_exifthumb=no]) - AC_LANG_POP([]) -]) -if test "$ac_cv_exiv2_have_new_exifthumb" = yes; then - AC_DEFINE(HAVE_EXIFTHUMB,1,[new Exiv2::ExifThumb API]) -fi -]) - -EXIV2_HAVE_NEW_THUMBNAILING_API - - -dnl ************************************************** dnl *** Check for ImageMagick *** dnl ************************************************** PKG_CHECK_MODULES(MAGICKWAND, MagickWand) @@ -114,6 +77,17 @@ PKG_CHECK_EXISTS([MagickWand >= 7], [Define to 1 if ImageMagick 7 is available])) +dnl ************************************************** +dnl *** Check for gexiv2 library *** +dnl ************************************************** +PKG_CHECK_MODULES(GEXIV2, gexiv2) + +dnl Check for gexiv2 version +AC_MSG_CHECKING(for gexiv2) +gexiv2_version=`$PKG_CONFIG --modversion gexiv2` +AC_MSG_RESULT(yes (version $gexiv2_version)) + + AC_CONFIG_FILES([ diff --git a/src/Makefile.am b/src/Makefile.am index ec7b3b1..fbf8672 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,7 +9,7 @@ AM_CPPFLAGS = \ $(DISABLE_DEPRECATED_CFLAGS) \ $(LIBXML2_CFLAGS) \ $(MAGICKWAND_CFLAGS) \ - $(EXIV2_CFLAGS) \ + $(GEXIV2_CFLAGS) \ -DG_LOG_DOMAIN=\"cgg\" bin_PROGRAMS = \ @@ -34,7 +34,7 @@ cgg_SOURCES = \ items.h \ job-manager.h \ job-manager.c \ - jpeg-utils.cpp \ + jpeg-utils.c \ jpeg-utils.h \ properties-table.c \ properties-table.h \ @@ -52,7 +52,7 @@ cgg_LDADD = \ $(GLIB_LIBS) \ $(LIBXML2_LIBS) \ $(MAGICKWAND_LIBS) \ - $(EXIV2_LIBS) \ + $(GEXIV2_LIBS) \ -lm EXTRA_DIST = cgg-dirgen diff --git a/src/jpeg-utils.cpp b/src/jpeg-utils.c index 2e214e4..64cfdff 100644 --- a/src/jpeg-utils.cpp +++ b/src/jpeg-utils.c @@ -15,14 +15,16 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#define _XOPEN_SOURCE + #include "config.h" #include <stdio.h> #include <string.h> #include <stdlib.h> +#include <time.h> -#include <exiv2/image.hpp> -#include <exiv2/exif.hpp> +#include <gexiv2/gexiv2.h> #ifdef HAVE_IMAGEMAGICK_7 # include <MagickWand/MagickWand.h> @@ -30,14 +32,12 @@ # include <wand/MagickWand.h> #endif -#include <config.h> - #include "jpeg-utils.h" #include "gallery-utils.h" struct ExifDataPrivate { - Exiv2::Image::AutoPtr image; + GExiv2Metadata *metadata; }; @@ -48,15 +48,12 @@ void init_jpeg_utils (void) { MagickWandGenesis(); - /* http://dev.exiv2.org/projects/exiv2/wiki/Thread_safety */ - /* https://bugs.kde.org/show_bug.cgi?id=166424 */ - Exiv2::XmpParser::initialize(); + g_assert (gexiv2_initialize () == TRUE); } void destroy_jpeg_utils (void) { - Exiv2::XmpParser::terminate(); MagickWandTerminus(); } @@ -100,13 +97,35 @@ parse_exif_date (const char *str) return tm; } +static struct tm * +parse_exif_date_with_overrides (const char *str, ExifData *exif) +{ + struct tm *tt; + + if (exif->override_datetime != (time_t) -1) { + tt = (struct tm *) g_malloc0 (sizeof (struct tm)); + localtime_r (&exif->override_datetime, tt); + return tt; + } + + tt = parse_exif_date (str); + if (tt) { + shift_time (tt, exif->timezone_shift); + return tt; + } + + return NULL; +} + static gchar * -format_exif_time (struct tm *tm) +format_exif_time (struct tm *tm, const gchar *format_string) { char conv[1024]; memset (&conv, 0, sizeof(conv)); - if (strftime (&conv[0], sizeof(conv), "%Y:%m:%d %H:%M:%S", tm)) + if (strftime (&conv[0], sizeof(conv), + format_string ? format_string : "%Y:%m:%d %H:%M:%S", + tm)) return g_strdup (&conv[0]); return NULL; @@ -119,18 +138,15 @@ ExifData * read_exif (const gchar *filename) { ExifData *data; + GError *error = NULL; - g_assert (filename != NULL); - data = exif_data_new_empty (); + g_return_val_if_fail (filename != NULL, NULL); - try { - data->priv->image = Exiv2::ImageFactory::open (filename); - g_assert (data->priv->image.get() != 0); - data->priv->image->readMetadata(); - } - catch (Exiv2::AnyError& e) - { - log_error ("read_exif: Caught Exiv2 exception: '%s'\n", e.what()); + data = exif_data_new_empty (); + data->priv->metadata = gexiv2_metadata_new (); + if (! gexiv2_metadata_open_path (data->priv->metadata, filename, &error)) { + log_error ("read_exif: %s\n", error->message); + g_error_free (error); exif_data_free (data); return NULL; } @@ -156,7 +172,7 @@ exif_data_free (ExifData *data) g_free (data->override_copyright); g_free (data->external_exif_data); g_free (data->override_artist_name); - /* FIXME: free data->priv->image */ + g_clear_object (&data->priv->metadata); g_free (data->priv); g_free (data); } @@ -182,28 +198,9 @@ get_real_key_name (const gchar *key) return key; } - -/* Returns newly allocated string */ -/* FIXME: for some reason exiv2 returns const strings with unknown lifetime, can't really trust the library */ -#define exiv2_str_val(data,key) g_strdup(data[key].toString().c_str()) - -static gboolean -iptc_has_key (Exiv2::IptcData iptcData, const char *key) -{ - Exiv2::IptcData::const_iterator md; - - if (! iptcData.empty()) { - md = iptcData.findKey(Exiv2::IptcKey(std::string(key))); - if (md != iptcData.end() && iptcData[key].count() > 0) - return TRUE; - } - return FALSE; -} - - /* * Retrieves value of the specified key or NULL if the key does not exist. - * The key argument belongs to Exiv2 namespace - see http://exiv2.org/tags.html + * The key argument belongs to Exiv2 namespace - see https://exiv2.org/metadata.html */ gchar * get_exif_data (ExifData *exif, const gchar *key) @@ -213,30 +210,41 @@ get_exif_data (ExifData *exif, const gchar *key) key = get_real_key_name (key); - try { - if (g_strcmp0 (key, JPEG_COMMENT) == 0) { - return g_strdup (exif->priv->image->comment().c_str()); - } + if (g_str_equal (key, JPEG_COMMENT)) + return exif->override_copyright ? g_strdup (exif->override_copyright) : gexiv2_metadata_get_comment (exif->priv->metadata); - if (g_str_has_prefix (key, "Exif.")) { - Exiv2::ExifData &exifData = exif->priv->image->exifData(); - if (! exifData.empty()) { - return exiv2_str_val (exifData, key); - } - } + if (g_str_equal (key, EXIF_ARTIST) && exif->override_artist_name) + return g_strdup (exif->override_artist_name); - if (g_str_has_prefix (key, "Iptc.")) { - Exiv2::IptcData &iptcData = exif->priv->image->iptcData(); - if (iptc_has_key (iptcData, key)) { - return exiv2_str_val (iptcData, key); - } - } + if (g_str_equal (key, EXIF_APERTURE) && exif->override_aperture != -1) + return g_strdup_printf ("%f", exif->override_aperture); - return NULL; - } - catch (...) { - return NULL; + if (g_str_equal (key, EXIF_FOCAL_LENGTH) && exif->override_focal_length != -1) + return g_strdup_printf ("%d/%d", (gint) (exif->override_focal_length * 1000000.0), 1000000); + + if ((exif->override_datetime != (time_t) -1 || exif->timezone_shift) && + (g_str_equal (key, EXIF_DATETIME) || + g_str_equal (key, "Exif.Photo.DateTimeOriginal") || + g_str_equal (key, "Exif.Photo.DateTimeDigitized") || + g_str_equal (key, "Exif.Image.DateTime"))) + { + gchar *val; + struct tm *tt; + + val = gexiv2_metadata_get_tag_string (exif->priv->metadata, key); + if (! val) + return NULL; + + tt = parse_exif_date_with_overrides (val, exif); + g_free (val); + if (tt) { + gchar *res = format_exif_time (tt, NULL); + g_free (tt); + return res; + } } + + return gexiv2_metadata_get_tag_string (exif->priv->metadata, key); } gchar * @@ -245,126 +253,79 @@ get_exif_data_fixed (ExifData *exif, const gchar *key) g_return_val_if_fail (exif != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); - try - { - if (g_str_has_prefix (key, "Exif.")) { - Exiv2::ExifData &exifData = exif->priv->image->exifData(); - if (exifData.empty()) - return NULL; - - if (g_str_equal (key, EXIF_APERTURE)) { - double val = -1; - if (exif->override_aperture != -1) - val = exif->override_aperture; - else { - if (exifData["Exif.Photo.FNumber"].count() > 0) - val = exifData["Exif.Photo.FNumber"].toFloat(); - else - if (exifData["Exif.Photo.ApertureValue"].count() > 0) - val = exifData["Exif.Photo.ApertureValue"].toFloat(); - } - if (val >= 0) - return g_strdup_printf ("ƒ/%.1f", val); - } - - if (g_str_equal (key, EXIF_DATETIME) || - g_str_equal (key, "Exif.Photo.DateTimeOriginal") || - g_str_equal (key, "Exif.Photo.DateTimeDigitized") || - g_str_equal (key, "Exif.Image.DateTime")) { - gchar *val = NULL; - try { - val = exiv2_str_val (exifData, "Exif.Photo.DateTimeOriginal"); - } catch (...) { } - if (! val || strlen (val) == 0) - try { - val = exiv2_str_val (exifData, "Exif.Photo.DateTimeDigitized"); - } catch (...) { } - if (! val || strlen (val) == 0) - try { - /* usually a modification date */ - val = exiv2_str_val (exifData, "Exif.Image.DateTime"); - } catch (...) { } - - if (val && strlen (val) > 0) { - struct tm *tt = NULL; - char conv[1024]; - gchar *res = NULL; - - memset (&conv, 0, sizeof (conv)); - - if (exif->override_datetime != (time_t) -1) { - tt = (struct tm *) g_malloc0 (sizeof (struct tm)); - localtime_r (&exif->override_datetime, tt); - if (strftime (&conv[0], sizeof (conv), exif->datetime_format ? exif->datetime_format : "%c", tt)) - res = g_strdup (&conv[0]); - } - - if (! res) { - tt = parse_exif_date (val); - if (tt) { - shift_time (tt, exif->timezone_shift); - if (strftime (&conv[0], sizeof (conv), exif->datetime_format ? exif->datetime_format : "%c", tt)) - res = g_strdup (&conv[0]); - } - } + if (g_str_equal (key, EXIF_APERTURE)) { + gdouble aperture = exif->override_aperture != -1 ? aperture = exif->override_aperture : gexiv2_metadata_get_fnumber (exif->priv->metadata); + if (aperture != -1.0) + return g_strdup_printf ("ƒ/%.1f", aperture); + } - g_free (tt); - if (res) - return res; - } - g_free (val); - } + if (g_str_equal (key, EXIF_EXPOSURE)) { + gint nom, den; - if (g_str_equal (key, EXIF_EXPOSURE)) { - float val = exifData["Exif.Photo.ExposureTime"].toFloat(); - if (val > 0) { - if (val < 0.5) - return g_strdup_printf ("1/%.0f s", 1/val); - else - return g_strdup_printf ("%.1f s", val); - } - } + if (gexiv2_metadata_get_exposure_time (exif->priv->metadata, &nom, &den)) { + gdouble val = (gdouble) nom / (gdouble) den; + if (val < 0.5) + return g_strdup_printf ("1/%.0f s", 1/val); + else + return g_strdup_printf ("%.1f s", val); + } + } - if (g_str_equal (key, EXIF_FLASH)) { - long int val = exifData["Exif.Photo.Flash"].toLong(); - if (val > 0 && (val & 1) == 1) - return g_strdup ("Flash fired"); - else - return g_strdup ("--"); - } + if (g_str_equal (key, EXIF_FLASH)) { + glong val = gexiv2_metadata_get_tag_long (exif->priv->metadata, EXIF_FLASH); + if (val > 0 && (val & 1) == 1) + return g_strdup ("Flash fired"); + else + return g_strdup ("--"); + } - if (g_str_equal (key, EXIF_FOCAL_LENGTH)) { - double val; - val = exif->override_focal_length != -1 ? exif->override_focal_length : exifData["Exif.Photo.FocalLength"].toFloat(); - if (val >= 0) - return g_strdup_printf ("%.0f mm", val); - } + if (g_str_equal (key, EXIF_FOCAL_LENGTH)) { + gdouble val; + val = exif->override_focal_length != -1 ? exif->override_focal_length : gexiv2_metadata_get_focal_length (exif->priv->metadata); + if (val >= 0) + return g_strdup_printf ("%.0f mm", val); + } - if (g_str_equal (key, EXIF_ISO)) { - long int val = exifData["Exif.Photo.ISOSpeedRatings"].toLong(); - if (val > 0) - return g_strdup_printf ("%ld", val); - } + if (g_str_equal (key, EXIF_ISO)) { + gint val = gexiv2_metadata_get_iso_speed (exif->priv->metadata); + if (val > 0) + return g_strdup_printf ("%d", val); + } - if (g_str_equal (key, EXIF_CANON_CAMERA_TEMP)) { - if (exifData["Exif.CanonSi.0x000c"].count() > 0) { - int long val = exifData["Exif.CanonSi.0x000c"].toLong(); - if (val > 0) - return g_strdup_printf ("%ld °C", val - 128); - } - } + if (g_str_equal (key, EXIF_CANON_CAMERA_TEMP)) { + if (gexiv2_metadata_has_tag (exif->priv->metadata, "Exif.CanonSi.0x000c")) { + glong val = gexiv2_metadata_get_tag_long (exif->priv->metadata, "Exif.CanonSi.0x000c"); + if (val > 0) + return g_strdup_printf ("%ld °C", val - 128); } + } - if (g_str_has_prefix (key, "Iptc.")) { - Exiv2::IptcData &iptcData = exif->priv->image->iptcData(); - if (iptcData.empty()) - return NULL; + if (g_str_equal (key, EXIF_DATETIME) || + g_str_equal (key, "Exif.Photo.DateTimeOriginal") || + g_str_equal (key, "Exif.Photo.DateTimeDigitized") || + g_str_equal (key, "Exif.Image.DateTime")) { + gchar *val; + + val = gexiv2_metadata_get_tag_string (exif->priv->metadata, "Exif.Photo.DateTimeOriginal"); + if (! val || strlen (val) == 0) + val = gexiv2_metadata_get_tag_string (exif->priv->metadata, "Exif.Photo.DateTimeDigitized"); + if (! val || strlen (val) == 0) + val = gexiv2_metadata_get_tag_string (exif->priv->metadata, "Exif.Image.DateTime"); /* usually a modification date */ + if (val && strlen (val) > 0) { + struct tm *tt; + + tt = parse_exif_date_with_overrides (val, exif); + g_free (val); + if (tt) { + gchar *res = format_exif_time (tt, exif->datetime_format ? exif->datetime_format : "%c"); + g_free (tt); + return res; + } } - } - catch (...) { - return NULL; + g_free (val); } + /* fall back to plain value retrieval */ return get_exif_data (exif, key); } @@ -374,42 +335,23 @@ get_exif_data_fixed (ExifData *exif, const gchar *key) gboolean exif_has_key (ExifData *exif, const gchar *key) { - const gchar *rkey; - g_return_val_if_fail (exif != NULL, FALSE); g_return_val_if_fail (key != NULL, FALSE); - rkey = get_real_key_name (key); - - try { - if (g_strcmp0 (rkey, JPEG_COMMENT) == 0) { - return (! exif->priv->image->comment().empty()); - } - - if (g_str_has_prefix (rkey, "Exif.")) { - Exiv2::ExifData &exifData = exif->priv->image->exifData(); - if (exifData.empty() || exifData[rkey].count() <= 0) - return FALSE; + key = get_real_key_name (key); - /* Special case for some keys */ - if (g_str_equal (key, EXIF_CANON_CAMERA_TEMP)) { - int long val = exifData["Exif.CanonSi.0x000c"].toLong(); - return val > 0; - } + if (g_str_equal (key, JPEG_COMMENT)) { + gchar *comment = gexiv2_metadata_get_comment (exif->priv->metadata); + gboolean ret = comment && strlen (comment) > 0; + g_free (comment); + return ret; + } - return TRUE; - } + if (g_str_equal (key, EXIF_CANON_CAMERA_TEMP)) + return gexiv2_metadata_has_tag (exif->priv->metadata, "Exif.CanonSi.0x000c") && + gexiv2_metadata_get_tag_long (exif->priv->metadata, "Exif.CanonSi.0x000c") > 0; - if (g_str_has_prefix (rkey, "Iptc.")) { - Exiv2::IptcData &iptcData = exif->priv->image->iptcData(); - return iptc_has_key (iptcData, rkey); - } - - return FALSE; - } - catch (...) { - return FALSE; - } + return gexiv2_metadata_has_tag (exif->priv->metadata, key); } @@ -760,7 +702,7 @@ calculate_sizes (const unsigned long max_width, const unsigned long max_height, unsigned long *width, unsigned long *height) { if (max_width > *width && max_height > *height) - return; + return; double max_ratio = (double) max_width / (double) max_height; double real_ratio = (double) *width / (double) *height; @@ -775,130 +717,65 @@ calculate_sizes (const unsigned long max_width, const unsigned long max_height, } static gboolean -shift_exif_time (Exiv2::ExifData& exifData, const char *key, int amount) +shift_exif_time (GExiv2Metadata *metadata, const char *key, int amount) { struct tm *tt; gchar *s; gchar *st; - gboolean res; - - res = FALSE; - try { - if (exifData[key].count() > 0) { - s = exiv2_str_val (exifData, key); - - if (s && strlen (s) > 0) { - tt = parse_exif_date (s); - if (tt) { - shift_time (tt, amount); - st = format_exif_time (tt); - if (st) - exifData[key] = st; - g_free (st); - g_free (tt); - res = TRUE; - } - } - g_free (s); - } - } catch (...) { } - - return res; -} - -static gboolean -shift_iptc_time (Exiv2::IptcData &iptcData, const char *date_key, const char *time_key, int amount) -{ - struct tm tt = {0}; - long int orig_time; - gboolean res; - Exiv2::DateValue dval; - Exiv2::TimeValue tval; - - res = FALSE; - if (iptc_has_key (iptcData, date_key) || iptc_has_key (iptcData, time_key)) { - orig_time = (iptc_has_key (iptcData, date_key) ? iptcData[date_key].toLong() : 0) + - (iptc_has_key (iptcData, time_key) ? iptcData[time_key].toLong() : 0); - if (orig_time > 0) { - orig_time += amount * 60; - localtime_r (&orig_time, &tt); - mktime (&tt); - if (tt.tm_isdst) - shift_time (&tt, -60); - - dval = Exiv2::DateValue(tt.tm_year + 1900, tt.tm_mon + 1, tt.tm_mday); - tval = Exiv2::TimeValue(tt.tm_hour, tt.tm_min, tt.tm_sec, 0, 0); - iptcData[date_key].setValue(&dval); - iptcData[time_key].setValue(&tval); - res = TRUE; - } - } - - return res; -} - -static gboolean -override_exif_time (Exiv2::ExifData &exifData, const char *key, time_t datetime) -{ - struct tm tt = {0}; - gchar *st; - gboolean res; - - res = FALSE; - try { - if (exifData[key].count() > 0) { - localtime_r (&datetime, &tt); - st = format_exif_time (&tt); + gboolean res = FALSE; + + s = gexiv2_metadata_get_tag_string (metadata, key); + if (s && strlen (s) > 0) { + tt = parse_exif_date (s); + if (tt) { + shift_time (tt, amount); + st = format_exif_time (tt, NULL); if (st) - exifData[key] = st; + res = gexiv2_metadata_set_tag_string (metadata, key, st); g_free (st); - res = TRUE; + g_free (tt); } - } catch (...) { } + } + g_free (s); return res; } static gboolean -override_iptc_time (Exiv2::IptcData &iptcData, const char *date_key, const char *time_key, time_t datetime) +override_exif_time (GExiv2Metadata *metadata, const char *key, time_t datetime) { struct tm tt = {0}; - Exiv2::DateValue dval; - Exiv2::TimeValue tval; + gchar *s; + gchar *st; + gboolean res = FALSE; - if (iptc_has_key (iptcData, date_key) || iptc_has_key (iptcData, time_key)) { + s = gexiv2_metadata_get_tag_string (metadata, key); + if (s && strlen (s) > 0) { localtime_r (&datetime, &tt); - - dval = Exiv2::DateValue(tt.tm_year + 1900, tt.tm_mon + 1, tt.tm_mday); - tval = Exiv2::TimeValue(tt.tm_hour, tt.tm_min, tt.tm_sec, 0, 0); - iptcData[date_key].setValue(&dval); - iptcData[time_key].setValue(&tval); - - return TRUE; + st = format_exif_time (&tt, NULL); + if (st) + res = gexiv2_metadata_set_tag_string (metadata, key, st); + g_free (st); } + g_free (s); - return FALSE; + return res; } - /* List of tags we don't want to copy from external EXIF data since they are related to the RAW file, - * not the processed image. Note that this list is not by far complete. + * not the processed image. Note that this list is far from complete. */ -static const gchar * image_size_tags[] = { +static const gchar *keep_source_tags[] = { "Exif.Image.ImageWidth", "Exif.Image.ImageHeight", "Exif.Image.ImageLength", "Exif.Image.Orientation", - "Exif.Photo.PixelXDimension", - "Exif.Photo.PixelYDimension", "Exif.Image.XResolution", "Exif.Image.YResolution", "Exif.Image.ResolutionUnit", "Exif.Image.Compression", "Exif.Image.BitsPerSample", "Exif.Image.SamplesPerPixel", - "Exif.Photo.ComponentsConfiguration", - "Exif.Photo.CompressedBitsPerPixel", "Exif.Image.JPEGTables", "Exif.Image.JPEGProc", "Exif.Image.JPEGInterchangeFormat", @@ -915,46 +792,82 @@ static const gchar * image_size_tags[] = { "Exif.Image.ReferenceBlackWhite", "Exif.Image.PhotometricInterpretation", "Exif.Image.PlanarConfiguration", + "Exif.Photo.ColorSpace", + "Exif.Photo.ComponentsConfiguration", + "Exif.Photo.CompressedBitsPerPixel", + "Exif.Photo.FocalPlaneXResolution", + "Exif.Photo.FocalPlaneYResolution", + "Exif.Photo.PixelXDimension", + "Exif.Photo.PixelYDimension", + "Exif.Canon.ColorSpace", }; static gboolean -is_image_size_tag (const gchar *s) +copy_tag (GExiv2Metadata *metadata, GExiv2Metadata *source_metadata, GExiv2Metadata *external_metadata, const gchar *tag) { + gchar *val; unsigned int i; + gboolean res = FALSE; + + /* filter-out tags that should be copied neither from the source or the external metadata */ + if (g_str_has_prefix (tag, "Exif.Thumbnail.") || + g_str_equal (tag, "Exif.Canon.0x4002") || + g_str_equal (tag, "Exif.Canon.0x4005")) + return TRUE; - for (i = 0; i < G_N_ELEMENTS (image_size_tags); i++) - if (g_str_equal (s, image_size_tags[i])) - return TRUE; - return FALSE; + /* should be copied from the source file */ + for (i = 0; i < G_N_ELEMENTS (keep_source_tags); i++) + if (g_str_equal (tag, keep_source_tags[i])) { + val = gexiv2_metadata_get_tag_string (source_metadata, tag); + if (val) + res = gexiv2_metadata_set_tag_string (metadata, tag, val); + g_free (val); + return res; + } + + val = gexiv2_metadata_get_tag_string (external_metadata, tag); + if (val) + res = gexiv2_metadata_set_tag_string (metadata, tag, val); + g_free (val); + return res; } static void -copy_metadata (Exiv2::Image::AutoPtr &img, Exiv2::Image::AutoPtr &external_img) +copy_metadata (GExiv2Metadata *metadata, const gchar *source_img, const gchar *external_img) { - Exiv2::ExifData exifData; - Exiv2::ExifData &img_exifData = img->exifData(); - Exiv2::ExifData &ext_exifData = external_img->exifData(); - - /* First copy metadata from the external image excluding size tags */ - Exiv2::ExifData::const_iterator end = ext_exifData.end(); - for (Exiv2::ExifData::const_iterator i = ext_exifData.begin(); i != end; ++i) { - gchar *s = g_strdup (i->key().c_str()); - if (! is_image_size_tag (s)) - exifData[s] = ext_exifData[s]; - g_free (s); + GExiv2Metadata *source_metadata; + GExiv2Metadata *external_metadata; + GError *error = NULL; + gchar **tags; + + source_metadata = gexiv2_metadata_new (); + if (! gexiv2_metadata_open_path (source_metadata, source_img, &error)) { + log_error ("copy_metadata: %s (ignoring...)\n", error->message); + g_error_free (error); + /* continue with empty source metadata */ } - /* Copy selected size tags from the processed image */ - end = img_exifData.end(); - for (Exiv2::ExifData::const_iterator i = img_exifData.begin(); i != end; ++i) { - gchar *s = g_strdup (i->key().c_str()); - if (is_image_size_tag (s)) - exifData[s] = img_exifData[s]; - g_free (s); + external_metadata = gexiv2_metadata_new (); + if (! gexiv2_metadata_open_path (external_metadata, external_img, &error)) { + log_error ("copy_metadata: %s\n", error->message); + g_error_free (error); + g_object_unref (source_metadata); + g_object_unref (external_metadata); + return; } - img->setMetadata (*external_img); - img->setExifData (exifData); + tags = gexiv2_metadata_get_exif_tags (external_metadata); + for (gchar **i = tags; *i; i++) + copy_tag (metadata, source_metadata, external_metadata, *i); + g_strfreev (tags); + + tags = gexiv2_metadata_get_iptc_tags (external_metadata); + for (gchar **i = tags; *i; i++) + copy_tag (metadata, source_metadata, external_metadata, *i); + g_strfreev (tags); + + g_object_unref (source_metadata); + g_object_unref (external_metadata); } @@ -966,140 +879,114 @@ copy_metadata (Exiv2::Image::AutoPtr &img, Exiv2::Image::AutoPtr &external_img) void modify_exif (const gchar *filename, ExifData *exif, gboolean strip_thumbnail, gboolean strip_xmp) { + GExiv2Metadata *metadata; + GError *error = NULL; gboolean modified; gboolean res; g_assert (filename != NULL); - - if (! strip_thumbnail && exif == NULL) + if (! strip_thumbnail && ! strip_xmp && ! exif) return; modified = FALSE; - try { - Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open (filename); - g_assert (image.get() != 0); - - image->readMetadata(); - - /* Write down metadata from external file if supplied */ - if (exif && exif->external_exif_data) { - Exiv2::Image::AutoPtr ext_image = Exiv2::ImageFactory::open (exif->external_exif_data); - if (ext_image.get() != 0) { - ext_image->clearMetadata(); - ext_image->readMetadata(); - copy_metadata (image, ext_image); - modified = TRUE; - } - } - - Exiv2::ExifData &exifData = image->exifData(); - Exiv2::IptcData &iptcData = image->iptcData(); - - if (exif) { - if (exif->override_copyright) { - if (! exifData.empty()) { - exifData["Exif.Image.Copyright"] = exif->override_copyright; - modified = TRUE; - } - if (! iptcData.empty()) { - iptcData["Iptc.Application2.Copyright"] = exif->override_copyright; - modified = TRUE; - } - } + res = FALSE; - if (exif->timezone_shift != 0 && exif->override_datetime == (time_t) -1) { - if (! exifData.empty()) { - res = shift_exif_time (exifData, "Exif.Photo.DateTimeOriginal", exif->timezone_shift); - res = shift_exif_time (exifData, "Exif.Photo.DateTimeDigitized", exif->timezone_shift) || res; - res = shift_exif_time (exifData, "Exif.Image.DateTime", exif->timezone_shift) || res; - modified = TRUE; - } - if (! iptcData.empty()) { - res = shift_iptc_time (iptcData, "Iptc.Application2.DateCreated", "Iptc.Application2.TimeCreated", exif->timezone_shift); - res = shift_iptc_time (iptcData, "Iptc.Application2.DigitizationDate", "Iptc.Application2.DigitizationTime", exif->timezone_shift) || res; - if (res) - modified = TRUE; - } - } + metadata = gexiv2_metadata_new (); + if (! gexiv2_metadata_open_path (metadata, filename, &error)) { + log_error ("modify_exif: %s\n", error->message); + g_error_free (error); + /* gexiv2 cannot operate on empty, newly constructed object, bail out */ + g_object_unref (metadata); + return; + } - if (exif->override_datetime != (time_t) -1 && !exifData.empty()) { - if (! exifData.empty()) { - res = override_exif_time (exifData, "Exif.Photo.DateTimeOriginal", exif->override_datetime); - res = override_exif_time (exifData, "Exif.Photo.DateTimeDigitized", exif->override_datetime) || res; - res = override_exif_time (exifData, "Exif.Image.DateTime", exif->override_datetime) || res; - modified = TRUE; - } - if (! iptcData.empty()) { - res = override_iptc_time (iptcData, "Iptc.Application2.DateCreated", "Iptc.Application2.TimeCreated", exif->override_datetime); - res = override_iptc_time (iptcData, "Iptc.Application2.DigitizationDate", "Iptc.Application2.DigitizationTime", exif->override_datetime) || res; - if (res) - modified = TRUE; - } - } + /* write down metadata from external file if supplied */ + if (exif->external_exif_data) { + gexiv2_metadata_clear (metadata); + copy_metadata (metadata, filename, exif->external_exif_data); + modified = TRUE; + } - if (exif->override_aperture != -1) { - if (! exifData.empty()) { - exifData["Exif.Photo.FNumber"] = Exiv2::floatToRationalCast (exif->override_aperture); - if (exifData["Exif.Photo.ApertureValue"].count() > 0) - exifData["Exif.Photo.ApertureValue"] = Exiv2::floatToRationalCast (exif->override_aperture); - modified = TRUE; - } - } + if (exif->override_copyright) { + if (gexiv2_metadata_set_tag_string (metadata, "Exif.Image.Copyright", exif->override_copyright)) + modified = TRUE; + if (gexiv2_metadata_set_tag_string (metadata, "Iptc.Application2.Copyright", exif->override_copyright)) + modified = TRUE; + } - if (exif->override_focal_length != -1) { - if (! exifData.empty()) { - exifData["Exif.Photo.FocalLength"] = Exiv2::floatToRationalCast (exif->override_focal_length); - modified = TRUE; - } - } + if (exif->timezone_shift != 0 && exif->override_datetime == (time_t) -1) { + if (gexiv2_metadata_has_exif (metadata)) { + res = shift_exif_time (metadata, "Exif.Photo.DateTimeOriginal", exif->timezone_shift) || res; + res = shift_exif_time (metadata, "Exif.Photo.DateTimeDigitized", exif->timezone_shift) || res; + res = shift_exif_time (metadata, "Exif.Image.DateTime", exif->timezone_shift) || res; + res = gexiv2_metadata_clear_tag (metadata, "Exif.Image.TimeZoneOffset") || res; + res = gexiv2_metadata_clear_tag (metadata, "Exif.Image.PreviewDateTime") || res; + } + if (gexiv2_metadata_has_iptc (metadata)) { + res = gexiv2_metadata_clear_tag (metadata, "Iptc.Application2.DateCreated") || res; + res = gexiv2_metadata_clear_tag (metadata, "Iptc.Application2.TimeCreated") || res; + res = gexiv2_metadata_clear_tag (metadata, "Iptc.Application2.DigitizationDate") || res; + res = gexiv2_metadata_clear_tag (metadata, "Iptc.Application2.DigitizationTime") || res; + } + modified = res || modified; + } - if (exif->override_artist_name) { - if (! exifData.empty()) { - exifData["Exif.Image.Artist"] = exif->override_artist_name; - if (exifData["Exif.Photo.CameraOwnerName"].count() >= 1) - exifData["Exif.Photo.CameraOwnerName"] = exif->override_artist_name; - if (exifData["Exif.Canon.OwnerName"].count() >= 1) - exifData["Exif.Canon.OwnerName"] = exif->override_artist_name; - modified = TRUE; - } - } + if (exif->override_datetime != (time_t) -1) { + if (gexiv2_metadata_has_exif (metadata)) { + res = override_exif_time (metadata, "Exif.Photo.DateTimeOriginal", exif->override_datetime); + res = override_exif_time (metadata, "Exif.Photo.DateTimeDigitized", exif->override_datetime) || res; + res = override_exif_time (metadata, "Exif.Image.DateTime", exif->override_datetime) || res; } + if (gexiv2_metadata_has_iptc (metadata)) { + res = gexiv2_metadata_clear_tag (metadata, "Iptc.Application2.DateCreated") || res; + res = gexiv2_metadata_clear_tag (metadata, "Iptc.Application2.TimeCreated") || res; + res = gexiv2_metadata_clear_tag (metadata, "Iptc.Application2.DigitizationDate") || res; + res = gexiv2_metadata_clear_tag (metadata, "Iptc.Application2.DigitizationTime") || res; + } + modified = res || modified; + } + if (exif->override_aperture != -1) { + if (gexiv2_metadata_set_exif_tag_rational (metadata, "Exif.Photo.FNumber", exif->override_aperture * 1000000.0, 1000000)) + modified = TRUE; + if (gexiv2_metadata_set_exif_tag_rational (metadata, "Exif.Photo.ApertureValue", exif->override_aperture * 1000000.0, 1000000)) + modified = TRUE; + if (gexiv2_metadata_set_exif_tag_rational (metadata, "Exif.CanonSi.ApertureValue", exif->override_aperture * 1000000.0, 1000000)) + modified = TRUE; + } - if (strip_thumbnail && ! exifData.empty()) { -#ifdef HAVE_EXIFTHUMB - Exiv2::ExifThumb exifThumb(image->exifData()); - std::string thumbExt = exifThumb.extension(); -#else - std::string thumbExt = exifData.thumbnailExtension(); -#endif + if (exif->override_focal_length != -1) { + if (gexiv2_metadata_set_exif_tag_rational (metadata, "Exif.Photo.FocalLength", exif->override_focal_length * 1000000.0, 1000000)) + modified = TRUE; + } - if (! thumbExt.empty()) { -#ifdef HAVE_EXIFTHUMB - exifThumb.erase(); -#else - exifData.eraseThumbnail(); -#endif - modified = TRUE; - } - } + if (exif->override_artist_name) { + if (gexiv2_metadata_set_tag_string (metadata, "Exif.Image.Artist", exif->override_artist_name)) + modified = TRUE; + if (gexiv2_metadata_set_tag_string (metadata, "Exif.Photo.CameraOwnerName", exif->override_artist_name)) + modified = TRUE; + if (gexiv2_metadata_set_tag_string (metadata, "Exif.Canon.OwnerName", exif->override_artist_name)) + modified = TRUE; + if (gexiv2_metadata_set_tag_string (metadata, "Iptc.Application2.Byline", exif->override_artist_name)) + modified = TRUE; + } - if (strip_xmp) { - if (! image->xmpData().empty()) { - image->clearXmpData (); - modified = TRUE; - } - if (! image->xmpPacket().empty()) { - image->clearXmpPacket (); - modified = TRUE; - } - } + if (strip_thumbnail) { + gexiv2_metadata_erase_exif_thumbnail (metadata); + modified = TRUE; + } - if (modified) - image->writeMetadata(); + if (strip_xmp && gexiv2_metadata_has_xmp (metadata)) { + gexiv2_metadata_clear_xmp (metadata); + modified = TRUE; } - catch (Exiv2::AnyError& e) - { - log_error ("modify_exif: Caught Exiv2 exception: '%s'\n", e.what()); + + if (modified) { + if (! gexiv2_metadata_save_file (metadata, filename, &error)) { + log_error ("modify_exif: couldn't write metadata to '%s': %s\n", filename, error->message); + g_error_free (error); + } } + + g_object_unref (metadata); } diff --git a/src/jpeg-utils.h b/src/jpeg-utils.h index 767320a..b73576b 100644 --- a/src/jpeg-utils.h +++ b/src/jpeg-utils.h @@ -28,6 +28,7 @@ G_BEGIN_DECLS #define CROP_SIMPLE_SHAVE_AMOUNT 0 /* percent */ /* EXIF data known keys */ +/* Exiv2 Tag Reference can be found at http://exiv2.org/metadata.html */ #define EXIF_APERTURE "Exif.Photo.FNumber" #define EXIF_CAMERA_MODEL "Exif.Image.Model" #define EXIF_DATETIME "Exif.Photo.DateTimeOriginal" @@ -99,12 +100,12 @@ gboolean exif_has_key (ExifData *exif, const gchar *key); */ gboolean resize_image (const gchar *src, const gchar *dst, unsigned long size_x, unsigned long size_y, - int quality, - gboolean thumbnail, - gboolean autorotate, - gboolean hidpi_strict_dimensions, - ExifData *exif, - gchar *resize_opts); + int quality, + gboolean thumbnail, + gboolean autorotate, + gboolean hidpi_strict_dimensions, + ExifData *exif, + gchar *resize_opts); /* * get_image_sizes: retrieve image dimensions @@ -118,7 +119,7 @@ void get_image_sizes (const gchar *img, * calculate_sizes: calculate maximal image sizes within specified limits keeping aspect ratio */ void calculate_sizes (const unsigned long max_width, const unsigned long max_height, - unsigned long *width, unsigned long *height); + unsigned long *width, unsigned long *height); /* * modify_exif: - strip thumbnail stored in EXIF table |
