/* Cataract - Static web photo gallery generator * Copyright (C) 2008 Tomas Bzatek * * 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 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define _XOPEN_SOURCE #include "config.h" #include #include #include #include #include #ifdef HAVE_IMAGEMAGICK_7 # include #else # include #endif #include "jpeg-utils.h" #include "gallery-utils.h" struct ExifDataPrivate { GExiv2Metadata *metadata; }; /* * Thread-safe ImageMagick and exiv2 libraries initialization and cleanup */ void init_jpeg_utils (void) { MagickWandGenesis(); g_assert (gexiv2_initialize () == TRUE); } void destroy_jpeg_utils (void) { MagickWandTerminus(); } static void shift_time (struct tm *tm, int offset_min) { time_t t; if (offset_min != 0) { /* FIXME: converting between time formats could make some data lost, better to operate over struct tm directly */ t = mktime (tm); if (t == (time_t) -1) { log_error ("Cannot shift time %p by %d minutes\n", tm, offset_min); return; } t += offset_min * 60; localtime_r (&t, tm); } } static struct tm * parse_exif_date (const char *str) { struct tm *tm; char *res; tm = (struct tm *) g_malloc0 (sizeof (struct tm)); res = strptime (str, "%Y:%m:%d %H:%M:%S", tm); if (res == NULL || *res != '\0') return NULL; mktime (tm); if (tm->tm_isdst) shift_time (tm, -60); 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, const gchar *format_string) { char conv[1024]; memset (&conv, 0, sizeof(conv)); if (strftime (&conv[0], sizeof(conv), format_string ? format_string : "%Y:%m:%d %H:%M:%S", tm)) return g_strdup (&conv[0]); return NULL; } /* * EXIF and IPTC info retrieval, keeps the source file open until freed */ ExifData * read_exif (const gchar *filename) { ExifData *data; GError *error = NULL; g_return_val_if_fail (filename != NULL, NULL); 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; } return data; } ExifData * exif_data_new_empty () { ExifData *data; data = (ExifData*) g_malloc0 (sizeof (ExifData)); data->priv = (ExifDataPrivate*) g_malloc0 (sizeof (ExifDataPrivate)); return data; } void exif_data_free (ExifData *data) { if (data) { g_free (data->override_copyright); g_free (data->external_exif_data); g_free (data->override_artist_name); g_clear_object (&data->priv->metadata); g_free (data->priv); g_free (data); } } static const gchar * get_real_key_name (const gchar *key) { struct StrKeyPair { const gchar *from; const gchar *to; }; static const struct StrKeyPair conv[] = { { EXIF_CANON_CAMERA_TEMP, "Exif.CanonSi.0x000c" }, }; guint i; for (i = 0; i < G_N_ELEMENTS (conv); i++) if (g_str_equal (conv[i].from, key)) return conv[i].to; return key; } /* * Retrieves value of the specified key or NULL if the key does not exist. * The key argument belongs to Exiv2 namespace - see https://exiv2.org/metadata.html */ gchar * get_exif_data (ExifData *exif, const gchar *key) { g_return_val_if_fail (exif != NULL, NULL); g_return_val_if_fail (key != NULL, NULL); key = get_real_key_name (key); 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_equal (key, EXIF_ARTIST) && exif->override_artist_name) return g_strdup (exif->override_artist_name); if (g_str_equal (key, EXIF_APERTURE) && exif->override_aperture != -1) return g_strdup_printf ("%f", exif->override_aperture); 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 * 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); 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); } if (g_str_equal (key, EXIF_EXPOSURE)) { gint nom, den; 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)) { 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)) { 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)) { 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 (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_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; } } g_free (val); } /* fall back to plain value retrieval */ return get_exif_data (exif, key); } /* * Returns TRUE if the image contains the key specified */ gboolean exif_has_key (ExifData *exif, const gchar *key) { g_return_val_if_fail (exif != NULL, FALSE); g_return_val_if_fail (key != NULL, FALSE); key = get_real_key_name (key); 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; } 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; return gexiv2_metadata_has_tag (exif->priv->metadata, key); } static void autorotate_image (MagickWand *magick_wand) { MagickBooleanType b; PixelWand *pixel_wand; ExceptionType severity; gchar *description; pixel_wand = NewPixelWand (); b = PixelSetColor (pixel_wand, "#000000"); if (b == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("autorotate_image: Error creating pixel wand: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); } b = MagickTrue; switch (MagickGetImageOrientation (magick_wand)) { case TopRightOrientation: b = MagickFlopImage (magick_wand); break; case BottomRightOrientation: b = MagickRotateImage (magick_wand, pixel_wand, 180.0); break; case BottomLeftOrientation: b = MagickFlipImage (magick_wand); break; case LeftTopOrientation: b = MagickTransposeImage (magick_wand); break; case RightTopOrientation: b = MagickRotateImage (magick_wand, pixel_wand, 90.0); break; case RightBottomOrientation: b = MagickTransverseImage (magick_wand); break; case LeftBottomOrientation: b = MagickRotateImage (magick_wand, pixel_wand, 270.0); break; default: break; } if (b == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("autorotate_image: Error rotating image: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); } b = MagickSetImageOrientation (magick_wand, TopLeftOrientation); if (b == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("autorotate_image: Error saving orientation: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); } DestroyPixelWand (pixel_wand); } static gchar ** parse_cmd_args (const gchar *resize_opts, const gchar *prepend, const gchar *append, const gchar *file_in, const gchar *file_out, unsigned long size_x, unsigned long size_y) { gchar *in; gchar **s; gchar *f; in = g_strdup_printf ("%s %s %s %s %s", prepend ? prepend : "", file_in ? file_in : "", resize_opts, file_out ? file_out : "", append ? append : ""); while (g_strstr_len (in, -1, "${WIDTH}")) { f = g_strdup_printf ("%lu", size_x); str_replace (&in, "${WIDTH}", f); g_free (f); } while (g_strstr_len (in, -1, "${HEIGHT}")) { f = g_strdup_printf ("%lu", size_y); str_replace (&in, "${HEIGHT}", f); g_free (f); } /* ImageMagick doesn't like empty elements */ str_trim_inside (&in); s = g_strsplit (in, " ", -1); g_free (in); return s; } /* * resize_image: resize image pointed by src and save result to dst */ 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) { MagickWand *magick_wand; ImageInfo *image_info; ExceptionInfo *exception_info; ExceptionType severity; unsigned long w, h; unsigned long new_w, new_h; double source_aspect, target_aspect; gchar *description; gchar **cmd_args; gchar *res_id = NULL; gchar *mpr_res_id; g_assert (src != NULL); g_assert (dst != NULL); /* Read an image. */ magick_wand = NewMagickWand(); if (MagickReadImage (magick_wand, src) == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("Error reading image: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); DestroyMagickWand (magick_wand); return FALSE; } if (autorotate) autorotate_image (magick_wand); /* Don't resize if smaller than desired size */ if (hidpi_strict_dimensions || MagickGetImageWidth (magick_wand) > size_x || MagickGetImageHeight (magick_wand) > size_y || exif->shave_amount > 0) { /* Shave borders if requested */ if (exif->shave_amount > 0) MagickShaveImage (magick_wand, exif->shave_amount, exif->shave_amount); /* Prepare image before resizing */ if (thumbnail) { if (exif->thumbnail_crop_style != CROP_STYLE_NORMAL) { w = MagickGetImageWidth (magick_wand); h = MagickGetImageHeight (magick_wand); new_w = w; new_h = h; if (exif->thumbnail_crop_style == CROP_STYLE_SQUARED) { new_w = MAX (w, h) * CROP_SIMPLE_SHAVE_AMOUNT / 100; new_w = MIN (w - 2*new_w, h - 2*new_w); new_h = new_w; } if (exif->thumbnail_crop_style == CROP_STYLE_FIXED) { source_aspect = (double) w / (double) h; target_aspect = (double) size_x / (double) size_y; if (target_aspect >= source_aspect) { new_w = w; new_h = (int) ((double) w / target_aspect); } else { new_w = (int) ((double) h * target_aspect); new_h = h; } new_w = (int) ((double) new_w * (double) (100 - CROP_SIMPLE_SHAVE_AMOUNT) / 100); new_h = (int) ((double) new_h * (double) (100 - CROP_SIMPLE_SHAVE_AMOUNT) / 100); } switch (exif->thumbnail_crop_hint) { case CROP_HINT_UNDEFINED: case CROP_HINT_CENTER: MagickCropImage (magick_wand, new_w, new_h, (w - new_w) / 2, (h - new_h) / 2); break; case CROP_HINT_LEFT: MagickCropImage (magick_wand, new_w, new_h, 0, (h - new_h) / 2); break; case CROP_HINT_RIGHT: MagickCropImage (magick_wand, new_w, new_h, w - new_w, (h - new_h) / 2); break; case CROP_HINT_TOP: MagickCropImage (magick_wand, new_w, new_h, (w - new_w) / 2, 0); break; case CROP_HINT_BOTTOM: MagickCropImage (magick_wand, new_w, new_h, (w - new_w) / 2, h - new_h); break; } } } /* Shave the source image to match exact dimensions after resize */ if (hidpi_strict_dimensions && (! thumbnail || exif->thumbnail_crop_style == CROP_STYLE_NORMAL)) { w = MagickGetImageWidth (magick_wand); h = MagickGetImageHeight (magick_wand); source_aspect = (double) w / (double) h; target_aspect = (double) size_x / (double) size_y; if (source_aspect != target_aspect) { if (target_aspect >= source_aspect) { new_w = w; new_h = (double) w / target_aspect; } else { new_w = (double) h * target_aspect; new_h = h; } MagickCropImage (magick_wand, new_w, new_h, (w - new_w) / 2, (h - new_h) / 2); g_warn_if_fail (MagickGetImageWidth (magick_wand) == new_w); g_warn_if_fail (MagickGetImageHeight (magick_wand) == new_h); } } if (resize_opts == NULL) { /* Perform internal resizing */ /* Note: MagickResizeImage() does no aspect correction, stretching the image to the required dimensions */ if (thumbnail) { MagickThumbnailImage (magick_wand, size_x, size_y); } else { #ifdef HAVE_IMAGEMAGICK_7 MagickResizeImage (magick_wand, size_x, size_y, LanczosFilter); #else MagickResizeImage (magick_wand, size_x, size_y, LanczosFilter, 1.0); #endif } } else { /* Perform resizing through ImageMagick commandline parser */ res_id = g_strdup_printf ("cgg_resize_image_%p", g_thread_self ()); mpr_res_id = g_strdup_printf ("mpr:%s", res_id); if (MagickWriteImage (magick_wand, mpr_res_id) == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("Error writing mpr image: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); DestroyMagickWand (magick_wand); g_free (res_id); g_free (mpr_res_id); return FALSE; } ClearMagickWand (magick_wand); cmd_args = parse_cmd_args (resize_opts, "convert", NULL, mpr_res_id, mpr_res_id, size_x, size_y); image_info = AcquireImageInfo (); g_assert (image_info != NULL); exception_info = AcquireExceptionInfo (); g_assert (exception_info != NULL); if (MagickCommandGenesis (image_info, ConvertImageCommand, g_strv_length (cmd_args), cmd_args, NULL, exception_info) == MagickFalse) { /* MagickCommandGenesis() should've printed verbose error message */ DestroyImageInfo (image_info); DestroyExceptionInfo (exception_info); DestroyMagickWand (magick_wand); g_free (res_id); g_free (mpr_res_id); return FALSE; } DestroyImageInfo (image_info); DestroyExceptionInfo (exception_info); g_strfreev (cmd_args); if (MagickReadImage (magick_wand, mpr_res_id) == MagickFalse) { description = MagickGetException (magick_wand, &severity); printf ("Error reading mpr image: %s %s %ld %s\n", GetMagickModule(), description); MagickRelinquishMemory (description); DestroyMagickWand (magick_wand); g_free (res_id); g_free (mpr_res_id); return FALSE; } g_free (mpr_res_id); } } if (thumbnail) { /* FIXME: this strips image ICC profile, should do proper conversion first */ MagickStripImage (magick_wand); } if ((int) MagickGetImageCompressionQuality (magick_wand) != quality) MagickSetImageCompressionQuality (magick_wand, quality); /* Write the image and destroy it. */ if (MagickWriteImage (magick_wand, dst) == MagickFalse) { description = MagickGetException (magick_wand, &severity); log_error ("Error writing image: %s %s %ld %s\n", GetMagickModule(), description); DeleteImageRegistry (res_id); g_free (res_id); return FALSE; } if (res_id) { /* This is potentially dangerous operation - modifying ImageMagick's internal image registry */ DeleteImageRegistry (res_id); g_free (res_id); } magick_wand = DestroyMagickWand (magick_wand); return TRUE; } /* * get_image_sizes: retrieve image dimensions */ void get_image_sizes (const gchar *img, unsigned long *width, unsigned long *height, int *quality, gboolean autorotate) { MagickWand *magick_wand; MagickBooleanType b; ExceptionType severity; gchar *description; *width = 0; *height = 0; if (quality) *quality = -1; g_assert (img != NULL); /* Read an image. */ magick_wand = NewMagickWand(); if (autorotate) b = MagickReadImage (magick_wand, img); else b = MagickPingImage (magick_wand, img); if (b == MagickFalse) { description = MagickGetException (magick_wand, &severity); /* -- make it silent log_error ("Error reading image info: %s %s %ld %s\n", GetMagickModule(), description); */ MagickRelinquishMemory (description); DestroyMagickWand (magick_wand); return; } if (autorotate) autorotate_image (magick_wand); *width = MagickGetImageWidth (magick_wand); *height = MagickGetImageHeight (magick_wand); if (quality) *quality = (int) MagickGetImageCompressionQuality (magick_wand); DestroyMagickWand (magick_wand); } /* * 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) { if (max_width > *width && max_height > *height) return; double max_ratio = (double) max_width / (double) max_height; double real_ratio = (double) *width / (double) *height; if (*width > *height && max_ratio <= real_ratio) { *height = (unsigned long) (max_width / real_ratio); *width = max_width; } else { *width = (unsigned long) (max_height * real_ratio); *height = max_height; } } static gboolean shift_exif_time (GExiv2Metadata *metadata, const char *key, int amount) { struct tm *tt; gchar *s; gchar *st; 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) res = gexiv2_metadata_set_tag_string (metadata, key, st); g_free (st); g_free (tt); } } g_free (s); return res; } static gboolean override_exif_time (GExiv2Metadata *metadata, const char *key, time_t datetime) { struct tm tt = {0}; gchar *s; gchar *st; gboolean res = FALSE; s = gexiv2_metadata_get_tag_string (metadata, key); if (s && strlen (s) > 0) { localtime_r (&datetime, &tt); st = format_exif_time (&tt, NULL); if (st) res = gexiv2_metadata_set_tag_string (metadata, key, st); g_free (st); } g_free (s); 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 far from complete. */ static const gchar *keep_source_tags[] = { "Exif.Image.ImageWidth", "Exif.Image.ImageHeight", "Exif.Image.ImageLength", "Exif.Image.Orientation", "Exif.Image.XResolution", "Exif.Image.YResolution", "Exif.Image.ResolutionUnit", "Exif.Image.Compression", "Exif.Image.BitsPerSample", "Exif.Image.SamplesPerPixel", "Exif.Image.JPEGTables", "Exif.Image.JPEGProc", "Exif.Image.JPEGInterchangeFormat", "Exif.Image.JPEGInterchangeFormatLength", "Exif.Image.JPEGRestartInterval", "Exif.Image.JPEGLosslessPredictors", "Exif.Image.JPEGPointTransforms", "Exif.Image.JPEGQTables", "Exif.Image.JPEGDCTables", "Exif.Image.JPEGACTables", "Exif.Image.YCbCrCoefficients", "Exif.Image.YCbCrSubSampling", "Exif.Image.YCbCrPositioning", "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 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; /* 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 (GExiv2Metadata *metadata, const gchar *source_img, const gchar *external_img) { 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 */ } 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; } 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); } /* * modify_exif: - strip thumbnail stored in EXIF table * - write down overriden keys */ 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 && ! strip_xmp && ! exif) return; modified = FALSE; res = FALSE; 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; } /* 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_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->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_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 (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 (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_thumbnail) { gexiv2_metadata_erase_exif_thumbnail (metadata); modified = TRUE; } if (strip_xmp && gexiv2_metadata_has_xmp (metadata)) { gexiv2_metadata_clear_xmp (metadata); modified = TRUE; } 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); }