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