[gimp] Metadata (Exif, XMP) export in HEIF plug-in



commit 76db57e73841581fe210804bd9001e1957af6329
Author: Daniel Novomesky <dnovomesky gmail com>
Date:   Thu Sep 17 13:48:03 2020 +0200

    Metadata (Exif, XMP) export in HEIF plug-in
    
    Exif metadata export is built only with GExiv2 0.12.2+ because gexiv2_metadata_get_exif_data is used.

 plug-ins/common/file-heif.c | 262 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 254 insertions(+), 8 deletions(-)
---
diff --git a/plug-ins/common/file-heif.c b/plug-ins/common/file-heif.c
index 29276d875c..27bae54517 100644
--- a/plug-ins/common/file-heif.c
+++ b/plug-ins/common/file-heif.c
@@ -21,6 +21,7 @@
 #include <libheif/heif.h>
 #include <lcms2.h>
 #include <gexiv2/gexiv2.h>
+#include <sys/time.h>
 
 #include <libgimp/gimp.h>
 #include <libgimp/gimpui.h>
@@ -33,6 +34,11 @@
 #define SAVE_PROC_AV1  "file-heif-av1-save"
 #define PLUG_IN_BINARY "file-heif"
 
+typedef struct
+{
+  gchar *tag;
+  gint  type;
+} XmpStructs;
 
 typedef struct _Heif      Heif;
 typedef struct _HeifClass HeifClass;
@@ -91,7 +97,8 @@ static gboolean         save_image            (GFile                        *fil
                                                GimpDrawable                 *drawable,
                                                GObject                      *config,
                                                GError                      **error,
-                                               enum heif_compression_format  compression);
+                                               enum heif_compression_format  compression,
+                                               GimpMetadata                 *metadata);
 
 static gboolean         load_dialog           (struct heif_context  *heif,
                                                uint32_t             *selected_image);
@@ -238,6 +245,18 @@ heif_create_procedure (GimpPlugIn  *plug_in,
                          "Bit depth of exported image",
                          8, 12, 8,
                          G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_BOOLEAN (procedure, "save-exif",
+                             "Save Exif",
+                             "Toggle saving Exif data",
+                             gimp_export_exif (),
+                             G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_BOOLEAN (procedure, "save-xmp",
+                             "Save XMP",
+                             "Toggle saving XMP data",
+                             gimp_export_xmp (),
+                             G_PARAM_READWRITE);
     }
 #if LIBHEIF_HAVE_VERSION(1,8,0)
   else if (! strcmp (name, SAVE_PROC_AV1))
@@ -289,6 +308,18 @@ heif_create_procedure (GimpPlugIn  *plug_in,
                          "Bit depth of exported image",
                          8, 12, 8,
                          G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_BOOLEAN (procedure, "save-exif",
+                             "Save Exif",
+                             "Toggle saving Exif data",
+                             gimp_export_exif (),
+                             G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_BOOLEAN (procedure, "save-xmp",
+                             "Save XMP",
+                             "Toggle saving XMP data",
+                             gimp_export_xmp (),
+                             G_PARAM_READWRITE);
     }
 #endif
   return procedure;
@@ -342,14 +373,15 @@ heif_save (GimpProcedure        *procedure,
   GimpProcedureConfig *config;
   GimpPDBStatusType    status = GIMP_PDB_SUCCESS;
   GimpExportReturn     export = GIMP_EXPORT_CANCEL;
+  GimpMetadata        *metadata;
   GError              *error  = NULL;
 
   INIT_I18N ();
   gegl_init (NULL, NULL);
 
   config = gimp_procedure_create_config (procedure);
-  gimp_procedure_config_begin_export (config, image, run_mode,
-                                      args, "image/heif");
+  metadata = gimp_procedure_config_begin_export (config, image, run_mode,
+                                                 args, "image/heif");
 
   switch (run_mode)
     {
@@ -390,7 +422,7 @@ heif_save (GimpProcedure        *procedure,
   if (status == GIMP_PDB_SUCCESS)
     {
       if (! save_image (file, image, drawables[0], G_OBJECT (config),
-                        &error, heif_compression_HEVC))
+                        &error, heif_compression_HEVC, metadata))
         {
           status = GIMP_PDB_EXECUTION_ERROR;
         }
@@ -422,14 +454,15 @@ heif_av1_save (GimpProcedure        *procedure,
   GimpProcedureConfig *config;
   GimpPDBStatusType    status = GIMP_PDB_SUCCESS;
   GimpExportReturn     export = GIMP_EXPORT_CANCEL;
+  GimpMetadata        *metadata;
   GError              *error  = NULL;
 
   INIT_I18N ();
   gegl_init (NULL, NULL);
 
   config = gimp_procedure_create_config (procedure);
-  gimp_procedure_config_begin_export (config, image, run_mode,
-                                      args, "image/avif");
+  metadata = gimp_procedure_config_begin_export (config, image, run_mode,
+                                                 args, "image/avif");
 
   switch (run_mode)
     {
@@ -470,7 +503,7 @@ heif_av1_save (GimpProcedure        *procedure,
   if (status == GIMP_PDB_SUCCESS)
     {
       if (! save_image (file, image, drawables[0], G_OBJECT (config),
-                        &error, heif_compression_AV1))
+                        &error, heif_compression_AV1, metadata))
         {
           status = GIMP_PDB_EXECUTION_ERROR;
         }
@@ -1240,6 +1273,30 @@ load_image (GFile              *file,
   return image;
 }
 
+static void
+heifplugin_image_metadata_copy_tag (GExiv2Metadata *src,
+                                    GExiv2Metadata *dest,
+                                    const gchar    *tag)
+{
+  gchar **values = gexiv2_metadata_get_tag_multiple (src, tag);
+
+  if (values)
+    {
+      gexiv2_metadata_set_tag_multiple (dest, tag, (const gchar **) values);
+      g_strfreev (values);
+    }
+  else
+    {
+      gchar *value = gexiv2_metadata_get_tag_string (src, tag);
+
+      if (value)
+        {
+          gexiv2_metadata_set_tag_string (dest, tag, value);
+          g_free (value);
+        }
+    }
+}
+
 static struct heif_error
 write_callback (struct heif_context *ctx,
                 const void          *data,
@@ -1269,7 +1326,8 @@ save_image (GFile                        *file,
             GimpDrawable                 *drawable,
             GObject                      *config,
             GError                      **error,
-            enum heif_compression_format  compression)
+            enum heif_compression_format  compression,
+            GimpMetadata                 *metadata)
 {
   struct heif_image        *h_image = NULL;
   struct heif_context      *context = heif_context_alloc ();
@@ -1292,6 +1350,10 @@ save_image (GFile                        *file,
   gint                      quality;
   gboolean                  save_profile;
   gint                      save_bit_depth = 8;
+#if GEXIV2_CHECK_VERSION(0, 12, 2)
+  gboolean                  save_exif = FALSE;
+#endif
+  gboolean                  save_xmp = FALSE;
 
   if (!context)
     {
@@ -1307,6 +1369,10 @@ save_image (GFile                        *file,
                 "save-bit-depth",     &save_bit_depth,
 #endif
                 "save-color-profile", &save_profile,
+#if GEXIV2_CHECK_VERSION(0, 12, 2)
+                "save-exif", &save_exif,
+#endif
+                "save-xmp", &save_xmp,
                 NULL);
 
   gimp_progress_init_printf (_("Exporting '%s'"),
@@ -1600,6 +1666,174 @@ save_image (GFile                        *file,
       return FALSE;
     }
 
+  /*  EXIF metadata  */
+#if GEXIV2_CHECK_VERSION(0, 12, 2)
+  if (save_exif && metadata)
+    {
+      if (gexiv2_metadata_get_supports_exif (GEXIV2_METADATA (metadata)) &&
+          gexiv2_metadata_has_exif (GEXIV2_METADATA (metadata)))
+        {
+          GimpMetadata   *new_exif_metadata = gimp_metadata_new ();
+          GExiv2Metadata *new_gexiv2metadata = GEXIV2_METADATA (new_exif_metadata);
+          GBytes         *raw_exif_data;
+          gchar         **exif_data = gexiv2_metadata_get_exif_tags (GEXIV2_METADATA (metadata));
+          guint           i;
+
+          gexiv2_metadata_clear_exif (new_gexiv2metadata);
+
+          for (i = 0; exif_data[i] != NULL; i++)
+            {
+              if (! gexiv2_metadata_has_tag (new_gexiv2metadata, exif_data[i]) &&
+                  gimp_metadata_is_tag_supported (exif_data[i], "image/heif"))
+                {
+                  heifplugin_image_metadata_copy_tag (GEXIV2_METADATA (metadata),
+                                                      new_gexiv2metadata,
+                                                      exif_data[i]);
+                }
+            }
+
+          g_strfreev (exif_data);
+
+          raw_exif_data = gexiv2_metadata_get_exif_data (new_gexiv2metadata, GEXIV2_BYTE_ORDER_LITTLE, 
error);
+          if (raw_exif_data)
+            {
+              gsize exif_size = 0;
+              gconstpointer exif_buffer = g_bytes_get_data (raw_exif_data, &exif_size);
+
+              if (exif_size >= 4)
+                {
+                  err = heif_context_add_exif_metadata (context, handle,
+                                                        exif_buffer, exif_size);
+                  if (err.code != 0)
+                    {
+                      g_printerr ("Failed to save EXIF metadata: %s", err.message);
+                    }
+                }
+              g_bytes_unref (raw_exif_data);
+            }
+          else
+            {
+              if (error && *error)
+                {
+                  g_printerr ("%s: error preparing EXIF metadata: %s",
+                              G_STRFUNC, (*error)->message);
+                  g_clear_error (error);
+                }
+            }
+
+          g_object_unref (new_exif_metadata);
+        }
+    }
+#endif
+
+  /*  XMP metadata  */
+  if (save_xmp && metadata)
+    {
+      if (gexiv2_metadata_get_supports_xmp (GEXIV2_METADATA (metadata)) &&
+          gexiv2_metadata_has_xmp (GEXIV2_METADATA (metadata)))
+        {
+          GimpMetadata   *new_metadata = gimp_metadata_new ();
+          GExiv2Metadata *new_g2metadata = GEXIV2_METADATA (new_metadata);
+          guint           i;
+
+          static const XmpStructs structlist[] =
+          {
+            { "Xmp.iptcExt.LocationCreated", GEXIV2_STRUCTURE_XA_BAG },
+            { "Xmp.iptcExt.LocationShown",   GEXIV2_STRUCTURE_XA_BAG },
+            { "Xmp.iptcExt.ArtworkOrObject", GEXIV2_STRUCTURE_XA_BAG },
+            { "Xmp.iptcExt.RegistryId",      GEXIV2_STRUCTURE_XA_BAG },
+            { "Xmp.xmpMM.History",           GEXIV2_STRUCTURE_XA_SEQ },
+            { "Xmp.plus.ImageSupplier",      GEXIV2_STRUCTURE_XA_SEQ },
+            { "Xmp.plus.ImageCreator",       GEXIV2_STRUCTURE_XA_SEQ },
+            { "Xmp.plus.CopyrightOwner",     GEXIV2_STRUCTURE_XA_SEQ },
+            { "Xmp.plus.Licensor",           GEXIV2_STRUCTURE_XA_SEQ }
+          };
+
+          gchar         **xmp_data;
+          struct timeval  timer_usec;
+          gint64          timestamp_usec;
+          gchar           ts[128];
+          gchar          *xmp_packet;
+
+          gexiv2_metadata_clear_xmp (new_g2metadata);
+
+          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_set_tag_string (GEXIV2_METADATA (metadata),
+                                          "Xmp.GIMP.TimeStamp",
+                                          ts);
+
+          gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
+                                          "Xmp.xmp.CreatorTool",
+                                          "GIMP");
+
+          gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
+                                          "Xmp.GIMP.Version",
+                                          GIMP_VERSION);
+
+          gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
+                                          "Xmp.GIMP.API",
+                                          GIMP_API_VERSION);
+          gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
+                                          "Xmp.GIMP.Platform",
+#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
+                                          "Windows"
+#elif defined(__linux__)
+                                          "Linux"
+#elif defined(__APPLE__) && defined(__MACH__)
+                                          "Mac OS"
+#elif defined(unix) || defined(__unix__) || defined(__unix)
+                                          "Unix"
+#else
+                                          "Unknown"
+#endif
+                                         );
+
+
+          xmp_data = gexiv2_metadata_get_xmp_tags (GEXIV2_METADATA (metadata));
+
+          /* Patch necessary structures */
+          for (i = 0; i < (gint) G_N_ELEMENTS (structlist); i++)
+            {
+              gexiv2_metadata_set_xmp_tag_struct (GEXIV2_METADATA (new_g2metadata),
+                                                  structlist[i].tag,
+                                                  structlist[i].type);
+            }
+
+          for (i = 0; xmp_data[i] != NULL; i++)
+            {
+              if (! gexiv2_metadata_has_tag (new_g2metadata, xmp_data[i]) &&
+                  gimp_metadata_is_tag_supported (xmp_data[i], "image/heif"))
+                {
+                  heifplugin_image_metadata_copy_tag (GEXIV2_METADATA (metadata),
+                                                      new_g2metadata,
+                                                      xmp_data[i]);
+                }
+            }
+
+          g_strfreev (xmp_data);
+
+          xmp_packet = gexiv2_metadata_generate_xmp_packet (new_g2metadata, GEXIV2_USE_COMPACT_FORMAT | 
GEXIV2_OMIT_ALL_FORMATTING, 0);
+          if (xmp_packet)
+            {
+              int xmp_size = strlen (xmp_packet);
+              if (xmp_size > 0)
+                {
+                  heif_context_add_XMP_metadata (context, handle,
+                                                 xmp_packet, xmp_size);
+                }
+              g_free (xmp_packet);
+            }
+
+          g_object_unref (new_metadata);
+        }
+    }
+
   heif_image_handle_release (handle);
 
   gimp_progress_update (0.66);
@@ -2107,6 +2341,18 @@ save_dialog (GimpProcedure *procedure,
   gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
 #endif
 
+  /* Save EXIF data */
+#if GEXIV2_CHECK_VERSION(0, 12, 2)
+  button = gimp_prop_check_button_new (config, "save-exif",
+                                       _("_Save Exif data"));
+  gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+#endif
+
+  /* XMP metadata */
+  button = gimp_prop_check_button_new (config, "save-xmp",
+                                       _("Save _XMP data"));
+  gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
+
   gtk_widget_show (dialog);
 
   run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]