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