various: typo fixes
[platform/upstream/gstreamer.git] / gst-libs / gst / tag / gstxmptag.c
index 758b977..abe3597 100644 (file)
@@ -35,6 +35,7 @@
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
+#include "tag.h"
 #include <gst/gsttagsetter.h>
 #include "gsttageditingprivate.h"
 #include <stdio.h>
 #include <time.h>
 #include <ctype.h>
 
+static const gchar *schema_list[] = {
+  "dc",
+  "xap",
+  "tiff",
+  "exif",
+  "photoshop",
+  "Iptc4xmpCore",
+  "Iptc4xmpExt",
+  NULL
+};
+
+/**
+ * gst_tag_xmp_list_schemas:
+ *
+ * Gets the list of supported schemas in the xmp lib
+ *
+ * Returns: a %NULL terminated array of strings with the schema names
+ *
+ * Since: 0.10.33
+ */
+const gchar **
+gst_tag_xmp_list_schemas (void)
+{
+  return schema_list;
+}
+
+typedef struct _XmpSerializationData XmpSerializationData;
 typedef struct _XmpTag XmpTag;
 
 /*
@@ -63,14 +91,61 @@ typedef void (*XmpDeserializationFunc) (XmpTag * xmptag, GstTagList * taglist,
     const gchar * gst_tag, const gchar * xmp_tag_value,
     const gchar * str, GSList ** pending_tags);
 
+struct _XmpSerializationData
+{
+  GString *data;
+  const gchar **schemas;
+};
+
+static gboolean
+xmp_serialization_data_use_schema (XmpSerializationData * serdata,
+    const gchar * schemaname)
+{
+  gint i = 0;
+  if (serdata->schemas == NULL)
+    return TRUE;
+
+  while (serdata->schemas[i] != NULL) {
+    if (strcmp (serdata->schemas[i], schemaname) == 0)
+      return TRUE;
+    i++;
+  }
+  return FALSE;
+}
+
+typedef enum
+{
+  GstXmpTagTypeNone = 0,
+  GstXmpTagTypeSimple,
+  GstXmpTagTypeBag,
+  GstXmpTagTypeSeq,
+  GstXmpTagTypeStruct,
+
+  /* Not really a xmp type, this is a tag that in gst is represented with
+   * a single value and on xmp it needs 2 (or more) simple values
+   *
+   * e.g. GST_TAG_GEO_LOCATION_ELEVATION needs to be mapped into 2 complementary
+   * tags in the exif's schema. One of them stores the absolute elevation,
+   * and the other one stores if it is above of below sea level.
+   */
+  GstXmpTagTypeCompound
+} GstXmpTagType;
 
-#define GST_XMP_TAG_TYPE_SIMPLE 0
-#define GST_XMP_TAG_TYPE_BAG    1
-#define GST_XMP_TAG_TYPE_SEQ    2
 struct _XmpTag
 {
+  const gchar *gst_tag;
   const gchar *tag_name;
-  gint type;
+  GstXmpTagType type;
+
+  /* some tags must be inside a Bag even
+   * if they are a single entry. Set it here so we know */
+  GstXmpTagType supertype;
+
+  /* For tags that need a rdf:parseType attribute */
+  const gchar *parse_type;
+
+  /* Used for struct and compound types */
+  GSList *children;
 
   XmpSerializationFunc serialize;
   XmpDeserializationFunc deserialize;
@@ -80,44 +155,38 @@ static GstTagMergeMode
 xmp_tag_get_merge_mode (XmpTag * xmptag)
 {
   switch (xmptag->type) {
-    case GST_XMP_TAG_TYPE_BAG:
-    case GST_XMP_TAG_TYPE_SEQ:
+    case GstXmpTagTypeBag:
+    case GstXmpTagTypeSeq:
       return GST_TAG_MERGE_APPEND;
-    case GST_XMP_TAG_TYPE_SIMPLE:
+    case GstXmpTagTypeSimple:
     default:
       return GST_TAG_MERGE_KEEP;
   }
 }
 
 static const gchar *
-xmp_tag_get_type_name (XmpTag * xmptag)
+xmp_tag_type_get_name (GstXmpTagType tagtype)
 {
-  switch (xmptag->type) {
-    case GST_XMP_TAG_TYPE_SEQ:
+  switch (tagtype) {
+    case GstXmpTagTypeSeq:
       return "rdf:Seq";
+    case GstXmpTagTypeBag:
+      return "rdf:Bag";
     default:
       g_assert_not_reached ();
-    case GST_XMP_TAG_TYPE_BAG:
-      return "rdf:Bag";
   }
 }
 
 struct _PendingXmpTag
 {
-  const gchar *gst_tag;
   XmpTag *xmp_tag;
   gchar *str;
 };
 typedef struct _PendingXmpTag PendingXmpTag;
 
-
 /*
  * A schema is a mapping of strings (the tag name in gstreamer) to a list of
- * tags in xmp (XmpTag). We need a list because some tags are split into 2
- * when serialized into xmp.
- * e.g. GST_TAG_GEO_LOCATION_ELEVATION needs to be mapped into 2 complementary
- * tags in the exif's schema. One of them stores the absolute elevation,
- * and the other one stores if it is above of below sea level.
+ * tags in xmp (XmpTag).
  */
 typedef GHashTable GstXmpSchema;
 #define gst_xmp_schema_lookup g_hash_table_lookup
@@ -133,6 +202,21 @@ gst_xmp_schema_new ()
  */
 static GHashTable *__xmp_schemas;
 
+static GstXmpSchema *
+_gst_xmp_get_schema (const gchar * name)
+{
+  GQuark key;
+  GstXmpSchema *schema;
+
+  key = g_quark_from_string (name);
+
+  schema = g_hash_table_lookup (__xmp_schemas, GUINT_TO_POINTER (key));
+  if (!schema) {
+    GST_WARNING ("Schema %s doesn't exist", name);
+  }
+  return schema;
+}
+
 static void
 _gst_xmp_add_schema (const gchar * name, GstXmpSchema * schema)
 {
@@ -150,40 +234,72 @@ _gst_xmp_add_schema (const gchar * name, GstXmpSchema * schema)
 }
 
 static void
-_gst_xmp_schema_add_mapping (GstXmpSchema * schema, const gchar * gst_tag,
-    GPtrArray * array)
+_gst_xmp_schema_add_mapping (GstXmpSchema * schema, XmpTag * tag)
 {
   GQuark key;
 
-  key = g_quark_from_string (gst_tag);
+  key = g_quark_from_string (tag->gst_tag);
 
   if (gst_xmp_schema_lookup (schema, GUINT_TO_POINTER (key))) {
-    GST_WARNING ("Tag %s already present for the schema", gst_tag);
+    GST_WARNING ("Tag %s already present for the schema", tag->gst_tag);
     g_assert_not_reached ();
     return;
   }
-  gst_xmp_schema_insert (schema, GUINT_TO_POINTER (key), array);
+  gst_xmp_schema_insert (schema, GUINT_TO_POINTER (key), tag);
 }
 
-static void
-_gst_xmp_schema_add_simple_mapping (GstXmpSchema * schema,
-    const gchar * gst_tag, const gchar * xmp_tag, gint xmp_type,
-    XmpSerializationFunc serialization_func,
+static XmpTag *
+gst_xmp_tag_create (const gchar * gst_tag, const gchar * xmp_tag,
+    gint xmp_type, XmpSerializationFunc serialization_func,
     XmpDeserializationFunc deserialization_func)
 {
   XmpTag *xmpinfo;
-  GPtrArray *array;
 
   xmpinfo = g_slice_new (XmpTag);
+  xmpinfo->gst_tag = gst_tag;
   xmpinfo->tag_name = xmp_tag;
   xmpinfo->type = xmp_type;
+  xmpinfo->supertype = GstXmpTagTypeNone;
+  xmpinfo->parse_type = NULL;
   xmpinfo->serialize = serialization_func;
   xmpinfo->deserialize = deserialization_func;
+  xmpinfo->children = NULL;
 
-  array = g_ptr_array_sized_new (1);
-  g_ptr_array_add (array, xmpinfo);
+  return xmpinfo;
+}
+
+static XmpTag *
+gst_xmp_tag_create_compound (const gchar * gst_tag, const gchar * xmp_tag_a,
+    const gchar * xmp_tag_b, XmpSerializationFunc serialization_func_a,
+    XmpSerializationFunc serialization_func_b,
+    XmpDeserializationFunc deserialization_func)
+{
+  XmpTag *xmptag;
+  XmpTag *xmptag_a =
+      gst_xmp_tag_create (gst_tag, xmp_tag_a, GstXmpTagTypeSimple,
+      serialization_func_a, deserialization_func);
+  XmpTag *xmptag_b =
+      gst_xmp_tag_create (gst_tag, xmp_tag_b, GstXmpTagTypeSimple,
+      serialization_func_b, deserialization_func);
+
+  xmptag =
+      gst_xmp_tag_create (gst_tag, NULL, GstXmpTagTypeCompound, NULL, NULL);
+
+  xmptag->children = g_slist_prepend (xmptag->children, xmptag_b);
+  xmptag->children = g_slist_prepend (xmptag->children, xmptag_a);
 
-  _gst_xmp_schema_add_mapping (schema, gst_tag, array);
+  return xmptag;
+}
+
+static void
+_gst_xmp_schema_add_simple_mapping (GstXmpSchema * schema,
+    const gchar * gst_tag, const gchar * xmp_tag, gint xmp_type,
+    XmpSerializationFunc serialization_func,
+    XmpDeserializationFunc deserialization_func)
+{
+  _gst_xmp_schema_add_mapping (schema,
+      gst_xmp_tag_create (gst_tag, xmp_tag, xmp_type, serialization_func,
+          deserialization_func));
 }
 
 /*
@@ -191,23 +307,28 @@ _gst_xmp_schema_add_simple_mapping (GstXmpSchema * schema,
  * appended, and the API is not public, so we shouldn't
  * have our lists modified during usage
  */
+#if 0
 static GPtrArray *
-_xmp_tag_get_mapping (const gchar * gst_tag)
+_xmp_tag_get_mapping (const gchar * gst_tag, XmpSerializationData * serdata)
 {
   GPtrArray *ret = NULL;
   GHashTableIter iter;
   GQuark key = g_quark_from_string (gst_tag);
   gpointer iterkey, value;
+  const gchar *schemaname;
 
   g_hash_table_iter_init (&iter, __xmp_schemas);
   while (!ret && g_hash_table_iter_next (&iter, &iterkey, &value)) {
     GstXmpSchema *schema = (GstXmpSchema *) value;
 
-    ret = (GPtrArray *) gst_xmp_schema_lookup (schema, GUINT_TO_POINTER (key));
+    schemaname = g_quark_to_string (GPOINTER_TO_UINT (iterkey));
+    if (xmp_serialization_data_use_schema (serdata, schemaname))
+      ret =
+          (GPtrArray *) gst_xmp_schema_lookup (schema, GUINT_TO_POINTER (key));
   }
-
   return ret;
 }
+#endif
 
 /* finds the gst tag that maps to this xmp tag in this schema */
 static const gchar *
@@ -217,22 +338,30 @@ _gst_xmp_schema_get_mapping_reverse (GstXmpSchema * schema,
   GHashTableIter iter;
   gpointer key, value;
   const gchar *ret = NULL;
-  gint index;
 
   /* Iterate over the hashtable */
   g_hash_table_iter_init (&iter, schema);
   while (!ret && g_hash_table_iter_next (&iter, &key, &value)) {
-    GPtrArray *array = (GPtrArray *) value;
-
-    /* each mapping might contain complementary tags */
-    for (index = 0; index < array->len; index++) {
-      XmpTag *xmpinfo = (XmpTag *) g_ptr_array_index (array, index);
+    XmpTag *xmpinfo = (XmpTag *) value;
 
+    if (xmpinfo->tag_name) {
       if (strcmp (xmpinfo->tag_name, xmp_tag) == 0) {
         *_xmp_tag = xmpinfo;
         ret = g_quark_to_string (GPOINTER_TO_UINT (key));
         goto out;
       }
+    } else if (xmpinfo->children) {
+      GSList *iter;
+      for (iter = xmpinfo->children; iter; iter = g_slist_next (iter)) {
+        XmpTag *child = iter->data;
+        if (strcmp (child->tag_name, xmp_tag) == 0) {
+          *_xmp_tag = child;
+          ret = g_quark_to_string (GPOINTER_TO_UINT (key));
+          goto out;
+        }
+      }
+    } else {
+      g_assert_not_reached ();
     }
   }
 
@@ -725,7 +854,7 @@ deserialize_xmp_rating (XmpTag * xmptag, GstTagList * taglist,
     return;
   }
 
-  if (value < 0 || value > 100) {
+  if (value > 100) {
     GST_WARNING ("Unsupported Rating tag %u (should be from 0 to 100), "
         "ignoring", value);
     return;
@@ -785,9 +914,8 @@ deserialize_tiff_orientation (XmpTag * xmptag, GstTagList * taglist,
  * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/XMP.html
  */
 static gpointer
-_init_xmp_tag_map ()
+_init_xmp_tag_map (gpointer user_data)
 {
-  GPtrArray *array;
   XmpTag *xmpinfo;
   GstXmpSchema *schema;
 
@@ -799,131 +927,117 @@ _init_xmp_tag_map ()
    */
   schema = gst_xmp_schema_new ();
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_ARTIST,
-      "dc:creator", GST_XMP_TAG_TYPE_SEQ, NULL, NULL);
+      "dc:creator", GstXmpTagTypeSeq, NULL, NULL);
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_COPYRIGHT,
-      "dc:rights", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
+      "dc:rights", GstXmpTagTypeSimple, NULL, NULL);
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DATE, "dc:date",
-      GST_XMP_TAG_TYPE_SEQ, NULL, NULL);
+      GstXmpTagTypeSeq, NULL, NULL);
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DESCRIPTION,
-      "dc:description", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
+      "dc:description", GstXmpTagTypeSimple, NULL, NULL);
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_KEYWORDS,
-      "dc:subject", GST_XMP_TAG_TYPE_BAG, NULL, NULL);
+      "dc:subject", GstXmpTagTypeBag, NULL, NULL);
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_TITLE, "dc:title",
-      GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
+      GstXmpTagTypeSimple, NULL, NULL);
   /* FIXME: we probably want GST_TAG_{,AUDIO_,VIDEO_}MIME_TYPE */
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_VIDEO_CODEC,
-      "dc:format", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
+      "dc:format", GstXmpTagTypeSimple, NULL, NULL);
   _gst_xmp_add_schema ("dc", schema);
 
   /* xap (xmp) schema */
   schema = gst_xmp_schema_new ();
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_USER_RATING,
-      "xmp:Rating", GST_XMP_TAG_TYPE_SIMPLE, NULL, deserialize_xmp_rating);
-  _gst_xmp_add_schema ("xmp", schema);
+      "xmp:Rating", GstXmpTagTypeSimple, NULL, deserialize_xmp_rating);
+  _gst_xmp_add_schema ("xap", schema);
 
   /* tiff */
+  schema = gst_xmp_schema_new ();
   _gst_xmp_schema_add_simple_mapping (schema,
-      GST_TAG_DEVICE_MANUFACTURER, "tiff:Make", GST_XMP_TAG_TYPE_SIMPLE, NULL,
+      GST_TAG_DEVICE_MANUFACTURER, "tiff:Make", GstXmpTagTypeSimple, NULL,
       NULL);
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DEVICE_MODEL,
-      "tiff:Model", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
+      "tiff:Model", GstXmpTagTypeSimple, NULL, NULL);
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_APPLICATION_NAME,
-      "tiff:Software", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
+      "tiff:Software", GstXmpTagTypeSimple, NULL, NULL);
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_IMAGE_ORIENTATION,
-      "tiff:Orientation", GST_XMP_TAG_TYPE_SIMPLE, serialize_tiff_orientation,
+      "tiff:Orientation", GstXmpTagTypeSimple, serialize_tiff_orientation,
       deserialize_tiff_orientation);
   _gst_xmp_add_schema ("tiff", schema);
 
   /* exif schema */
+  schema = gst_xmp_schema_new ();
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DATE_TIME,
-      "exif:DateTimeOriginal", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
+      "exif:DateTimeOriginal", GstXmpTagTypeSimple, NULL, NULL);
   _gst_xmp_schema_add_simple_mapping (schema,
       GST_TAG_GEO_LOCATION_LATITUDE, "exif:GPSLatitude",
-      GST_XMP_TAG_TYPE_SIMPLE, serialize_exif_latitude,
-      deserialize_exif_latitude);
+      GstXmpTagTypeSimple, serialize_exif_latitude, deserialize_exif_latitude);
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_GEO_LOCATION_LONGITUDE,
-      "exif:GPSLongitude", GST_XMP_TAG_TYPE_SIMPLE, serialize_exif_longitude,
+      "exif:GPSLongitude", GstXmpTagTypeSimple, serialize_exif_longitude,
       deserialize_exif_longitude);
+  _gst_xmp_schema_add_simple_mapping (schema,
+      GST_TAG_CAPTURING_EXPOSURE_COMPENSATION, "exif:ExposureBiasValue",
+      GstXmpTagTypeSimple, NULL, NULL);
 
   /* compound exif tags */
-  array = g_ptr_array_sized_new (2);
-  xmpinfo = g_slice_new (XmpTag);
-  xmpinfo->tag_name = "exif:GPSAltitude";
-  xmpinfo->serialize = serialize_exif_altitude;
-  xmpinfo->deserialize = deserialize_exif_altitude;
-  xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
-  g_ptr_array_add (array, xmpinfo);
-  xmpinfo = g_slice_new (XmpTag);
-  xmpinfo->tag_name = "exif:GPSAltitudeRef";
-  xmpinfo->serialize = serialize_exif_altituderef;
-  xmpinfo->deserialize = deserialize_exif_altitude;
-  xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
-  g_ptr_array_add (array, xmpinfo);
-  _gst_xmp_schema_add_mapping (schema, GST_TAG_GEO_LOCATION_ELEVATION, array);
-
-  array = g_ptr_array_sized_new (2);
-  xmpinfo = g_slice_new (XmpTag);
-  xmpinfo->tag_name = "exif:GPSSpeed";
-  xmpinfo->serialize = serialize_exif_gps_speed;
-  xmpinfo->deserialize = deserialize_exif_gps_speed;
-  xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
-  g_ptr_array_add (array, xmpinfo);
-  xmpinfo = g_slice_new (XmpTag);
-  xmpinfo->tag_name = "exif:GPSSpeedRef";
-  xmpinfo->serialize = serialize_exif_gps_speedref;
-  xmpinfo->deserialize = deserialize_exif_gps_speed;
-  xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
-  g_ptr_array_add (array, xmpinfo);
-  _gst_xmp_schema_add_mapping (schema,
-      GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, array);
-
-  array = g_ptr_array_sized_new (2);
-  xmpinfo = g_slice_new (XmpTag);
-  xmpinfo->tag_name = "exif:GPSTrack";
-  xmpinfo->serialize = serialize_exif_gps_direction;
-  xmpinfo->deserialize = deserialize_exif_gps_track;
-  xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
-  g_ptr_array_add (array, xmpinfo);
-  xmpinfo = g_slice_new (XmpTag);
-  xmpinfo->tag_name = "exif:GPSTrackRef";
-  xmpinfo->serialize = serialize_exif_gps_directionref;
-  xmpinfo->deserialize = deserialize_exif_gps_track;
-  xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
-  g_ptr_array_add (array, xmpinfo);
-  _gst_xmp_schema_add_mapping (schema,
-      GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION, array);
+  xmpinfo = gst_xmp_tag_create_compound (GST_TAG_GEO_LOCATION_ELEVATION,
+      "exif:GPSAltitude", "exif:GPSAltitudeRef", serialize_exif_altitude,
+      serialize_exif_altituderef, deserialize_exif_altitude);
+  _gst_xmp_schema_add_mapping (schema, xmpinfo);
+
+  xmpinfo = gst_xmp_tag_create_compound (GST_TAG_GEO_LOCATION_MOVEMENT_SPEED,
+      "exif:GPSSpeed", "exif:GPSSpeedRef", serialize_exif_gps_speed,
+      serialize_exif_gps_speedref, deserialize_exif_gps_speed);
+  _gst_xmp_schema_add_mapping (schema, xmpinfo);
+
+  xmpinfo =
+      gst_xmp_tag_create_compound (GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION,
+      "exif:GPSTrack", "exif:GPSTrackRef", serialize_exif_gps_direction,
+      serialize_exif_gps_directionref, deserialize_exif_gps_track);
+  _gst_xmp_schema_add_mapping (schema, xmpinfo);
+
+  xmpinfo = gst_xmp_tag_create_compound (GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION,
+      "exif:GPSImgDirection", "exif:GPSImgDirectionRef",
+      serialize_exif_gps_direction, serialize_exif_gps_directionref,
+      deserialize_exif_gps_img_direction);
+  _gst_xmp_schema_add_mapping (schema, xmpinfo);
 
-  array = g_ptr_array_sized_new (2);
-  xmpinfo = g_slice_new (XmpTag);
-  xmpinfo->tag_name = "exif:GPSImgDirection";
-  xmpinfo->serialize = serialize_exif_gps_direction;
-  xmpinfo->deserialize = deserialize_exif_gps_img_direction;
-  xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
-  g_ptr_array_add (array, xmpinfo);
-  xmpinfo = g_slice_new (XmpTag);
-  xmpinfo->tag_name = "exif:GPSImgDirectionRef";
-  xmpinfo->serialize = serialize_exif_gps_directionref;
-  xmpinfo->deserialize = deserialize_exif_gps_img_direction;
-  xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
-  g_ptr_array_add (array, xmpinfo);
-  _gst_xmp_schema_add_mapping (schema,
-      GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION, array);
   _gst_xmp_add_schema ("exif", schema);
 
   /* photoshop schema */
+  schema = gst_xmp_schema_new ();
   _gst_xmp_schema_add_simple_mapping (schema,
       GST_TAG_GEO_LOCATION_COUNTRY, "photoshop:Country",
-      GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
+      GstXmpTagTypeSimple, NULL, NULL);
   _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_GEO_LOCATION_CITY,
-      "photoshop:City", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
+      "photoshop:City", GstXmpTagTypeSimple, NULL, NULL);
   _gst_xmp_add_schema ("photoshop", schema);
 
   /* iptc4xmpcore schema */
+  schema = gst_xmp_schema_new ();
   _gst_xmp_schema_add_simple_mapping (schema,
       GST_TAG_GEO_LOCATION_SUBLOCATION, "Iptc4xmpCore:Location",
-      GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
+      GstXmpTagTypeSimple, NULL, NULL);
   _gst_xmp_add_schema ("Iptc4xmpCore", schema);
 
+  /* iptc4xmpext schema */
+  schema = gst_xmp_schema_new ();
+  xmpinfo = gst_xmp_tag_create (NULL, "Iptc4xmpExt:LocationShown",
+      GstXmpTagTypeStruct, NULL, NULL);
+  xmpinfo->supertype = GstXmpTagTypeBag;
+  xmpinfo->parse_type = "Resource";
+  xmpinfo->children = g_slist_prepend (xmpinfo->children,
+      gst_xmp_tag_create (GST_TAG_GEO_LOCATION_SUBLOCATION,
+          "LocationDetails:Sublocation", GstXmpTagTypeSimple, NULL, NULL));
+  xmpinfo->children =
+      g_slist_prepend (xmpinfo->children,
+      gst_xmp_tag_create (GST_TAG_GEO_LOCATION_CITY,
+          "LocationDetails:City", GstXmpTagTypeSimple, NULL, NULL));
+  xmpinfo->children =
+      g_slist_prepend (xmpinfo->children,
+      gst_xmp_tag_create (GST_TAG_GEO_LOCATION_COUNTRY,
+          "LocationDetails:Country", GstXmpTagTypeSimple, NULL, NULL));
+  _gst_xmp_schema_add_mapping (schema, xmpinfo);
+  _gst_xmp_add_schema ("Iptc4xmpExt", schema);
+
   return NULL;
 }
 
@@ -931,7 +1045,7 @@ static void
 xmp_tags_initialize ()
 {
   static GOnce my_once = G_ONCE_INIT;
-  g_once (&my_once, _init_xmp_tag_map, NULL);
+  g_once (&my_once, (GThreadFunc) _init_xmp_tag_map, NULL);
 }
 
 typedef struct _GstXmpNamespaceMatch GstXmpNamespaceMatch;
@@ -939,16 +1053,26 @@ struct _GstXmpNamespaceMatch
 {
   const gchar *ns_prefix;
   const gchar *ns_uri;
+
+  /*
+   * Stores extra namespaces for array tags
+   * The namespaces should be writen in the form:
+   *
+   * xmlns:XpTo="http://some.org/your/ns/name/ (next ones)"
+   */
+  const gchar *extra_ns;
 };
 
 static const GstXmpNamespaceMatch ns_match[] = {
-  {"dc", "http://purl.org/dc/elements/1.1/"},
-  {"exif", "http://ns.adobe.com/exif/1.0/"},
-  {"tiff", "http://ns.adobe.com/tiff/1.0/"},
-  {"xap", "http://ns.adobe.com/xap/1.0/"},
-  {"photoshop", "http://ns.adobe.com/photoshop/1.0/"},
-  {"Iptc4xmpCore", "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"},
-  {NULL, NULL}
+  {"dc", "http://purl.org/dc/elements/1.1/", NULL},
+  {"exif", "http://ns.adobe.com/exif/1.0/", NULL},
+  {"tiff", "http://ns.adobe.com/tiff/1.0/", NULL},
+  {"xap", "http://ns.adobe.com/xap/1.0/", NULL},
+  {"photoshop", "http://ns.adobe.com/photoshop/1.0/", NULL},
+  {"Iptc4xmpCore", "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/", NULL},
+  {"Iptc4xmpExt", "http://iptc.org/std/Iptc4xmpExt/2008-02-29/",
+      "xmlns:LocationDetails=\"http://iptc.org/std/Iptc4xmpExt/2008-02-29/LocationDetails/\""},
+  {NULL, NULL, NULL}
 };
 
 typedef struct _GstXmpNamespaceMap GstXmpNamespaceMap;
@@ -957,26 +1081,20 @@ struct _GstXmpNamespaceMap
   const gchar *original_ns;
   gchar *gstreamer_ns;
 };
-static GstXmpNamespaceMap ns_map[] = {
-  {"dc", NULL},
-  {"exif", NULL},
-  {"tiff", NULL},
-  {"xap", NULL},
-  {"photoshop", NULL},
-  {"Iptc4xmpCore", NULL},
-  {NULL, NULL}
-};
 
 /* parsing */
 
 static void
-read_one_tag (GstTagList * list, const gchar * tag, XmpTag * xmptag,
+read_one_tag (GstTagList * list, XmpTag * xmptag,
     const gchar * v, GSList ** pending_tags)
 {
   GType tag_type;
   GstTagMergeMode merge_mode;
+  const gchar *tag = xmptag->gst_tag;
 
-  if (xmptag && xmptag->deserialize) {
+  g_return_if_fail (tag != NULL);
+
+  if (xmptag->deserialize) {
     xmptag->deserialize (xmptag, list, tag, xmptag->tag_name, v, pending_tags);
     return;
   }
@@ -990,6 +1108,18 @@ read_one_tag (GstTagList * list, const gchar * tag, XmpTag * xmptag,
       gst_tag_list_add (list, merge_mode, tag, v, NULL);
       break;
     }
+    case G_TYPE_DOUBLE:{
+      gdouble value = 0;
+      gint frac_n, frac_d;
+
+      if (sscanf (v, "%d/%d", &frac_n, &frac_d) == 2) {
+        gst_util_fraction_to_double (frac_n, frac_d, &value);
+        gst_tag_list_add (list, merge_mode, tag, value, NULL);
+      } else {
+        GST_WARNING ("Failed to parse fraction: %s", v);
+      }
+      break;
+    }
     default:
       if (tag_type == GST_TYPE_DATE_TIME) {
         GstDateTime *datetime = NULL;
@@ -1021,7 +1151,7 @@ read_one_tag (GstTagList * list, const gchar * tag, XmpTag * xmptag,
           gint num_digits = 0;
 
           /* find the number of digits */
-          while (isdigit (usec_str[num_digits++]) && num_digits < 6);
+          while (isdigit ((gint) usec_str[num_digits++]) && num_digits < 6);
 
           if (num_digits > 0) {
             /* fill up to 6 digits with 0 */
@@ -1149,15 +1279,30 @@ gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
   gboolean in_tag;
   gchar *part, *pp;
   guint i;
-  const gchar *last_tag = NULL;
   XmpTag *last_xmp_tag = NULL;
   GSList *pending_tags = NULL;
 
+  /* Used for strucuture xmp tags */
+  XmpTag *context_tag = NULL;
+
+  GstXmpNamespaceMap ns_map[] = {
+    {"dc", NULL},
+    {"exif", NULL},
+    {"tiff", NULL},
+    {"xap", NULL},
+    {"photoshop", NULL},
+    {"Iptc4xmpCore", NULL},
+    {"Iptc4xmpExt", NULL},
+    {NULL, NULL}
+  };
+
   xmp_tags_initialize ();
 
   g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
   g_return_val_if_fail (GST_BUFFER_SIZE (buffer) > 0, NULL);
 
+  GST_LOG ("Starting xmp parsing");
+
   xps = (const gchar *) GST_BUFFER_DATA (buffer);
   len = GST_BUFFER_SIZE (buffer);
   xpe = &xps[len + 1];
@@ -1172,7 +1317,7 @@ gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
   if (*xp1 != '>')
     goto missing_header;
 
-  max_ft_len = 1 + strlen ("<?xpacket end=\".\"?>\n");
+  max_ft_len = 1 + strlen ("<?xpacket end=\".\"?>");
   if (len < max_ft_len)
     goto missing_footer;
 
@@ -1257,19 +1402,41 @@ gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
                   }
                 }
               } else {
-                const gchar *gst_tag;
                 XmpTag *xmp_tag = NULL;
-                /* FIXME: eventualy rewrite ns
+                /* FIXME: eventually rewrite ns
                  * find ':'
                  * check if ns before ':' is in ns_map and ns_map[i].gstreamer_ns!=NULL
                  * do 2 stage filter in tag_matches
                  */
-                gst_tag = _gst_xmp_tag_get_mapping_reverse (as, &xmp_tag);
-                if (gst_tag) {
+                if (context_tag) {
+                  GSList *iter;
+
+                  for (iter = context_tag->children; iter;
+                      iter = g_slist_next (iter)) {
+                    XmpTag *child = iter->data;
+
+                    GST_DEBUG ("Looking at child tag %s : %s", child->tag_name,
+                        as);
+                    if (strcmp (child->tag_name, as) == 0) {
+                      xmp_tag = child;
+                      break;
+                    }
+                  }
+
+                } else {
+                  GST_LOG ("Looking for tag: %s", as);
+                  _gst_xmp_tag_get_mapping_reverse (as, &xmp_tag);
+                }
+                if (xmp_tag) {
                   PendingXmpTag *ptag;
 
+                  GST_DEBUG ("Found xmp tag: %s -> %s", xmp_tag->tag_name,
+                      xmp_tag->gst_tag);
+
+                  /* we shouldn't find a xmp structure here */
+                  g_assert (xmp_tag->gst_tag != NULL);
+
                   ptag = g_slice_new (PendingXmpTag);
-                  ptag->gst_tag = gst_tag;
                   ptag->xmp_tag = xmp_tag;
                   ptag->str = g_strdup (v);
 
@@ -1292,19 +1459,46 @@ gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
              <dc:type><rdf:Bag><rdf:li>Image</rdf:li></rdf:Bag></dc:type>
              <dc:creator><rdf:Seq><rdf:li/></rdf:Seq></dc:creator>
            */
-          /* FIXME: eventualy rewrite ns */
+          /* FIXME: eventually rewrite ns */
 
           /* skip rdf tags for now */
           if (strncmp (part, "rdf:", 4)) {
-            const gchar *parttag;
+            /* if we're inside some struct, we look only on its children */
+            if (context_tag) {
+              GSList *iter;
+
+              /* check if this is the closing of the context */
+              if (part[0] == '/'
+                  && strcmp (part + 1, context_tag->tag_name) == 0) {
+                GST_DEBUG ("Closing context tag %s", part);
+                context_tag = NULL;
+              } else {
+
+                for (iter = context_tag->children; iter;
+                    iter = g_slist_next (iter)) {
+                  XmpTag *child = iter->data;
 
-            parttag = _gst_xmp_tag_get_mapping_reverse (part, &last_xmp_tag);
-            if (parttag) {
-              last_tag = parttag;
+                  GST_DEBUG ("Looking at child tag %s : %s", child->tag_name,
+                      part);
+                  if (strcmp (child->tag_name, part) == 0) {
+                    last_xmp_tag = child;
+                    break;
+                  }
+                }
+              }
+
+            } else {
+              GST_LOG ("Looking for tag: %s", part);
+              _gst_xmp_tag_get_mapping_reverse (part, &last_xmp_tag);
+              if (last_xmp_tag && last_xmp_tag->type == GstXmpTagTypeStruct) {
+                context_tag = last_xmp_tag;
+                last_xmp_tag = NULL;
+              }
             }
           }
         }
       }
+      GST_LOG ("Next cycle");
       /* next cycle */
       ne++;
       if (ne < xp2) {
@@ -1324,15 +1518,23 @@ gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
       if (ns[0] != '\n' && &ns[1] <= ne) {
         /* only log non-newline nodes, we still have to parse them */
         GST_INFO ("txt: %s", part);
-        if (last_tag) {
+        if (last_xmp_tag) {
           PendingXmpTag *ptag;
 
-          ptag = g_slice_new (PendingXmpTag);
-          ptag->gst_tag = last_tag;
-          ptag->xmp_tag = last_xmp_tag;
-          ptag->str = g_strdup (part);
+          GST_DEBUG ("Found tag %s -> %s", last_xmp_tag->tag_name,
+              last_xmp_tag->gst_tag);
+
+          if (last_xmp_tag->type == GstXmpTagTypeStruct) {
+            g_assert (context_tag == NULL);     /* we can't handle struct nesting currently */
+
+            context_tag = last_xmp_tag;
+          } else {
+            ptag = g_slice_new (PendingXmpTag);
+            ptag->xmp_tag = last_xmp_tag;
+            ptag->str = g_strdup (part);
 
-          pending_tags = g_slist_append (pending_tags, ptag);
+            pending_tags = g_slist_append (pending_tags, ptag);
+          }
         }
       }
       /* next cycle */
@@ -1347,7 +1549,7 @@ gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
 
     pending_tags = g_slist_delete_link (pending_tags, pending_tags);
 
-    read_one_tag (list, ptag->gst_tag, ptag->xmp_tag, ptag->str, &pending_tags);
+    read_one_tag (list, ptag->xmp_tag, ptag->str, &pending_tags);
 
     g_free (ptag->str);
     g_slice_free (PendingXmpTag, ptag);
@@ -1407,6 +1609,8 @@ gst_value_serialize_xmp (const GValue * value)
       return g_strdup_printf ("%d", g_value_get_int (value));
     case G_TYPE_UINT:
       return g_strdup_printf ("%u", g_value_get_uint (value));
+    case G_TYPE_DOUBLE:
+      return double_to_fraction_string (g_value_get_double (value));
     default:
       break;
   }
@@ -1449,89 +1653,141 @@ gst_value_serialize_xmp (const GValue * value)
 }
 
 static void
-write_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
+write_one_tag (const GstTagList * list, XmpTag * xmp_tag, gpointer user_data)
 {
-  guint i = 0, ct = gst_tag_list_get_tag_size (list, tag), tag_index;
-  GString *data = user_data;
-  GPtrArray *xmp_tag_array = NULL;
+  guint i = 0, ct;
+  XmpSerializationData *serialization_data = user_data;
+  GString *data = serialization_data->data;
   char *s;
 
-  /* map gst-tag to xmp tag */
-  xmp_tag_array = _xmp_tag_get_mapping (tag);
+  /* struct type handled differently */
+  if (xmp_tag->type == GstXmpTagTypeStruct ||
+      xmp_tag->type == GstXmpTagTypeCompound) {
+    GSList *iter;
+    gboolean use_it = FALSE;
+
+    /* check if any of the inner tags are present on the taglist */
+    for (iter = xmp_tag->children; iter && !use_it; iter = g_slist_next (iter)) {
+      XmpTag *child_tag = iter->data;
+
+      if (gst_tag_list_get_value_index (list, child_tag->gst_tag, 0) != NULL) {
+        use_it = TRUE;
+        break;
+      }
+    }
+
+    if (use_it) {
+      if (xmp_tag->tag_name)
+        string_open_tag (data, xmp_tag->tag_name);
+
+      if (xmp_tag->supertype) {
+        string_open_tag (data, xmp_tag_type_get_name (xmp_tag->supertype));
+        if (xmp_tag->parse_type) {
+          g_string_append (data, "<rdf:li rdf:parseType=\"");
+          g_string_append (data, xmp_tag->parse_type);
+          g_string_append_c (data, '"');
+          g_string_append_c (data, '>');
+        } else {
+          string_open_tag (data, "rdf:li");
+        }
+      }
+
+      /* now write it */
+      for (iter = xmp_tag->children; iter; iter = g_slist_next (iter)) {
+        write_one_tag (list, iter->data, user_data);
+      }
 
-  if (!xmp_tag_array) {
-    GST_WARNING ("no mapping for %s to xmp", tag);
+      if (xmp_tag->supertype) {
+        string_close_tag (data, "rdf:li");
+        string_close_tag (data, xmp_tag_type_get_name (xmp_tag->supertype));
+      }
+
+      if (xmp_tag->tag_name)
+        string_close_tag (data, xmp_tag->tag_name);
+    }
     return;
   }
 
-  for (tag_index = 0; tag_index < xmp_tag_array->len; tag_index++) {
-    XmpTag *xmp_tag;
+  /* at this point we must have a gst_tag */
+  g_assert (xmp_tag->gst_tag);
+  if (gst_tag_list_get_value_index (list, xmp_tag->gst_tag, 0) == NULL)
+    return;
 
-    xmp_tag = g_ptr_array_index (xmp_tag_array, tag_index);
-    string_open_tag (data, xmp_tag->tag_name);
+  ct = gst_tag_list_get_tag_size (list, xmp_tag->gst_tag);
+  string_open_tag (data, xmp_tag->tag_name);
 
-    /* fast path for single valued tag */
-    if (ct == 1 || xmp_tag->type == GST_XMP_TAG_TYPE_SIMPLE) {
+  /* fast path for single valued tag */
+  if (ct == 1 || xmp_tag->type == GstXmpTagTypeSimple) {
+    if (xmp_tag->serialize) {
+      s = xmp_tag->serialize (gst_tag_list_get_value_index (list,
+              xmp_tag->gst_tag, 0));
+    } else {
+      s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list,
+              xmp_tag->gst_tag, 0));
+    }
+    if (s) {
+      g_string_append (data, s);
+      g_free (s);
+    } else {
+      GST_WARNING ("unhandled type for %s to xmp", xmp_tag->gst_tag);
+    }
+  } else {
+    const gchar *typename;
+
+    typename = xmp_tag_type_get_name (xmp_tag->type);
+
+    string_open_tag (data, typename);
+    for (i = 0; i < ct; i++) {
+      GST_DEBUG ("mapping %s[%u/%u] to xmp", xmp_tag->gst_tag, i, ct);
       if (xmp_tag->serialize) {
-        s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, 0));
+        s = xmp_tag->serialize (gst_tag_list_get_value_index (list,
+                xmp_tag->gst_tag, i));
       } else {
-        s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
-                0));
+        s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list,
+                xmp_tag->gst_tag, i));
       }
       if (s) {
+        string_open_tag (data, "rdf:li");
         g_string_append (data, s);
+        string_close_tag (data, "rdf:li");
         g_free (s);
       } else {
-        GST_WARNING ("unhandled type for %s to xmp", tag);
-      }
-    } else {
-      const gchar *typename;
-
-      typename = xmp_tag_get_type_name (xmp_tag);
-
-      string_open_tag (data, typename);
-      for (i = 0; i < ct; i++) {
-        GST_DEBUG ("mapping %s[%u/%u] to xmp", tag, i, ct);
-        if (xmp_tag->serialize) {
-          s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, i));
-        } else {
-          s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
-                  i));
-        }
-        if (s) {
-          string_open_tag (data, "rdf:li");
-          g_string_append (data, s);
-          string_close_tag (data, "rdf:li");
-          g_free (s);
-        } else {
-          GST_WARNING ("unhandled type for %s to xmp", tag);
-        }
+        GST_WARNING ("unhandled type for %s to xmp", xmp_tag->gst_tag);
       }
-      string_close_tag (data, typename);
     }
-
-    string_close_tag (data, xmp_tag->tag_name);
+    string_close_tag (data, typename);
   }
+
+  string_close_tag (data, xmp_tag->tag_name);
 }
 
 /**
- * gst_tag_list_to_xmp_buffer:
+ * gst_tag_list_to_xmp_buffer_full:
  * @list: tags
  * @read_only: does the container forbid inplace editing
+ * @schemas: %NULL terminated array of schemas to be used on serialization
  *
- * Formats a taglist as a xmp packet.
+ * Formats a taglist as a xmp packet using only the selected
+ * schemas. An empty list (%NULL) means that all schemas should
+ * be used
  *
  * Returns: new buffer or %NULL, unref the buffer when done
  *
- * Since: 0.10.29
+ * Since: 0.10.33
  */
 GstBuffer *
-gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
+gst_tag_list_to_xmp_buffer_full (const GstTagList * list, gboolean read_only,
+    const gchar ** schemas)
 {
   GstBuffer *buffer = NULL;
-  GString *data = g_string_sized_new (4096);
+  XmpSerializationData serialization_data;
+  GString *data;
   guint i;
 
+  serialization_data.data = g_string_sized_new (4096);
+  serialization_data.schemas = schemas;
+  data = serialization_data.data;
+
   xmp_tags_initialize ();
 
   g_return_val_if_fail (GST_IS_TAG_LIST (list), NULL);
@@ -1545,15 +1801,38 @@ gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
       "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"");
   i = 0;
   while (ns_match[i].ns_prefix) {
-    g_string_append_printf (data, " xmlns:%s=\"%s\"", ns_match[i].ns_prefix,
-        ns_match[i].ns_uri);
+    if (xmp_serialization_data_use_schema (&serialization_data,
+            ns_match[i].ns_prefix)) {
+      g_string_append_printf (data, " xmlns:%s=\"%s\"",
+          ns_match[i].ns_prefix, ns_match[i].ns_uri);
+      if (ns_match[i].extra_ns) {
+        g_string_append_printf (data, " %s", ns_match[i].extra_ns);
+      }
+    }
     i++;
   }
   g_string_append (data, ">\n");
   g_string_append (data, "<rdf:Description rdf:about=\"\">\n");
 
-  /* iterate the taglist */
-  gst_tag_list_foreach (list, write_one_tag, data);
+  /* iterate the schemas */
+  if (schemas == NULL) {
+    /* use all schemas */
+    schemas = gst_tag_xmp_list_schemas ();
+  }
+  for (i = 0; schemas[i] != NULL; i++) {
+    GstXmpSchema *schema = _gst_xmp_get_schema (schemas[i]);
+    GHashTableIter iter;
+    gpointer key, value;
+
+    if (schema == NULL)
+      continue;
+
+    /* Iterate over the hashtable */
+    g_hash_table_iter_init (&iter, schema);
+    while (g_hash_table_iter_next (&iter, &key, &value)) {
+      write_one_tag (list, value, (gpointer) & serialization_data);
+    }
+  }
 
   /* xmp footer */
   g_string_append (data, "</rdf:Description>\n");
@@ -1561,7 +1840,7 @@ gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
   g_string_append (data, "</x:xmpmeta>\n");
 
   if (!read_only) {
-    /* the xmp spec recommand to add 2-4KB padding for in-place editable xmp */
+    /* the xmp spec recommends to add 2-4KB padding for in-place editable xmp */
     guint i;
 
     for (i = 0; i < 32; i++) {
@@ -1569,16 +1848,33 @@ gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
           "                " "                " "\n");
     }
   }
-  g_string_append_printf (data, "<?xpacket end=\"%c\"?>\n",
+  g_string_append_printf (data, "<?xpacket end=\"%c\"?>",
       (read_only ? 'r' : 'w'));
 
   buffer = gst_buffer_new ();
-  GST_BUFFER_SIZE (buffer) = data->len + 1;
+  GST_BUFFER_SIZE (buffer) = data->len;
   GST_BUFFER_DATA (buffer) = (guint8 *) g_string_free (data, FALSE);
   GST_BUFFER_MALLOCDATA (buffer) = GST_BUFFER_DATA (buffer);
 
   return buffer;
 }
 
+/**
+ * gst_tag_list_to_xmp_buffer:
+ * @list: tags
+ * @read_only: does the container forbid inplace editing
+ *
+ * Formats a taglist as a xmp packet.
+ *
+ * Returns: new buffer or %NULL, unref the buffer when done
+ *
+ * Since: 0.10.29
+ */
+GstBuffer *
+gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
+{
+  return gst_tag_list_to_xmp_buffer_full (list, read_only, NULL);
+}
+
 #undef gst_xmp_schema_lookup
 #undef gst_xmp_schema_insert