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