[gimp] libgimp: improve saving xmp metadata.



commit 5eb9a998bbb7ff1948fc62e6c860622fa693623d
Author: Jacob Boerema <jgboerema gmail com>
Date:   Sun Mar 21 13:04:30 2021 -0400

    libgimp: improve saving xmp metadata.
    
    1. Convert xmp /Iptc4xmpExt tag parts to /iptcExt because
    exiv2 fails when we try to use the default namespace.
    
    2. Don't only set structs from a fixed list but find all
    xmp array elements and check what the best struct
    type is: bag or seq.
    
    3. Work around a sorting issue in (g)exiv2 by using a natural
    sorting algorithm ourselves.
    
    4. Added some g_debug statements to make it easier to
    determine the cause of issues.

 libgimp/gimpimagemetadata-save.c | 183 +++++++++++++++++++++++++++++++++------
 1 file changed, 158 insertions(+), 25 deletions(-)
---
diff --git a/libgimp/gimpimagemetadata-save.c b/libgimp/gimpimagemetadata-save.c
index f84763c5d8..f310966fa2 100644
--- a/libgimp/gimpimagemetadata-save.c
+++ b/libgimp/gimpimagemetadata-save.c
@@ -239,6 +239,42 @@ gimp_image_metadata_save_prepare (GimpImage             *image,
   return metadata;
 }
 
+static const gchar *
+gimp_fix_xmp_tag (const gchar *tag)
+{
+  gchar *substring;
+
+  /* Due to problems using /Iptc4xmpExt namespace (/iptcExt is used
+   * instead by Exiv2) we replace all occurrences with /iptcExt which
+   * is valid but less common. Not doing so would cause saving xmp
+   * metadata to fail. This has to be done after getting the values
+   * from the source metadata since that source uses the original
+   * tag names and would otherwise return NULL as value.
+   * /Iptc4xmpExt length = 12
+   * /iptcExt     length =  8
+   */
+
+  substring = strstr (tag, "/Iptc4xmpExt");
+  while (substring)
+    {
+      gint len_tag = strlen (tag);
+      gint len_end;
+
+      len_end = len_tag - (substring - tag) - 12;
+      strncpy (substring, "/iptcExt", 8);
+      substring += 8;
+      /* Using memmove: we have overlapping source and dest */
+      memmove (substring, substring+4, len_end);
+      substring[len_end] = '\0';
+      g_debug ("Fixed tag value: %s", tag);
+
+      /* Multiple occurrences are possible: e.g.:
+       * Xmp.iptcExt.ImageRegion[3]/Iptc4xmpExt:RegionBoundary/Iptc4xmpExt:rbVertices[1]/Iptc4xmpExt:rbX
+       */
+      substring = strstr (tag, "/Iptc4xmpExt");
+    }
+  return tag;
+}
 
 static void
 gimp_image_metadata_copy_tag (GExiv2Metadata *src,
@@ -249,7 +285,17 @@ gimp_image_metadata_copy_tag (GExiv2Metadata *src,
 
   if (values)
     {
-      gexiv2_metadata_set_tag_multiple (dest, tag, (const gchar **) values);
+      gchar *temp_tag;
+
+      /* Xmp always seems to return multiple values */
+      if (g_str_has_prefix (tag, "Xmp."))
+        temp_tag = (gchar *) gimp_fix_xmp_tag (g_strdup (tag));
+      else
+        temp_tag = g_strdup (tag);
+
+      g_debug ("Copy multi tag %s, first value: %s", temp_tag, values[0]);
+      gexiv2_metadata_set_tag_multiple (dest, temp_tag, (const gchar **) values);
+      g_free (temp_tag);
       g_strfreev (values);
     }
   else
@@ -258,12 +304,111 @@ gimp_image_metadata_copy_tag (GExiv2Metadata *src,
 
       if (value)
         {
+          g_debug ("Copy tag %s, value: %s", tag, value);
           gexiv2_metadata_set_tag_string (dest, tag, value);
           g_free (value);
         }
     }
 }
 
+static gint
+gimp_natural_sort_compare (gconstpointer left,
+                           gconstpointer right)
+{
+  gint   compare;
+  gchar *left_key  = g_utf8_collate_key_for_filename ((gchar *) left, -1);
+  gchar *right_key = g_utf8_collate_key_for_filename ((gchar *) right, -1);
+
+  compare = g_strcmp0 (left_key, right_key);
+  g_free (left_key);
+  g_free (right_key);
+
+  return compare;
+}
+
+static GList*
+gimp_image_metadata_convert_tags_to_list (gchar **xmp_tags)
+{
+  GList *list = NULL;
+  gint   i;
+
+  for (i = 0; xmp_tags[i] != NULL; i++)
+    {
+      g_debug ("Tag: %s, tag type: %s", xmp_tags[i], gexiv2_metadata_get_tag_type(xmp_tags[i]));
+      list = g_list_prepend (list, xmp_tags[i]);
+    }
+  return list;
+}
+
+static GExiv2StructureType
+gimp_image_metadata_get_xmp_struct_type (const gchar *tag)
+{
+  g_debug ("Struct type for tag: %s, type: %s", tag, gexiv2_metadata_get_tag_type (tag));
+
+  if (! g_strcmp0 (gexiv2_metadata_get_tag_type (tag), "XmpSeq"))
+    {
+      return GEXIV2_STRUCTURE_XA_SEQ;
+    }
+
+  return GEXIV2_STRUCTURE_XA_BAG;
+}
+
+static void
+gimp_image_metadata_set_xmp_structs (GList          *xmp_list,
+                                     GExiv2Metadata *metadata)
+{
+  GList *list;
+  gchar *prev_one = NULL;
+  gchar *prev_two = NULL;
+
+  for (list = xmp_list; list != NULL; list = list->next)
+    {
+      gchar **tag_split;
+
+      /*
+       * Most tags with structs have only one struct part, like:
+       * Xmp.xmpMM.History[1]...
+       * However there are also Xmp tags that have two
+       * structs in one tag, e.g.:
+       * Xmp.crs.GradientBasedCorrections[1]/crs:CorrectionMasks[1]...
+       */
+      tag_split = g_strsplit ((gchar *) list->data, "[1]", 3);
+      /* Check if there are at least two parts but don't catch xxx[2]/yyy[1]/zzz */
+      if (tag_split && tag_split[1] && ! strstr (tag_split[0], "["))
+        {
+          if (! prev_one || strcmp (tag_split[0], prev_one) != 0)
+            {
+              GExiv2StructureType  type;
+
+              g_free (prev_one);
+              prev_one = g_strdup (tag_split[0]);
+
+              type = gimp_image_metadata_get_xmp_struct_type (gimp_fix_xmp_tag (tag_split[0]));
+              gexiv2_metadata_set_xmp_tag_struct (GEXIV2_METADATA (metadata),
+                                                  prev_one, type);
+            }
+          if (tag_split[2] && (!prev_two || strcmp (tag_split[1], prev_two) != 0))
+            {
+              gchar               *second_struct;
+              GExiv2StructureType  type;
+
+              g_free (prev_two);
+              prev_two = g_strdup (tag_split[1]);
+              second_struct = g_strdup_printf ("%s[1]%s", prev_one, gimp_fix_xmp_tag(prev_two));
+
+              type = gimp_image_metadata_get_xmp_struct_type (gimp_fix_xmp_tag (tag_split[1]));
+              gexiv2_metadata_set_xmp_tag_struct (GEXIV2_METADATA (metadata),
+                                                  second_struct, type);
+              g_free (second_struct);
+            }
+        }
+
+      g_strfreev (tag_split);
+    }
+  g_free (prev_one);
+  g_free (prev_two);
+}
+
 /**
  * gimp_image_metadata_save_finish:
  * @image:     The actually saved image
@@ -344,23 +489,12 @@ gimp_image_metadata_save_finish (GimpImage              *image,
 
   if ((flags & GIMP_METADATA_SAVE_XMP) && support_xmp)
     {
-      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];
+      GList          *xmp_list = NULL;
+      GList          *list;
 
       gettimeofday (&timer_usec, NULL);
       timestamp_usec = ((gint64) timer_usec.tv_sec) * 1000000ll +
@@ -402,25 +536,24 @@ gimp_image_metadata_save_finish (GimpImage              *image,
 
       xmp_data = gexiv2_metadata_get_xmp_tags (GEXIV2_METADATA (metadata));
 
-      /* Patch necessary structures */
-      for (i = 0; i < G_N_ELEMENTS (structlist); i++)
-        {
-          gexiv2_metadata_set_xmp_tag_struct (GEXIV2_METADATA (new_g2metadata),
-                                              structlist[i].tag,
-                                              structlist[i].type);
-        }
+      xmp_list = gimp_image_metadata_convert_tags_to_list (xmp_data);
+      xmp_list = g_list_sort (xmp_list, (GCompareFunc) gimp_natural_sort_compare);
+      gimp_image_metadata_set_xmp_structs (xmp_list, new_g2metadata);
 
-      for (i = 0; xmp_data[i] != NULL; i++)
+      for (list = xmp_list; list != NULL; list = list->next)
         {
-          if (! gexiv2_metadata_has_tag (new_g2metadata, xmp_data[i]) &&
-              gimp_metadata_is_tag_supported (xmp_data[i], mime_type))
+          if (! gexiv2_metadata_has_tag (new_g2metadata, (gchar *) list->data) &&
+              gimp_metadata_is_tag_supported ((gchar *) list->data, mime_type))
             {
               gimp_image_metadata_copy_tag (GEXIV2_METADATA (metadata),
                                             new_g2metadata,
-                                            xmp_data[i]);
+                                            (gchar *) list->data);
             }
+          else
+            g_debug ("Ignored tag: %s", (gchar *) list->data);
         }
 
+      g_list_free (xmp_list);
       g_strfreev (xmp_data);
     }
 


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