X-Git-Url: http://vcs.maemo.org/git/?a=blobdiff_plain;f=gst-plugins-base-subtitles0.10%2Fgst-libs%2Fgst%2Ftag%2Fgstxmptag.c;fp=gst-plugins-base-subtitles0.10%2Fgst-libs%2Fgst%2Ftag%2Fgstxmptag.c;h=ca05895010f25714455fc2ea739479409ed79609;hb=57ba96e291a055f69dbfd4ae9f1ae2390e36986e;hp=0000000000000000000000000000000000000000;hpb=be2c98fb83895d10ac44af7b9a9c3e00ca54bf49;p=mafwsubrenderer diff --git a/gst-plugins-base-subtitles0.10/gst-libs/gst/tag/gstxmptag.c b/gst-plugins-base-subtitles0.10/gst-libs/gst/tag/gstxmptag.c new file mode 100644 index 0000000..ca05895 --- /dev/null +++ b/gst-plugins-base-subtitles0.10/gst-libs/gst/tag/gstxmptag.c @@ -0,0 +1,1684 @@ +/* GStreamer + * Copyright (C) 2010 Stefan Kost + * Copyright (C) 2010 Thiago Santos + * + * gstxmptag.c: library for reading / modifying xmp tags + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gsttagxmp + * @short_description: tag mappings and support functions for plugins + * dealing with xmp packets + * @see_also: #GstTagList + * + * Contains various utility functions for plugins to parse or create + * xmp packets and map them to and from #GstTagLists. + * + * Please note that the xmp parser is very lightweight and not strict at all. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "tag.h" +#include +#include "gsttageditingprivate.h" +#include +#include +#include +#include +#include + +static const gchar *schema_list[] = { + "dc", + "xap", + "tiff", + "exif", + "photoshop", + "Iptc4xmpCore", + 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; + +/* + * Serializes a GValue into a string. + */ +typedef gchar *(*XmpSerializationFunc) (const GValue * value); + +/* + * Deserializes @str that is the gstreamer tag @gst_tag represented in + * XMP as the @xmp_tag_value and adds the result to the @taglist. + * + * @pending_tags is passed so that compound xmp tags can search for its + * complements on the list and use them. Note that used complements should + * be freed and removed from the list. + * The list is of PendingXmpTag + */ +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; +} + + +#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 *tag_name; + gint type; + + XmpSerializationFunc serialize; + XmpDeserializationFunc deserialize; +}; + +static GstTagMergeMode +xmp_tag_get_merge_mode (XmpTag * xmptag) +{ + switch (xmptag->type) { + case GST_XMP_TAG_TYPE_BAG: + case GST_XMP_TAG_TYPE_SEQ: + return GST_TAG_MERGE_APPEND; + case GST_XMP_TAG_TYPE_SIMPLE: + default: + return GST_TAG_MERGE_KEEP; + } +} + +static const gchar * +xmp_tag_get_type_name (XmpTag * xmptag) +{ + switch (xmptag->type) { + case GST_XMP_TAG_TYPE_SEQ: + return "rdf:Seq"; + 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. + */ +typedef GHashTable GstXmpSchema; +#define gst_xmp_schema_lookup g_hash_table_lookup +#define gst_xmp_schema_insert g_hash_table_insert +static GstXmpSchema * +gst_xmp_schema_new () +{ + return g_hash_table_new (g_direct_hash, g_direct_equal); +} + +/* + * Mappings from schema names into the schema group of tags (GstXmpSchema) + */ +static GHashTable *__xmp_schemas; + +static void +_gst_xmp_add_schema (const gchar * name, GstXmpSchema * schema) +{ + GQuark key; + + key = g_quark_from_string (name); + + if (g_hash_table_lookup (__xmp_schemas, GUINT_TO_POINTER (key))) { + GST_WARNING ("Schema %s already exists, ignoring", name); + g_assert_not_reached (); + return; + } + + g_hash_table_insert (__xmp_schemas, GUINT_TO_POINTER (key), schema); +} + +static void +_gst_xmp_schema_add_mapping (GstXmpSchema * schema, const gchar * gst_tag, + GPtrArray * array) +{ + GQuark key; + + key = g_quark_from_string (gst_tag); + + if (gst_xmp_schema_lookup (schema, GUINT_TO_POINTER (key))) { + GST_WARNING ("Tag %s already present for the schema", gst_tag); + g_assert_not_reached (); + return; + } + gst_xmp_schema_insert (schema, GUINT_TO_POINTER (key), array); +} + +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) +{ + XmpTag *xmpinfo; + GPtrArray *array; + + xmpinfo = g_slice_new (XmpTag); + xmpinfo->tag_name = xmp_tag; + xmpinfo->type = xmp_type; + xmpinfo->serialize = serialization_func; + xmpinfo->deserialize = deserialization_func; + + array = g_ptr_array_sized_new (1); + g_ptr_array_add (array, xmpinfo); + + _gst_xmp_schema_add_mapping (schema, gst_tag, array); +} + +/* + * We do not return a copy here because elements are + * appended, and the API is not public, so we shouldn't + * have our lists modified during usage + */ +static GPtrArray * +_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; + + 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; +} + +/* finds the gst tag that maps to this xmp tag in this schema */ +static const gchar * +_gst_xmp_schema_get_mapping_reverse (GstXmpSchema * schema, + const gchar * xmp_tag, XmpTag ** _xmp_tag) +{ + 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); + + if (strcmp (xmpinfo->tag_name, xmp_tag) == 0) { + *_xmp_tag = xmpinfo; + ret = g_quark_to_string (GPOINTER_TO_UINT (key)); + goto out; + } + } + } + +out: + return ret; +} + +/* finds the gst tag that maps to this xmp tag (searches on all schemas) */ +static const gchar * +_gst_xmp_tag_get_mapping_reverse (const gchar * xmp_tag, XmpTag ** _xmp_tag) +{ + GHashTableIter iter; + gpointer key, value; + const gchar *ret = NULL; + + /* Iterate over the hashtable */ + g_hash_table_iter_init (&iter, __xmp_schemas); + while (!ret && g_hash_table_iter_next (&iter, &key, &value)) { + ret = _gst_xmp_schema_get_mapping_reverse ((GstXmpSchema *) value, xmp_tag, + _xmp_tag); + } + return ret; +} + +/* utility functions/macros */ + +#define METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR (3.6) +#define KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND (1/3.6) +#define MILES_PER_HOUR_TO_METERS_PER_SECOND (0.44704) +#define KNOTS_TO_METERS_PER_SECOND (0.514444) + +static gchar * +double_to_fraction_string (gdouble num) +{ + gint frac_n; + gint frac_d; + + gst_util_double_to_fraction (num, &frac_n, &frac_d); + return g_strdup_printf ("%d/%d", frac_n, frac_d); +} + +/* (de)serialize functions */ +static gchar * +serialize_exif_gps_coordinate (const GValue * value, gchar pos, gchar neg) +{ + gdouble num; + gchar c; + gint integer; + gchar fraction[G_ASCII_DTOSTR_BUF_SIZE]; + + g_return_val_if_fail (G_VALUE_TYPE (value) == G_TYPE_DOUBLE, NULL); + + num = g_value_get_double (value); + if (num < 0) { + c = neg; + num *= -1; + } else { + c = pos; + } + integer = (gint) num; + + g_ascii_dtostr (fraction, sizeof (fraction), (num - integer) * 60); + + /* FIXME review GPSCoordinate serialization spec for the .mm or ,ss + * decision. Couldn't understand it clearly */ + return g_strdup_printf ("%d,%s%c", integer, fraction, c); +} + +static gchar * +serialize_exif_latitude (const GValue * value) +{ + return serialize_exif_gps_coordinate (value, 'N', 'S'); +} + +static gchar * +serialize_exif_longitude (const GValue * value) +{ + return serialize_exif_gps_coordinate (value, 'E', 'W'); +} + +static void +deserialize_exif_gps_coordinate (XmpTag * xmptag, GstTagList * taglist, + const gchar * gst_tag, const gchar * str, gchar pos, gchar neg) +{ + gdouble value = 0; + gint d = 0, m = 0, s = 0; + gdouble m2 = 0; + gchar c = 0; + const gchar *current; + + /* get the degrees */ + if (sscanf (str, "%d", &d) != 1) + goto error; + + /* find the beginning of the minutes */ + current = strchr (str, ','); + if (current == NULL) + goto end; + current += 1; + + /* check if it uses ,SS or .mm */ + if (strchr (current, ',') != NULL) { + sscanf (current, "%d,%d%c", &m, &s, &c); + } else { + gchar *copy = g_strdup (current); + gint len = strlen (copy); + gint i; + + /* check the last letter */ + for (i = len - 1; len >= 0; len--) { + if (g_ascii_isspace (copy[i])) + continue; + + if (g_ascii_isalpha (copy[i])) { + /* found it */ + c = copy[i]; + copy[i] = '\0'; + break; + + } else { + /* something is wrong */ + g_free (copy); + goto error; + } + } + + /* use a copy so we can change the last letter as E can cause + * problems here */ + m2 = g_ascii_strtod (copy, NULL); + g_free (copy); + } + +end: + /* we can add them all as those that aren't parsed are 0 */ + value = d + (m / 60.0) + (s / (60.0 * 60.0)) + (m2 / 60.0); + + if (c == pos) { + //NOP + } else if (c == neg) { + value *= -1; + } else { + goto error; + } + + gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag, value, + NULL); + return; + +error: + GST_WARNING ("Failed to deserialize gps coordinate: %s", str); +} + +static void +deserialize_exif_latitude (XmpTag * xmptag, GstTagList * taglist, + const gchar * gst_tag, const gchar * xmp_tag, const gchar * str, + GSList ** pending_tags) +{ + deserialize_exif_gps_coordinate (xmptag, taglist, gst_tag, str, 'N', 'S'); +} + +static void +deserialize_exif_longitude (XmpTag * xmptag, GstTagList * taglist, + const gchar * gst_tag, const gchar * xmp_tag, const gchar * str, + GSList ** pending_tags) +{ + deserialize_exif_gps_coordinate (xmptag, taglist, gst_tag, str, 'E', 'W'); +} + +static gchar * +serialize_exif_altitude (const GValue * value) +{ + gdouble num; + + num = g_value_get_double (value); + + if (num < 0) + num *= -1; + + return double_to_fraction_string (num); +} + +static gchar * +serialize_exif_altituderef (const GValue * value) +{ + gdouble num; + + num = g_value_get_double (value); + + /* 0 means above sea level, 1 means below */ + if (num >= 0) + return g_strdup ("0"); + return g_strdup ("1"); +} + +static void +deserialize_exif_altitude (XmpTag * xmptag, GstTagList * taglist, + const gchar * gst_tag, const gchar * xmp_tag, const gchar * str, + GSList ** pending_tags) +{ + const gchar *altitude_str = NULL; + const gchar *altituderef_str = NULL; + gint frac_n; + gint frac_d; + gdouble value; + + GSList *entry; + PendingXmpTag *ptag = NULL; + + /* find the other missing part */ + if (strcmp (xmp_tag, "exif:GPSAltitude") == 0) { + altitude_str = str; + + for (entry = *pending_tags; entry; entry = g_slist_next (entry)) { + ptag = (PendingXmpTag *) entry->data; + + if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitudeRef") == 0) { + altituderef_str = ptag->str; + break; + } + } + + } else if (strcmp (xmp_tag, "exif:GPSAltitudeRef") == 0) { + altituderef_str = str; + + for (entry = *pending_tags; entry; entry = g_slist_next (entry)) { + ptag = (PendingXmpTag *) entry->data; + + if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitude") == 0) { + altitude_str = ptag->str; + break; + } + } + + } else { + GST_WARNING ("Unexpected xmp tag %s", xmp_tag); + return; + } + + if (!altitude_str) { + GST_WARNING ("Missing exif:GPSAltitude tag"); + return; + } + if (!altituderef_str) { + GST_WARNING ("Missing exif:GPSAltitudeRef tag"); + return; + } + + if (sscanf (altitude_str, "%d/%d", &frac_n, &frac_d) != 2) { + GST_WARNING ("Failed to parse fraction: %s", altitude_str); + return; + } + + gst_util_fraction_to_double (frac_n, frac_d, &value); + + if (altituderef_str[0] == '0') { + /* nop */ + } else if (altituderef_str[0] == '1') { + value *= -1; + } else { + GST_WARNING ("Unexpected exif:AltitudeRef value: %s", altituderef_str); + return; + } + + /* add to the taglist */ + gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), + GST_TAG_GEO_LOCATION_ELEVATION, value, NULL); + + /* clean up entry */ + g_free (ptag->str); + g_slice_free (PendingXmpTag, ptag); + *pending_tags = g_slist_delete_link (*pending_tags, entry); +} + +static gchar * +serialize_exif_gps_speed (const GValue * value) +{ + return double_to_fraction_string (g_value_get_double (value) * + METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR); +} + +static gchar * +serialize_exif_gps_speedref (const GValue * value) +{ + /* we always use km/h */ + return g_strdup ("K"); +} + +static void +deserialize_exif_gps_speed (XmpTag * xmptag, GstTagList * taglist, + const gchar * gst_tag, const gchar * xmp_tag, const gchar * str, + GSList ** pending_tags) +{ + const gchar *speed_str = NULL; + const gchar *speedref_str = NULL; + gint frac_n; + gint frac_d; + gdouble value; + + GSList *entry; + PendingXmpTag *ptag = NULL; + + /* find the other missing part */ + if (strcmp (xmp_tag, "exif:GPSSpeed") == 0) { + speed_str = str; + + for (entry = *pending_tags; entry; entry = g_slist_next (entry)) { + ptag = (PendingXmpTag *) entry->data; + + if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSSpeedRef") == 0) { + speedref_str = ptag->str; + break; + } + } + + } else if (strcmp (xmp_tag, "exif:GPSSpeedRef") == 0) { + speedref_str = str; + + for (entry = *pending_tags; entry; entry = g_slist_next (entry)) { + ptag = (PendingXmpTag *) entry->data; + + if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSSpeed") == 0) { + speed_str = ptag->str; + break; + } + } + + } else { + GST_WARNING ("Unexpected xmp tag %s", xmp_tag); + return; + } + + if (!speed_str) { + GST_WARNING ("Missing exif:GPSSpeed tag"); + return; + } + if (!speedref_str) { + GST_WARNING ("Missing exif:GPSSpeedRef tag"); + return; + } + + if (sscanf (speed_str, "%d/%d", &frac_n, &frac_d) != 2) { + GST_WARNING ("Failed to parse fraction: %s", speed_str); + return; + } + + gst_util_fraction_to_double (frac_n, frac_d, &value); + + if (speedref_str[0] == 'K') { + value *= KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND; + } else if (speedref_str[0] == 'M') { + value *= MILES_PER_HOUR_TO_METERS_PER_SECOND; + } else if (speedref_str[0] == 'N') { + value *= KNOTS_TO_METERS_PER_SECOND; + } else { + GST_WARNING ("Unexpected exif:SpeedRef value: %s", speedref_str); + return; + } + + /* add to the taglist */ + gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), + GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, value, NULL); + + /* clean up entry */ + g_free (ptag->str); + g_slice_free (PendingXmpTag, ptag); + *pending_tags = g_slist_delete_link (*pending_tags, entry); +} + +static gchar * +serialize_exif_gps_direction (const GValue * value) +{ + return double_to_fraction_string (g_value_get_double (value)); +} + +static gchar * +serialize_exif_gps_directionref (const GValue * value) +{ + /* T for true geographic direction (M would mean magnetic) */ + return g_strdup ("T"); +} + +static void +deserialize_exif_gps_direction (XmpTag * xmptag, GstTagList * taglist, + const gchar * gst_tag, const gchar * xmp_tag, const gchar * str, + GSList ** pending_tags, const gchar * direction_tag, + const gchar * directionref_tag) +{ + const gchar *dir_str = NULL; + const gchar *dirref_str = NULL; + gint frac_n; + gint frac_d; + gdouble value; + + GSList *entry; + PendingXmpTag *ptag = NULL; + + /* find the other missing part */ + if (strcmp (xmp_tag, direction_tag) == 0) { + dir_str = str; + + for (entry = *pending_tags; entry; entry = g_slist_next (entry)) { + ptag = (PendingXmpTag *) entry->data; + + if (strcmp (ptag->xmp_tag->tag_name, directionref_tag) == 0) { + dirref_str = ptag->str; + break; + } + } + + } else if (strcmp (xmp_tag, directionref_tag) == 0) { + dirref_str = str; + + for (entry = *pending_tags; entry; entry = g_slist_next (entry)) { + ptag = (PendingXmpTag *) entry->data; + + if (strcmp (ptag->xmp_tag->tag_name, direction_tag) == 0) { + dir_str = ptag->str; + break; + } + } + + } else { + GST_WARNING ("Unexpected xmp tag %s", xmp_tag); + return; + } + + if (!dir_str) { + GST_WARNING ("Missing %s tag", dir_str); + return; + } + if (!dirref_str) { + GST_WARNING ("Missing %s tag", dirref_str); + return; + } + + if (sscanf (dir_str, "%d/%d", &frac_n, &frac_d) != 2) { + GST_WARNING ("Failed to parse fraction: %s", dir_str); + return; + } + + gst_util_fraction_to_double (frac_n, frac_d, &value); + + if (dirref_str[0] == 'T') { + /* nop */ + } else if (dirref_str[0] == 'M') { + GST_WARNING ("Magnetic direction tags aren't supported yet"); + return; + } else { + GST_WARNING ("Unexpected %s value: %s", directionref_tag, dirref_str); + return; + } + + /* add to the taglist */ + gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag, value, + NULL); + + /* clean up entry */ + g_free (ptag->str); + g_slice_free (PendingXmpTag, ptag); + *pending_tags = g_slist_delete_link (*pending_tags, entry); +} + +static void +deserialize_exif_gps_track (XmpTag * xmptag, GstTagList * taglist, + const gchar * gst_tag, const gchar * xmp_tag, const gchar * str, + GSList ** pending_tags) +{ + deserialize_exif_gps_direction (xmptag, taglist, gst_tag, xmp_tag, str, + pending_tags, "exif:GPSTrack", "exif:GPSTrackRef"); +} + +static void +deserialize_exif_gps_img_direction (XmpTag * xmptag, GstTagList * taglist, + const gchar * gst_tag, const gchar * xmp_tag, const gchar * str, + GSList ** pending_tags) +{ + deserialize_exif_gps_direction (xmptag, taglist, gst_tag, xmp_tag, str, + pending_tags, "exif:GPSImgDirection", "exif:GPSImgDirectionRef"); +} + +static void +deserialize_xmp_rating (XmpTag * xmptag, GstTagList * taglist, + const gchar * gst_tag, const gchar * xmp_tag, const gchar * str, + GSList ** pending_tags) +{ + guint value; + + if (sscanf (str, "%u", &value) != 1) { + GST_WARNING ("Failed to parse xmp:Rating %s", str); + return; + } + + if (value < 0 || value > 100) { + GST_WARNING ("Unsupported Rating tag %u (should be from 0 to 100), " + "ignoring", value); + return; + } + + gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag, value, + NULL); +} + +static gchar * +serialize_tiff_orientation (const GValue * value) +{ + const gchar *str; + gint num; + + str = g_value_get_string (value); + if (str == NULL) { + GST_WARNING ("Failed to get image orientation tag value"); + return NULL; + } + + num = __exif_tag_image_orientation_to_exif_value (str); + if (num == -1) + return NULL; + + return g_strdup_printf ("%d", num); +} + +static void +deserialize_tiff_orientation (XmpTag * xmptag, GstTagList * taglist, + const gchar * gst_tag, const gchar * xmp_tag, const gchar * str, + GSList ** pending_tags) +{ + guint value; + const gchar *orientation = NULL; + + if (sscanf (str, "%u", &value) != 1) { + GST_WARNING ("Failed to parse tiff:Orientation %s", str); + return; + } + + if (value < 1 || value > 8) { + GST_WARNING ("Invalid tiff:Orientation tag %u (should be from 1 to 8), " + "ignoring", value); + return; + } + + orientation = __exif_tag_image_orientation_from_exif_value (value); + if (orientation == NULL) + return; + gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag, + orientation, NULL); +} + + +/* look at this page for addtional schemas + * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/XMP.html + */ +static gpointer +_init_xmp_tag_map (gpointer user_data) +{ + GPtrArray *array; + XmpTag *xmpinfo; + GstXmpSchema *schema; + + __xmp_schemas = g_hash_table_new (g_direct_hash, g_direct_equal); + + /* add the maps */ + /* dublic code metadata + * http://dublincore.org/documents/dces/ + */ + schema = gst_xmp_schema_new (); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_ARTIST, + "dc:creator", GST_XMP_TAG_TYPE_SEQ, NULL, NULL); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_COPYRIGHT, + "dc:rights", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DATE, "dc:date", + GST_XMP_TAG_TYPE_SEQ, NULL, NULL); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DESCRIPTION, + "dc:description", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_KEYWORDS, + "dc:subject", GST_XMP_TAG_TYPE_BAG, NULL, NULL); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_TITLE, "dc:title", + GST_XMP_TAG_TYPE_SIMPLE, 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); + _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 ("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, + NULL); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DEVICE_MODEL, + "tiff:Model", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_APPLICATION_NAME, + "tiff:Software", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_IMAGE_ORIENTATION, + "tiff:Orientation", GST_XMP_TAG_TYPE_SIMPLE, serialize_tiff_orientation, + deserialize_tiff_orientation); + _gst_xmp_add_schema ("tiff", schema); + + /* exif schema */ + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DATE_TIME, + "exif:DateTimeOriginal", GST_XMP_TAG_TYPE_SIMPLE, 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); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_GEO_LOCATION_LONGITUDE, + "exif:GPSLongitude", GST_XMP_TAG_TYPE_SIMPLE, serialize_exif_longitude, + deserialize_exif_longitude); + _gst_xmp_schema_add_simple_mapping (schema, + GST_TAG_CAPTURING_EXPOSURE_COMPENSATION, "exif:ExposureBiasValue", + GST_XMP_TAG_TYPE_SIMPLE, 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); + + 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 */ + _gst_xmp_schema_add_simple_mapping (schema, + GST_TAG_GEO_LOCATION_COUNTRY, "photoshop:Country", + GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL); + _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_GEO_LOCATION_CITY, + "photoshop:City", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL); + _gst_xmp_add_schema ("photoshop", schema); + + /* iptc4xmpcore schema */ + _gst_xmp_schema_add_simple_mapping (schema, + GST_TAG_GEO_LOCATION_SUBLOCATION, "Iptc4xmpCore:Location", + GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL); + _gst_xmp_add_schema ("Iptc4xmpCore", schema); + + return NULL; +} + +static void +xmp_tags_initialize () +{ + static GOnce my_once = G_ONCE_INIT; + g_once (&my_once, (GThreadFunc) _init_xmp_tag_map, NULL); +} + +typedef struct _GstXmpNamespaceMatch GstXmpNamespaceMatch; +struct _GstXmpNamespaceMatch +{ + const gchar *ns_prefix; + const gchar *ns_uri; +}; + +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} +}; + +typedef struct _GstXmpNamespaceMap GstXmpNamespaceMap; +struct _GstXmpNamespaceMap +{ + const gchar *original_ns; + gchar *gstreamer_ns; +}; + +/* parsing */ + +static void +read_one_tag (GstTagList * list, const gchar * tag, XmpTag * xmptag, + const gchar * v, GSList ** pending_tags) +{ + GType tag_type; + GstTagMergeMode merge_mode; + + if (xmptag && xmptag->deserialize) { + xmptag->deserialize (xmptag, list, tag, xmptag->tag_name, v, pending_tags); + return; + } + + merge_mode = xmp_tag_get_merge_mode (xmptag); + tag_type = gst_tag_get_type (tag); + + /* add gstreamer tag depending on type */ + switch (tag_type) { + case G_TYPE_STRING:{ + 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; + gint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0; + gint usecs = 0; + gint gmt_offset_hour = -1, gmt_offset_min = -1, gmt_offset = -1; + gchar usec_str[16]; + gint ret; + gint len; + + len = strlen (v); + if (len == 0) { + GST_WARNING ("Empty string for datetime parsing"); + return; + } + + GST_DEBUG ("Parsing %s into a datetime", v); + + ret = sscanf (v, "%04d-%02d-%02dT%02d:%02d:%02d.%15s", + &year, &month, &day, &hour, &minute, &second, usec_str); + if (ret < 3) { + /* FIXME theoretically, xmp can express datetimes with only year + * or year and month, but gstdatetime doesn't support it */ + GST_WARNING ("Invalid datetime value: %s", v); + } + + /* parse the usecs */ + if (ret >= 7) { + gint num_digits = 0; + + /* find the number of digits */ + while (isdigit ((gint) usec_str[num_digits++]) && num_digits < 6); + + if (num_digits > 0) { + /* fill up to 6 digits with 0 */ + while (num_digits < 6) { + usec_str[num_digits++] = 0; + } + + g_assert (num_digits == 6); + + usec_str[num_digits] = '\0'; + usecs = atoi (usec_str); + } + } + + /* parse the timezone info */ + if (v[len - 1] == 'Z') { + GST_LOG ("UTC timezone"); + + /* Having a Z at the end means UTC */ + datetime = gst_date_time_new (0, year, month, day, hour, minute, + second + usecs / 1000000.0); + } else { + gchar *plus_pos = NULL; + gchar *neg_pos = NULL; + gchar *pos = NULL; + + GST_LOG ("Checking for timezone information"); + + /* check if there is timezone info */ + plus_pos = strrchr (v, '+'); + neg_pos = strrchr (v, '-'); + if (plus_pos) { + pos = plus_pos + 1; + } else if (neg_pos) { + pos = neg_pos + 1; + } + + if (pos) { + gint ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour, + &gmt_offset_min); + + GST_DEBUG ("Parsing timezone: %s", pos); + + if (ret_tz == 2) { + gmt_offset = gmt_offset_hour * 60 + gmt_offset_min; + if (neg_pos != NULL && neg_pos + 1 == pos) + gmt_offset *= -1; + + GST_LOG ("Timezone offset: %f (%d minutes)", gmt_offset / 60.0, + gmt_offset); + + /* no way to know if it is DST or not */ + datetime = + gst_date_time_new (gmt_offset / 60.0, + year, month, day, hour, minute, + second + usecs / ((gdouble) G_USEC_PER_SEC)); + } else { + GST_WARNING ("Failed to parse timezone information"); + } + } else { + GST_WARNING ("No timezone signal found"); + } + } + + if (datetime) { + gst_tag_list_add (list, merge_mode, tag, datetime, NULL); + gst_date_time_unref (datetime); + } + + } else if (tag_type == GST_TYPE_DATE) { + GDate *date; + gint d, m, y; + + /* this is ISO 8601 Date and Time Format + * %F Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99) + * %T The time in 24-hour notation (%H:%M:%S). (SU) + * e.g. 2009-05-30T18:26:14+03:00 */ + + /* FIXME: this would be the proper way, but needs + #define _XOPEN_SOURCE before #include + + date = g_date_new (); + struct tm tm={0,}; + strptime (dts, "%FT%TZ", &tm); + g_date_set_time_t (date, mktime(&tm)); + */ + /* FIXME: this cannot parse the date + date = g_date_new (); + g_date_set_parse (date, v); + if (g_date_valid (date)) { + gst_tag_list_add (list, merge_mode, tag, + date, NULL); + } else { + GST_WARNING ("unparsable date: '%s'", v); + } + */ + /* poor mans straw */ + sscanf (v, "%04d-%02d-%02dT", &y, &m, &d); + date = g_date_new_dmy (d, m, y); + gst_tag_list_add (list, merge_mode, tag, date, NULL); + g_date_free (date); + } else { + GST_WARNING ("unhandled type for %s from xmp", tag); + } + break; + } +} + +/** + * gst_tag_list_from_xmp_buffer: + * @buffer: buffer + * + * Parse a xmp packet into a taglist. + * + * Returns: new taglist or %NULL, free the list when done + * + * Since: 0.10.29 + */ +GstTagList * +gst_tag_list_from_xmp_buffer (const GstBuffer * buffer) +{ + GstTagList *list = NULL; + const gchar *xps, *xp1, *xp2, *xpe, *ns, *ne; + guint len, max_ft_len; + gboolean in_tag; + gchar *part, *pp; + guint i; + const gchar *last_tag = NULL; + XmpTag *last_xmp_tag = NULL; + GSList *pending_tags = NULL; + + GstXmpNamespaceMap ns_map[] = { + {"dc", NULL}, + {"exif", NULL}, + {"tiff", NULL}, + {"xap", NULL}, + {"photoshop", NULL}, + {"Iptc4xmpCore", 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); + + xps = (const gchar *) GST_BUFFER_DATA (buffer); + len = GST_BUFFER_SIZE (buffer); + xpe = &xps[len + 1]; + + /* check header and footer */ + xp1 = g_strstr_len (xps, len, "' && *xp1 != '<' && xp1 < xpe) + xp1++; + if (*xp1 != '>') + goto missing_header; + + max_ft_len = 1 + strlen ("\n"); + if (len < max_ft_len) + goto missing_footer; + + GST_DEBUG ("checking footer: [%s]", &xps[len - max_ft_len]); + xp2 = g_strstr_len (&xps[len - max_ft_len], max_ft_len, " and text until first xml-node */ + xp1++; + while (*xp1 != '<' && xp1 < xpe) + xp1++; + + /* no tag can be longer that the whole buffer */ + part = g_malloc (xp2 - xp1); + list = gst_tag_list_new (); + + /* parse data into a list of nodes */ + /* data is between xp1..xp2 */ + in_tag = TRUE; + ns = ne = xp1; + pp = part; + while (ne < xp2) { + if (in_tag) { + ne++; + while (ne < xp2 && *ne != '>' && *ne != '<') { + if (*ne == '\n' || *ne == '\t' || *ne == ' ') { + while (ne < xp2 && (*ne == '\n' || *ne == '\t' || *ne == ' ')) + ne++; + *pp++ = ' '; + } else { + *pp++ = *ne++; + } + } + *pp = '\0'; + if (*ne != '>') + goto broken_xml; + /* create node */ + /* {XML, ns, ne-ns} */ + if (ns[0] != '/') { + gchar *as = strchr (part, ' '); + /* only log start nodes */ + GST_INFO ("xml: %s", part); + + if (as) { + gchar *ae, *d; + + /* skip ' ' and scan the attributes */ + as++; + d = ae = as; + + /* split attr=value pairs */ + while (*ae != '\0') { + if (*ae == '=') { + /* attr/value delimmiter */ + d = ae; + } else if (*ae == '"') { + /* scan values */ + gchar *v; + + ae++; + while (*ae != '\0' && *ae != '"') + ae++; + + *d = *ae = '\0'; + v = &d[2]; + GST_INFO (" : [%s][%s]", as, v); + if (!strncmp (as, "xmlns:", 6)) { + i = 0; + /* we need to rewrite known namespaces to what we use in + * tag_matches */ + while (ns_match[i].ns_prefix) { + if (!strcmp (ns_match[i].ns_uri, v)) + break; + i++; + } + if (ns_match[i].ns_prefix) { + if (strcmp (ns_map[i].original_ns, &as[6])) { + ns_map[i].gstreamer_ns = g_strdup (&as[6]); + } + } + } else { + const gchar *gst_tag; + XmpTag *xmp_tag = NULL; + /* FIXME: eventualy 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) { + PendingXmpTag *ptag; + + ptag = g_slice_new (PendingXmpTag); + ptag->gst_tag = gst_tag; + ptag->xmp_tag = xmp_tag; + ptag->str = g_strdup (v); + + pending_tags = g_slist_append (pending_tags, ptag); + } + } + /* restore chars overwritten by '\0' */ + *d = '='; + *ae = '"'; + } else if (*ae == '\0' || *ae == ' ') { + /* end of attr/value pair */ + as = &ae[1]; + } + /* to next char if not eos */ + if (*ae != '\0') + ae++; + } + } else { + /* + Image + + */ + /* FIXME: eventualy rewrite ns */ + + /* skip rdf tags for now */ + if (strncmp (part, "rdf:", 4)) { + const gchar *parttag; + + parttag = _gst_xmp_tag_get_mapping_reverse (part, &last_xmp_tag); + if (parttag) { + last_tag = parttag; + } + } + } + } + /* next cycle */ + ne++; + if (ne < xp2) { + if (*ne != '<') + in_tag = FALSE; + ns = ne; + pp = part; + } + } else { + while (ne < xp2 && *ne != '<') { + *pp++ = *ne; + ne++; + } + *pp = '\0'; + /* create node */ + /* {TXT, ns, (ne-ns)-1} */ + 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) { + PendingXmpTag *ptag; + + ptag = g_slice_new (PendingXmpTag); + ptag->gst_tag = last_tag; + ptag->xmp_tag = last_xmp_tag; + ptag->str = g_strdup (part); + + pending_tags = g_slist_append (pending_tags, ptag); + } + } + /* next cycle */ + in_tag = TRUE; + ns = ne; + pp = part; + } + } + + while (pending_tags) { + PendingXmpTag *ptag = (PendingXmpTag *) pending_tags->data; + + pending_tags = g_slist_delete_link (pending_tags, pending_tags); + + read_one_tag (list, ptag->gst_tag, ptag->xmp_tag, ptag->str, &pending_tags); + + g_free (ptag->str); + g_slice_free (PendingXmpTag, ptag); + } + + GST_INFO ("xmp packet parsed, %d entries", + gst_structure_n_fields ((GstStructure *) list)); + + /* free resources */ + i = 0; + while (ns_map[i].original_ns) { + g_free (ns_map[i].gstreamer_ns); + i++; + } + g_free (part); + + return list; + + /* Errors */ +missing_header: + GST_WARNING ("malformed xmp packet header"); + return NULL; +missing_footer: + GST_WARNING ("malformed xmp packet footer"); + return NULL; +broken_xml: + GST_WARNING ("malformed xml tag: %s", part); + return NULL; +} + + +/* formatting */ + +static void +string_open_tag (GString * string, const char *tag) +{ + g_string_append_c (string, '<'); + g_string_append (string, tag); + g_string_append_c (string, '>'); +} + +static void +string_close_tag (GString * string, const char *tag) +{ + g_string_append (string, "\n"); +} + +static char * +gst_value_serialize_xmp (const GValue * value) +{ + switch (G_VALUE_TYPE (value)) { + case G_TYPE_STRING: + return g_markup_escape_text (g_value_get_string (value), -1); + case G_TYPE_INT: + 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; + } + /* put non-switchable types here */ + if (G_VALUE_TYPE (value) == GST_TYPE_DATE) { + const GDate *date = gst_value_get_date (value); + + return g_strdup_printf ("%04d-%02d-%02d", + (gint) g_date_get_year (date), (gint) g_date_get_month (date), + (gint) g_date_get_day (date)); + } else if (G_VALUE_TYPE (value) == GST_TYPE_DATE_TIME) { + gint year, month, day, hour, min, sec, microsec; + gfloat gmt_offset = 0; + gint gmt_offset_hour, gmt_offset_min; + GstDateTime *datetime = (GstDateTime *) g_value_get_boxed (value); + + year = gst_date_time_get_year (datetime); + month = gst_date_time_get_month (datetime); + day = gst_date_time_get_day (datetime); + hour = gst_date_time_get_hour (datetime); + min = gst_date_time_get_minute (datetime); + sec = gst_date_time_get_second (datetime); + microsec = gst_date_time_get_microsecond (datetime); + gmt_offset = gst_date_time_get_time_zone_offset (datetime); + if (gmt_offset == 0) { + /* UTC */ + return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d.%06dZ", + year, month, day, hour, min, sec, microsec); + } else { + gmt_offset_hour = ABS (gmt_offset); + gmt_offset_min = (ABS (gmt_offset) - gmt_offset_hour) * 60; + + return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d.%06d%c%02d:%02d", + year, month, day, hour, min, sec, microsec, + gmt_offset >= 0 ? '+' : '-', gmt_offset_hour, gmt_offset_min); + } + } else { + return NULL; + } +} + +static void +write_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data) +{ + guint i = 0, ct = gst_tag_list_get_tag_size (list, tag), tag_index; + XmpSerializationData *serialization_data = user_data; + GString *data = serialization_data->data; + GPtrArray *xmp_tag_array = NULL; + char *s; + + /* map gst-tag to xmp tag */ + xmp_tag_array = _xmp_tag_get_mapping (tag, serialization_data); + + if (!xmp_tag_array) { + GST_WARNING ("no mapping for %s to xmp", tag); + return; + } + + for (tag_index = 0; tag_index < xmp_tag_array->len; tag_index++) { + XmpTag *xmp_tag; + + xmp_tag = g_ptr_array_index (xmp_tag_array, tag_index); + 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) { + if (xmp_tag->serialize) { + s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, 0)); + } else { + s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag, + 0)); + } + if (s) { + g_string_append (data, s); + 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); + } + } + string_close_tag (data, typename); + } + + string_close_tag (data, xmp_tag->tag_name); + } +} + +/** + * 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 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.33 + */ +GstBuffer * +gst_tag_list_to_xmp_buffer_full (const GstTagList * list, gboolean read_only, + const gchar ** schemas) +{ + GstBuffer *buffer = NULL; + 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); + + /* xmp header */ + g_string_append (data, + "\n"); + g_string_append (data, + "\n"); + g_string_append (data, + "\n"); + g_string_append (data, "\n"); + + /* iterate the taglist */ + gst_tag_list_foreach (list, write_one_tag, &serialization_data); + + /* xmp footer */ + g_string_append (data, "\n"); + g_string_append (data, "\n"); + g_string_append (data, "\n"); + + if (!read_only) { + /* the xmp spec recommand to add 2-4KB padding for in-place editable xmp */ + guint i; + + for (i = 0; i < 32; i++) { + g_string_append (data, " " " " + " " " " "\n"); + } + } + g_string_append_printf (data, "\n", + (read_only ? 'r' : 'w')); + + buffer = gst_buffer_new (); + GST_BUFFER_SIZE (buffer) = data->len + 1; + 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