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