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