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