From 877d5b0c1a46d39d0b1951cbd9a062d752c4db8f Mon Sep 17 00:00:00 2001 From: Jacob Boerema Date: Fri, 18 Jul 2025 19:03:25 -0400 Subject: [PATCH] app, libgimp, libgimpbase: Add preference to allow saving and updating extra metadata Closes #5856 - provide a way to export images without adding or changing any metadata. Handles issue #3490 together with !2367. The latter makes sure that an image comment when present is favored over similar metadata tags. This commit makes sure that when you disable the preference to update metadata automatically, it does not synchronize the image comment with similar metadata tags (possibly overwriting other metadata), it does not update the modification date, and does not add or update software and change history metadata. This adds a metadata preference (enabled by default) that on export determines whether we add and update some non essential metadata. When this setting is disabled, we only touch the metadata that we cannot avoid (e.g. updating size, presence of thumbnail, etc.). --- app/config/gimpcoreconfig.c | 14 ++ app/config/gimpcoreconfig.h | 1 + app/config/gimprc-blurbs.h | 9 + app/dialogs/preferences-dialog.c | 3 + app/plug-in/gimppluginmanager-call.c | 1 + libgimp/gimp.c | 24 +++ libgimp/gimp.def | 1 + libgimp/gimp.h | 1 + libgimp/gimpimagemetadata-save.c | 250 ++++++++++++++------------- libgimpbase/gimpmetadata.h | 3 + libgimpbase/gimpprotocol.c | 8 + libgimpbase/gimpprotocol.h | 1 + 12 files changed, 200 insertions(+), 116 deletions(-) diff --git a/app/config/gimpcoreconfig.c b/app/config/gimpcoreconfig.c index 691b728a7e..a4591055d6 100644 --- a/app/config/gimpcoreconfig.c +++ b/app/config/gimpcoreconfig.c @@ -121,6 +121,7 @@ enum PROP_EXPORT_METADATA_EXIF, PROP_EXPORT_METADATA_XMP, PROP_EXPORT_METADATA_IPTC, + PROP_EXPORT_UPDATE_METADATA, PROP_DEBUG_POLICY, PROP_CHECK_UPDATES, PROP_CHECK_UPDATE_TIMESTAMP, @@ -793,6 +794,13 @@ gimp_core_config_class_init (GimpCoreConfigClass *klass) TRUE, GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_EXPORT_UPDATE_METADATA, + "export-update-metadata", + "Update metadata automatically", + EXPORT_UPDATE_METADATA_BLURB, + TRUE, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_ENUM (object_class, PROP_DEBUG_POLICY, "debug-policy", "Try generating backtrace upon errors", @@ -1194,6 +1202,9 @@ gimp_core_config_set_property (GObject *object, case PROP_EXPORT_METADATA_IPTC: core_config->export_metadata_iptc = g_value_get_boolean (value); break; + case PROP_EXPORT_UPDATE_METADATA: + core_config->export_update_metadata = g_value_get_boolean (value); + break; case PROP_DEBUG_POLICY: core_config->debug_policy = g_value_get_enum (value); break; @@ -1462,6 +1473,9 @@ gimp_core_config_get_property (GObject *object, case PROP_EXPORT_METADATA_IPTC: g_value_set_boolean (value, core_config->export_metadata_iptc); break; + case PROP_EXPORT_UPDATE_METADATA: + g_value_set_boolean (value, core_config->export_update_metadata); + break; case PROP_DEBUG_POLICY: g_value_set_enum (value, core_config->debug_policy); break; diff --git a/app/config/gimpcoreconfig.h b/app/config/gimpcoreconfig.h index 39543ae94b..134f447041 100644 --- a/app/config/gimpcoreconfig.h +++ b/app/config/gimpcoreconfig.h @@ -104,6 +104,7 @@ struct _GimpCoreConfig gboolean export_metadata_exif; gboolean export_metadata_xmp; gboolean export_metadata_iptc; + gboolean export_update_metadata; GimpDebugPolicy debug_policy; #ifdef G_OS_WIN32 GimpWin32PointerInputAPI win32_pointer_input_api; diff --git a/app/config/gimprc-blurbs.h b/app/config/gimprc-blurbs.h index bd143ec7b4..ca5fabe0f5 100644 --- a/app/config/gimprc-blurbs.h +++ b/app/config/gimprc-blurbs.h @@ -257,6 +257,15 @@ _("Export XMP metadata by default.") #define EXPORT_METADATA_IPTC_BLURB \ _("Export IPTC metadata by default.") +/* Translators: tooltip for configuration option (checkbox). + * It determines what metadata is updated when exporting. + */ +#define EXPORT_UPDATE_METADATA_BLURB \ +_("When enabled, add and update metadata automatically. When disabled, " \ + "only the minimum necessary metadata changes are made, without changing " \ + "modification date, synchronizing tags, or updating the software and " \ + "change history metadata.") + #define GENERATE_BACKTRACE_BLURB \ _("Try generating debug data for bug reporting when appropriate.") diff --git a/app/dialogs/preferences-dialog.c b/app/dialogs/preferences-dialog.c index f084405fcb..c46a03d090 100644 --- a/app/dialogs/preferences-dialog.c +++ b/app/dialogs/preferences-dialog.c @@ -1589,6 +1589,9 @@ prefs_dialog_new (Gimp *gimp, */ _("Export _IPTC metadata by default when available"), GTK_BOX (vbox2)); + button = prefs_check_button_add (object, "export-update-metadata", + _("Update metadata automatically"), + GTK_BOX (vbox2)); hbox = prefs_hint_box_new (GIMP_ICON_DIALOG_WARNING, _("Metadata can contain sensitive information.")); gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); diff --git a/app/plug-in/gimppluginmanager-call.c b/app/plug-in/gimppluginmanager-call.c index a736776bd2..73a497816f 100644 --- a/app/plug-in/gimppluginmanager-call.c +++ b/app/plug-in/gimppluginmanager-call.c @@ -242,6 +242,7 @@ gimp_plug_in_manager_call_run (GimpPlugInManager *manager, config.export_exif = core_config->export_metadata_exif; config.export_xmp = core_config->export_metadata_xmp; config.export_iptc = core_config->export_metadata_iptc; + config.update_metadata = core_config->export_update_metadata; config.default_display_id = display_id; config.app_name = (gchar *) g_get_application_name (); config.wm_class = (gchar *) gimp_get_program_class (manager->gimp); diff --git a/libgimp/gimp.c b/libgimp/gimp.c index 5c900aca5b..b768bc4295 100644 --- a/libgimp/gimp.c +++ b/libgimp/gimp.c @@ -129,6 +129,7 @@ static gboolean _export_exif = FALSE; static gboolean _export_xmp = FALSE; static gboolean _export_iptc = FALSE; static gboolean _export_thumbnail = TRUE; +static gboolean _update_metadata = TRUE; static gint32 _num_processors = 1; static GimpCheckSize _check_size = GIMP_CHECK_SIZE_MEDIUM_CHECKS; static GimpCheckType _check_type = GIMP_CHECK_TYPE_GRAY_CHECKS; @@ -747,6 +748,28 @@ gimp_export_thumbnail (void) return _export_thumbnail; } +/** + * gimp_update_metadata: + * + * Returns whether file plug-ins should update the + * image's metadata. + * + * Note that metadata that reflects the image characteristics + * will still be updated even if this is set to FALSE. This only + * concerns metadata changes that are nonessential, like setting + * GIMP in Exif.Image.Software, synchronizing the comment with its + * equivalent metadata tags, etc. + * + * Returns: TRUE if preferences are set to update the metadata. + * + * Since: 3.1 + **/ +gboolean +gimp_update_metadata (void) +{ + return _update_metadata; +} + /** * gimp_get_num_processors: * @@ -1090,6 +1113,7 @@ _gimp_config (GPConfig *config) _export_exif = config->export_exif ? TRUE : FALSE; _export_xmp = config->export_xmp ? TRUE : FALSE; _export_iptc = config->export_iptc ? TRUE : FALSE; + _update_metadata = config->update_metadata ? TRUE : FALSE; _export_comment = config->export_comment; _num_processors = config->num_processors; _default_display_id = config->default_display_id; diff --git a/libgimp/gimp.def b/libgimp/gimp.def index 836bfaa87e..789b84ae2b 100644 --- a/libgimp/gimp.def +++ b/libgimp/gimp.def @@ -1061,6 +1061,7 @@ EXPORTS gimp_tile_width gimp_trc_type_get_type gimp_unit_new + gimp_update_metadata gimp_user_time gimp_vector_load_procedure_extract_dimensions gimp_vector_load_procedure_get_type diff --git a/libgimp/gimp.h b/libgimp/gimp.h index 64391e8056..482bae2fe7 100644 --- a/libgimp/gimp.h +++ b/libgimp/gimp.h @@ -186,6 +186,7 @@ gboolean gimp_export_exif (void) G_GNUC_CONST; gboolean gimp_export_xmp (void) G_GNUC_CONST; gboolean gimp_export_iptc (void) G_GNUC_CONST; gboolean gimp_export_thumbnail (void) G_GNUC_CONST; +gboolean gimp_update_metadata (void) G_GNUC_CONST; gint gimp_get_num_processors (void) G_GNUC_CONST; GimpCheckSize gimp_check_size (void) G_GNUC_CONST; GimpCheckType gimp_check_type (void) G_GNUC_CONST; diff --git a/libgimp/gimpimagemetadata-save.c b/libgimp/gimpimagemetadata-save.c index 8f191053c0..ae384bc8b7 100644 --- a/libgimp/gimpimagemetadata-save.c +++ b/libgimp/gimpimagemetadata-save.c @@ -132,63 +132,69 @@ gimp_image_metadata_save_prepare (GimpImage *image, gimp_parasite_free (comment_parasite); } + if (! gimp_update_metadata ()) + *suggested_flags &= ~GIMP_METADATA_UPDATE; + /* Exif */ if (! gimp_export_exif () || ! gexiv2_metadata_has_exif (g2metadata)) *suggested_flags &= ~GIMP_METADATA_SAVE_EXIF; - if (comment) + if (gimp_update_metadata ()) { + if (comment) + { + gexiv2_metadata_try_set_tag_string (g2metadata, + "Exif.Photo.UserComment", + comment, &error); + if (error) + { + g_warning ("%s: failed to set metadata '%s': %s\n", + G_STRFUNC, "Exif.Photo.UserComment", error->message); + g_clear_error (&error); + } + + gexiv2_metadata_try_set_tag_string (g2metadata, + "Exif.Image.ImageDescription", + comment, &error); + if (error) + { + g_warning ("%s: failed to set metadata '%s': %s\n", + G_STRFUNC, "Exif.Image.ImageDescription", error->message); + g_clear_error (&error); + } + } + + g_snprintf (buffer, sizeof (buffer), + "%d:%02d:%02d %02d:%02d:%02d", + g_date_time_get_year (datetime), + g_date_time_get_month (datetime), + g_date_time_get_day_of_month (datetime), + g_date_time_get_hour (datetime), + g_date_time_get_minute (datetime), + g_date_time_get_second (datetime)); gexiv2_metadata_try_set_tag_string (g2metadata, - "Exif.Photo.UserComment", - comment, &error); + "Exif.Image.DateTime", + buffer, &error); if (error) { g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Exif.Photo.UserComment", error->message); + G_STRFUNC, "Exif.Image.DateTime", error->message); g_clear_error (&error); } gexiv2_metadata_try_set_tag_string (g2metadata, - "Exif.Image.ImageDescription", - comment, &error); + "Exif.Image.Software", + PACKAGE_STRING, &error); if (error) { g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Exif.Image.ImageDescription", error->message); + G_STRFUNC, "Exif.Image.Software", error->message); g_clear_error (&error); } } - g_snprintf (buffer, sizeof (buffer), - "%d:%02d:%02d %02d:%02d:%02d", - g_date_time_get_year (datetime), - g_date_time_get_month (datetime), - g_date_time_get_day_of_month (datetime), - g_date_time_get_hour (datetime), - g_date_time_get_minute (datetime), - g_date_time_get_second (datetime)); - gexiv2_metadata_try_set_tag_string (g2metadata, - "Exif.Image.DateTime", - buffer, &error); - if (error) - { - g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Exif.Image.DateTime", error->message); - g_clear_error (&error); - } - - gexiv2_metadata_try_set_tag_string (g2metadata, - "Exif.Image.Software", - PACKAGE_STRING, &error); - if (error) - { - g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Exif.Image.Software", error->message); - g_clear_error (&error); - } - gimp_metadata_set_pixel_size (metadata, image_width, image_height); @@ -212,26 +218,29 @@ gimp_image_metadata_save_prepare (GimpImage *image, g_clear_error (&error); } - /* XMP uses datetime in ISO 8601 format */ - datetime_buf = g_date_time_format (datetime, "%Y:%m:%dT%T\%:z"); + if (gimp_update_metadata ()) + { + /* XMP uses datetime in ISO 8601 format */ + datetime_buf = g_date_time_format (datetime, "%Y:%m:%dT%T\%:z"); - gexiv2_metadata_try_set_tag_string (g2metadata, - "Xmp.xmp.ModifyDate", - datetime_buf, &error); - if (error) - { - g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Xmp.xmp.ModifyDate", error->message); - g_clear_error (&error); - } - gexiv2_metadata_try_set_tag_string (g2metadata, - "Xmp.xmp.MetadataDate", - datetime_buf, &error); - if (error) - { - g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Xmp.xmp.MetadataDate", error->message); - g_clear_error (&error); + gexiv2_metadata_try_set_tag_string (g2metadata, + "Xmp.xmp.ModifyDate", + datetime_buf, &error); + if (error) + { + g_warning ("%s: failed to set metadata '%s': %s\n", + G_STRFUNC, "Xmp.xmp.ModifyDate", error->message); + g_clear_error (&error); + } + gexiv2_metadata_try_set_tag_string (g2metadata, + "Xmp.xmp.MetadataDate", + datetime_buf, &error); + if (error) + { + g_warning ("%s: failed to set metadata '%s': %s\n", + G_STRFUNC, "Xmp.xmp.MetadataDate", error->message); + g_clear_error (&error); + } } if (! g_strcmp0 (mime_type, "image/tiff")) @@ -260,14 +269,17 @@ gimp_image_metadata_save_prepare (GimpImage *image, g_clear_error (&error); } - gexiv2_metadata_try_set_tag_string (g2metadata, - "Xmp.tiff.DateTime", - datetime_buf, &error); - if (error) + if (gimp_update_metadata ()) { - g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Xmp.tiff.DateTime", error->message); - g_clear_error (&error); + gexiv2_metadata_try_set_tag_string (g2metadata, + "Xmp.tiff.DateTime", + datetime_buf, &error); + if (error) + { + g_warning ("%s: failed to set metadata '%s': %s\n", + G_STRFUNC, "Xmp.tiff.DateTime", error->message); + g_clear_error (&error); + } } } @@ -320,6 +332,9 @@ gimp_image_metadata_save_prepare (GimpImage *image, if (! gimp_export_thumbnail ()) *suggested_flags &= ~GIMP_METADATA_SAVE_THUMBNAIL; + + if (! gimp_update_metadata ()) + *suggested_flags &= ~GIMP_METADATA_UPDATE; } /* Color profile */ @@ -748,72 +763,75 @@ gimp_image_metadata_save_filter (GimpImage *image, GList *exclude_list = NULL; GList *list; - gettimeofday (&timer_usec, NULL); - timestamp_usec = ((gint64) timer_usec.tv_sec) * 1000000ll + - (gint64) timer_usec.tv_usec; - g_snprintf (ts, sizeof (ts), "%" G_GINT64_FORMAT, timestamp_usec); - - gimp_metadata_add_xmp_history (metadata, ""); - - gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata), - "Xmp.GIMP.TimeStamp", - ts, &code_error); - if (code_error) + if (gimp_update_metadata ()) { - g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Xmp.GIMP.TimeStamp", code_error->message); - g_clear_error (&code_error); - } + gettimeofday (&timer_usec, NULL); + timestamp_usec = ((gint64) timer_usec.tv_sec) * 1000000ll + + (gint64) timer_usec.tv_usec; + g_snprintf (ts, sizeof (ts), "%" G_GINT64_FORMAT, timestamp_usec); - gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata), - "Xmp.xmp.CreatorTool", - N_("GIMP"), &code_error); - if (code_error) - { - g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Xmp.xmp.CreatorTool", code_error->message); - g_clear_error (&code_error); - } + gimp_metadata_add_xmp_history (metadata, ""); - gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata), - "Xmp.GIMP.Version", - GIMP_VERSION, &code_error); - if (code_error) - { - g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Xmp.GIMP.Version", code_error->message); - g_clear_error (&code_error); - } + gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata), + "Xmp.GIMP.TimeStamp", + ts, &code_error); + if (code_error) + { + g_warning ("%s: failed to set metadata '%s': %s\n", + G_STRFUNC, "Xmp.GIMP.TimeStamp", code_error->message); + g_clear_error (&code_error); + } - gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata), - "Xmp.GIMP.API", - GIMP_API_VERSION, &code_error); - if (code_error) - { - g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Xmp.GIMP.API", code_error->message); - g_clear_error (&code_error); - } + gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata), + "Xmp.xmp.CreatorTool", + N_("GIMP"), &code_error); + if (code_error) + { + g_warning ("%s: failed to set metadata '%s': %s\n", + G_STRFUNC, "Xmp.xmp.CreatorTool", code_error->message); + g_clear_error (&code_error); + } - gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata), - "Xmp.GIMP.Platform", + gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata), + "Xmp.GIMP.Version", + GIMP_VERSION, &code_error); + if (code_error) + { + g_warning ("%s: failed to set metadata '%s': %s\n", + G_STRFUNC, "Xmp.GIMP.Version", code_error->message); + g_clear_error (&code_error); + } + + gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata), + "Xmp.GIMP.API", + GIMP_API_VERSION, &code_error); + if (code_error) + { + g_warning ("%s: failed to set metadata '%s': %s\n", + G_STRFUNC, "Xmp.GIMP.API", code_error->message); + g_clear_error (&code_error); + } + + gexiv2_metadata_try_set_tag_string (GEXIV2_METADATA (metadata), + "Xmp.GIMP.Platform", #if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) - "Windows", + "Windows", #elif defined(__linux__) - "Linux", + "Linux", #elif defined(__APPLE__) && defined(__MACH__) - "Mac OS", + "Mac OS", #elif defined(unix) || defined(__unix__) || defined(__unix) - "Unix", + "Unix", #else - "Unknown", + "Unknown", #endif - &code_error); - if (code_error) - { - g_warning ("%s: failed to set metadata '%s': %s\n", - G_STRFUNC, "Xmp.GIMP.Platform", code_error->message); - g_clear_error (&code_error); + &code_error); + if (code_error) + { + g_warning ("%s: failed to set metadata '%s': %s\n", + G_STRFUNC, "Xmp.GIMP.Platform", code_error->message); + g_clear_error (&code_error); + } } xmp_data = gexiv2_metadata_get_xmp_tags (GEXIV2_METADATA (metadata)); diff --git a/libgimpbase/gimpmetadata.h b/libgimpbase/gimpmetadata.h index 02860c907b..5f37292c9d 100644 --- a/libgimpbase/gimpmetadata.h +++ b/libgimpbase/gimpmetadata.h @@ -65,6 +65,8 @@ typedef enum * Since: 2.10.10 * @GIMP_METADATA_SAVE_COMMENT: Save the image's comment * Since: 3.0 + * @GIMP_METADATA_UPDATE: Update metadata automatically + * Since: 3.1 * @GIMP_METADATA_SAVE_ALL: Save all of the above * * What kinds of metadata to save when exporting images. @@ -77,6 +79,7 @@ typedef enum GIMP_METADATA_SAVE_THUMBNAIL = 1 << 3, GIMP_METADATA_SAVE_COLOR_PROFILE = 1 << 4, GIMP_METADATA_SAVE_COMMENT = 1 << 5, + GIMP_METADATA_UPDATE = 1 << 6, GIMP_METADATA_SAVE_ALL = 0xffffffff } GimpMetadataSaveFlags; diff --git a/libgimpbase/gimpprotocol.c b/libgimpbase/gimpprotocol.c index 5d740997d0..58821a6dad 100644 --- a/libgimpbase/gimpprotocol.c +++ b/libgimpbase/gimpprotocol.c @@ -540,6 +540,10 @@ _gp_config_read (GIOChannel *channel, (guint8 *) &config->export_iptc, 1, user_data)) goto cleanup; + if (! _gimp_wire_read_int8 (channel, + (guint8 *) &config->update_metadata, 1, + user_data)) + goto cleanup; if (! _gimp_wire_read_int32 (channel, (guint32 *) &config->default_display_id, 1, user_data)) @@ -667,6 +671,10 @@ _gp_config_write (GIOChannel *channel, (const guint8 *) &config->export_iptc, 1, user_data)) return; + if (! _gimp_wire_write_int8 (channel, + (const guint8 *) &config->update_metadata, 1, + user_data)) + return; if (! _gimp_wire_write_int32 (channel, (const guint32 *) &config->default_display_id, 1, user_data)) diff --git a/libgimpbase/gimpprotocol.h b/libgimpbase/gimpprotocol.h index c5427991f5..8944805d9b 100644 --- a/libgimpbase/gimpprotocol.h +++ b/libgimpbase/gimpprotocol.h @@ -130,6 +130,7 @@ struct _GPConfig gint8 export_exif; gint8 export_xmp; gint8 export_iptc; + gint8 update_metadata; gint32 default_display_id; gchar *app_name; gchar *wm_class;