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