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