tag: xmp: Adds _USER_RATING mapping for xmp
[platform/upstream/gstreamer.git] / gst-libs / gst / tag / gstxmptag.c
1 /* GStreamer
2  * Copyright (C) 2010 Stefan Kost <stefan.kost@nokia.com>
3  *
4  * gstxmptag.c: library for reading / modifying xmp tags
5  *
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.
10  *
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.
15  *
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.
20  */
21
22 /**
23  * SECTION:gsttagxmp
24  * @short_description: tag mappings and support functions for plugins
25  *                     dealing with xmp packets
26  * @see_also: #GstTagList
27  *
28  * Contains various utility functions for plugins to parse or create
29  * xmp packets and map them to and from #GstTagList<!-- -->s.
30  *
31  * Please note that the xmp parser is very lightweight and not strict at all.
32  */
33
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37 #include <gst/gsttagsetter.h>
38 #include "gsttageditingprivate.h"
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <time.h>
43
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);
48
49 struct _XmpTag
50 {
51   const gchar *tag_name;
52   XmpSerializationFunc serialize;
53   XmpDeserializationFunc deserialize;
54 };
55 typedef struct _XmpTag XmpTag;
56
57 struct _PendingXmpTag
58 {
59   const gchar *gst_tag;
60   XmpTag *xmp_tag;
61   gchar *str;
62 };
63 typedef struct _PendingXmpTag PendingXmpTag;
64
65 /*
66  * Mappings from gstreamer tags to xmp tags
67  *
68  * The mapping here are from a gstreamer tag (as a GQuark)
69  * into a GSList of GArray of XmpTag.
70  *
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.
75  *
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.
81  */
82 static GHashTable *__xmp_tag_map;
83 static GMutex *__xmp_tag_map_mutex;
84
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)
87
88 static void
89 _xmp_tag_add_mapping (const gchar * gst_tag, GPtrArray * array)
90 {
91   GQuark key;
92   GSList *list = NULL;
93
94   key = g_quark_from_string (gst_tag);
95
96   XMP_TAG_MAP_LOCK;
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);
100   XMP_TAG_MAP_UNLOCK;
101 }
102
103 static void
104 _xmp_tag_add_simple_mapping (const gchar * gst_tag, const gchar * xmp_tag,
105     XmpSerializationFunc serialization_func,
106     XmpDeserializationFunc deserialization_func)
107 {
108   XmpTag *xmpinfo;
109   GPtrArray *array;
110
111   xmpinfo = g_slice_new (XmpTag);
112   xmpinfo->tag_name = xmp_tag;
113   xmpinfo->serialize = serialization_func;
114   xmpinfo->deserialize = deserialization_func;
115
116   array = g_ptr_array_sized_new (1);
117   g_ptr_array_add (array, xmpinfo);
118
119   _xmp_tag_add_mapping (gst_tag, array);
120 }
121
122 /*
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
126  */
127 static GSList *
128 _xmp_tag_get_mapping (const gchar * gst_tag)
129 {
130   GSList *ret = NULL;
131   GQuark key = g_quark_from_string (gst_tag);
132
133   XMP_TAG_MAP_LOCK;
134   ret = (GSList *) g_hash_table_lookup (__xmp_tag_map, GUINT_TO_POINTER (key));
135   XMP_TAG_MAP_UNLOCK;
136
137   return ret;
138 }
139
140 /* finds the gst tag that maps to this xmp tag */
141 static const gchar *
142 _xmp_tag_get_mapping_reverse (const gchar * xmp_tag, XmpTag ** _xmp_tag)
143 {
144   GHashTableIter iter;
145   gpointer key, value;
146   const gchar *ret = NULL;
147   GSList *walk;
148   gint index;
149
150   XMP_TAG_MAP_LOCK;
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;
154
155     for (walk = list; walk; walk = g_slist_next (walk)) {
156       GPtrArray *array = (GPtrArray *) walk->data;
157
158       for (index = 0; index < array->len; index++) {
159         XmpTag *xmpinfo = (XmpTag *) g_ptr_array_index (array, index);
160
161         if (strcmp (xmpinfo->tag_name, xmp_tag) == 0) {
162           *_xmp_tag = xmpinfo;
163           ret = g_quark_to_string (GPOINTER_TO_UINT (key));
164           goto out;
165         }
166       }
167     }
168   }
169
170 out:
171   XMP_TAG_MAP_UNLOCK;
172   return ret;
173 }
174
175 /* (de)serialize functions */
176 static gchar *
177 serialize_exif_gps_coordinate (const GValue * value, gchar pos, gchar neg)
178 {
179   gdouble num;
180   gchar c;
181   gint integer;
182   gchar fraction[G_ASCII_DTOSTR_BUF_SIZE];
183
184   g_return_val_if_fail (G_VALUE_TYPE (value) == G_TYPE_DOUBLE, NULL);
185
186   num = g_value_get_double (value);
187   if (num < 0) {
188     c = neg;
189     num *= -1;
190   } else {
191     c = pos;
192   }
193   integer = (gint) num;
194
195   g_ascii_dtostr (fraction, sizeof (fraction), (num - integer) * 60);
196
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);
200 }
201
202 static gchar *
203 serialize_exif_latitude (const GValue * value)
204 {
205   return serialize_exif_gps_coordinate (value, 'N', 'S');
206 }
207
208 static gchar *
209 serialize_exif_longitude (const GValue * value)
210 {
211   return serialize_exif_gps_coordinate (value, 'E', 'W');
212 }
213
214 static void
215 deserialize_exif_gps_coordinate (GstTagList * taglist, const gchar * gst_tag,
216     const gchar * str, gchar pos, gchar neg)
217 {
218   gdouble value = 0;
219   gint d = 0, m = 0, s = 0;
220   gdouble m2 = 0;
221   gchar c;
222   const gchar *current;
223
224   /* get the degrees */
225   if (sscanf (str, "%d", &d) != 1)
226     goto error;
227
228   /* find the beginning of the minutes */
229   current = strchr (str, ',');
230   if (current == NULL)
231     goto end;
232   current += 1;
233
234   /* check if it uses ,SS or .mm */
235   if (strchr (current, ',') != NULL) {
236     sscanf (current, "%d,%d%c", &m, &s, &c);
237   } else {
238     gchar *copy = g_strdup (current);
239     gint len = strlen (copy);
240     gint i;
241
242     /* check the last letter */
243     for (i = len - 1; len >= 0; len--) {
244       if (g_ascii_isspace (copy[i]))
245         continue;
246
247       if (g_ascii_isalpha (copy[i])) {
248         /* found it */
249         c = copy[i];
250         copy[i] = '\0';
251         break;
252
253       } else {
254         /* something is wrong */
255         g_free (copy);
256         goto error;
257       }
258     }
259
260     /* use a copy so we can change the last letter as E can cause
261      * problems here */
262     m2 = g_ascii_strtod (copy, NULL);
263     g_free (copy);
264   }
265
266 end:
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);
269
270   if (c == pos) {
271     //NOP
272   } else if (c == neg) {
273     value *= -1;
274   } else {
275     goto error;
276   }
277
278   gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, value, NULL);
279   return;
280
281 error:
282   GST_WARNING ("Failed to deserialize gps coordinate: %s", str);
283 }
284
285 static void
286 deserialize_exif_latitude (GstTagList * taglist, const gchar * gst_tag,
287     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
288 {
289   deserialize_exif_gps_coordinate (taglist, gst_tag, str, 'N', 'S');
290 }
291
292 static void
293 deserialize_exif_longitude (GstTagList * taglist, const gchar * gst_tag,
294     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
295 {
296   deserialize_exif_gps_coordinate (taglist, gst_tag, str, 'E', 'W');
297 }
298
299 static gchar *
300 serialize_exif_altitude (const GValue * value)
301 {
302   gdouble num;
303   gint frac_n;
304   gint frac_d;
305
306   num = g_value_get_double (value);
307
308   if (num < 0)
309     num *= -1;
310
311   gst_util_double_to_fraction (num, &frac_n, &frac_d);
312
313   return g_strdup_printf ("%d/%d", frac_n, frac_d);
314 }
315
316 static gchar *
317 serialize_exif_altituderef (const GValue * value)
318 {
319   gdouble num;
320
321   num = g_value_get_double (value);
322
323   if (num >= 0)
324     return g_strdup ("0");
325   return g_strdup ("1");
326 }
327
328 static void
329 deserialize_exif_altitude (GstTagList * taglist, const gchar * gst_tag,
330     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
331 {
332   const gchar *altitude_str = NULL;
333   const gchar *altituderef_str = NULL;
334   gint frac_n;
335   gint frac_d;
336   gdouble value;
337
338   GSList *entry;
339   PendingXmpTag *ptag = NULL;
340
341   /* find the other missing part */
342   if (strcmp (xmp_tag, "exif:GPSAltitude") == 0) {
343     altitude_str = str;
344
345     for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
346       ptag = (PendingXmpTag *) entry->data;
347
348       if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitudeRef") == 0) {
349         altituderef_str = ptag->str;
350         break;
351       }
352     }
353
354   } else if (strcmp (xmp_tag, "exif:GPSAltitudeRef") == 0) {
355     altituderef_str = str;
356
357     for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
358       ptag = (PendingXmpTag *) entry->data;
359
360       if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitude") == 0) {
361         altitude_str = ptag->str;
362         break;
363       }
364     }
365
366   } else {
367     GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
368     return;
369   }
370
371   if (!altitude_str) {
372     GST_WARNING ("Missing exif:GPSAltitude tag");
373     return;
374   }
375   if (!altituderef_str) {
376     GST_WARNING ("Missing exif:GPSAltitudeRef tag");
377     return;
378   }
379
380   if (sscanf (altitude_str, "%d/%d", &frac_n, &frac_d) != 2) {
381     GST_WARNING ("Failed to parse fraction: %s", altitude_str);
382     return;
383   }
384
385   gst_util_fraction_to_double (frac_n, frac_d, &value);
386
387   if (altituderef_str[0] == '0') {
388   } else if (altituderef_str[0] == '1') {
389     value *= -1;
390   } else {
391     GST_WARNING ("Unexpected exif:AltitudeRef value: %s", altituderef_str);
392     return;
393   }
394
395   /* add to the taglist */
396   gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
397       GST_TAG_GEO_LOCATION_ELEVATION, value, NULL);
398
399   /* clean up entry */
400   g_free (ptag->str);
401   g_slice_free (PendingXmpTag, ptag);
402   *pending_tags = g_slist_delete_link (*pending_tags, entry);
403 }
404
405 static void
406 deserialize_xmp_rating (GstTagList * taglist, const gchar * gst_tag,
407     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
408 {
409   guint value;
410
411   if (sscanf (str, "%u", &value) != 1) {
412     GST_WARNING ("Failed to parse xmp:Rating %s", str);
413     return;
414   }
415
416   if (value < 0 || value > 100) {
417     GST_WARNING ("Unsupported Rating tag %u (should be from 0 to 100), "
418         "ignoring", value);
419     return;
420   }
421
422   gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, value, NULL);
423 }
424
425 /* look at this page for addtional schemas
426  * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/XMP.html
427  */
428 static gpointer
429 _init_xmp_tag_map ()
430 {
431   GPtrArray *array;
432   XmpTag *xmpinfo;
433
434   __xmp_tag_map_mutex = g_mutex_new ();
435   __xmp_tag_map = g_hash_table_new (g_direct_hash, g_direct_equal);
436
437   /* add the maps */
438   /* dublic code metadata
439    * http://dublincore.org/documents/dces/
440    */
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,
445       NULL);
446   _xmp_tag_add_simple_mapping (GST_TAG_DESCRIPTION, "dc:description", NULL,
447       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);
452
453   /* xap (xmp) schema */
454   _xmp_tag_add_simple_mapping (GST_TAG_USER_RATING, "xmp:Rating", NULL,
455       deserialize_xmp_rating);
456
457   /* exif schema */
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);
463
464   /* compound tag */
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);
477
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",
482       NULL, NULL);
483
484   /* iptc4xmpcore schema */
485   _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_SUBLOCATION,
486       "Iptc4xmpCore:Location", NULL, NULL);
487
488   return NULL;
489 }
490
491 static void
492 xmp_tags_initialize ()
493 {
494   static GOnce my_once = G_ONCE_INIT;
495   g_once (&my_once, _init_xmp_tag_map, NULL);
496 }
497
498 typedef struct _GstXmpNamespaceMatch GstXmpNamespaceMatch;
499 struct _GstXmpNamespaceMatch
500 {
501   const gchar *ns_prefix;
502   const gchar *ns_uri;
503 };
504
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/"},
512   {NULL, NULL}
513 };
514
515 typedef struct _GstXmpNamespaceMap GstXmpNamespaceMap;
516 struct _GstXmpNamespaceMap
517 {
518   const gchar *original_ns;
519   gchar *gstreamer_ns;
520 };
521 static GstXmpNamespaceMap ns_map[] = {
522   {"dc", NULL},
523   {"exif", NULL},
524   {"tiff", NULL},
525   {"xap", NULL},
526   {"photoshop", NULL},
527   {"Iptc4xmpCore", NULL},
528   {NULL, NULL}
529 };
530
531 /* parsing */
532
533 static void
534 read_one_tag (GstTagList * list, const gchar * tag, XmpTag * xmptag,
535     const gchar * v, GSList ** pending_tags)
536 {
537   GType tag_type;
538
539   if (xmptag && xmptag->deserialize) {
540     xmptag->deserialize (list, tag, xmptag->tag_name, v, pending_tags);
541     return;
542   }
543
544   tag_type = gst_tag_get_type (tag);
545
546   /* add gstreamer tag depending on type */
547   switch (tag_type) {
548     case G_TYPE_STRING:{
549       gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, v, NULL);
550       break;
551     }
552     default:
553       if (tag_type == GST_TYPE_DATE) {
554         GDate *date;
555         gint d, m, y;
556
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 */
561
562         /* FIXME: this would be the proper way, but needs
563            #define _XOPEN_SOURCE before #include <time.h>
564
565            date = g_date_new ();
566            struct tm tm={0,};
567            strptime (dts, "%FT%TZ", &tm);
568            g_date_set_time_t (date, mktime(&tm));
569          */
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,
575            date, NULL);
576            } else {
577            GST_WARNING ("unparsable date: '%s'", v);
578            }
579          */
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);
584         g_date_free (date);
585       } else {
586         GST_WARNING ("unhandled type for %s from xmp", tag);
587       }
588       break;
589   }
590 }
591
592 /**
593  * gst_tag_list_from_xmp_buffer:
594  * @buffer: buffer
595  *
596  * Parse a xmp packet into a taglist.
597  *
598  * Returns: new taglist or %NULL, free the list when done
599  *
600  * Since: 0.10.29
601  */
602 GstTagList *
603 gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
604 {
605   GstTagList *list = NULL;
606   const gchar *xps, *xp1, *xp2, *xpe, *ns, *ne;
607   guint len, max_ft_len;
608   gboolean in_tag;
609   gchar *part, *pp;
610   guint i;
611   const gchar *last_tag = NULL;
612   XmpTag *last_xmp_tag = NULL;
613   GSList *pending_tags = NULL;
614
615   xmp_tags_initialize ();
616
617   g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
618   g_return_val_if_fail (GST_BUFFER_SIZE (buffer) > 0, NULL);
619
620   xps = (const gchar *) GST_BUFFER_DATA (buffer);
621   len = GST_BUFFER_SIZE (buffer);
622   xpe = &xps[len + 1];
623
624   /* check header and footer */
625   xp1 = g_strstr_len (xps, len, "<?xpacket begin");
626   if (!xp1)
627     goto missing_header;
628   xp1 = &xp1[strlen ("<?xpacket begin")];
629   while (*xp1 != '>' && *xp1 != '<' && xp1 < xpe)
630     xp1++;
631   if (*xp1 != '>')
632     goto missing_header;
633
634   max_ft_len = 1 + strlen ("<?xpacket end=\".\"?>\n");
635   if (len < max_ft_len)
636     goto missing_footer;
637
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 ");
640   if (!xp2)
641     goto missing_footer;
642
643   GST_INFO ("xmp header okay");
644
645   /* skip > and text until first xml-node */
646   xp1++;
647   while (*xp1 != '<' && xp1 < xpe)
648     xp1++;
649
650   /* no tag can be longer that the whole buffer */
651   part = g_malloc (xp2 - xp1);
652   list = gst_tag_list_new ();
653
654   /* parse data into a list of nodes */
655   /* data is between xp1..xp2 */
656   in_tag = TRUE;
657   ns = ne = xp1;
658   pp = part;
659   while (ne < xp2) {
660     if (in_tag) {
661       ne++;
662       while (ne < xp2 && *ne != '>' && *ne != '<') {
663         if (*ne == '\n' || *ne == '\t' || *ne == ' ') {
664           while (ne < xp2 && (*ne == '\n' || *ne == '\t' || *ne == ' '))
665             ne++;
666           *pp++ = ' ';
667         } else {
668           *pp++ = *ne++;
669         }
670       }
671       *pp = '\0';
672       if (*ne != '>')
673         goto broken_xml;
674       /* create node */
675       /* {XML, ns, ne-ns} */
676       if (ns[0] != '/') {
677         gchar *as = strchr (part, ' ');
678         /* only log start nodes */
679         GST_INFO ("xml: %s", part);
680
681         if (as) {
682           gchar *ae, *d;
683
684           /* skip ' ' and scan the attributes */
685           as++;
686           d = ae = as;
687
688           /* split attr=value pairs */
689           while (*ae != '\0') {
690             if (*ae == '=') {
691               /* attr/value delimmiter */
692               d = ae;
693             } else if (*ae == '"') {
694               /* scan values */
695               gchar *v;
696
697               ae++;
698               while (*ae != '\0' && *ae != '"')
699                 ae++;
700
701               *d = *ae = '\0';
702               v = &d[2];
703               GST_INFO ("   : [%s][%s]", as, v);
704               if (!strncmp (as, "xmlns:", 6)) {
705                 i = 0;
706                 /* we need to rewrite known namespaces to what we use in
707                  * tag_matches */
708                 while (ns_match[i].ns_prefix) {
709                   if (!strcmp (ns_match[i].ns_uri, v))
710                     break;
711                   i++;
712                 }
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]);
716                   }
717                 }
718               } else {
719                 const gchar *gst_tag;
720                 XmpTag *xmp_tag = NULL;
721                 /* FIXME: eventualy rewrite ns
722                  * find ':'
723                  * check if ns before ':' is in ns_map and ns_map[i].gstreamer_ns!=NULL
724                  * do 2 stage filter in tag_matches
725                  */
726                 gst_tag = _xmp_tag_get_mapping_reverse (as, &xmp_tag);
727                 if (gst_tag) {
728                   PendingXmpTag *ptag;
729
730                   ptag = g_slice_new (PendingXmpTag);
731                   ptag->gst_tag = gst_tag;
732                   ptag->xmp_tag = xmp_tag;
733                   ptag->str = g_strdup (v);
734
735                   pending_tags = g_slist_append (pending_tags, ptag);
736                 }
737               }
738               /* restore chars overwritten by '\0' */
739               *d = '=';
740               *ae = '"';
741             } else if (*ae == '\0' || *ae == ' ') {
742               /* end of attr/value pair */
743               as = &ae[1];
744             }
745             /* to next char if not eos */
746             if (*ae != '\0')
747               ae++;
748           }
749         } else {
750           /*
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>
753            */
754           /* FIXME: eventualy rewrite ns */
755
756           /* skip rdf tags for now */
757           if (strncmp (part, "rdf:", 4)) {
758             const gchar *parttag;
759
760             parttag = _xmp_tag_get_mapping_reverse (part, &last_xmp_tag);
761             if (parttag) {
762               last_tag = parttag;
763             }
764           }
765         }
766       }
767       /* next cycle */
768       ne++;
769       if (ne < xp2) {
770         if (*ne != '<')
771           in_tag = FALSE;
772         ns = ne;
773         pp = part;
774       }
775     } else {
776       while (ne < xp2 && *ne != '<') {
777         *pp++ = *ne;
778         ne++;
779       }
780       *pp = '\0';
781       /* create node */
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);
786         if (last_tag) {
787           PendingXmpTag *ptag;
788
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);
793
794           pending_tags = g_slist_append (pending_tags, ptag);
795         }
796       }
797       /* next cycle */
798       in_tag = TRUE;
799       ns = ne;
800       pp = part;
801     }
802   }
803
804   while (pending_tags) {
805     PendingXmpTag *ptag = (PendingXmpTag *) pending_tags->data;
806
807     pending_tags = g_slist_delete_link (pending_tags, pending_tags);
808
809     read_one_tag (list, ptag->gst_tag, ptag->xmp_tag, ptag->str, &pending_tags);
810
811     g_free (ptag->str);
812     g_slice_free (PendingXmpTag, ptag);
813
814     pending_tags = g_slist_delete_link (pending_tags, pending_tags);
815   }
816
817   GST_INFO ("xmp packet parsed, %d entries",
818       gst_structure_n_fields ((GstStructure *) list));
819
820   /* free resources */
821   i = 0;
822   while (ns_map[i].original_ns) {
823     g_free (ns_map[i].gstreamer_ns);
824     i++;
825   }
826   g_free (part);
827
828   return list;
829
830   /* Errors */
831 missing_header:
832   GST_WARNING ("malformed xmp packet header");
833   return NULL;
834 missing_footer:
835   GST_WARNING ("malformed xmp packet footer");
836   return NULL;
837 broken_xml:
838   GST_WARNING ("malformed xml tag: %s", part);
839   return NULL;
840 }
841
842
843 /* formatting */
844
845 static void
846 string_open_tag (GString * string, const char *tag)
847 {
848   g_string_append_c (string, '<');
849   g_string_append (string, tag);
850   g_string_append_c (string, '>');
851 }
852
853 static void
854 string_close_tag (GString * string, const char *tag)
855 {
856   g_string_append (string, "</");
857   g_string_append (string, tag);
858   g_string_append (string, ">\n");
859 }
860
861 static char *
862 gst_value_serialize_xmp (const GValue * value)
863 {
864   switch (G_VALUE_TYPE (value)) {
865     case G_TYPE_STRING:
866       return g_markup_escape_text (g_value_get_string (value), -1);
867     case G_TYPE_INT:
868       return g_strdup_printf ("%d", g_value_get_int (value));
869     case G_TYPE_UINT:
870       return g_strdup_printf ("%u", g_value_get_uint (value));
871     default:
872       break;
873   }
874   /* put non-switchable types here */
875   if (G_VALUE_TYPE (value) == GST_TYPE_DATE) {
876     const GDate *date = gst_value_get_date (value);
877
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));
881   } else {
882     return NULL;
883   }
884 }
885
886 static void
887 write_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
888 {
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;
892   char *s;
893   GSList *xmptaglist;
894
895   /* map gst-tag to xmp tag */
896   xmptaglist = _xmp_tag_get_mapping (tag);
897   if (xmptaglist) {
898     /* FIXME - we are always chosing the first tag mapped on the list */
899     xmp_tag_array = (GPtrArray *) xmptaglist->data;
900   }
901
902   if (!xmp_tag_array) {
903     GST_WARNING ("no mapping for %s to xmp", tag);
904     return;
905   }
906
907   for (tag_index = 0; tag_index < xmp_tag_array->len; tag_index++) {
908     XmpTag *xmp_tag;
909
910     xmp_tag = g_ptr_array_index (xmp_tag_array, tag_index);
911     string_open_tag (data, xmp_tag->tag_name);
912
913     /* fast path for single valued tag */
914     if (ct == 1) {
915       if (xmp_tag->serialize) {
916         s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, 0));
917       } else {
918         s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
919                 0));
920       }
921       if (s) {
922         g_string_append (data, s);
923         g_free (s);
924       } else {
925         GST_WARNING ("unhandled type for %s to xmp", tag);
926       }
927     } else {
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));
933         } else {
934           s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
935                   i));
936         }
937         if (s) {
938           string_open_tag (data, "rdf:li");
939           g_string_append (data, s);
940           string_close_tag (data, "rdf:li");
941           g_free (s);
942         } else {
943           GST_WARNING ("unhandled type for %s to xmp", tag);
944         }
945       }
946       string_close_tag (data, "rdf:Bag");
947     }
948
949     string_close_tag (data, xmp_tag->tag_name);
950   }
951 }
952
953 /**
954  * gst_tag_list_to_xmp_buffer:
955  * @list: tags
956  * @read_only: does the container forbid inplace editing
957  *
958  * Formats a taglist as a xmp packet.
959  *
960  * Returns: new buffer or %NULL, unref the buffer when done
961  *
962  * Since: 0.10.29
963  */
964 GstBuffer *
965 gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
966 {
967   GstBuffer *buffer = NULL;
968   GString *data = g_string_sized_new (4096);
969   guint i;
970
971   xmp_tags_initialize ();
972
973   g_return_val_if_fail (GST_IS_TAG_LIST (list), NULL);
974
975   /* xmp header */
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#\"");
982   i = 0;
983   while (ns_match[i].ns_prefix) {
984     g_string_append_printf (data, " xmlns:%s=\"%s\"", ns_match[i].ns_prefix,
985         ns_match[i].ns_uri);
986     i++;
987   }
988   g_string_append (data, ">\n");
989   g_string_append (data, "<rdf:Description rdf:about=\"\">\n");
990
991   /* iterate the taglist */
992   gst_tag_list_foreach (list, write_one_tag, data);
993
994   /* xmp footer */
995   g_string_append (data, "</rdf:Description>\n");
996   g_string_append (data, "</rdf:RDF>\n");
997   g_string_append (data, "</x:xmpmeta>\n");
998
999   if (!read_only) {
1000     /* the xmp spec recommand to add 2-4KB padding for in-place editable xmp */
1001     guint i;
1002
1003     for (i = 0; i < 32; i++) {
1004       g_string_append (data, "                " "                "
1005           "                " "                " "\n");
1006     }
1007   }
1008   g_string_append_printf (data, "<?xpacket end=\"%c\"?>\n",
1009       (read_only ? 'r' : 'w'));
1010
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);
1015
1016   return buffer;
1017 }