2 * Copyright (C) 2010 Stefan Kost <stefan.kost@nokia.com>
4 * gstxmptag.c: library for reading / modifying xmp tags
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
24 * @short_description: tag mappings and support functions for plugins
25 * dealing with xmp packets
26 * @see_also: #GstTagList
28 * Contains various utility functions for plugins to parse or create
29 * xmp packets and map them to and from #GstTagList<!-- -->s.
31 * Please note that the xmp parser is very lightweight and not strict at all.
37 #include <gst/gsttagsetter.h>
38 #include "gsttageditingprivate.h"
44 typedef gchar *(*XmpSerializationFunc) (const GValue * value);
45 typedef void (*XmpDeserializationFunc) (GstTagList * taglist,
46 const gchar * gst_tag, const gchar * xmp_tag,
47 const gchar * str, GSList ** pending_tags);
51 const gchar *tag_name;
52 XmpSerializationFunc serialize;
53 XmpDeserializationFunc deserialize;
55 typedef struct _XmpTag XmpTag;
63 typedef struct _PendingXmpTag PendingXmpTag;
66 * Mappings from gstreamer tags to xmp tags
68 * The mapping here are from a gstreamer tag (as a GQuark)
69 * into a GSList of GArray of XmpTag.
71 * There might be multiple xmp tags that a single gstreamer tag can be
72 * mapped to. For example, GST_TAG_DATE might be mapped into dc:date
73 * or exif:DateTimeOriginal, hence the first list, to be able to store
74 * alternative mappings of the same gstreamer tag.
76 * Some other tags, like GST_TAG_GEO_LOCATION_ELEVATION needs to be
77 * mapped into 2 complementary tags in the exif's schema. One of them
78 * stores the absolute elevation, and the other one stores if it is
79 * above of below sea level. That's why we need a GArray as the item
80 * of each GSList in the mapping.
82 static GHashTable *__xmp_tag_map;
83 static GMutex *__xmp_tag_map_mutex;
85 #define XMP_TAG_MAP_LOCK g_mutex_lock (__xmp_tag_map_mutex)
86 #define XMP_TAG_MAP_UNLOCK g_mutex_unlock (__xmp_tag_map_mutex)
89 _xmp_tag_add_mapping (const gchar * gst_tag, GPtrArray * array)
94 key = g_quark_from_string (gst_tag);
97 list = g_hash_table_lookup (__xmp_tag_map, GUINT_TO_POINTER (key));
98 list = g_slist_append (list, (gpointer) array);
99 g_hash_table_insert (__xmp_tag_map, GUINT_TO_POINTER (key), list);
104 _xmp_tag_add_simple_mapping (const gchar * gst_tag, const gchar * xmp_tag,
105 XmpSerializationFunc serialization_func,
106 XmpDeserializationFunc deserialization_func)
111 xmpinfo = g_slice_new (XmpTag);
112 xmpinfo->tag_name = xmp_tag;
113 xmpinfo->serialize = serialization_func;
114 xmpinfo->deserialize = deserialization_func;
116 array = g_ptr_array_sized_new (1);
117 g_ptr_array_add (array, xmpinfo);
119 _xmp_tag_add_mapping (gst_tag, array);
123 * We do not return a copy here because elements are
124 * appended, and the API is not public, so we shouldn't
125 * have our lists modified during usage
128 _xmp_tag_get_mapping (const gchar * gst_tag)
131 GQuark key = g_quark_from_string (gst_tag);
134 ret = (GSList *) g_hash_table_lookup (__xmp_tag_map, GUINT_TO_POINTER (key));
140 /* finds the gst tag that maps to this xmp tag */
142 _xmp_tag_get_mapping_reverse (const gchar * xmp_tag, XmpTag ** _xmp_tag)
146 const gchar *ret = NULL;
151 g_hash_table_iter_init (&iter, __xmp_tag_map);
152 while (!ret && g_hash_table_iter_next (&iter, &key, &value)) {
153 GSList *list = (GSList *) value;
155 for (walk = list; walk; walk = g_slist_next (walk)) {
156 GPtrArray *array = (GPtrArray *) walk->data;
158 for (index = 0; index < array->len; index++) {
159 XmpTag *xmpinfo = (XmpTag *) g_ptr_array_index (array, index);
161 if (strcmp (xmpinfo->tag_name, xmp_tag) == 0) {
163 ret = g_quark_to_string (GPOINTER_TO_UINT (key));
175 /* (de)serialize functions */
177 serialize_exif_gps_coordinate (const GValue * value, gchar pos, gchar neg)
182 gchar fraction[G_ASCII_DTOSTR_BUF_SIZE];
184 g_return_val_if_fail (G_VALUE_TYPE (value) == G_TYPE_DOUBLE, NULL);
186 num = g_value_get_double (value);
193 integer = (gint) num;
195 g_ascii_dtostr (fraction, sizeof (fraction), (num - integer) * 60);
197 /* FIXME review GPSCoordinate serialization spec for the .mm or ,ss
198 * decision. Couldn't understand it clearly */
199 return g_strdup_printf ("%d,%s%c", integer, fraction, c);
203 serialize_exif_latitude (const GValue * value)
205 return serialize_exif_gps_coordinate (value, 'N', 'S');
209 serialize_exif_longitude (const GValue * value)
211 return serialize_exif_gps_coordinate (value, 'E', 'W');
215 deserialize_exif_gps_coordinate (GstTagList * taglist, const gchar * gst_tag,
216 const gchar * str, gchar pos, gchar neg)
219 gint d = 0, m = 0, s = 0;
222 const gchar *current;
224 /* get the degrees */
225 if (sscanf (str, "%d", &d) != 1)
228 /* find the beginning of the minutes */
229 current = strchr (str, ',');
234 /* check if it uses ,SS or .mm */
235 if (strchr (current, ',') != NULL) {
236 sscanf (current, "%d,%d%c", &m, &s, &c);
238 gchar *copy = g_strdup (current);
239 gint len = strlen (copy);
242 /* check the last letter */
243 for (i = len - 1; len >= 0; len--) {
244 if (g_ascii_isspace (copy[i]))
247 if (g_ascii_isalpha (copy[i])) {
254 /* something is wrong */
260 /* use a copy so we can change the last letter as E can cause
262 m2 = g_ascii_strtod (copy, NULL);
267 /* we can add them all as those that aren't parsed are 0 */
268 value = d + (m / 60.0) + (s / (60.0 * 60.0)) + (m2 / 60.0);
272 } else if (c == neg) {
278 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, value, NULL);
282 GST_WARNING ("Failed to deserialize gps coordinate: %s", str);
286 deserialize_exif_latitude (GstTagList * taglist, const gchar * gst_tag,
287 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
289 deserialize_exif_gps_coordinate (taglist, gst_tag, str, 'N', 'S');
293 deserialize_exif_longitude (GstTagList * taglist, const gchar * gst_tag,
294 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
296 deserialize_exif_gps_coordinate (taglist, gst_tag, str, 'E', 'W');
300 serialize_exif_altitude (const GValue * value)
306 num = g_value_get_double (value);
311 gst_util_double_to_fraction (num, &frac_n, &frac_d);
313 return g_strdup_printf ("%d/%d", frac_n, frac_d);
317 serialize_exif_altituderef (const GValue * value)
321 num = g_value_get_double (value);
324 return g_strdup ("0");
325 return g_strdup ("1");
329 deserialize_exif_altitude (GstTagList * taglist, const gchar * gst_tag,
330 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
332 const gchar *altitude_str = NULL;
333 const gchar *altituderef_str = NULL;
339 PendingXmpTag *ptag = NULL;
341 /* find the other missing part */
342 if (strcmp (xmp_tag, "exif:GPSAltitude") == 0) {
345 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
346 ptag = (PendingXmpTag *) entry->data;
348 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitudeRef") == 0) {
349 altituderef_str = ptag->str;
354 } else if (strcmp (xmp_tag, "exif:GPSAltitudeRef") == 0) {
355 altituderef_str = str;
357 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
358 ptag = (PendingXmpTag *) entry->data;
360 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitude") == 0) {
361 altitude_str = ptag->str;
367 GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
372 GST_WARNING ("Missing exif:GPSAltitude tag");
375 if (!altituderef_str) {
376 GST_WARNING ("Missing exif:GPSAltitudeRef tag");
380 if (sscanf (altitude_str, "%d/%d", &frac_n, &frac_d) != 2) {
381 GST_WARNING ("Failed to parse fraction: %s", altitude_str);
385 gst_util_fraction_to_double (frac_n, frac_d, &value);
387 if (altituderef_str[0] == '0') {
388 } else if (altituderef_str[0] == '1') {
391 GST_WARNING ("Unexpected exif:AltitudeRef value: %s", altituderef_str);
395 /* add to the taglist */
396 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
397 GST_TAG_GEO_LOCATION_ELEVATION, value, NULL);
401 g_slice_free (PendingXmpTag, ptag);
402 *pending_tags = g_slist_delete_link (*pending_tags, entry);
406 deserialize_xmp_rating (GstTagList * taglist, const gchar * gst_tag,
407 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
411 if (sscanf (str, "%u", &value) != 1) {
412 GST_WARNING ("Failed to parse xmp:Rating %s", str);
416 if (value < 0 || value > 100) {
417 GST_WARNING ("Unsupported Rating tag %u (should be from 0 to 100), "
422 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, value, NULL);
425 /* look at this page for addtional schemas
426 * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/XMP.html
434 __xmp_tag_map_mutex = g_mutex_new ();
435 __xmp_tag_map = g_hash_table_new (g_direct_hash, g_direct_equal);
438 /* dublic code metadata
439 * http://dublincore.org/documents/dces/
441 _xmp_tag_add_simple_mapping (GST_TAG_ARTIST, "dc:creator", NULL, NULL);
442 _xmp_tag_add_simple_mapping (GST_TAG_COPYRIGHT, "dc:rights", NULL, NULL);
443 _xmp_tag_add_simple_mapping (GST_TAG_DATE, "dc:date", NULL, NULL);
444 _xmp_tag_add_simple_mapping (GST_TAG_DATE, "exif:DateTimeOriginal", NULL,
446 _xmp_tag_add_simple_mapping (GST_TAG_DESCRIPTION, "dc:description", NULL,
448 _xmp_tag_add_simple_mapping (GST_TAG_KEYWORDS, "dc:subject", NULL, NULL);
449 _xmp_tag_add_simple_mapping (GST_TAG_TITLE, "dc:title", NULL, NULL);
450 /* FIXME: we probably want GST_TAG_{,AUDIO_,VIDEO_}MIME_TYPE */
451 _xmp_tag_add_simple_mapping (GST_TAG_VIDEO_CODEC, "dc:format", NULL, NULL);
453 /* xap (xmp) schema */
454 _xmp_tag_add_simple_mapping (GST_TAG_USER_RATING, "xmp:Rating", NULL,
455 deserialize_xmp_rating);
458 _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_LATITUDE,
459 "exif:GPSLatitude", serialize_exif_latitude, deserialize_exif_latitude);
460 _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_LONGITUDE,
461 "exif:GPSLongitude", serialize_exif_longitude,
462 deserialize_exif_longitude);
465 array = g_ptr_array_sized_new (2);
466 xmpinfo = g_slice_new (XmpTag);
467 xmpinfo->tag_name = "exif:GPSAltitude";
468 xmpinfo->serialize = serialize_exif_altitude;
469 xmpinfo->deserialize = deserialize_exif_altitude;
470 g_ptr_array_add (array, xmpinfo);
471 xmpinfo = g_slice_new (XmpTag);
472 xmpinfo->tag_name = "exif:GPSAltitudeRef";
473 xmpinfo->serialize = serialize_exif_altituderef;
474 xmpinfo->deserialize = deserialize_exif_altitude;
475 g_ptr_array_add (array, xmpinfo);
476 _xmp_tag_add_mapping (GST_TAG_GEO_LOCATION_ELEVATION, array);
478 /* photoshop schema */
479 _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_COUNTRY,
480 "photoshop:Country", NULL, NULL);
481 _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_CITY, "photoshop:City",
484 /* iptc4xmpcore schema */
485 _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_SUBLOCATION,
486 "Iptc4xmpCore:Location", NULL, NULL);
492 xmp_tags_initialize ()
494 static GOnce my_once = G_ONCE_INIT;
495 g_once (&my_once, _init_xmp_tag_map, NULL);
498 typedef struct _GstXmpNamespaceMatch GstXmpNamespaceMatch;
499 struct _GstXmpNamespaceMatch
501 const gchar *ns_prefix;
505 static const GstXmpNamespaceMatch ns_match[] = {
506 {"dc", "http://purl.org/dc/elements/1.1/"},
507 {"exif", "http://ns.adobe.com/exif/1.0/"},
508 {"tiff", "http://ns.adobe.com/tiff/1.0/"},
509 {"xap", "http://ns.adobe.com/xap/1.0/"},
510 {"photoshop", "http://ns.adobe.com/photoshop/1.0/"},
511 {"Iptc4xmpCore", "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"},
515 typedef struct _GstXmpNamespaceMap GstXmpNamespaceMap;
516 struct _GstXmpNamespaceMap
518 const gchar *original_ns;
521 static GstXmpNamespaceMap ns_map[] = {
527 {"Iptc4xmpCore", NULL},
534 read_one_tag (GstTagList * list, const gchar * tag, XmpTag * xmptag,
535 const gchar * v, GSList ** pending_tags)
539 if (xmptag && xmptag->deserialize) {
540 xmptag->deserialize (list, tag, xmptag->tag_name, v, pending_tags);
544 tag_type = gst_tag_get_type (tag);
546 /* add gstreamer tag depending on type */
549 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, v, NULL);
553 if (tag_type == GST_TYPE_DATE) {
557 /* this is ISO 8601 Date and Time Format
558 * %F Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)
559 * %T The time in 24-hour notation (%H:%M:%S). (SU)
560 * e.g. 2009-05-30T18:26:14+03:00 */
562 /* FIXME: this would be the proper way, but needs
563 #define _XOPEN_SOURCE before #include <time.h>
565 date = g_date_new ();
567 strptime (dts, "%FT%TZ", &tm);
568 g_date_set_time_t (date, mktime(&tm));
570 /* FIXME: this cannot parse the date
571 date = g_date_new ();
572 g_date_set_parse (date, v);
573 if (g_date_valid (date)) {
574 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag,
577 GST_WARNING ("unparsable date: '%s'", v);
580 /* poor mans straw */
581 sscanf (v, "%04d-%02d-%02dT", &y, &m, &d);
582 date = g_date_new_dmy (d, m, y);
583 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, date, NULL);
586 GST_WARNING ("unhandled type for %s from xmp", tag);
593 * gst_tag_list_from_xmp_buffer:
596 * Parse a xmp packet into a taglist.
598 * Returns: new taglist or %NULL, free the list when done
603 gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
605 GstTagList *list = NULL;
606 const gchar *xps, *xp1, *xp2, *xpe, *ns, *ne;
607 guint len, max_ft_len;
611 const gchar *last_tag = NULL;
612 XmpTag *last_xmp_tag = NULL;
613 GSList *pending_tags = NULL;
615 xmp_tags_initialize ();
617 g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
618 g_return_val_if_fail (GST_BUFFER_SIZE (buffer) > 0, NULL);
620 xps = (const gchar *) GST_BUFFER_DATA (buffer);
621 len = GST_BUFFER_SIZE (buffer);
624 /* check header and footer */
625 xp1 = g_strstr_len (xps, len, "<?xpacket begin");
628 xp1 = &xp1[strlen ("<?xpacket begin")];
629 while (*xp1 != '>' && *xp1 != '<' && xp1 < xpe)
634 max_ft_len = 1 + strlen ("<?xpacket end=\".\"?>\n");
635 if (len < max_ft_len)
638 GST_DEBUG ("checking footer: [%s]", &xps[len - max_ft_len]);
639 xp2 = g_strstr_len (&xps[len - max_ft_len], max_ft_len, "<?xpacket ");
643 GST_INFO ("xmp header okay");
645 /* skip > and text until first xml-node */
647 while (*xp1 != '<' && xp1 < xpe)
650 /* no tag can be longer that the whole buffer */
651 part = g_malloc (xp2 - xp1);
652 list = gst_tag_list_new ();
654 /* parse data into a list of nodes */
655 /* data is between xp1..xp2 */
662 while (ne < xp2 && *ne != '>' && *ne != '<') {
663 if (*ne == '\n' || *ne == '\t' || *ne == ' ') {
664 while (ne < xp2 && (*ne == '\n' || *ne == '\t' || *ne == ' '))
675 /* {XML, ns, ne-ns} */
677 gchar *as = strchr (part, ' ');
678 /* only log start nodes */
679 GST_INFO ("xml: %s", part);
684 /* skip ' ' and scan the attributes */
688 /* split attr=value pairs */
689 while (*ae != '\0') {
691 /* attr/value delimmiter */
693 } else if (*ae == '"') {
698 while (*ae != '\0' && *ae != '"')
703 GST_INFO (" : [%s][%s]", as, v);
704 if (!strncmp (as, "xmlns:", 6)) {
706 /* we need to rewrite known namespaces to what we use in
708 while (ns_match[i].ns_prefix) {
709 if (!strcmp (ns_match[i].ns_uri, v))
713 if (ns_match[i].ns_prefix) {
714 if (strcmp (ns_map[i].original_ns, &as[6])) {
715 ns_map[i].gstreamer_ns = g_strdup (&as[6]);
719 const gchar *gst_tag;
720 XmpTag *xmp_tag = NULL;
721 /* FIXME: eventualy rewrite ns
723 * check if ns before ':' is in ns_map and ns_map[i].gstreamer_ns!=NULL
724 * do 2 stage filter in tag_matches
726 gst_tag = _xmp_tag_get_mapping_reverse (as, &xmp_tag);
730 ptag = g_slice_new (PendingXmpTag);
731 ptag->gst_tag = gst_tag;
732 ptag->xmp_tag = xmp_tag;
733 ptag->str = g_strdup (v);
735 pending_tags = g_slist_append (pending_tags, ptag);
738 /* restore chars overwritten by '\0' */
741 } else if (*ae == '\0' || *ae == ' ') {
742 /* end of attr/value pair */
745 /* to next char if not eos */
751 <dc:type><rdf:Bag><rdf:li>Image</rdf:li></rdf:Bag></dc:type>
752 <dc:creator><rdf:Seq><rdf:li/></rdf:Seq></dc:creator>
754 /* FIXME: eventualy rewrite ns */
756 /* skip rdf tags for now */
757 if (strncmp (part, "rdf:", 4)) {
758 const gchar *parttag;
760 parttag = _xmp_tag_get_mapping_reverse (part, &last_xmp_tag);
776 while (ne < xp2 && *ne != '<') {
782 /* {TXT, ns, (ne-ns)-1} */
783 if (ns[0] != '\n' && &ns[1] <= ne) {
784 /* only log non-newline nodes, we still have to parse them */
785 GST_INFO ("txt: %s", part);
789 ptag = g_slice_new (PendingXmpTag);
790 ptag->gst_tag = last_tag;
791 ptag->xmp_tag = last_xmp_tag;
792 ptag->str = g_strdup (part);
794 pending_tags = g_slist_append (pending_tags, ptag);
804 while (pending_tags) {
805 PendingXmpTag *ptag = (PendingXmpTag *) pending_tags->data;
807 pending_tags = g_slist_delete_link (pending_tags, pending_tags);
809 read_one_tag (list, ptag->gst_tag, ptag->xmp_tag, ptag->str, &pending_tags);
812 g_slice_free (PendingXmpTag, ptag);
814 pending_tags = g_slist_delete_link (pending_tags, pending_tags);
817 GST_INFO ("xmp packet parsed, %d entries",
818 gst_structure_n_fields ((GstStructure *) list));
822 while (ns_map[i].original_ns) {
823 g_free (ns_map[i].gstreamer_ns);
832 GST_WARNING ("malformed xmp packet header");
835 GST_WARNING ("malformed xmp packet footer");
838 GST_WARNING ("malformed xml tag: %s", part);
846 string_open_tag (GString * string, const char *tag)
848 g_string_append_c (string, '<');
849 g_string_append (string, tag);
850 g_string_append_c (string, '>');
854 string_close_tag (GString * string, const char *tag)
856 g_string_append (string, "</");
857 g_string_append (string, tag);
858 g_string_append (string, ">\n");
862 gst_value_serialize_xmp (const GValue * value)
864 switch (G_VALUE_TYPE (value)) {
866 return g_markup_escape_text (g_value_get_string (value), -1);
868 return g_strdup_printf ("%d", g_value_get_int (value));
870 return g_strdup_printf ("%u", g_value_get_uint (value));
874 /* put non-switchable types here */
875 if (G_VALUE_TYPE (value) == GST_TYPE_DATE) {
876 const GDate *date = gst_value_get_date (value);
878 return g_strdup_printf ("%04d-%02d-%02d",
879 (gint) g_date_get_year (date), (gint) g_date_get_month (date),
880 (gint) g_date_get_day (date));
887 write_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
889 guint i = 0, ct = gst_tag_list_get_tag_size (list, tag), tag_index;
890 GString *data = user_data;
891 GPtrArray *xmp_tag_array = NULL;
895 /* map gst-tag to xmp tag */
896 xmptaglist = _xmp_tag_get_mapping (tag);
898 /* FIXME - we are always chosing the first tag mapped on the list */
899 xmp_tag_array = (GPtrArray *) xmptaglist->data;
902 if (!xmp_tag_array) {
903 GST_WARNING ("no mapping for %s to xmp", tag);
907 for (tag_index = 0; tag_index < xmp_tag_array->len; tag_index++) {
910 xmp_tag = g_ptr_array_index (xmp_tag_array, tag_index);
911 string_open_tag (data, xmp_tag->tag_name);
913 /* fast path for single valued tag */
915 if (xmp_tag->serialize) {
916 s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, 0));
918 s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
922 g_string_append (data, s);
925 GST_WARNING ("unhandled type for %s to xmp", tag);
928 string_open_tag (data, "rdf:Bag");
929 for (i = 0; i < ct; i++) {
930 GST_DEBUG ("mapping %s[%u/%u] to xmp", tag, i, ct);
931 if (xmp_tag->serialize) {
932 s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, i));
934 s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
938 string_open_tag (data, "rdf:li");
939 g_string_append (data, s);
940 string_close_tag (data, "rdf:li");
943 GST_WARNING ("unhandled type for %s to xmp", tag);
946 string_close_tag (data, "rdf:Bag");
949 string_close_tag (data, xmp_tag->tag_name);
954 * gst_tag_list_to_xmp_buffer:
956 * @read_only: does the container forbid inplace editing
958 * Formats a taglist as a xmp packet.
960 * Returns: new buffer or %NULL, unref the buffer when done
965 gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
967 GstBuffer *buffer = NULL;
968 GString *data = g_string_sized_new (4096);
971 xmp_tags_initialize ();
973 g_return_val_if_fail (GST_IS_TAG_LIST (list), NULL);
976 g_string_append (data,
977 "<?xpacket begin=\"\xEF\xBB\xBF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n");
978 g_string_append (data,
979 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"GStreamer\">\n");
980 g_string_append (data,
981 "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"");
983 while (ns_match[i].ns_prefix) {
984 g_string_append_printf (data, " xmlns:%s=\"%s\"", ns_match[i].ns_prefix,
988 g_string_append (data, ">\n");
989 g_string_append (data, "<rdf:Description rdf:about=\"\">\n");
991 /* iterate the taglist */
992 gst_tag_list_foreach (list, write_one_tag, data);
995 g_string_append (data, "</rdf:Description>\n");
996 g_string_append (data, "</rdf:RDF>\n");
997 g_string_append (data, "</x:xmpmeta>\n");
1000 /* the xmp spec recommand to add 2-4KB padding for in-place editable xmp */
1003 for (i = 0; i < 32; i++) {
1004 g_string_append (data, " " " "
1008 g_string_append_printf (data, "<?xpacket end=\"%c\"?>\n",
1009 (read_only ? 'r' : 'w'));
1011 buffer = gst_buffer_new ();
1012 GST_BUFFER_SIZE (buffer) = data->len + 1;
1013 GST_BUFFER_DATA (buffer) = (guint8 *) g_string_free (data, FALSE);
1014 GST_BUFFER_MALLOCDATA (buffer) = GST_BUFFER_DATA (buffer);