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