tag: xmp: removing useless locking
[platform/upstream/gstreamer.git] / gst-libs / gst / tag / gstxmptag.c
1 /* GStreamer
2  * Copyright (C) 2010 Stefan Kost <stefan.kost@nokia.com>
3  * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
4  *
5  * gstxmptag.c: library for reading / modifying xmp tags
6  *
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.
11  *
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.
16  *
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.
21  */
22
23 /**
24  * SECTION:gsttagxmp
25  * @short_description: tag mappings and support functions for plugins
26  *                     dealing with xmp packets
27  * @see_also: #GstTagList
28  *
29  * Contains various utility functions for plugins to parse or create
30  * xmp packets and map them to and from #GstTagList<!-- -->s.
31  *
32  * Please note that the xmp parser is very lightweight and not strict at all.
33  */
34
35 #ifdef HAVE_CONFIG_H
36 #include "config.h"
37 #endif
38 #include <gst/gsttagsetter.h>
39 #include "gsttageditingprivate.h"
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <time.h>
44 #include <ctype.h>
45
46 /*
47  * Serializes a GValue into a string.
48  */
49 typedef gchar *(*XmpSerializationFunc) (const GValue * value);
50
51 /*
52  * Deserializes @str that is the gstreamer tag @gst_tag represented in
53  * XMP as the @xmp_tag and adds the result to the @taglist.
54  *
55  * @pending_tags is passed so that compound xmp tags can search for its
56  * complements on the list and use them. Note that used complements should
57  * be freed and removed from the list.
58  * The list is of PendingXmpTag
59  */
60 typedef void (*XmpDeserializationFunc) (GstTagList * taglist,
61     const gchar * gst_tag, const gchar * xmp_tag,
62     const gchar * str, GSList ** pending_tags);
63
64 struct _XmpTag
65 {
66   const gchar *tag_name;
67   XmpSerializationFunc serialize;
68   XmpDeserializationFunc deserialize;
69 };
70 typedef struct _XmpTag XmpTag;
71
72 struct _PendingXmpTag
73 {
74   const gchar *gst_tag;
75   XmpTag *xmp_tag;
76   gchar *str;
77 };
78 typedef struct _PendingXmpTag PendingXmpTag;
79
80 /*
81  * Mappings from gstreamer tags to xmp tags
82  *
83  * The mapping here are from a gstreamer tag (as a GQuark)
84  * into a GSList of GArray of XmpTag.
85  *
86  * There might be multiple xmp tags that a single gstreamer tag can be
87  * mapped to. For example, GST_TAG_DATE might be mapped into dc:date
88  * or exif:DateTimeOriginal, hence the first list, to be able to store
89  * alternative mappings of the same gstreamer tag.
90  *
91  * Some other tags, like GST_TAG_GEO_LOCATION_ELEVATION needs to be
92  * mapped into 2 complementary tags in the exif's schema. One of them
93  * stores the absolute elevation, and the other one stores if it is
94  * above of below sea level. That's why we need a GArray as the item
95  * of each GSList in the mapping.
96  */
97 static GHashTable *__xmp_tag_map;
98
99 static void
100 _xmp_tag_add_mapping (const gchar * gst_tag, GPtrArray * array)
101 {
102   GQuark key;
103   GSList *list = NULL;
104
105   key = g_quark_from_string (gst_tag);
106
107   list = g_hash_table_lookup (__xmp_tag_map, GUINT_TO_POINTER (key));
108   list = g_slist_append (list, (gpointer) array);
109   g_hash_table_insert (__xmp_tag_map, GUINT_TO_POINTER (key), list);
110 }
111
112 static void
113 _xmp_tag_add_simple_mapping (const gchar * gst_tag, const gchar * xmp_tag,
114     XmpSerializationFunc serialization_func,
115     XmpDeserializationFunc deserialization_func)
116 {
117   XmpTag *xmpinfo;
118   GPtrArray *array;
119
120   xmpinfo = g_slice_new (XmpTag);
121   xmpinfo->tag_name = xmp_tag;
122   xmpinfo->serialize = serialization_func;
123   xmpinfo->deserialize = deserialization_func;
124
125   array = g_ptr_array_sized_new (1);
126   g_ptr_array_add (array, xmpinfo);
127
128   _xmp_tag_add_mapping (gst_tag, array);
129 }
130
131 /*
132  * We do not return a copy here because elements are
133  * appended, and the API is not public, so we shouldn't
134  * have our lists modified during usage
135  */
136 static GSList *
137 _xmp_tag_get_mapping (const gchar * gst_tag)
138 {
139   GSList *ret = NULL;
140   GQuark key = g_quark_from_string (gst_tag);
141
142   ret = (GSList *) g_hash_table_lookup (__xmp_tag_map, GUINT_TO_POINTER (key));
143
144   return ret;
145 }
146
147 /* finds the gst tag that maps to this xmp tag */
148 static const gchar *
149 _xmp_tag_get_mapping_reverse (const gchar * xmp_tag, XmpTag ** _xmp_tag)
150 {
151   GHashTableIter iter;
152   gpointer key, value;
153   const gchar *ret = NULL;
154   GSList *walk;
155   gint index;
156
157
158   /* Iterate over the hashtable */
159   g_hash_table_iter_init (&iter, __xmp_tag_map);
160   while (!ret && g_hash_table_iter_next (&iter, &key, &value)) {
161     GSList *list = (GSList *) value;
162
163     /* each entry might contain multiple mappigns */
164     for (walk = list; walk; walk = g_slist_next (walk)) {
165       GPtrArray *array = (GPtrArray *) walk->data;
166
167       /* each mapping might contain complementary tags */
168       for (index = 0; index < array->len; index++) {
169         XmpTag *xmpinfo = (XmpTag *) g_ptr_array_index (array, index);
170
171         if (strcmp (xmpinfo->tag_name, xmp_tag) == 0) {
172           *_xmp_tag = xmpinfo;
173           ret = g_quark_to_string (GPOINTER_TO_UINT (key));
174           goto out;
175         }
176       }
177     }
178   }
179
180 out:
181   return ret;
182 }
183
184 /* utility functions/macros */
185
186 #define METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR (3.6)
187 #define KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND (1/3.6)
188 #define MILES_PER_HOUR_TO_METERS_PER_SECOND (0.44704)
189 #define KNOTS_TO_METERS_PER_SECOND (0.514444)
190
191 static gchar *
192 double_to_fraction_string (gdouble num)
193 {
194   gint frac_n;
195   gint frac_d;
196
197   gst_util_double_to_fraction (num, &frac_n, &frac_d);
198   return g_strdup_printf ("%d/%d", frac_n, frac_d);
199 }
200
201 /* (de)serialize functions */
202 static gchar *
203 serialize_exif_gps_coordinate (const GValue * value, gchar pos, gchar neg)
204 {
205   gdouble num;
206   gchar c;
207   gint integer;
208   gchar fraction[G_ASCII_DTOSTR_BUF_SIZE];
209
210   g_return_val_if_fail (G_VALUE_TYPE (value) == G_TYPE_DOUBLE, NULL);
211
212   num = g_value_get_double (value);
213   if (num < 0) {
214     c = neg;
215     num *= -1;
216   } else {
217     c = pos;
218   }
219   integer = (gint) num;
220
221   g_ascii_dtostr (fraction, sizeof (fraction), (num - integer) * 60);
222
223   /* FIXME review GPSCoordinate serialization spec for the .mm or ,ss
224    * decision. Couldn't understand it clearly */
225   return g_strdup_printf ("%d,%s%c", integer, fraction, c);
226 }
227
228 static gchar *
229 serialize_exif_latitude (const GValue * value)
230 {
231   return serialize_exif_gps_coordinate (value, 'N', 'S');
232 }
233
234 static gchar *
235 serialize_exif_longitude (const GValue * value)
236 {
237   return serialize_exif_gps_coordinate (value, 'E', 'W');
238 }
239
240 static void
241 deserialize_exif_gps_coordinate (GstTagList * taglist, const gchar * gst_tag,
242     const gchar * str, gchar pos, gchar neg)
243 {
244   gdouble value = 0;
245   gint d = 0, m = 0, s = 0;
246   gdouble m2 = 0;
247   gchar c = 0;
248   const gchar *current;
249
250   /* get the degrees */
251   if (sscanf (str, "%d", &d) != 1)
252     goto error;
253
254   /* find the beginning of the minutes */
255   current = strchr (str, ',');
256   if (current == NULL)
257     goto end;
258   current += 1;
259
260   /* check if it uses ,SS or .mm */
261   if (strchr (current, ',') != NULL) {
262     sscanf (current, "%d,%d%c", &m, &s, &c);
263   } else {
264     gchar *copy = g_strdup (current);
265     gint len = strlen (copy);
266     gint i;
267
268     /* check the last letter */
269     for (i = len - 1; len >= 0; len--) {
270       if (g_ascii_isspace (copy[i]))
271         continue;
272
273       if (g_ascii_isalpha (copy[i])) {
274         /* found it */
275         c = copy[i];
276         copy[i] = '\0';
277         break;
278
279       } else {
280         /* something is wrong */
281         g_free (copy);
282         goto error;
283       }
284     }
285
286     /* use a copy so we can change the last letter as E can cause
287      * problems here */
288     m2 = g_ascii_strtod (copy, NULL);
289     g_free (copy);
290   }
291
292 end:
293   /* we can add them all as those that aren't parsed are 0 */
294   value = d + (m / 60.0) + (s / (60.0 * 60.0)) + (m2 / 60.0);
295
296   if (c == pos) {
297     //NOP
298   } else if (c == neg) {
299     value *= -1;
300   } else {
301     goto error;
302   }
303
304   gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, value, NULL);
305   return;
306
307 error:
308   GST_WARNING ("Failed to deserialize gps coordinate: %s", str);
309 }
310
311 static void
312 deserialize_exif_latitude (GstTagList * taglist, const gchar * gst_tag,
313     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
314 {
315   deserialize_exif_gps_coordinate (taglist, gst_tag, str, 'N', 'S');
316 }
317
318 static void
319 deserialize_exif_longitude (GstTagList * taglist, const gchar * gst_tag,
320     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
321 {
322   deserialize_exif_gps_coordinate (taglist, gst_tag, str, 'E', 'W');
323 }
324
325 static gchar *
326 serialize_exif_altitude (const GValue * value)
327 {
328   gdouble num;
329
330   num = g_value_get_double (value);
331
332   if (num < 0)
333     num *= -1;
334
335   return double_to_fraction_string (num);
336 }
337
338 static gchar *
339 serialize_exif_altituderef (const GValue * value)
340 {
341   gdouble num;
342
343   num = g_value_get_double (value);
344
345   /* 0 means above sea level, 1 means below */
346   if (num >= 0)
347     return g_strdup ("0");
348   return g_strdup ("1");
349 }
350
351 static void
352 deserialize_exif_altitude (GstTagList * taglist, const gchar * gst_tag,
353     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
354 {
355   const gchar *altitude_str = NULL;
356   const gchar *altituderef_str = NULL;
357   gint frac_n;
358   gint frac_d;
359   gdouble value;
360
361   GSList *entry;
362   PendingXmpTag *ptag = NULL;
363
364   /* find the other missing part */
365   if (strcmp (xmp_tag, "exif:GPSAltitude") == 0) {
366     altitude_str = str;
367
368     for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
369       ptag = (PendingXmpTag *) entry->data;
370
371       if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitudeRef") == 0) {
372         altituderef_str = ptag->str;
373         break;
374       }
375     }
376
377   } else if (strcmp (xmp_tag, "exif:GPSAltitudeRef") == 0) {
378     altituderef_str = str;
379
380     for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
381       ptag = (PendingXmpTag *) entry->data;
382
383       if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitude") == 0) {
384         altitude_str = ptag->str;
385         break;
386       }
387     }
388
389   } else {
390     GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
391     return;
392   }
393
394   if (!altitude_str) {
395     GST_WARNING ("Missing exif:GPSAltitude tag");
396     return;
397   }
398   if (!altituderef_str) {
399     GST_WARNING ("Missing exif:GPSAltitudeRef tag");
400     return;
401   }
402
403   if (sscanf (altitude_str, "%d/%d", &frac_n, &frac_d) != 2) {
404     GST_WARNING ("Failed to parse fraction: %s", altitude_str);
405     return;
406   }
407
408   gst_util_fraction_to_double (frac_n, frac_d, &value);
409
410   if (altituderef_str[0] == '0') {
411     /* nop */
412   } else if (altituderef_str[0] == '1') {
413     value *= -1;
414   } else {
415     GST_WARNING ("Unexpected exif:AltitudeRef value: %s", altituderef_str);
416     return;
417   }
418
419   /* add to the taglist */
420   gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
421       GST_TAG_GEO_LOCATION_ELEVATION, value, NULL);
422
423   /* clean up entry */
424   g_free (ptag->str);
425   g_slice_free (PendingXmpTag, ptag);
426   *pending_tags = g_slist_delete_link (*pending_tags, entry);
427 }
428
429 static gchar *
430 serialize_exif_gps_speed (const GValue * value)
431 {
432   return double_to_fraction_string (g_value_get_double (value) *
433       METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR);
434 }
435
436 static gchar *
437 serialize_exif_gps_speedref (const GValue * value)
438 {
439   /* we always use km/h */
440   return g_strdup ("K");
441 }
442
443 static void
444 deserialize_exif_gps_speed (GstTagList * taglist, const gchar * gst_tag,
445     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
446 {
447   const gchar *speed_str = NULL;
448   const gchar *speedref_str = NULL;
449   gint frac_n;
450   gint frac_d;
451   gdouble value;
452
453   GSList *entry;
454   PendingXmpTag *ptag = NULL;
455
456   /* find the other missing part */
457   if (strcmp (xmp_tag, "exif:GPSSpeed") == 0) {
458     speed_str = str;
459
460     for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
461       ptag = (PendingXmpTag *) entry->data;
462
463       if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSSpeedRef") == 0) {
464         speedref_str = ptag->str;
465         break;
466       }
467     }
468
469   } else if (strcmp (xmp_tag, "exif:GPSSpeedRef") == 0) {
470     speedref_str = str;
471
472     for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
473       ptag = (PendingXmpTag *) entry->data;
474
475       if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSSpeed") == 0) {
476         speed_str = ptag->str;
477         break;
478       }
479     }
480
481   } else {
482     GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
483     return;
484   }
485
486   if (!speed_str) {
487     GST_WARNING ("Missing exif:GPSSpeed tag");
488     return;
489   }
490   if (!speedref_str) {
491     GST_WARNING ("Missing exif:GPSSpeedRef tag");
492     return;
493   }
494
495   if (sscanf (speed_str, "%d/%d", &frac_n, &frac_d) != 2) {
496     GST_WARNING ("Failed to parse fraction: %s", speed_str);
497     return;
498   }
499
500   gst_util_fraction_to_double (frac_n, frac_d, &value);
501
502   if (speedref_str[0] == 'K') {
503     value *= KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND;
504   } else if (speedref_str[0] == 'M') {
505     value *= MILES_PER_HOUR_TO_METERS_PER_SECOND;
506   } else if (speedref_str[0] == 'N') {
507     value *= KNOTS_TO_METERS_PER_SECOND;
508   } else {
509     GST_WARNING ("Unexpected exif:SpeedRef value: %s", speedref_str);
510     return;
511   }
512
513   /* add to the taglist */
514   gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
515       GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, value, NULL);
516
517   /* clean up entry */
518   g_free (ptag->str);
519   g_slice_free (PendingXmpTag, ptag);
520   *pending_tags = g_slist_delete_link (*pending_tags, entry);
521 }
522
523 static gchar *
524 serialize_exif_gps_direction (const GValue * value)
525 {
526   return double_to_fraction_string (g_value_get_double (value));
527 }
528
529 static gchar *
530 serialize_exif_gps_directionref (const GValue * value)
531 {
532   /* T for true geographic direction (M would mean magnetic) */
533   return g_strdup ("T");
534 }
535
536 static void
537 deserialize_exif_gps_direction (GstTagList * taglist, const gchar * gst_tag,
538     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags,
539     const gchar * direction_tag, const gchar * directionref_tag)
540 {
541   const gchar *dir_str = NULL;
542   const gchar *dirref_str = NULL;
543   gint frac_n;
544   gint frac_d;
545   gdouble value;
546
547   GSList *entry;
548   PendingXmpTag *ptag = NULL;
549
550   /* find the other missing part */
551   if (strcmp (xmp_tag, direction_tag) == 0) {
552     dir_str = str;
553
554     for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
555       ptag = (PendingXmpTag *) entry->data;
556
557       if (strcmp (ptag->xmp_tag->tag_name, directionref_tag) == 0) {
558         dirref_str = ptag->str;
559         break;
560       }
561     }
562
563   } else if (strcmp (xmp_tag, directionref_tag) == 0) {
564     dirref_str = str;
565
566     for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
567       ptag = (PendingXmpTag *) entry->data;
568
569       if (strcmp (ptag->xmp_tag->tag_name, direction_tag) == 0) {
570         dir_str = ptag->str;
571         break;
572       }
573     }
574
575   } else {
576     GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
577     return;
578   }
579
580   if (!dir_str) {
581     GST_WARNING ("Missing %s tag", dir_str);
582     return;
583   }
584   if (!dirref_str) {
585     GST_WARNING ("Missing %s tag", dirref_str);
586     return;
587   }
588
589   if (sscanf (dir_str, "%d/%d", &frac_n, &frac_d) != 2) {
590     GST_WARNING ("Failed to parse fraction: %s", dir_str);
591     return;
592   }
593
594   gst_util_fraction_to_double (frac_n, frac_d, &value);
595
596   if (dirref_str[0] == 'T') {
597     /* nop */
598   } else if (dirref_str[0] == 'M') {
599     GST_WARNING ("Magnetic direction tags aren't supported yet");
600     return;
601   } else {
602     GST_WARNING ("Unexpected %s value: %s", directionref_tag, dirref_str);
603     return;
604   }
605
606   /* add to the taglist */
607   gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, value, NULL);
608
609   /* clean up entry */
610   g_free (ptag->str);
611   g_slice_free (PendingXmpTag, ptag);
612   *pending_tags = g_slist_delete_link (*pending_tags, entry);
613 }
614
615 static void
616 deserialize_exif_gps_track (GstTagList * taglist, const gchar * gst_tag,
617     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
618 {
619   deserialize_exif_gps_direction (taglist, gst_tag, xmp_tag, str, pending_tags,
620       "exif:GPSTrack", "exif:GPSTrackRef");
621 }
622
623 static void
624 deserialize_exif_gps_img_direction (GstTagList * taglist, const gchar * gst_tag,
625     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
626 {
627   deserialize_exif_gps_direction (taglist, gst_tag, xmp_tag, str, pending_tags,
628       "exif:GPSImgDirection", "exif:GPSImgDirectionRef");
629 }
630
631 static void
632 deserialize_xmp_rating (GstTagList * taglist, const gchar * gst_tag,
633     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
634 {
635   guint value;
636
637   if (sscanf (str, "%u", &value) != 1) {
638     GST_WARNING ("Failed to parse xmp:Rating %s", str);
639     return;
640   }
641
642   if (value < 0 || value > 100) {
643     GST_WARNING ("Unsupported Rating tag %u (should be from 0 to 100), "
644         "ignoring", value);
645     return;
646   }
647
648   gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, value, NULL);
649 }
650
651 static gchar *
652 serialize_tiff_orientation (const GValue * value)
653 {
654   const gchar *str;
655   gint num;
656
657   str = g_value_get_string (value);
658   if (str == NULL) {
659     GST_WARNING ("Failed to get image orientation tag value");
660     return NULL;
661   }
662
663   num = gst_tag_image_orientation_to_exif_value (str);
664   if (num == -1)
665     return NULL;
666
667   return g_strdup_printf ("%d", num);
668 }
669
670 static void
671 deserialize_tiff_orientation (GstTagList * taglist, const gchar * gst_tag,
672     const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
673 {
674   guint value;
675   const gchar *orientation = NULL;
676
677   if (sscanf (str, "%u", &value) != 1) {
678     GST_WARNING ("Failed to parse tiff:Orientation %s", str);
679     return;
680   }
681
682   if (value < 1 || value > 8) {
683     GST_WARNING ("Invalid tiff:Orientation tag %u (should be from 1 to 8), "
684         "ignoring", value);
685     return;
686   }
687
688   orientation = gst_tag_image_orientation_from_exif_value (value);
689   if (orientation == NULL)
690     return;
691   gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, orientation, NULL);
692 }
693
694
695 /* look at this page for addtional schemas
696  * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/XMP.html
697  */
698 static gpointer
699 _init_xmp_tag_map ()
700 {
701   GPtrArray *array;
702   XmpTag *xmpinfo;
703
704   __xmp_tag_map = g_hash_table_new (g_direct_hash, g_direct_equal);
705
706   /* add the maps */
707   /* dublic code metadata
708    * http://dublincore.org/documents/dces/
709    */
710   _xmp_tag_add_simple_mapping (GST_TAG_ARTIST, "dc:creator", NULL, NULL);
711   _xmp_tag_add_simple_mapping (GST_TAG_COPYRIGHT, "dc:rights", NULL, NULL);
712   _xmp_tag_add_simple_mapping (GST_TAG_DATE, "dc:date", NULL, NULL);
713   _xmp_tag_add_simple_mapping (GST_TAG_DATE_TIME, "exif:DateTimeOriginal", NULL,
714       NULL);
715   _xmp_tag_add_simple_mapping (GST_TAG_DESCRIPTION, "dc:description", NULL,
716       NULL);
717   _xmp_tag_add_simple_mapping (GST_TAG_KEYWORDS, "dc:subject", NULL, NULL);
718   _xmp_tag_add_simple_mapping (GST_TAG_TITLE, "dc:title", NULL, NULL);
719   /* FIXME: we probably want GST_TAG_{,AUDIO_,VIDEO_}MIME_TYPE */
720   _xmp_tag_add_simple_mapping (GST_TAG_VIDEO_CODEC, "dc:format", NULL, NULL);
721
722   /* xap (xmp) schema */
723   _xmp_tag_add_simple_mapping (GST_TAG_USER_RATING, "xmp:Rating", NULL,
724       deserialize_xmp_rating);
725
726   /* tiff */
727   _xmp_tag_add_simple_mapping (GST_TAG_DEVICE_MANUFACTURER, "tiff:Make", NULL,
728       NULL);
729   _xmp_tag_add_simple_mapping (GST_TAG_DEVICE_MODEL, "tiff:Model", NULL, NULL);
730   _xmp_tag_add_simple_mapping (GST_TAG_IMAGE_ORIENTATION, "tiff:Orientation",
731       serialize_tiff_orientation, deserialize_tiff_orientation);
732
733   /* exif schema */
734   _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_LATITUDE,
735       "exif:GPSLatitude", serialize_exif_latitude, deserialize_exif_latitude);
736   _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_LONGITUDE,
737       "exif:GPSLongitude", serialize_exif_longitude,
738       deserialize_exif_longitude);
739
740   /* compound exif tags */
741   array = g_ptr_array_sized_new (2);
742   xmpinfo = g_slice_new (XmpTag);
743   xmpinfo->tag_name = "exif:GPSAltitude";
744   xmpinfo->serialize = serialize_exif_altitude;
745   xmpinfo->deserialize = deserialize_exif_altitude;
746   g_ptr_array_add (array, xmpinfo);
747   xmpinfo = g_slice_new (XmpTag);
748   xmpinfo->tag_name = "exif:GPSAltitudeRef";
749   xmpinfo->serialize = serialize_exif_altituderef;
750   xmpinfo->deserialize = deserialize_exif_altitude;
751   g_ptr_array_add (array, xmpinfo);
752   _xmp_tag_add_mapping (GST_TAG_GEO_LOCATION_ELEVATION, array);
753
754   array = g_ptr_array_sized_new (2);
755   xmpinfo = g_slice_new (XmpTag);
756   xmpinfo->tag_name = "exif:GPSSpeed";
757   xmpinfo->serialize = serialize_exif_gps_speed;
758   xmpinfo->deserialize = deserialize_exif_gps_speed;
759   g_ptr_array_add (array, xmpinfo);
760   xmpinfo = g_slice_new (XmpTag);
761   xmpinfo->tag_name = "exif:GPSSpeedRef";
762   xmpinfo->serialize = serialize_exif_gps_speedref;
763   xmpinfo->deserialize = deserialize_exif_gps_speed;
764   g_ptr_array_add (array, xmpinfo);
765   _xmp_tag_add_mapping (GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, array);
766
767   array = g_ptr_array_sized_new (2);
768   xmpinfo = g_slice_new (XmpTag);
769   xmpinfo->tag_name = "exif:GPSTrack";
770   xmpinfo->serialize = serialize_exif_gps_direction;
771   xmpinfo->deserialize = deserialize_exif_gps_track;
772   g_ptr_array_add (array, xmpinfo);
773   xmpinfo = g_slice_new (XmpTag);
774   xmpinfo->tag_name = "exif:GPSTrackRef";
775   xmpinfo->serialize = serialize_exif_gps_directionref;
776   xmpinfo->deserialize = deserialize_exif_gps_track;
777   g_ptr_array_add (array, xmpinfo);
778   _xmp_tag_add_mapping (GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION, array);
779
780   array = g_ptr_array_sized_new (2);
781   xmpinfo = g_slice_new (XmpTag);
782   xmpinfo->tag_name = "exif:GPSImgDirection";
783   xmpinfo->serialize = serialize_exif_gps_direction;
784   xmpinfo->deserialize = deserialize_exif_gps_img_direction;
785   g_ptr_array_add (array, xmpinfo);
786   xmpinfo = g_slice_new (XmpTag);
787   xmpinfo->tag_name = "exif:GPSImgDirectionRef";
788   xmpinfo->serialize = serialize_exif_gps_directionref;
789   xmpinfo->deserialize = deserialize_exif_gps_img_direction;
790   g_ptr_array_add (array, xmpinfo);
791   _xmp_tag_add_mapping (GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION, array);
792
793   /* photoshop schema */
794   _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_COUNTRY,
795       "photoshop:Country", NULL, NULL);
796   _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_CITY, "photoshop:City",
797       NULL, NULL);
798
799   /* iptc4xmpcore schema */
800   _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_SUBLOCATION,
801       "Iptc4xmpCore:Location", NULL, NULL);
802
803   return NULL;
804 }
805
806 static void
807 xmp_tags_initialize ()
808 {
809   static GOnce my_once = G_ONCE_INIT;
810   g_once (&my_once, _init_xmp_tag_map, NULL);
811 }
812
813 typedef struct _GstXmpNamespaceMatch GstXmpNamespaceMatch;
814 struct _GstXmpNamespaceMatch
815 {
816   const gchar *ns_prefix;
817   const gchar *ns_uri;
818 };
819
820 static const GstXmpNamespaceMatch ns_match[] = {
821   {"dc", "http://purl.org/dc/elements/1.1/"},
822   {"exif", "http://ns.adobe.com/exif/1.0/"},
823   {"tiff", "http://ns.adobe.com/tiff/1.0/"},
824   {"xap", "http://ns.adobe.com/xap/1.0/"},
825   {"photoshop", "http://ns.adobe.com/photoshop/1.0/"},
826   {"Iptc4xmpCore", "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"},
827   {NULL, NULL}
828 };
829
830 typedef struct _GstXmpNamespaceMap GstXmpNamespaceMap;
831 struct _GstXmpNamespaceMap
832 {
833   const gchar *original_ns;
834   gchar *gstreamer_ns;
835 };
836 static GstXmpNamespaceMap ns_map[] = {
837   {"dc", NULL},
838   {"exif", NULL},
839   {"tiff", NULL},
840   {"xap", NULL},
841   {"photoshop", NULL},
842   {"Iptc4xmpCore", NULL},
843   {NULL, NULL}
844 };
845
846 /* parsing */
847
848 static void
849 read_one_tag (GstTagList * list, const gchar * tag, XmpTag * xmptag,
850     const gchar * v, GSList ** pending_tags)
851 {
852   GType tag_type;
853
854   if (xmptag && xmptag->deserialize) {
855     xmptag->deserialize (list, tag, xmptag->tag_name, v, pending_tags);
856     return;
857   }
858
859   tag_type = gst_tag_get_type (tag);
860
861   /* add gstreamer tag depending on type */
862   switch (tag_type) {
863     case G_TYPE_STRING:{
864       gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, v, NULL);
865       break;
866     }
867     default:
868       if (tag_type == GST_TYPE_DATE_TIME) {
869         GstDateTime *datetime = NULL;
870         gint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
871         gint usecs = 0;
872         gint gmt_offset_hour = -1, gmt_offset_min = -1, gmt_offset = -1;
873         gchar usec_str[16];
874         gint ret;
875         gint len;
876
877         len = strlen (v);
878         if (len == 0) {
879           GST_WARNING ("Empty string for datetime parsing");
880           return;
881         }
882
883         GST_DEBUG ("Parsing %s into a datetime", v);
884
885         ret = sscanf (v, "%04d-%02d-%02dT%02d:%02d:%02d.%15s",
886             &year, &month, &day, &hour, &minute, &second, usec_str);
887         if (ret < 3) {
888           /* FIXME theoretically, xmp can express datetimes with only year
889            * or year and month, but gstdatetime doesn't support it */
890           GST_WARNING ("Invalid datetime value: %s", v);
891         }
892
893         /* parse the usecs */
894         if (ret >= 7) {
895           gint num_digits = 0;
896
897           /* find the number of digits */
898           while (isdigit (usec_str[num_digits++]) && num_digits < 6);
899
900           if (num_digits > 0) {
901             /* fill up to 6 digits with 0 */
902             while (num_digits < 6) {
903               usec_str[num_digits++] = 0;
904             }
905
906             g_assert (num_digits == 6);
907
908             usec_str[num_digits] = '\0';
909             usecs = atoi (usec_str);
910           }
911         }
912
913         /* parse the timezone info */
914         if (v[len - 1] == 'Z') {
915           GST_LOG ("UTC timezone");
916
917           /* Having a Z at the end means UTC */
918           datetime = gst_date_time_new (year, month, day, hour, minute,
919               second, usecs, 0);
920         } else {
921           gchar *plus_pos = NULL;
922           gchar *neg_pos = NULL;
923           gchar *pos = NULL;
924
925           GST_LOG ("Checking for timezone information");
926
927           /* check if there is timezone info */
928           plus_pos = strrchr (v, '+');
929           neg_pos = strrchr (v, '-');
930           if (plus_pos) {
931             pos = plus_pos + 1;
932           } else if (neg_pos) {
933             pos = neg_pos + 1;
934           }
935
936           if (pos) {
937             gint ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour,
938                 &gmt_offset_min);
939
940             GST_DEBUG ("Parsing timezone: %s", pos);
941
942             if (ret_tz == 2) {
943               gmt_offset = gmt_offset_hour * 60 + gmt_offset_min;
944               if (neg_pos != NULL && neg_pos + 1 == pos)
945                 gmt_offset *= -1;
946
947               GST_LOG ("Timezone offset: %f (%d minutes)", gmt_offset / 60.0,
948                   gmt_offset);
949               /* no way to know if it is DST or not */
950               datetime = gst_date_time_new (year, month, day, hour, minute,
951                   second, usecs, gmt_offset / 60.0f);
952             } else {
953               GST_WARNING ("Failed to parse timezone information");
954             }
955           } else {
956             GST_WARNING ("No timezone signal found");
957           }
958         }
959
960         if (datetime) {
961           gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, datetime, NULL);
962           gst_date_time_unref (datetime);
963         }
964
965       } else if (tag_type == GST_TYPE_DATE) {
966         GDate *date;
967         gint d, m, y;
968
969         /* this is ISO 8601 Date and Time Format
970          * %F     Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)
971          * %T     The time in 24-hour notation (%H:%M:%S). (SU)
972          * e.g. 2009-05-30T18:26:14+03:00 */
973
974         /* FIXME: this would be the proper way, but needs
975            #define _XOPEN_SOURCE before #include <time.h>
976
977            date = g_date_new ();
978            struct tm tm={0,};
979            strptime (dts, "%FT%TZ", &tm);
980            g_date_set_time_t (date, mktime(&tm));
981          */
982         /* FIXME: this cannot parse the date
983            date = g_date_new ();
984            g_date_set_parse (date, v);
985            if (g_date_valid (date)) {
986            gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag,
987            date, NULL);
988            } else {
989            GST_WARNING ("unparsable date: '%s'", v);
990            }
991          */
992         /* poor mans straw */
993         sscanf (v, "%04d-%02d-%02dT", &y, &m, &d);
994         date = g_date_new_dmy (d, m, y);
995         gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, date, NULL);
996         g_date_free (date);
997       } else {
998         GST_WARNING ("unhandled type for %s from xmp", tag);
999       }
1000       break;
1001   }
1002 }
1003
1004 /**
1005  * gst_tag_list_from_xmp_buffer:
1006  * @buffer: buffer
1007  *
1008  * Parse a xmp packet into a taglist.
1009  *
1010  * Returns: new taglist or %NULL, free the list when done
1011  *
1012  * Since: 0.10.29
1013  */
1014 GstTagList *
1015 gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
1016 {
1017   GstTagList *list = NULL;
1018   const gchar *xps, *xp1, *xp2, *xpe, *ns, *ne;
1019   guint len, max_ft_len;
1020   gboolean in_tag;
1021   gchar *part, *pp;
1022   guint i;
1023   const gchar *last_tag = NULL;
1024   XmpTag *last_xmp_tag = NULL;
1025   GSList *pending_tags = NULL;
1026
1027   xmp_tags_initialize ();
1028
1029   g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
1030   g_return_val_if_fail (GST_BUFFER_SIZE (buffer) > 0, NULL);
1031
1032   xps = (const gchar *) GST_BUFFER_DATA (buffer);
1033   len = GST_BUFFER_SIZE (buffer);
1034   xpe = &xps[len + 1];
1035
1036   /* check header and footer */
1037   xp1 = g_strstr_len (xps, len, "<?xpacket begin");
1038   if (!xp1)
1039     goto missing_header;
1040   xp1 = &xp1[strlen ("<?xpacket begin")];
1041   while (*xp1 != '>' && *xp1 != '<' && xp1 < xpe)
1042     xp1++;
1043   if (*xp1 != '>')
1044     goto missing_header;
1045
1046   max_ft_len = 1 + strlen ("<?xpacket end=\".\"?>\n");
1047   if (len < max_ft_len)
1048     goto missing_footer;
1049
1050   GST_DEBUG ("checking footer: [%s]", &xps[len - max_ft_len]);
1051   xp2 = g_strstr_len (&xps[len - max_ft_len], max_ft_len, "<?xpacket ");
1052   if (!xp2)
1053     goto missing_footer;
1054
1055   GST_INFO ("xmp header okay");
1056
1057   /* skip > and text until first xml-node */
1058   xp1++;
1059   while (*xp1 != '<' && xp1 < xpe)
1060     xp1++;
1061
1062   /* no tag can be longer that the whole buffer */
1063   part = g_malloc (xp2 - xp1);
1064   list = gst_tag_list_new ();
1065
1066   /* parse data into a list of nodes */
1067   /* data is between xp1..xp2 */
1068   in_tag = TRUE;
1069   ns = ne = xp1;
1070   pp = part;
1071   while (ne < xp2) {
1072     if (in_tag) {
1073       ne++;
1074       while (ne < xp2 && *ne != '>' && *ne != '<') {
1075         if (*ne == '\n' || *ne == '\t' || *ne == ' ') {
1076           while (ne < xp2 && (*ne == '\n' || *ne == '\t' || *ne == ' '))
1077             ne++;
1078           *pp++ = ' ';
1079         } else {
1080           *pp++ = *ne++;
1081         }
1082       }
1083       *pp = '\0';
1084       if (*ne != '>')
1085         goto broken_xml;
1086       /* create node */
1087       /* {XML, ns, ne-ns} */
1088       if (ns[0] != '/') {
1089         gchar *as = strchr (part, ' ');
1090         /* only log start nodes */
1091         GST_INFO ("xml: %s", part);
1092
1093         if (as) {
1094           gchar *ae, *d;
1095
1096           /* skip ' ' and scan the attributes */
1097           as++;
1098           d = ae = as;
1099
1100           /* split attr=value pairs */
1101           while (*ae != '\0') {
1102             if (*ae == '=') {
1103               /* attr/value delimmiter */
1104               d = ae;
1105             } else if (*ae == '"') {
1106               /* scan values */
1107               gchar *v;
1108
1109               ae++;
1110               while (*ae != '\0' && *ae != '"')
1111                 ae++;
1112
1113               *d = *ae = '\0';
1114               v = &d[2];
1115               GST_INFO ("   : [%s][%s]", as, v);
1116               if (!strncmp (as, "xmlns:", 6)) {
1117                 i = 0;
1118                 /* we need to rewrite known namespaces to what we use in
1119                  * tag_matches */
1120                 while (ns_match[i].ns_prefix) {
1121                   if (!strcmp (ns_match[i].ns_uri, v))
1122                     break;
1123                   i++;
1124                 }
1125                 if (ns_match[i].ns_prefix) {
1126                   if (strcmp (ns_map[i].original_ns, &as[6])) {
1127                     ns_map[i].gstreamer_ns = g_strdup (&as[6]);
1128                   }
1129                 }
1130               } else {
1131                 const gchar *gst_tag;
1132                 XmpTag *xmp_tag = NULL;
1133                 /* FIXME: eventualy rewrite ns
1134                  * find ':'
1135                  * check if ns before ':' is in ns_map and ns_map[i].gstreamer_ns!=NULL
1136                  * do 2 stage filter in tag_matches
1137                  */
1138                 gst_tag = _xmp_tag_get_mapping_reverse (as, &xmp_tag);
1139                 if (gst_tag) {
1140                   PendingXmpTag *ptag;
1141
1142                   ptag = g_slice_new (PendingXmpTag);
1143                   ptag->gst_tag = gst_tag;
1144                   ptag->xmp_tag = xmp_tag;
1145                   ptag->str = g_strdup (v);
1146
1147                   pending_tags = g_slist_append (pending_tags, ptag);
1148                 }
1149               }
1150               /* restore chars overwritten by '\0' */
1151               *d = '=';
1152               *ae = '"';
1153             } else if (*ae == '\0' || *ae == ' ') {
1154               /* end of attr/value pair */
1155               as = &ae[1];
1156             }
1157             /* to next char if not eos */
1158             if (*ae != '\0')
1159               ae++;
1160           }
1161         } else {
1162           /*
1163              <dc:type><rdf:Bag><rdf:li>Image</rdf:li></rdf:Bag></dc:type>
1164              <dc:creator><rdf:Seq><rdf:li/></rdf:Seq></dc:creator>
1165            */
1166           /* FIXME: eventualy rewrite ns */
1167
1168           /* skip rdf tags for now */
1169           if (strncmp (part, "rdf:", 4)) {
1170             const gchar *parttag;
1171
1172             parttag = _xmp_tag_get_mapping_reverse (part, &last_xmp_tag);
1173             if (parttag) {
1174               last_tag = parttag;
1175             }
1176           }
1177         }
1178       }
1179       /* next cycle */
1180       ne++;
1181       if (ne < xp2) {
1182         if (*ne != '<')
1183           in_tag = FALSE;
1184         ns = ne;
1185         pp = part;
1186       }
1187     } else {
1188       while (ne < xp2 && *ne != '<') {
1189         *pp++ = *ne;
1190         ne++;
1191       }
1192       *pp = '\0';
1193       /* create node */
1194       /* {TXT, ns, (ne-ns)-1} */
1195       if (ns[0] != '\n' && &ns[1] <= ne) {
1196         /* only log non-newline nodes, we still have to parse them */
1197         GST_INFO ("txt: %s", part);
1198         if (last_tag) {
1199           PendingXmpTag *ptag;
1200
1201           ptag = g_slice_new (PendingXmpTag);
1202           ptag->gst_tag = last_tag;
1203           ptag->xmp_tag = last_xmp_tag;
1204           ptag->str = g_strdup (part);
1205
1206           pending_tags = g_slist_append (pending_tags, ptag);
1207         }
1208       }
1209       /* next cycle */
1210       in_tag = TRUE;
1211       ns = ne;
1212       pp = part;
1213     }
1214   }
1215
1216   while (pending_tags) {
1217     PendingXmpTag *ptag = (PendingXmpTag *) pending_tags->data;
1218
1219     pending_tags = g_slist_delete_link (pending_tags, pending_tags);
1220
1221     read_one_tag (list, ptag->gst_tag, ptag->xmp_tag, ptag->str, &pending_tags);
1222
1223     g_free (ptag->str);
1224     g_slice_free (PendingXmpTag, ptag);
1225   }
1226
1227   GST_INFO ("xmp packet parsed, %d entries",
1228       gst_structure_n_fields ((GstStructure *) list));
1229
1230   /* free resources */
1231   i = 0;
1232   while (ns_map[i].original_ns) {
1233     g_free (ns_map[i].gstreamer_ns);
1234     i++;
1235   }
1236   g_free (part);
1237
1238   return list;
1239
1240   /* Errors */
1241 missing_header:
1242   GST_WARNING ("malformed xmp packet header");
1243   return NULL;
1244 missing_footer:
1245   GST_WARNING ("malformed xmp packet footer");
1246   return NULL;
1247 broken_xml:
1248   GST_WARNING ("malformed xml tag: %s", part);
1249   return NULL;
1250 }
1251
1252
1253 /* formatting */
1254
1255 static void
1256 string_open_tag (GString * string, const char *tag)
1257 {
1258   g_string_append_c (string, '<');
1259   g_string_append (string, tag);
1260   g_string_append_c (string, '>');
1261 }
1262
1263 static void
1264 string_close_tag (GString * string, const char *tag)
1265 {
1266   g_string_append (string, "</");
1267   g_string_append (string, tag);
1268   g_string_append (string, ">\n");
1269 }
1270
1271 static char *
1272 gst_value_serialize_xmp (const GValue * value)
1273 {
1274   switch (G_VALUE_TYPE (value)) {
1275     case G_TYPE_STRING:
1276       return g_markup_escape_text (g_value_get_string (value), -1);
1277     case G_TYPE_INT:
1278       return g_strdup_printf ("%d", g_value_get_int (value));
1279     case G_TYPE_UINT:
1280       return g_strdup_printf ("%u", g_value_get_uint (value));
1281     default:
1282       break;
1283   }
1284   /* put non-switchable types here */
1285   if (G_VALUE_TYPE (value) == GST_TYPE_DATE) {
1286     const GDate *date = gst_value_get_date (value);
1287
1288     return g_strdup_printf ("%04d-%02d-%02d",
1289         (gint) g_date_get_year (date), (gint) g_date_get_month (date),
1290         (gint) g_date_get_day (date));
1291   } else if (G_VALUE_TYPE (value) == GST_TYPE_DATE_TIME) {
1292     gint year, month, day, hour, min, sec, microsec;
1293     gint gmt_offset = 0;
1294     gint gmt_offset_hour, gmt_offset_min;
1295     GstDateTime *datetime = (GstDateTime *) g_value_get_boxed (value);
1296
1297     year = gst_date_time_get_year (datetime);
1298     month = gst_date_time_get_month (datetime);
1299     day = gst_date_time_get_day (datetime);
1300     hour = gst_date_time_get_hour (datetime);
1301     min = gst_date_time_get_minute (datetime);
1302     sec = gst_date_time_get_second (datetime);
1303     microsec = gst_date_time_get_microsecond (datetime);
1304     gmt_offset = (gint) (60 * gst_date_time_get_time_zone_offset (datetime));
1305     if (gmt_offset == 0) {
1306       /* UTC */
1307       return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d.%06dZ",
1308           year, month, day, hour, min, sec, microsec);
1309     } else {
1310       gmt_offset_hour = ABS (gmt_offset) / 60;
1311       gmt_offset_min = ABS (gmt_offset) % 60;
1312
1313       return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d.%06d%c%02d:%02d",
1314           year, month, day, hour, min, sec, microsec,
1315           gmt_offset >= 0 ? '+' : '-', gmt_offset_hour, gmt_offset_min);
1316     }
1317   } else {
1318     return NULL;
1319   }
1320 }
1321
1322 static void
1323 write_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
1324 {
1325   guint i = 0, ct = gst_tag_list_get_tag_size (list, tag), tag_index;
1326   GString *data = user_data;
1327   GPtrArray *xmp_tag_array = NULL;
1328   char *s;
1329   GSList *xmptaglist;
1330
1331   /* map gst-tag to xmp tag */
1332   xmptaglist = _xmp_tag_get_mapping (tag);
1333   if (xmptaglist) {
1334     /* FIXME - we are always chosing the first tag mapped on the list */
1335     xmp_tag_array = (GPtrArray *) xmptaglist->data;
1336   }
1337
1338   if (!xmp_tag_array) {
1339     GST_WARNING ("no mapping for %s to xmp", tag);
1340     return;
1341   }
1342
1343   for (tag_index = 0; tag_index < xmp_tag_array->len; tag_index++) {
1344     XmpTag *xmp_tag;
1345
1346     xmp_tag = g_ptr_array_index (xmp_tag_array, tag_index);
1347     string_open_tag (data, xmp_tag->tag_name);
1348
1349     /* fast path for single valued tag */
1350     if (ct == 1) {
1351       if (xmp_tag->serialize) {
1352         s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, 0));
1353       } else {
1354         s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
1355                 0));
1356       }
1357       if (s) {
1358         g_string_append (data, s);
1359         g_free (s);
1360       } else {
1361         GST_WARNING ("unhandled type for %s to xmp", tag);
1362       }
1363     } else {
1364       string_open_tag (data, "rdf:Bag");
1365       for (i = 0; i < ct; i++) {
1366         GST_DEBUG ("mapping %s[%u/%u] to xmp", tag, i, ct);
1367         if (xmp_tag->serialize) {
1368           s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, i));
1369         } else {
1370           s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
1371                   i));
1372         }
1373         if (s) {
1374           string_open_tag (data, "rdf:li");
1375           g_string_append (data, s);
1376           string_close_tag (data, "rdf:li");
1377           g_free (s);
1378         } else {
1379           GST_WARNING ("unhandled type for %s to xmp", tag);
1380         }
1381       }
1382       string_close_tag (data, "rdf:Bag");
1383     }
1384
1385     string_close_tag (data, xmp_tag->tag_name);
1386   }
1387 }
1388
1389 /**
1390  * gst_tag_list_to_xmp_buffer:
1391  * @list: tags
1392  * @read_only: does the container forbid inplace editing
1393  *
1394  * Formats a taglist as a xmp packet.
1395  *
1396  * Returns: new buffer or %NULL, unref the buffer when done
1397  *
1398  * Since: 0.10.29
1399  */
1400 GstBuffer *
1401 gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
1402 {
1403   GstBuffer *buffer = NULL;
1404   GString *data = g_string_sized_new (4096);
1405   guint i;
1406
1407   xmp_tags_initialize ();
1408
1409   g_return_val_if_fail (GST_IS_TAG_LIST (list), NULL);
1410
1411   /* xmp header */
1412   g_string_append (data,
1413       "<?xpacket begin=\"\xEF\xBB\xBF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n");
1414   g_string_append (data,
1415       "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"GStreamer\">\n");
1416   g_string_append (data,
1417       "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"");
1418   i = 0;
1419   while (ns_match[i].ns_prefix) {
1420     g_string_append_printf (data, " xmlns:%s=\"%s\"", ns_match[i].ns_prefix,
1421         ns_match[i].ns_uri);
1422     i++;
1423   }
1424   g_string_append (data, ">\n");
1425   g_string_append (data, "<rdf:Description rdf:about=\"\">\n");
1426
1427   /* iterate the taglist */
1428   gst_tag_list_foreach (list, write_one_tag, data);
1429
1430   /* xmp footer */
1431   g_string_append (data, "</rdf:Description>\n");
1432   g_string_append (data, "</rdf:RDF>\n");
1433   g_string_append (data, "</x:xmpmeta>\n");
1434
1435   if (!read_only) {
1436     /* the xmp spec recommand to add 2-4KB padding for in-place editable xmp */
1437     guint i;
1438
1439     for (i = 0; i < 32; i++) {
1440       g_string_append (data, "                " "                "
1441           "                " "                " "\n");
1442     }
1443   }
1444   g_string_append_printf (data, "<?xpacket end=\"%c\"?>\n",
1445       (read_only ? 'r' : 'w'));
1446
1447   buffer = gst_buffer_new ();
1448   GST_BUFFER_SIZE (buffer) = data->len + 1;
1449   GST_BUFFER_DATA (buffer) = (guint8 *) g_string_free (data, FALSE);
1450   GST_BUFFER_MALLOCDATA (buffer) = GST_BUFFER_DATA (buffer);
1451
1452   return buffer;
1453 }