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.
This commit is contained in:
Jacob Boerema 2021-03-21 13:04:30 -04:00
parent 362e00f209
commit 5eb9a998bb

View file

@ -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);
}