2 * Copyright (C) 2010 Stefan Kost <stefan.kost@nokia.com>
3 * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
5 * gstxmptag.c: library for reading / modifying xmp tags
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
25 * @short_description: tag mappings and support functions for plugins
26 * dealing with xmp packets
27 * @see_also: #GstTagList
29 * Contains various utility functions for plugins to parse or create
30 * xmp packets and map them to and from #GstTagList<!-- -->s.
32 * Please note that the xmp parser is very lightweight and not strict at all.
39 #include <gst/gsttagsetter.h>
40 #include "gsttageditingprivate.h"
47 static const gchar *schema_list[] = {
59 * gst_tag_xmp_list_schemas:
61 * Gets the list of supported schemas in the xmp lib
63 * Returns: a %NULL terminated array of strings with the schema names
68 gst_tag_xmp_list_schemas (void)
73 typedef struct _XmpSerializationData XmpSerializationData;
74 typedef struct _XmpTag XmpTag;
77 * Serializes a GValue into a string.
79 typedef gchar *(*XmpSerializationFunc) (const GValue * value);
82 * Deserializes @str that is the gstreamer tag @gst_tag represented in
83 * XMP as the @xmp_tag_value and adds the result to the @taglist.
85 * @pending_tags is passed so that compound xmp tags can search for its
86 * complements on the list and use them. Note that used complements should
87 * be freed and removed from the list.
88 * The list is of PendingXmpTag
90 typedef void (*XmpDeserializationFunc) (XmpTag * xmptag, GstTagList * taglist,
91 const gchar * gst_tag, const gchar * xmp_tag_value,
92 const gchar * str, GSList ** pending_tags);
94 struct _XmpSerializationData
97 const gchar **schemas;
101 xmp_serialization_data_use_schema (XmpSerializationData * serdata,
102 const gchar * schemaname)
105 if (serdata->schemas == NULL)
108 while (serdata->schemas[i] != NULL) {
109 if (strcmp (serdata->schemas[i], schemaname) == 0)
118 GstXmpTagTypeNone = 0,
124 /* Not really a xmp type, this is a tag that in gst is represented with
125 * a single value and on xmp it needs 2 (or more) simple values
127 * e.g. GST_TAG_GEO_LOCATION_ELEVATION needs to be mapped into 2 complementary
128 * tags in the exif's schema. One of them stores the absolute elevation,
129 * and the other one stores if it is above of below sea level.
131 GstXmpTagTypeCompound
136 const gchar *gst_tag;
137 const gchar *tag_name;
140 /* some tags must be inside a Bag even
141 * if they are a single entry. Set it here so we know */
142 GstXmpTagType supertype;
144 /* For tags that need a rdf:parseType attribute */
145 const gchar *parse_type;
147 /* Used for struct and compound types */
150 XmpSerializationFunc serialize;
151 XmpDeserializationFunc deserialize;
154 static GstTagMergeMode
155 xmp_tag_get_merge_mode (XmpTag * xmptag)
157 switch (xmptag->type) {
158 case GstXmpTagTypeBag:
159 case GstXmpTagTypeSeq:
160 return GST_TAG_MERGE_APPEND;
161 case GstXmpTagTypeSimple:
163 return GST_TAG_MERGE_KEEP;
168 xmp_tag_type_get_name (GstXmpTagType tagtype)
171 case GstXmpTagTypeSeq:
173 case GstXmpTagTypeBag:
179 /* Make compiler happy */
180 g_return_val_if_reached ("");
183 struct _PendingXmpTag
188 typedef struct _PendingXmpTag PendingXmpTag;
191 * A schema is a mapping of strings (the tag name in gstreamer) to a list of
192 * tags in xmp (XmpTag).
194 typedef GHashTable GstXmpSchema;
195 #define gst_xmp_schema_lookup g_hash_table_lookup
196 #define gst_xmp_schema_insert g_hash_table_insert
197 static GstXmpSchema *
198 gst_xmp_schema_new ()
200 return g_hash_table_new (g_direct_hash, g_direct_equal);
204 * Mappings from schema names into the schema group of tags (GstXmpSchema)
206 static GHashTable *__xmp_schemas;
208 static GstXmpSchema *
209 _gst_xmp_get_schema (const gchar * name)
212 GstXmpSchema *schema;
214 key = g_quark_from_string (name);
216 schema = g_hash_table_lookup (__xmp_schemas, GUINT_TO_POINTER (key));
218 GST_WARNING ("Schema %s doesn't exist", name);
224 _gst_xmp_add_schema (const gchar * name, GstXmpSchema * schema)
228 key = g_quark_from_string (name);
230 if (g_hash_table_lookup (__xmp_schemas, GUINT_TO_POINTER (key))) {
231 GST_WARNING ("Schema %s already exists, ignoring", name);
232 g_assert_not_reached ();
236 g_hash_table_insert (__xmp_schemas, GUINT_TO_POINTER (key), schema);
240 _gst_xmp_schema_add_mapping (GstXmpSchema * schema, XmpTag * tag)
244 key = g_quark_from_string (tag->gst_tag);
246 if (gst_xmp_schema_lookup (schema, GUINT_TO_POINTER (key))) {
247 GST_WARNING ("Tag %s already present for the schema", tag->gst_tag);
248 g_assert_not_reached ();
251 gst_xmp_schema_insert (schema, GUINT_TO_POINTER (key), tag);
255 gst_xmp_tag_create (const gchar * gst_tag, const gchar * xmp_tag,
256 gint xmp_type, XmpSerializationFunc serialization_func,
257 XmpDeserializationFunc deserialization_func)
261 xmpinfo = g_slice_new (XmpTag);
262 xmpinfo->gst_tag = gst_tag;
263 xmpinfo->tag_name = xmp_tag;
264 xmpinfo->type = xmp_type;
265 xmpinfo->supertype = GstXmpTagTypeNone;
266 xmpinfo->parse_type = NULL;
267 xmpinfo->serialize = serialization_func;
268 xmpinfo->deserialize = deserialization_func;
269 xmpinfo->children = NULL;
275 gst_xmp_tag_create_compound (const gchar * gst_tag, const gchar * xmp_tag_a,
276 const gchar * xmp_tag_b, XmpSerializationFunc serialization_func_a,
277 XmpSerializationFunc serialization_func_b,
278 XmpDeserializationFunc deserialization_func)
282 gst_xmp_tag_create (gst_tag, xmp_tag_a, GstXmpTagTypeSimple,
283 serialization_func_a, deserialization_func);
285 gst_xmp_tag_create (gst_tag, xmp_tag_b, GstXmpTagTypeSimple,
286 serialization_func_b, deserialization_func);
289 gst_xmp_tag_create (gst_tag, NULL, GstXmpTagTypeCompound, NULL, NULL);
291 xmptag->children = g_slist_prepend (xmptag->children, xmptag_b);
292 xmptag->children = g_slist_prepend (xmptag->children, xmptag_a);
298 _gst_xmp_schema_add_simple_mapping (GstXmpSchema * schema,
299 const gchar * gst_tag, const gchar * xmp_tag, gint xmp_type,
300 XmpSerializationFunc serialization_func,
301 XmpDeserializationFunc deserialization_func)
303 _gst_xmp_schema_add_mapping (schema,
304 gst_xmp_tag_create (gst_tag, xmp_tag, xmp_type, serialization_func,
305 deserialization_func));
309 * We do not return a copy here because elements are
310 * appended, and the API is not public, so we shouldn't
311 * have our lists modified during usage
315 _xmp_tag_get_mapping (const gchar * gst_tag, XmpSerializationData * serdata)
317 GPtrArray *ret = NULL;
319 GQuark key = g_quark_from_string (gst_tag);
320 gpointer iterkey, value;
321 const gchar *schemaname;
323 g_hash_table_iter_init (&iter, __xmp_schemas);
324 while (!ret && g_hash_table_iter_next (&iter, &iterkey, &value)) {
325 GstXmpSchema *schema = (GstXmpSchema *) value;
327 schemaname = g_quark_to_string (GPOINTER_TO_UINT (iterkey));
328 if (xmp_serialization_data_use_schema (serdata, schemaname))
330 (GPtrArray *) gst_xmp_schema_lookup (schema, GUINT_TO_POINTER (key));
336 /* finds the gst tag that maps to this xmp tag in this schema */
338 _gst_xmp_schema_get_mapping_reverse (GstXmpSchema * schema,
339 const gchar * xmp_tag, XmpTag ** _xmp_tag)
343 const gchar *ret = NULL;
345 /* Iterate over the hashtable */
346 g_hash_table_iter_init (&iter, schema);
347 while (!ret && g_hash_table_iter_next (&iter, &key, &value)) {
348 XmpTag *xmpinfo = (XmpTag *) value;
350 if (xmpinfo->tag_name) {
351 if (strcmp (xmpinfo->tag_name, xmp_tag) == 0) {
353 ret = g_quark_to_string (GPOINTER_TO_UINT (key));
356 } else if (xmpinfo->children) {
358 for (iter = xmpinfo->children; iter; iter = g_slist_next (iter)) {
359 XmpTag *child = iter->data;
360 if (strcmp (child->tag_name, xmp_tag) == 0) {
362 ret = g_quark_to_string (GPOINTER_TO_UINT (key));
367 g_assert_not_reached ();
375 /* finds the gst tag that maps to this xmp tag (searches on all schemas) */
377 _gst_xmp_tag_get_mapping_reverse (const gchar * xmp_tag, XmpTag ** _xmp_tag)
381 const gchar *ret = NULL;
383 /* Iterate over the hashtable */
384 g_hash_table_iter_init (&iter, __xmp_schemas);
385 while (!ret && g_hash_table_iter_next (&iter, &key, &value)) {
386 ret = _gst_xmp_schema_get_mapping_reverse ((GstXmpSchema *) value, xmp_tag,
392 /* utility functions/macros */
394 #define METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR (3.6)
395 #define KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND (1/3.6)
396 #define MILES_PER_HOUR_TO_METERS_PER_SECOND (0.44704)
397 #define KNOTS_TO_METERS_PER_SECOND (0.514444)
400 double_to_fraction_string (gdouble num)
405 gst_util_double_to_fraction (num, &frac_n, &frac_d);
406 return g_strdup_printf ("%d/%d", frac_n, frac_d);
409 /* (de)serialize functions */
411 serialize_exif_gps_coordinate (const GValue * value, gchar pos, gchar neg)
416 gchar fraction[G_ASCII_DTOSTR_BUF_SIZE];
418 g_return_val_if_fail (G_VALUE_TYPE (value) == G_TYPE_DOUBLE, NULL);
420 num = g_value_get_double (value);
427 integer = (gint) num;
429 g_ascii_dtostr (fraction, sizeof (fraction), (num - integer) * 60);
431 /* FIXME review GPSCoordinate serialization spec for the .mm or ,ss
432 * decision. Couldn't understand it clearly */
433 return g_strdup_printf ("%d,%s%c", integer, fraction, c);
437 serialize_exif_latitude (const GValue * value)
439 return serialize_exif_gps_coordinate (value, 'N', 'S');
443 serialize_exif_longitude (const GValue * value)
445 return serialize_exif_gps_coordinate (value, 'E', 'W');
449 deserialize_exif_gps_coordinate (XmpTag * xmptag, GstTagList * taglist,
450 const gchar * gst_tag, const gchar * str, gchar pos, gchar neg)
453 gint d = 0, m = 0, s = 0;
456 const gchar *current;
458 /* get the degrees */
459 if (sscanf (str, "%d", &d) != 1)
462 /* find the beginning of the minutes */
463 current = strchr (str, ',');
468 /* check if it uses ,SS or .mm */
469 if (strchr (current, ',') != NULL) {
470 sscanf (current, "%d,%d%c", &m, &s, &c);
472 gchar *copy = g_strdup (current);
473 gint len = strlen (copy);
476 /* check the last letter */
477 for (i = len - 1; len >= 0; len--) {
478 if (g_ascii_isspace (copy[i]))
481 if (g_ascii_isalpha (copy[i])) {
488 /* something is wrong */
494 /* use a copy so we can change the last letter as E can cause
496 m2 = g_ascii_strtod (copy, NULL);
501 /* we can add them all as those that aren't parsed are 0 */
502 value = d + (m / 60.0) + (s / (60.0 * 60.0)) + (m2 / 60.0);
506 } else if (c == neg) {
512 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag, value,
517 GST_WARNING ("Failed to deserialize gps coordinate: %s", str);
521 deserialize_exif_latitude (XmpTag * xmptag, GstTagList * taglist,
522 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
523 GSList ** pending_tags)
525 deserialize_exif_gps_coordinate (xmptag, taglist, gst_tag, str, 'N', 'S');
529 deserialize_exif_longitude (XmpTag * xmptag, GstTagList * taglist,
530 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
531 GSList ** pending_tags)
533 deserialize_exif_gps_coordinate (xmptag, taglist, gst_tag, str, 'E', 'W');
537 serialize_exif_altitude (const GValue * value)
541 num = g_value_get_double (value);
546 return double_to_fraction_string (num);
550 serialize_exif_altituderef (const GValue * value)
554 num = g_value_get_double (value);
556 /* 0 means above sea level, 1 means below */
558 return g_strdup ("0");
559 return g_strdup ("1");
563 deserialize_exif_altitude (XmpTag * xmptag, GstTagList * taglist,
564 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
565 GSList ** pending_tags)
567 const gchar *altitude_str = NULL;
568 const gchar *altituderef_str = NULL;
574 PendingXmpTag *ptag = NULL;
576 /* find the other missing part */
577 if (strcmp (xmp_tag, "exif:GPSAltitude") == 0) {
580 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
581 ptag = (PendingXmpTag *) entry->data;
583 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitudeRef") == 0) {
584 altituderef_str = ptag->str;
589 } else if (strcmp (xmp_tag, "exif:GPSAltitudeRef") == 0) {
590 altituderef_str = str;
592 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
593 ptag = (PendingXmpTag *) entry->data;
595 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitude") == 0) {
596 altitude_str = ptag->str;
602 GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
607 GST_WARNING ("Missing exif:GPSAltitude tag");
610 if (!altituderef_str) {
611 GST_WARNING ("Missing exif:GPSAltitudeRef tag");
615 if (sscanf (altitude_str, "%d/%d", &frac_n, &frac_d) != 2) {
616 GST_WARNING ("Failed to parse fraction: %s", altitude_str);
620 gst_util_fraction_to_double (frac_n, frac_d, &value);
622 if (altituderef_str[0] == '0') {
624 } else if (altituderef_str[0] == '1') {
627 GST_WARNING ("Unexpected exif:AltitudeRef value: %s", altituderef_str);
631 /* add to the taglist */
632 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag),
633 GST_TAG_GEO_LOCATION_ELEVATION, value, NULL);
637 g_slice_free (PendingXmpTag, ptag);
638 *pending_tags = g_slist_delete_link (*pending_tags, entry);
642 serialize_exif_gps_speed (const GValue * value)
644 return double_to_fraction_string (g_value_get_double (value) *
645 METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR);
649 serialize_exif_gps_speedref (const GValue * value)
651 /* we always use km/h */
652 return g_strdup ("K");
656 deserialize_exif_gps_speed (XmpTag * xmptag, GstTagList * taglist,
657 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
658 GSList ** pending_tags)
660 const gchar *speed_str = NULL;
661 const gchar *speedref_str = NULL;
667 PendingXmpTag *ptag = NULL;
669 /* find the other missing part */
670 if (strcmp (xmp_tag, "exif:GPSSpeed") == 0) {
673 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
674 ptag = (PendingXmpTag *) entry->data;
676 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSSpeedRef") == 0) {
677 speedref_str = ptag->str;
682 } else if (strcmp (xmp_tag, "exif:GPSSpeedRef") == 0) {
685 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
686 ptag = (PendingXmpTag *) entry->data;
688 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSSpeed") == 0) {
689 speed_str = ptag->str;
695 GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
700 GST_WARNING ("Missing exif:GPSSpeed tag");
704 GST_WARNING ("Missing exif:GPSSpeedRef tag");
708 if (sscanf (speed_str, "%d/%d", &frac_n, &frac_d) != 2) {
709 GST_WARNING ("Failed to parse fraction: %s", speed_str);
713 gst_util_fraction_to_double (frac_n, frac_d, &value);
715 if (speedref_str[0] == 'K') {
716 value *= KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND;
717 } else if (speedref_str[0] == 'M') {
718 value *= MILES_PER_HOUR_TO_METERS_PER_SECOND;
719 } else if (speedref_str[0] == 'N') {
720 value *= KNOTS_TO_METERS_PER_SECOND;
722 GST_WARNING ("Unexpected exif:SpeedRef value: %s", speedref_str);
726 /* add to the taglist */
727 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag),
728 GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, value, NULL);
732 g_slice_free (PendingXmpTag, ptag);
733 *pending_tags = g_slist_delete_link (*pending_tags, entry);
737 serialize_exif_gps_direction (const GValue * value)
739 return double_to_fraction_string (g_value_get_double (value));
743 serialize_exif_gps_directionref (const GValue * value)
745 /* T for true geographic direction (M would mean magnetic) */
746 return g_strdup ("T");
750 deserialize_exif_gps_direction (XmpTag * xmptag, GstTagList * taglist,
751 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
752 GSList ** pending_tags, const gchar * direction_tag,
753 const gchar * directionref_tag)
755 const gchar *dir_str = NULL;
756 const gchar *dirref_str = NULL;
762 PendingXmpTag *ptag = NULL;
764 /* find the other missing part */
765 if (strcmp (xmp_tag, direction_tag) == 0) {
768 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
769 ptag = (PendingXmpTag *) entry->data;
771 if (strcmp (ptag->xmp_tag->tag_name, directionref_tag) == 0) {
772 dirref_str = ptag->str;
777 } else if (strcmp (xmp_tag, directionref_tag) == 0) {
780 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
781 ptag = (PendingXmpTag *) entry->data;
783 if (strcmp (ptag->xmp_tag->tag_name, direction_tag) == 0) {
790 GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
795 GST_WARNING ("Missing %s tag", dir_str);
799 GST_WARNING ("Missing %s tag", dirref_str);
803 if (sscanf (dir_str, "%d/%d", &frac_n, &frac_d) != 2) {
804 GST_WARNING ("Failed to parse fraction: %s", dir_str);
808 gst_util_fraction_to_double (frac_n, frac_d, &value);
810 if (dirref_str[0] == 'T') {
812 } else if (dirref_str[0] == 'M') {
813 GST_WARNING ("Magnetic direction tags aren't supported yet");
816 GST_WARNING ("Unexpected %s value: %s", directionref_tag, dirref_str);
820 /* add to the taglist */
821 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag, value,
826 g_slice_free (PendingXmpTag, ptag);
827 *pending_tags = g_slist_delete_link (*pending_tags, entry);
831 deserialize_exif_gps_track (XmpTag * xmptag, GstTagList * taglist,
832 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
833 GSList ** pending_tags)
835 deserialize_exif_gps_direction (xmptag, taglist, gst_tag, xmp_tag, str,
836 pending_tags, "exif:GPSTrack", "exif:GPSTrackRef");
840 deserialize_exif_gps_img_direction (XmpTag * xmptag, GstTagList * taglist,
841 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
842 GSList ** pending_tags)
844 deserialize_exif_gps_direction (xmptag, taglist, gst_tag, xmp_tag, str,
845 pending_tags, "exif:GPSImgDirection", "exif:GPSImgDirectionRef");
849 deserialize_xmp_rating (XmpTag * xmptag, GstTagList * taglist,
850 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
851 GSList ** pending_tags)
855 if (sscanf (str, "%u", &value) != 1) {
856 GST_WARNING ("Failed to parse xmp:Rating %s", str);
861 GST_WARNING ("Unsupported Rating tag %u (should be from 0 to 100), "
866 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag, value,
871 serialize_tiff_orientation (const GValue * value)
876 str = g_value_get_string (value);
878 GST_WARNING ("Failed to get image orientation tag value");
882 num = __exif_tag_image_orientation_to_exif_value (str);
886 return g_strdup_printf ("%d", num);
890 deserialize_tiff_orientation (XmpTag * xmptag, GstTagList * taglist,
891 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
892 GSList ** pending_tags)
895 const gchar *orientation = NULL;
897 if (sscanf (str, "%u", &value) != 1) {
898 GST_WARNING ("Failed to parse tiff:Orientation %s", str);
902 if (value < 1 || value > 8) {
903 GST_WARNING ("Invalid tiff:Orientation tag %u (should be from 1 to 8), "
908 orientation = __exif_tag_image_orientation_from_exif_value (value);
909 if (orientation == NULL)
911 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag,
916 /* look at this page for addtional schemas
917 * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/XMP.html
920 _init_xmp_tag_map (gpointer user_data)
923 GstXmpSchema *schema;
925 __xmp_schemas = g_hash_table_new (g_direct_hash, g_direct_equal);
928 /* dublic code metadata
929 * http://dublincore.org/documents/dces/
931 schema = gst_xmp_schema_new ();
932 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_ARTIST,
933 "dc:creator", GstXmpTagTypeSeq, NULL, NULL);
934 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_COPYRIGHT,
935 "dc:rights", GstXmpTagTypeSimple, NULL, NULL);
936 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DATE, "dc:date",
937 GstXmpTagTypeSeq, NULL, NULL);
938 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DESCRIPTION,
939 "dc:description", GstXmpTagTypeSimple, NULL, NULL);
940 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_KEYWORDS,
941 "dc:subject", GstXmpTagTypeBag, NULL, NULL);
942 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_TITLE, "dc:title",
943 GstXmpTagTypeSimple, NULL, NULL);
944 /* FIXME: we probably want GST_TAG_{,AUDIO_,VIDEO_}MIME_TYPE */
945 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_VIDEO_CODEC,
946 "dc:format", GstXmpTagTypeSimple, NULL, NULL);
947 _gst_xmp_add_schema ("dc", schema);
949 /* xap (xmp) schema */
950 schema = gst_xmp_schema_new ();
951 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_USER_RATING,
952 "xmp:Rating", GstXmpTagTypeSimple, NULL, deserialize_xmp_rating);
953 _gst_xmp_add_schema ("xap", schema);
956 schema = gst_xmp_schema_new ();
957 _gst_xmp_schema_add_simple_mapping (schema,
958 GST_TAG_DEVICE_MANUFACTURER, "tiff:Make", GstXmpTagTypeSimple, NULL,
960 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DEVICE_MODEL,
961 "tiff:Model", GstXmpTagTypeSimple, NULL, NULL);
962 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_APPLICATION_NAME,
963 "tiff:Software", GstXmpTagTypeSimple, NULL, NULL);
964 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_IMAGE_ORIENTATION,
965 "tiff:Orientation", GstXmpTagTypeSimple, serialize_tiff_orientation,
966 deserialize_tiff_orientation);
967 _gst_xmp_add_schema ("tiff", schema);
970 schema = gst_xmp_schema_new ();
971 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DATE_TIME,
972 "exif:DateTimeOriginal", GstXmpTagTypeSimple, NULL, NULL);
973 _gst_xmp_schema_add_simple_mapping (schema,
974 GST_TAG_GEO_LOCATION_LATITUDE, "exif:GPSLatitude",
975 GstXmpTagTypeSimple, serialize_exif_latitude, deserialize_exif_latitude);
976 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_GEO_LOCATION_LONGITUDE,
977 "exif:GPSLongitude", GstXmpTagTypeSimple, serialize_exif_longitude,
978 deserialize_exif_longitude);
979 _gst_xmp_schema_add_simple_mapping (schema,
980 GST_TAG_CAPTURING_EXPOSURE_COMPENSATION, "exif:ExposureBiasValue",
981 GstXmpTagTypeSimple, NULL, NULL);
983 /* compound exif tags */
984 xmpinfo = gst_xmp_tag_create_compound (GST_TAG_GEO_LOCATION_ELEVATION,
985 "exif:GPSAltitude", "exif:GPSAltitudeRef", serialize_exif_altitude,
986 serialize_exif_altituderef, deserialize_exif_altitude);
987 _gst_xmp_schema_add_mapping (schema, xmpinfo);
989 xmpinfo = gst_xmp_tag_create_compound (GST_TAG_GEO_LOCATION_MOVEMENT_SPEED,
990 "exif:GPSSpeed", "exif:GPSSpeedRef", serialize_exif_gps_speed,
991 serialize_exif_gps_speedref, deserialize_exif_gps_speed);
992 _gst_xmp_schema_add_mapping (schema, xmpinfo);
995 gst_xmp_tag_create_compound (GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION,
996 "exif:GPSTrack", "exif:GPSTrackRef", serialize_exif_gps_direction,
997 serialize_exif_gps_directionref, deserialize_exif_gps_track);
998 _gst_xmp_schema_add_mapping (schema, xmpinfo);
1000 xmpinfo = gst_xmp_tag_create_compound (GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION,
1001 "exif:GPSImgDirection", "exif:GPSImgDirectionRef",
1002 serialize_exif_gps_direction, serialize_exif_gps_directionref,
1003 deserialize_exif_gps_img_direction);
1004 _gst_xmp_schema_add_mapping (schema, xmpinfo);
1006 _gst_xmp_add_schema ("exif", schema);
1008 /* photoshop schema */
1009 schema = gst_xmp_schema_new ();
1010 _gst_xmp_schema_add_simple_mapping (schema,
1011 GST_TAG_GEO_LOCATION_COUNTRY, "photoshop:Country",
1012 GstXmpTagTypeSimple, NULL, NULL);
1013 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_GEO_LOCATION_CITY,
1014 "photoshop:City", GstXmpTagTypeSimple, NULL, NULL);
1015 _gst_xmp_add_schema ("photoshop", schema);
1017 /* iptc4xmpcore schema */
1018 schema = gst_xmp_schema_new ();
1019 _gst_xmp_schema_add_simple_mapping (schema,
1020 GST_TAG_GEO_LOCATION_SUBLOCATION, "Iptc4xmpCore:Location",
1021 GstXmpTagTypeSimple, NULL, NULL);
1022 _gst_xmp_add_schema ("Iptc4xmpCore", schema);
1024 /* iptc4xmpext schema */
1025 schema = gst_xmp_schema_new ();
1026 xmpinfo = gst_xmp_tag_create (NULL, "Iptc4xmpExt:LocationShown",
1027 GstXmpTagTypeStruct, NULL, NULL);
1028 xmpinfo->supertype = GstXmpTagTypeBag;
1029 xmpinfo->parse_type = "Resource";
1030 xmpinfo->children = g_slist_prepend (xmpinfo->children,
1031 gst_xmp_tag_create (GST_TAG_GEO_LOCATION_SUBLOCATION,
1032 "LocationDetails:Sublocation", GstXmpTagTypeSimple, NULL, NULL));
1034 g_slist_prepend (xmpinfo->children,
1035 gst_xmp_tag_create (GST_TAG_GEO_LOCATION_CITY,
1036 "LocationDetails:City", GstXmpTagTypeSimple, NULL, NULL));
1038 g_slist_prepend (xmpinfo->children,
1039 gst_xmp_tag_create (GST_TAG_GEO_LOCATION_COUNTRY,
1040 "LocationDetails:Country", GstXmpTagTypeSimple, NULL, NULL));
1041 _gst_xmp_schema_add_mapping (schema, xmpinfo);
1042 _gst_xmp_add_schema ("Iptc4xmpExt", schema);
1048 xmp_tags_initialize ()
1050 static GOnce my_once = G_ONCE_INIT;
1051 g_once (&my_once, (GThreadFunc) _init_xmp_tag_map, NULL);
1054 typedef struct _GstXmpNamespaceMatch GstXmpNamespaceMatch;
1055 struct _GstXmpNamespaceMatch
1057 const gchar *ns_prefix;
1058 const gchar *ns_uri;
1061 * Stores extra namespaces for array tags
1062 * The namespaces should be writen in the form:
1064 * xmlns:XpTo="http://some.org/your/ns/name/ (next ones)"
1066 const gchar *extra_ns;
1069 static const GstXmpNamespaceMatch ns_match[] = {
1070 {"dc", "http://purl.org/dc/elements/1.1/", NULL},
1071 {"exif", "http://ns.adobe.com/exif/1.0/", NULL},
1072 {"tiff", "http://ns.adobe.com/tiff/1.0/", NULL},
1073 {"xap", "http://ns.adobe.com/xap/1.0/", NULL},
1074 {"photoshop", "http://ns.adobe.com/photoshop/1.0/", NULL},
1075 {"Iptc4xmpCore", "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/", NULL},
1076 {"Iptc4xmpExt", "http://iptc.org/std/Iptc4xmpExt/2008-02-29/",
1077 "xmlns:LocationDetails=\"http://iptc.org/std/Iptc4xmpExt/2008-02-29/LocationDetails/\""},
1081 typedef struct _GstXmpNamespaceMap GstXmpNamespaceMap;
1082 struct _GstXmpNamespaceMap
1084 const gchar *original_ns;
1085 gchar *gstreamer_ns;
1091 read_one_tag (GstTagList * list, XmpTag * xmptag,
1092 const gchar * v, GSList ** pending_tags)
1095 GstTagMergeMode merge_mode;
1096 const gchar *tag = xmptag->gst_tag;
1098 g_return_if_fail (tag != NULL);
1100 if (xmptag->deserialize) {
1101 xmptag->deserialize (xmptag, list, tag, xmptag->tag_name, v, pending_tags);
1105 merge_mode = xmp_tag_get_merge_mode (xmptag);
1106 tag_type = gst_tag_get_type (tag);
1108 /* add gstreamer tag depending on type */
1110 case G_TYPE_STRING:{
1111 gst_tag_list_add (list, merge_mode, tag, v, NULL);
1114 case G_TYPE_DOUBLE:{
1116 gint frac_n, frac_d;
1118 if (sscanf (v, "%d/%d", &frac_n, &frac_d) == 2) {
1119 gst_util_fraction_to_double (frac_n, frac_d, &value);
1120 gst_tag_list_add (list, merge_mode, tag, value, NULL);
1122 GST_WARNING ("Failed to parse fraction: %s", v);
1127 if (tag_type == GST_TYPE_DATE_TIME) {
1128 GstDateTime *datetime = NULL;
1129 gint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
1131 gint gmt_offset_hour = -1, gmt_offset_min = -1, gmt_offset = -1;
1138 GST_WARNING ("Empty string for datetime parsing");
1142 GST_DEBUG ("Parsing %s into a datetime", v);
1144 ret = sscanf (v, "%04d-%02d-%02dT%02d:%02d:%02d.%15s",
1145 &year, &month, &day, &hour, &minute, &second, usec_str);
1147 /* FIXME theoretically, xmp can express datetimes with only year
1148 * or year and month, but gstdatetime doesn't support it */
1149 GST_WARNING ("Invalid datetime value: %s", v);
1152 /* parse the usecs */
1154 gint num_digits = 0;
1156 /* find the number of digits */
1157 while (isdigit ((gint) usec_str[num_digits++]) && num_digits < 6);
1159 if (num_digits > 0) {
1160 /* fill up to 6 digits with 0 */
1161 while (num_digits < 6) {
1162 usec_str[num_digits++] = 0;
1165 g_assert (num_digits == 6);
1167 usec_str[num_digits] = '\0';
1168 usecs = atoi (usec_str);
1172 /* parse the timezone info */
1173 if (v[len - 1] == 'Z') {
1174 GST_LOG ("UTC timezone");
1176 /* Having a Z at the end means UTC */
1177 datetime = gst_date_time_new (0, year, month, day, hour, minute,
1178 second + usecs / 1000000.0);
1180 gchar *plus_pos = NULL;
1181 gchar *neg_pos = NULL;
1184 GST_LOG ("Checking for timezone information");
1186 /* check if there is timezone info */
1187 plus_pos = strrchr (v, '+');
1188 neg_pos = strrchr (v, '-');
1191 } else if (neg_pos) {
1196 gint ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour,
1199 GST_DEBUG ("Parsing timezone: %s", pos);
1202 gmt_offset = gmt_offset_hour * 60 + gmt_offset_min;
1203 if (neg_pos != NULL && neg_pos + 1 == pos)
1206 GST_LOG ("Timezone offset: %f (%d minutes)", gmt_offset / 60.0,
1209 /* no way to know if it is DST or not */
1211 gst_date_time_new (gmt_offset / 60.0,
1212 year, month, day, hour, minute,
1213 second + usecs / ((gdouble) G_USEC_PER_SEC));
1215 GST_WARNING ("Failed to parse timezone information");
1218 GST_WARNING ("No timezone signal found");
1223 gst_tag_list_add (list, merge_mode, tag, datetime, NULL);
1224 gst_date_time_unref (datetime);
1227 } else if (tag_type == G_TYPE_DATE) {
1231 /* this is ISO 8601 Date and Time Format
1232 * %F Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)
1233 * %T The time in 24-hour notation (%H:%M:%S). (SU)
1234 * e.g. 2009-05-30T18:26:14+03:00 */
1236 /* FIXME: this would be the proper way, but needs
1237 #define _XOPEN_SOURCE before #include <time.h>
1239 date = g_date_new ();
1241 strptime (dts, "%FT%TZ", &tm);
1242 g_date_set_time_t (date, mktime(&tm));
1244 /* FIXME: this cannot parse the date
1245 date = g_date_new ();
1246 g_date_set_parse (date, v);
1247 if (g_date_valid (date)) {
1248 gst_tag_list_add (list, merge_mode, tag,
1251 GST_WARNING ("unparsable date: '%s'", v);
1254 /* poor mans straw */
1255 sscanf (v, "%04d-%02d-%02dT", &y, &m, &d);
1256 date = g_date_new_dmy (d, m, y);
1257 gst_tag_list_add (list, merge_mode, tag, date, NULL);
1260 GST_WARNING ("unhandled type for %s from xmp", tag);
1267 * gst_tag_list_from_xmp_buffer:
1270 * Parse a xmp packet into a taglist.
1272 * Returns: new taglist or %NULL, free the list when done
1277 gst_tag_list_from_xmp_buffer (GstBuffer * buffer)
1279 GstTagList *list = NULL;
1281 gchar *xps, *xp1, *xp2, *xpe, *ns, *ne;
1282 gsize len, max_ft_len;
1286 XmpTag *last_xmp_tag = NULL;
1287 GSList *pending_tags = NULL;
1289 /* Used for strucuture xmp tags */
1290 XmpTag *context_tag = NULL;
1292 GstXmpNamespaceMap ns_map[] = {
1303 {"Iptc4xmpCore", NULL}
1305 {"Iptc4xmpExt", NULL}
1310 xmp_tags_initialize ();
1312 g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
1314 GST_LOG ("Starting xmp parsing");
1316 gst_buffer_map (buffer, &info, GST_MAP_READ);
1317 xps = (gchar *) info.data;
1319 g_return_val_if_fail (len > 0, NULL);
1321 xpe = &xps[len + 1];
1323 /* check header and footer */
1324 xp1 = g_strstr_len (xps, len, "<?xpacket begin");
1326 goto missing_header;
1327 xp1 = &xp1[strlen ("<?xpacket begin")];
1328 while (*xp1 != '>' && *xp1 != '<' && xp1 < xpe)
1331 goto missing_header;
1333 /* Use 2 here to count for an extra trailing \n that was added
1334 * in old versions, this makes it able to parse xmp packets with
1335 * and without this trailing char */
1336 max_ft_len = 2 + strlen ("<?xpacket end=\".\"?>");
1337 if (len < max_ft_len)
1338 goto missing_footer;
1340 GST_DEBUG ("checking footer: [%s]", &xps[len - max_ft_len]);
1341 xp2 = g_strstr_len (&xps[len - max_ft_len], max_ft_len, "<?xpacket ");
1343 goto missing_footer;
1345 GST_INFO ("xmp header okay");
1347 /* skip > and text until first xml-node */
1349 while (*xp1 != '<' && xp1 < xpe)
1352 /* no tag can be longer that the whole buffer */
1353 part = g_malloc (xp2 - xp1);
1354 list = gst_tag_list_new_empty ();
1356 /* parse data into a list of nodes */
1357 /* data is between xp1..xp2 */
1364 while (ne < xp2 && *ne != '>' && *ne != '<') {
1365 if (*ne == '\n' || *ne == '\t' || *ne == ' ') {
1366 while (ne < xp2 && (*ne == '\n' || *ne == '\t' || *ne == ' '))
1377 /* {XML, ns, ne-ns} */
1379 gchar *as = strchr (part, ' ');
1380 /* only log start nodes */
1381 GST_INFO ("xml: %s", part);
1386 /* skip ' ' and scan the attributes */
1390 /* split attr=value pairs */
1391 while (*ae != '\0') {
1393 /* attr/value delimmiter */
1395 } else if (*ae == '"') {
1400 while (*ae != '\0' && *ae != '"')
1405 GST_INFO (" : [%s][%s]", as, v);
1406 if (!strncmp (as, "xmlns:", 6)) {
1408 /* we need to rewrite known namespaces to what we use in
1410 while (ns_match[i].ns_prefix) {
1411 if (!strcmp (ns_match[i].ns_uri, v))
1415 if (ns_match[i].ns_prefix) {
1416 if (strcmp (ns_map[i].original_ns, &as[6])) {
1417 ns_map[i].gstreamer_ns = g_strdup (&as[6]);
1421 XmpTag *xmp_tag = NULL;
1422 /* FIXME: eventually rewrite ns
1424 * check if ns before ':' is in ns_map and ns_map[i].gstreamer_ns!=NULL
1425 * do 2 stage filter in tag_matches
1430 for (iter = context_tag->children; iter;
1431 iter = g_slist_next (iter)) {
1432 XmpTag *child = iter->data;
1434 GST_DEBUG ("Looking at child tag %s : %s", child->tag_name,
1436 if (strcmp (child->tag_name, as) == 0) {
1443 GST_LOG ("Looking for tag: %s", as);
1444 _gst_xmp_tag_get_mapping_reverse (as, &xmp_tag);
1447 PendingXmpTag *ptag;
1449 GST_DEBUG ("Found xmp tag: %s -> %s", xmp_tag->tag_name,
1452 /* we shouldn't find a xmp structure here */
1453 g_assert (xmp_tag->gst_tag != NULL);
1455 ptag = g_slice_new (PendingXmpTag);
1456 ptag->xmp_tag = xmp_tag;
1457 ptag->str = g_strdup (v);
1459 pending_tags = g_slist_append (pending_tags, ptag);
1462 /* restore chars overwritten by '\0' */
1465 } else if (*ae == '\0' || *ae == ' ') {
1466 /* end of attr/value pair */
1469 /* to next char if not eos */
1475 <dc:type><rdf:Bag><rdf:li>Image</rdf:li></rdf:Bag></dc:type>
1476 <dc:creator><rdf:Seq><rdf:li/></rdf:Seq></dc:creator>
1478 /* FIXME: eventually rewrite ns */
1480 /* skip rdf tags for now */
1481 if (strncmp (part, "rdf:", 4)) {
1482 /* if we're inside some struct, we look only on its children */
1486 /* check if this is the closing of the context */
1488 && strcmp (part + 1, context_tag->tag_name) == 0) {
1489 GST_DEBUG ("Closing context tag %s", part);
1493 for (iter = context_tag->children; iter;
1494 iter = g_slist_next (iter)) {
1495 XmpTag *child = iter->data;
1497 GST_DEBUG ("Looking at child tag %s : %s", child->tag_name,
1499 if (strcmp (child->tag_name, part) == 0) {
1500 last_xmp_tag = child;
1507 GST_LOG ("Looking for tag: %s", part);
1508 _gst_xmp_tag_get_mapping_reverse (part, &last_xmp_tag);
1509 if (last_xmp_tag && last_xmp_tag->type == GstXmpTagTypeStruct) {
1510 context_tag = last_xmp_tag;
1511 last_xmp_tag = NULL;
1517 GST_LOG ("Next cycle");
1527 while (ne < xp2 && *ne != '<') {
1533 /* {TXT, ns, (ne-ns)-1} */
1534 if (ns[0] != '\n' && &ns[1] <= ne) {
1535 /* only log non-newline nodes, we still have to parse them */
1536 GST_INFO ("txt: %s", part);
1538 PendingXmpTag *ptag;
1540 GST_DEBUG ("Found tag %s -> %s", last_xmp_tag->tag_name,
1541 last_xmp_tag->gst_tag);
1543 if (last_xmp_tag->type == GstXmpTagTypeStruct) {
1544 g_assert (context_tag == NULL); /* we can't handle struct nesting currently */
1546 context_tag = last_xmp_tag;
1548 ptag = g_slice_new (PendingXmpTag);
1549 ptag->xmp_tag = last_xmp_tag;
1550 ptag->str = g_strdup (part);
1552 pending_tags = g_slist_append (pending_tags, ptag);
1563 while (pending_tags) {
1564 PendingXmpTag *ptag = (PendingXmpTag *) pending_tags->data;
1566 pending_tags = g_slist_delete_link (pending_tags, pending_tags);
1568 read_one_tag (list, ptag->xmp_tag, ptag->str, &pending_tags);
1571 g_slice_free (PendingXmpTag, ptag);
1574 GST_INFO ("xmp packet parsed, %d entries",
1575 gst_structure_n_fields ((GstStructure *) list));
1577 /* free resources */
1579 while (ns_map[i].original_ns) {
1580 g_free (ns_map[i].gstreamer_ns);
1585 gst_buffer_unmap (buffer, &info);
1591 GST_WARNING ("malformed xmp packet header");
1594 GST_WARNING ("malformed xmp packet footer");
1597 GST_WARNING ("malformed xml tag: %s", part);
1605 string_open_tag (GString * string, const char *tag)
1607 g_string_append_c (string, '<');
1608 g_string_append (string, tag);
1609 g_string_append_c (string, '>');
1613 string_close_tag (GString * string, const char *tag)
1615 g_string_append (string, "</");
1616 g_string_append (string, tag);
1617 g_string_append (string, ">\n");
1621 gst_value_serialize_xmp (const GValue * value)
1623 switch (G_VALUE_TYPE (value)) {
1625 return g_markup_escape_text (g_value_get_string (value), -1);
1627 return g_strdup_printf ("%d", g_value_get_int (value));
1629 return g_strdup_printf ("%u", g_value_get_uint (value));
1631 return double_to_fraction_string (g_value_get_double (value));
1635 /* put non-switchable types here */
1636 if (G_VALUE_TYPE (value) == G_TYPE_DATE) {
1637 const GDate *date = g_value_get_boxed (value);
1639 return g_strdup_printf ("%04d-%02d-%02d",
1640 (gint) g_date_get_year (date), (gint) g_date_get_month (date),
1641 (gint) g_date_get_day (date));
1642 } else if (G_VALUE_TYPE (value) == GST_TYPE_DATE_TIME) {
1643 gint year, month, day, hour, min, sec, microsec;
1644 gfloat gmt_offset = 0;
1645 gint gmt_offset_hour, gmt_offset_min;
1646 GstDateTime *datetime = (GstDateTime *) g_value_get_boxed (value);
1648 year = gst_date_time_get_year (datetime);
1649 month = gst_date_time_get_month (datetime);
1650 day = gst_date_time_get_day (datetime);
1651 hour = gst_date_time_get_hour (datetime);
1652 min = gst_date_time_get_minute (datetime);
1653 sec = gst_date_time_get_second (datetime);
1654 microsec = gst_date_time_get_microsecond (datetime);
1655 gmt_offset = gst_date_time_get_time_zone_offset (datetime);
1656 if (gmt_offset == 0) {
1658 return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d.%06dZ",
1659 year, month, day, hour, min, sec, microsec);
1661 gmt_offset_hour = ABS (gmt_offset);
1662 gmt_offset_min = (ABS (gmt_offset) - gmt_offset_hour) * 60;
1664 return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d.%06d%c%02d:%02d",
1665 year, month, day, hour, min, sec, microsec,
1666 gmt_offset >= 0 ? '+' : '-', gmt_offset_hour, gmt_offset_min);
1674 write_one_tag (const GstTagList * list, XmpTag * xmp_tag, gpointer user_data)
1677 XmpSerializationData *serialization_data = user_data;
1678 GString *data = serialization_data->data;
1681 /* struct type handled differently */
1682 if (xmp_tag->type == GstXmpTagTypeStruct ||
1683 xmp_tag->type == GstXmpTagTypeCompound) {
1685 gboolean use_it = FALSE;
1687 /* check if any of the inner tags are present on the taglist */
1688 for (iter = xmp_tag->children; iter && !use_it; iter = g_slist_next (iter)) {
1689 XmpTag *child_tag = iter->data;
1691 if (gst_tag_list_get_value_index (list, child_tag->gst_tag, 0) != NULL) {
1698 if (xmp_tag->tag_name)
1699 string_open_tag (data, xmp_tag->tag_name);
1701 if (xmp_tag->supertype) {
1702 string_open_tag (data, xmp_tag_type_get_name (xmp_tag->supertype));
1703 if (xmp_tag->parse_type) {
1704 g_string_append (data, "<rdf:li rdf:parseType=\"");
1705 g_string_append (data, xmp_tag->parse_type);
1706 g_string_append_c (data, '"');
1707 g_string_append_c (data, '>');
1709 string_open_tag (data, "rdf:li");
1714 for (iter = xmp_tag->children; iter; iter = g_slist_next (iter)) {
1715 write_one_tag (list, iter->data, user_data);
1718 if (xmp_tag->supertype) {
1719 string_close_tag (data, "rdf:li");
1720 string_close_tag (data, xmp_tag_type_get_name (xmp_tag->supertype));
1723 if (xmp_tag->tag_name)
1724 string_close_tag (data, xmp_tag->tag_name);
1729 /* at this point we must have a gst_tag */
1730 g_assert (xmp_tag->gst_tag);
1731 if (gst_tag_list_get_value_index (list, xmp_tag->gst_tag, 0) == NULL)
1734 ct = gst_tag_list_get_tag_size (list, xmp_tag->gst_tag);
1735 string_open_tag (data, xmp_tag->tag_name);
1737 /* fast path for single valued tag */
1738 if (ct == 1 || xmp_tag->type == GstXmpTagTypeSimple) {
1739 if (xmp_tag->serialize) {
1740 s = xmp_tag->serialize (gst_tag_list_get_value_index (list,
1741 xmp_tag->gst_tag, 0));
1743 s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list,
1744 xmp_tag->gst_tag, 0));
1747 g_string_append (data, s);
1750 GST_WARNING ("unhandled type for %s to xmp", xmp_tag->gst_tag);
1753 const gchar *typename;
1755 typename = xmp_tag_type_get_name (xmp_tag->type);
1757 string_open_tag (data, typename);
1758 for (i = 0; i < ct; i++) {
1759 GST_DEBUG ("mapping %s[%u/%u] to xmp", xmp_tag->gst_tag, i, ct);
1760 if (xmp_tag->serialize) {
1761 s = xmp_tag->serialize (gst_tag_list_get_value_index (list,
1762 xmp_tag->gst_tag, i));
1764 s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list,
1765 xmp_tag->gst_tag, i));
1768 string_open_tag (data, "rdf:li");
1769 g_string_append (data, s);
1770 string_close_tag (data, "rdf:li");
1773 GST_WARNING ("unhandled type for %s to xmp", xmp_tag->gst_tag);
1776 string_close_tag (data, typename);
1779 string_close_tag (data, xmp_tag->tag_name);
1783 * gst_tag_list_to_xmp_buffer_full:
1785 * @read_only: does the container forbid inplace editing
1786 * @schemas: %NULL terminated array of schemas to be used on serialization
1788 * Formats a taglist as a xmp packet using only the selected
1789 * schemas. An empty list (%NULL) means that all schemas should
1792 * Returns: new buffer or %NULL, unref the buffer when done
1797 gst_tag_list_to_xmp_buffer_full (const GstTagList * list, gboolean read_only,
1798 const gchar ** schemas)
1800 GstBuffer *buffer = NULL;
1801 XmpSerializationData serialization_data;
1807 serialization_data.data = g_string_sized_new (4096);
1808 serialization_data.schemas = schemas;
1809 data = serialization_data.data;
1811 xmp_tags_initialize ();
1813 g_return_val_if_fail (GST_IS_TAG_LIST (list), NULL);
1816 g_string_append (data,
1817 "<?xpacket begin=\"\xEF\xBB\xBF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n");
1818 g_string_append (data,
1819 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"GStreamer\">\n");
1820 g_string_append (data,
1821 "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"");
1823 while (ns_match[i].ns_prefix) {
1824 if (xmp_serialization_data_use_schema (&serialization_data,
1825 ns_match[i].ns_prefix)) {
1826 g_string_append_printf (data, " xmlns:%s=\"%s\"",
1827 ns_match[i].ns_prefix, ns_match[i].ns_uri);
1828 if (ns_match[i].extra_ns) {
1829 g_string_append_printf (data, " %s", ns_match[i].extra_ns);
1834 g_string_append (data, ">\n");
1835 g_string_append (data, "<rdf:Description rdf:about=\"\">\n");
1837 /* iterate the schemas */
1838 if (schemas == NULL) {
1839 /* use all schemas */
1840 schemas = gst_tag_xmp_list_schemas ();
1842 for (i = 0; schemas[i] != NULL; i++) {
1843 GstXmpSchema *schema = _gst_xmp_get_schema (schemas[i]);
1844 GHashTableIter iter;
1845 gpointer key, value;
1850 /* Iterate over the hashtable */
1851 g_hash_table_iter_init (&iter, schema);
1852 while (g_hash_table_iter_next (&iter, &key, &value)) {
1853 write_one_tag (list, value, (gpointer) & serialization_data);
1858 g_string_append (data, "</rdf:Description>\n");
1859 g_string_append (data, "</rdf:RDF>\n");
1860 g_string_append (data, "</x:xmpmeta>\n");
1863 /* the xmp spec recommends to add 2-4KB padding for in-place editable xmp */
1866 for (i = 0; i < 32; i++) {
1867 g_string_append (data, " " " "
1871 g_string_append_printf (data, "<?xpacket end=\"%c\"?>",
1872 (read_only ? 'r' : 'w'));
1875 bdata = g_string_free (data, FALSE);
1877 buffer = gst_buffer_new ();
1878 gst_buffer_take_memory (buffer, -1,
1879 gst_memory_new_wrapped (0, bdata, bsize, 0, bsize, bdata, g_free));
1885 * gst_tag_list_to_xmp_buffer:
1887 * @read_only: does the container forbid inplace editing
1889 * Formats a taglist as a xmp packet.
1891 * Returns: new buffer or %NULL, unref the buffer when done
1896 gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
1898 return gst_tag_list_to_xmp_buffer_full (list, read_only, NULL);
1901 #undef gst_xmp_schema_lookup
1902 #undef gst_xmp_schema_insert