2 * Copyright (C) 2010 Stefan Kost <stefan.kost@nokia.com>
3 * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
5 * gstxmptag.c: library for reading / modifying xmp tags
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.
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.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
25 * @short_description: tag mappings and support functions for plugins
26 * dealing with xmp packets
27 * @see_also: #GstTagList
29 * Contains various utility functions for plugins to parse or create
30 * xmp packets and map them to and from #GstTagList<!-- -->s.
32 * Please note that the xmp parser is very lightweight and not strict at all.
38 #include <gst/gsttagsetter.h>
39 #include "gsttageditingprivate.h"
47 * Serializes a GValue into a string.
49 typedef gchar *(*XmpSerializationFunc) (const GValue * value);
52 * Deserializes @str that is the gstreamer tag @gst_tag represented in
53 * XMP as the @xmp_tag and adds the result to the @taglist.
55 * @pending_tags is passed so that compound xmp tags can search for its
56 * complements on the list and use them. Note that used complements should
57 * be freed and removed from the list.
58 * The list is of PendingXmpTag
60 typedef void (*XmpDeserializationFunc) (GstTagList * taglist,
61 const gchar * gst_tag, const gchar * xmp_tag,
62 const gchar * str, GSList ** pending_tags);
66 const gchar *tag_name;
67 XmpSerializationFunc serialize;
68 XmpDeserializationFunc deserialize;
70 typedef struct _XmpTag XmpTag;
78 typedef struct _PendingXmpTag PendingXmpTag;
81 * Mappings from gstreamer tags to xmp tags
83 * The mapping here are from a gstreamer tag (as a GQuark)
84 * into a GSList of GArray of XmpTag.
86 * There might be multiple xmp tags that a single gstreamer tag can be
87 * mapped to. For example, GST_TAG_DATE might be mapped into dc:date
88 * or exif:DateTimeOriginal, hence the first list, to be able to store
89 * alternative mappings of the same gstreamer tag.
91 * Some other tags, like GST_TAG_GEO_LOCATION_ELEVATION needs to be
92 * mapped into 2 complementary tags in the exif's schema. One of them
93 * stores the absolute elevation, and the other one stores if it is
94 * above of below sea level. That's why we need a GArray as the item
95 * of each GSList in the mapping.
97 static GHashTable *__xmp_tag_map;
100 _xmp_tag_add_mapping (const gchar * gst_tag, GPtrArray * array)
105 key = g_quark_from_string (gst_tag);
107 list = g_hash_table_lookup (__xmp_tag_map, GUINT_TO_POINTER (key));
108 list = g_slist_append (list, (gpointer) array);
109 g_hash_table_insert (__xmp_tag_map, GUINT_TO_POINTER (key), list);
113 _xmp_tag_add_simple_mapping (const gchar * gst_tag, const gchar * xmp_tag,
114 XmpSerializationFunc serialization_func,
115 XmpDeserializationFunc deserialization_func)
120 xmpinfo = g_slice_new (XmpTag);
121 xmpinfo->tag_name = xmp_tag;
122 xmpinfo->serialize = serialization_func;
123 xmpinfo->deserialize = deserialization_func;
125 array = g_ptr_array_sized_new (1);
126 g_ptr_array_add (array, xmpinfo);
128 _xmp_tag_add_mapping (gst_tag, array);
132 * We do not return a copy here because elements are
133 * appended, and the API is not public, so we shouldn't
134 * have our lists modified during usage
137 _xmp_tag_get_mapping (const gchar * gst_tag)
140 GQuark key = g_quark_from_string (gst_tag);
142 ret = (GSList *) g_hash_table_lookup (__xmp_tag_map, GUINT_TO_POINTER (key));
147 /* finds the gst tag that maps to this xmp tag */
149 _xmp_tag_get_mapping_reverse (const gchar * xmp_tag, XmpTag ** _xmp_tag)
153 const gchar *ret = NULL;
158 /* Iterate over the hashtable */
159 g_hash_table_iter_init (&iter, __xmp_tag_map);
160 while (!ret && g_hash_table_iter_next (&iter, &key, &value)) {
161 GSList *list = (GSList *) value;
163 /* each entry might contain multiple mappigns */
164 for (walk = list; walk; walk = g_slist_next (walk)) {
165 GPtrArray *array = (GPtrArray *) walk->data;
167 /* each mapping might contain complementary tags */
168 for (index = 0; index < array->len; index++) {
169 XmpTag *xmpinfo = (XmpTag *) g_ptr_array_index (array, index);
171 if (strcmp (xmpinfo->tag_name, xmp_tag) == 0) {
173 ret = g_quark_to_string (GPOINTER_TO_UINT (key));
184 /* utility functions/macros */
186 #define METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR (3.6)
187 #define KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND (1/3.6)
188 #define MILES_PER_HOUR_TO_METERS_PER_SECOND (0.44704)
189 #define KNOTS_TO_METERS_PER_SECOND (0.514444)
192 double_to_fraction_string (gdouble num)
197 gst_util_double_to_fraction (num, &frac_n, &frac_d);
198 return g_strdup_printf ("%d/%d", frac_n, frac_d);
201 /* (de)serialize functions */
203 serialize_exif_gps_coordinate (const GValue * value, gchar pos, gchar neg)
208 gchar fraction[G_ASCII_DTOSTR_BUF_SIZE];
210 g_return_val_if_fail (G_VALUE_TYPE (value) == G_TYPE_DOUBLE, NULL);
212 num = g_value_get_double (value);
219 integer = (gint) num;
221 g_ascii_dtostr (fraction, sizeof (fraction), (num - integer) * 60);
223 /* FIXME review GPSCoordinate serialization spec for the .mm or ,ss
224 * decision. Couldn't understand it clearly */
225 return g_strdup_printf ("%d,%s%c", integer, fraction, c);
229 serialize_exif_latitude (const GValue * value)
231 return serialize_exif_gps_coordinate (value, 'N', 'S');
235 serialize_exif_longitude (const GValue * value)
237 return serialize_exif_gps_coordinate (value, 'E', 'W');
241 deserialize_exif_gps_coordinate (GstTagList * taglist, const gchar * gst_tag,
242 const gchar * str, gchar pos, gchar neg)
245 gint d = 0, m = 0, s = 0;
248 const gchar *current;
250 /* get the degrees */
251 if (sscanf (str, "%d", &d) != 1)
254 /* find the beginning of the minutes */
255 current = strchr (str, ',');
260 /* check if it uses ,SS or .mm */
261 if (strchr (current, ',') != NULL) {
262 sscanf (current, "%d,%d%c", &m, &s, &c);
264 gchar *copy = g_strdup (current);
265 gint len = strlen (copy);
268 /* check the last letter */
269 for (i = len - 1; len >= 0; len--) {
270 if (g_ascii_isspace (copy[i]))
273 if (g_ascii_isalpha (copy[i])) {
280 /* something is wrong */
286 /* use a copy so we can change the last letter as E can cause
288 m2 = g_ascii_strtod (copy, NULL);
293 /* we can add them all as those that aren't parsed are 0 */
294 value = d + (m / 60.0) + (s / (60.0 * 60.0)) + (m2 / 60.0);
298 } else if (c == neg) {
304 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, value, NULL);
308 GST_WARNING ("Failed to deserialize gps coordinate: %s", str);
312 deserialize_exif_latitude (GstTagList * taglist, const gchar * gst_tag,
313 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
315 deserialize_exif_gps_coordinate (taglist, gst_tag, str, 'N', 'S');
319 deserialize_exif_longitude (GstTagList * taglist, const gchar * gst_tag,
320 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
322 deserialize_exif_gps_coordinate (taglist, gst_tag, str, 'E', 'W');
326 serialize_exif_altitude (const GValue * value)
330 num = g_value_get_double (value);
335 return double_to_fraction_string (num);
339 serialize_exif_altituderef (const GValue * value)
343 num = g_value_get_double (value);
345 /* 0 means above sea level, 1 means below */
347 return g_strdup ("0");
348 return g_strdup ("1");
352 deserialize_exif_altitude (GstTagList * taglist, const gchar * gst_tag,
353 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
355 const gchar *altitude_str = NULL;
356 const gchar *altituderef_str = NULL;
362 PendingXmpTag *ptag = NULL;
364 /* find the other missing part */
365 if (strcmp (xmp_tag, "exif:GPSAltitude") == 0) {
368 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
369 ptag = (PendingXmpTag *) entry->data;
371 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitudeRef") == 0) {
372 altituderef_str = ptag->str;
377 } else if (strcmp (xmp_tag, "exif:GPSAltitudeRef") == 0) {
378 altituderef_str = str;
380 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
381 ptag = (PendingXmpTag *) entry->data;
383 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitude") == 0) {
384 altitude_str = ptag->str;
390 GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
395 GST_WARNING ("Missing exif:GPSAltitude tag");
398 if (!altituderef_str) {
399 GST_WARNING ("Missing exif:GPSAltitudeRef tag");
403 if (sscanf (altitude_str, "%d/%d", &frac_n, &frac_d) != 2) {
404 GST_WARNING ("Failed to parse fraction: %s", altitude_str);
408 gst_util_fraction_to_double (frac_n, frac_d, &value);
410 if (altituderef_str[0] == '0') {
412 } else if (altituderef_str[0] == '1') {
415 GST_WARNING ("Unexpected exif:AltitudeRef value: %s", altituderef_str);
419 /* add to the taglist */
420 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
421 GST_TAG_GEO_LOCATION_ELEVATION, value, NULL);
425 g_slice_free (PendingXmpTag, ptag);
426 *pending_tags = g_slist_delete_link (*pending_tags, entry);
430 serialize_exif_gps_speed (const GValue * value)
432 return double_to_fraction_string (g_value_get_double (value) *
433 METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR);
437 serialize_exif_gps_speedref (const GValue * value)
439 /* we always use km/h */
440 return g_strdup ("K");
444 deserialize_exif_gps_speed (GstTagList * taglist, const gchar * gst_tag,
445 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
447 const gchar *speed_str = NULL;
448 const gchar *speedref_str = NULL;
454 PendingXmpTag *ptag = NULL;
456 /* find the other missing part */
457 if (strcmp (xmp_tag, "exif:GPSSpeed") == 0) {
460 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
461 ptag = (PendingXmpTag *) entry->data;
463 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSSpeedRef") == 0) {
464 speedref_str = ptag->str;
469 } else if (strcmp (xmp_tag, "exif:GPSSpeedRef") == 0) {
472 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
473 ptag = (PendingXmpTag *) entry->data;
475 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSSpeed") == 0) {
476 speed_str = ptag->str;
482 GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
487 GST_WARNING ("Missing exif:GPSSpeed tag");
491 GST_WARNING ("Missing exif:GPSSpeedRef tag");
495 if (sscanf (speed_str, "%d/%d", &frac_n, &frac_d) != 2) {
496 GST_WARNING ("Failed to parse fraction: %s", speed_str);
500 gst_util_fraction_to_double (frac_n, frac_d, &value);
502 if (speedref_str[0] == 'K') {
503 value *= KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND;
504 } else if (speedref_str[0] == 'M') {
505 value *= MILES_PER_HOUR_TO_METERS_PER_SECOND;
506 } else if (speedref_str[0] == 'N') {
507 value *= KNOTS_TO_METERS_PER_SECOND;
509 GST_WARNING ("Unexpected exif:SpeedRef value: %s", speedref_str);
513 /* add to the taglist */
514 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
515 GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, value, NULL);
519 g_slice_free (PendingXmpTag, ptag);
520 *pending_tags = g_slist_delete_link (*pending_tags, entry);
524 serialize_exif_gps_direction (const GValue * value)
526 return double_to_fraction_string (g_value_get_double (value));
530 serialize_exif_gps_directionref (const GValue * value)
532 /* T for true geographic direction (M would mean magnetic) */
533 return g_strdup ("T");
537 deserialize_exif_gps_direction (GstTagList * taglist, const gchar * gst_tag,
538 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags,
539 const gchar * direction_tag, const gchar * directionref_tag)
541 const gchar *dir_str = NULL;
542 const gchar *dirref_str = NULL;
548 PendingXmpTag *ptag = NULL;
550 /* find the other missing part */
551 if (strcmp (xmp_tag, direction_tag) == 0) {
554 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
555 ptag = (PendingXmpTag *) entry->data;
557 if (strcmp (ptag->xmp_tag->tag_name, directionref_tag) == 0) {
558 dirref_str = ptag->str;
563 } else if (strcmp (xmp_tag, directionref_tag) == 0) {
566 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
567 ptag = (PendingXmpTag *) entry->data;
569 if (strcmp (ptag->xmp_tag->tag_name, direction_tag) == 0) {
576 GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
581 GST_WARNING ("Missing %s tag", dir_str);
585 GST_WARNING ("Missing %s tag", dirref_str);
589 if (sscanf (dir_str, "%d/%d", &frac_n, &frac_d) != 2) {
590 GST_WARNING ("Failed to parse fraction: %s", dir_str);
594 gst_util_fraction_to_double (frac_n, frac_d, &value);
596 if (dirref_str[0] == 'T') {
598 } else if (dirref_str[0] == 'M') {
599 GST_WARNING ("Magnetic direction tags aren't supported yet");
602 GST_WARNING ("Unexpected %s value: %s", directionref_tag, dirref_str);
606 /* add to the taglist */
607 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, value, NULL);
611 g_slice_free (PendingXmpTag, ptag);
612 *pending_tags = g_slist_delete_link (*pending_tags, entry);
616 deserialize_exif_gps_track (GstTagList * taglist, const gchar * gst_tag,
617 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
619 deserialize_exif_gps_direction (taglist, gst_tag, xmp_tag, str, pending_tags,
620 "exif:GPSTrack", "exif:GPSTrackRef");
624 deserialize_exif_gps_img_direction (GstTagList * taglist, const gchar * gst_tag,
625 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
627 deserialize_exif_gps_direction (taglist, gst_tag, xmp_tag, str, pending_tags,
628 "exif:GPSImgDirection", "exif:GPSImgDirectionRef");
632 deserialize_xmp_rating (GstTagList * taglist, const gchar * gst_tag,
633 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
637 if (sscanf (str, "%u", &value) != 1) {
638 GST_WARNING ("Failed to parse xmp:Rating %s", str);
642 if (value < 0 || value > 100) {
643 GST_WARNING ("Unsupported Rating tag %u (should be from 0 to 100), "
648 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, value, NULL);
652 serialize_tiff_orientation (const GValue * value)
657 str = g_value_get_string (value);
659 GST_WARNING ("Failed to get image orientation tag value");
663 num = gst_tag_image_orientation_to_exif_value (str);
667 return g_strdup_printf ("%d", num);
671 deserialize_tiff_orientation (GstTagList * taglist, const gchar * gst_tag,
672 const gchar * xmp_tag, const gchar * str, GSList ** pending_tags)
675 const gchar *orientation = NULL;
677 if (sscanf (str, "%u", &value) != 1) {
678 GST_WARNING ("Failed to parse tiff:Orientation %s", str);
682 if (value < 1 || value > 8) {
683 GST_WARNING ("Invalid tiff:Orientation tag %u (should be from 1 to 8), "
688 orientation = gst_tag_image_orientation_from_exif_value (value);
689 if (orientation == NULL)
691 gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, gst_tag, orientation, NULL);
695 /* look at this page for addtional schemas
696 * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/XMP.html
704 __xmp_tag_map = g_hash_table_new (g_direct_hash, g_direct_equal);
707 /* dublic code metadata
708 * http://dublincore.org/documents/dces/
710 _xmp_tag_add_simple_mapping (GST_TAG_ARTIST, "dc:creator", NULL, NULL);
711 _xmp_tag_add_simple_mapping (GST_TAG_COPYRIGHT, "dc:rights", NULL, NULL);
712 _xmp_tag_add_simple_mapping (GST_TAG_DATE, "dc:date", NULL, NULL);
713 _xmp_tag_add_simple_mapping (GST_TAG_DATE_TIME, "exif:DateTimeOriginal", NULL,
715 _xmp_tag_add_simple_mapping (GST_TAG_DESCRIPTION, "dc:description", NULL,
717 _xmp_tag_add_simple_mapping (GST_TAG_KEYWORDS, "dc:subject", NULL, NULL);
718 _xmp_tag_add_simple_mapping (GST_TAG_TITLE, "dc:title", NULL, NULL);
719 /* FIXME: we probably want GST_TAG_{,AUDIO_,VIDEO_}MIME_TYPE */
720 _xmp_tag_add_simple_mapping (GST_TAG_VIDEO_CODEC, "dc:format", NULL, NULL);
722 /* xap (xmp) schema */
723 _xmp_tag_add_simple_mapping (GST_TAG_USER_RATING, "xmp:Rating", NULL,
724 deserialize_xmp_rating);
727 _xmp_tag_add_simple_mapping (GST_TAG_DEVICE_MANUFACTURER, "tiff:Make", NULL,
729 _xmp_tag_add_simple_mapping (GST_TAG_DEVICE_MODEL, "tiff:Model", NULL, NULL);
730 _xmp_tag_add_simple_mapping (GST_TAG_IMAGE_ORIENTATION, "tiff:Orientation",
731 serialize_tiff_orientation, deserialize_tiff_orientation);
734 _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_LATITUDE,
735 "exif:GPSLatitude", serialize_exif_latitude, deserialize_exif_latitude);
736 _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_LONGITUDE,
737 "exif:GPSLongitude", serialize_exif_longitude,
738 deserialize_exif_longitude);
740 /* compound exif tags */
741 array = g_ptr_array_sized_new (2);
742 xmpinfo = g_slice_new (XmpTag);
743 xmpinfo->tag_name = "exif:GPSAltitude";
744 xmpinfo->serialize = serialize_exif_altitude;
745 xmpinfo->deserialize = deserialize_exif_altitude;
746 g_ptr_array_add (array, xmpinfo);
747 xmpinfo = g_slice_new (XmpTag);
748 xmpinfo->tag_name = "exif:GPSAltitudeRef";
749 xmpinfo->serialize = serialize_exif_altituderef;
750 xmpinfo->deserialize = deserialize_exif_altitude;
751 g_ptr_array_add (array, xmpinfo);
752 _xmp_tag_add_mapping (GST_TAG_GEO_LOCATION_ELEVATION, array);
754 array = g_ptr_array_sized_new (2);
755 xmpinfo = g_slice_new (XmpTag);
756 xmpinfo->tag_name = "exif:GPSSpeed";
757 xmpinfo->serialize = serialize_exif_gps_speed;
758 xmpinfo->deserialize = deserialize_exif_gps_speed;
759 g_ptr_array_add (array, xmpinfo);
760 xmpinfo = g_slice_new (XmpTag);
761 xmpinfo->tag_name = "exif:GPSSpeedRef";
762 xmpinfo->serialize = serialize_exif_gps_speedref;
763 xmpinfo->deserialize = deserialize_exif_gps_speed;
764 g_ptr_array_add (array, xmpinfo);
765 _xmp_tag_add_mapping (GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, array);
767 array = g_ptr_array_sized_new (2);
768 xmpinfo = g_slice_new (XmpTag);
769 xmpinfo->tag_name = "exif:GPSTrack";
770 xmpinfo->serialize = serialize_exif_gps_direction;
771 xmpinfo->deserialize = deserialize_exif_gps_track;
772 g_ptr_array_add (array, xmpinfo);
773 xmpinfo = g_slice_new (XmpTag);
774 xmpinfo->tag_name = "exif:GPSTrackRef";
775 xmpinfo->serialize = serialize_exif_gps_directionref;
776 xmpinfo->deserialize = deserialize_exif_gps_track;
777 g_ptr_array_add (array, xmpinfo);
778 _xmp_tag_add_mapping (GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION, array);
780 array = g_ptr_array_sized_new (2);
781 xmpinfo = g_slice_new (XmpTag);
782 xmpinfo->tag_name = "exif:GPSImgDirection";
783 xmpinfo->serialize = serialize_exif_gps_direction;
784 xmpinfo->deserialize = deserialize_exif_gps_img_direction;
785 g_ptr_array_add (array, xmpinfo);
786 xmpinfo = g_slice_new (XmpTag);
787 xmpinfo->tag_name = "exif:GPSImgDirectionRef";
788 xmpinfo->serialize = serialize_exif_gps_directionref;
789 xmpinfo->deserialize = deserialize_exif_gps_img_direction;
790 g_ptr_array_add (array, xmpinfo);
791 _xmp_tag_add_mapping (GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION, array);
793 /* photoshop schema */
794 _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_COUNTRY,
795 "photoshop:Country", NULL, NULL);
796 _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_CITY, "photoshop:City",
799 /* iptc4xmpcore schema */
800 _xmp_tag_add_simple_mapping (GST_TAG_GEO_LOCATION_SUBLOCATION,
801 "Iptc4xmpCore:Location", NULL, NULL);
807 xmp_tags_initialize ()
809 static GOnce my_once = G_ONCE_INIT;
810 g_once (&my_once, _init_xmp_tag_map, NULL);
813 typedef struct _GstXmpNamespaceMatch GstXmpNamespaceMatch;
814 struct _GstXmpNamespaceMatch
816 const gchar *ns_prefix;
820 static const GstXmpNamespaceMatch ns_match[] = {
821 {"dc", "http://purl.org/dc/elements/1.1/"},
822 {"exif", "http://ns.adobe.com/exif/1.0/"},
823 {"tiff", "http://ns.adobe.com/tiff/1.0/"},
824 {"xap", "http://ns.adobe.com/xap/1.0/"},
825 {"photoshop", "http://ns.adobe.com/photoshop/1.0/"},
826 {"Iptc4xmpCore", "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"},
830 typedef struct _GstXmpNamespaceMap GstXmpNamespaceMap;
831 struct _GstXmpNamespaceMap
833 const gchar *original_ns;
836 static GstXmpNamespaceMap ns_map[] = {
842 {"Iptc4xmpCore", NULL},
849 read_one_tag (GstTagList * list, const gchar * tag, XmpTag * xmptag,
850 const gchar * v, GSList ** pending_tags)
854 if (xmptag && xmptag->deserialize) {
855 xmptag->deserialize (list, tag, xmptag->tag_name, v, pending_tags);
859 tag_type = gst_tag_get_type (tag);
861 /* add gstreamer tag depending on type */
864 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, v, NULL);
868 if (tag_type == GST_TYPE_DATE_TIME) {
869 GstDateTime *datetime = NULL;
870 gint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
872 gint gmt_offset_hour = -1, gmt_offset_min = -1, gmt_offset = -1;
879 GST_WARNING ("Empty string for datetime parsing");
883 GST_DEBUG ("Parsing %s into a datetime", v);
885 ret = sscanf (v, "%04d-%02d-%02dT%02d:%02d:%02d.%15s",
886 &year, &month, &day, &hour, &minute, &second, usec_str);
888 /* FIXME theoretically, xmp can express datetimes with only year
889 * or year and month, but gstdatetime doesn't support it */
890 GST_WARNING ("Invalid datetime value: %s", v);
893 /* parse the usecs */
897 /* find the number of digits */
898 while (isdigit (usec_str[num_digits++]) && num_digits < 6);
900 if (num_digits > 0) {
901 /* fill up to 6 digits with 0 */
902 while (num_digits < 6) {
903 usec_str[num_digits++] = 0;
906 g_assert (num_digits == 6);
908 usec_str[num_digits] = '\0';
909 usecs = atoi (usec_str);
913 /* parse the timezone info */
914 if (v[len - 1] == 'Z') {
915 GST_LOG ("UTC timezone");
917 /* Having a Z at the end means UTC */
918 datetime = gst_date_time_new (year, month, day, hour, minute,
921 gchar *plus_pos = NULL;
922 gchar *neg_pos = NULL;
925 GST_LOG ("Checking for timezone information");
927 /* check if there is timezone info */
928 plus_pos = strrchr (v, '+');
929 neg_pos = strrchr (v, '-');
932 } else if (neg_pos) {
937 gint ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour,
940 GST_DEBUG ("Parsing timezone: %s", pos);
943 gmt_offset = gmt_offset_hour * 60 + gmt_offset_min;
944 if (neg_pos != NULL && neg_pos + 1 == pos)
947 GST_LOG ("Timezone offset: %f (%d minutes)", gmt_offset / 60.0,
949 /* no way to know if it is DST or not */
950 datetime = gst_date_time_new (year, month, day, hour, minute,
951 second, usecs, gmt_offset / 60.0f);
953 GST_WARNING ("Failed to parse timezone information");
956 GST_WARNING ("No timezone signal found");
961 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, datetime, NULL);
962 gst_date_time_unref (datetime);
965 } else if (tag_type == GST_TYPE_DATE) {
969 /* this is ISO 8601 Date and Time Format
970 * %F Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)
971 * %T The time in 24-hour notation (%H:%M:%S). (SU)
972 * e.g. 2009-05-30T18:26:14+03:00 */
974 /* FIXME: this would be the proper way, but needs
975 #define _XOPEN_SOURCE before #include <time.h>
977 date = g_date_new ();
979 strptime (dts, "%FT%TZ", &tm);
980 g_date_set_time_t (date, mktime(&tm));
982 /* FIXME: this cannot parse the date
983 date = g_date_new ();
984 g_date_set_parse (date, v);
985 if (g_date_valid (date)) {
986 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag,
989 GST_WARNING ("unparsable date: '%s'", v);
992 /* poor mans straw */
993 sscanf (v, "%04d-%02d-%02dT", &y, &m, &d);
994 date = g_date_new_dmy (d, m, y);
995 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, date, NULL);
998 GST_WARNING ("unhandled type for %s from xmp", tag);
1005 * gst_tag_list_from_xmp_buffer:
1008 * Parse a xmp packet into a taglist.
1010 * Returns: new taglist or %NULL, free the list when done
1015 gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
1017 GstTagList *list = NULL;
1018 const gchar *xps, *xp1, *xp2, *xpe, *ns, *ne;
1019 guint len, max_ft_len;
1023 const gchar *last_tag = NULL;
1024 XmpTag *last_xmp_tag = NULL;
1025 GSList *pending_tags = NULL;
1027 xmp_tags_initialize ();
1029 g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
1030 g_return_val_if_fail (GST_BUFFER_SIZE (buffer) > 0, NULL);
1032 xps = (const gchar *) GST_BUFFER_DATA (buffer);
1033 len = GST_BUFFER_SIZE (buffer);
1034 xpe = &xps[len + 1];
1036 /* check header and footer */
1037 xp1 = g_strstr_len (xps, len, "<?xpacket begin");
1039 goto missing_header;
1040 xp1 = &xp1[strlen ("<?xpacket begin")];
1041 while (*xp1 != '>' && *xp1 != '<' && xp1 < xpe)
1044 goto missing_header;
1046 max_ft_len = 1 + strlen ("<?xpacket end=\".\"?>\n");
1047 if (len < max_ft_len)
1048 goto missing_footer;
1050 GST_DEBUG ("checking footer: [%s]", &xps[len - max_ft_len]);
1051 xp2 = g_strstr_len (&xps[len - max_ft_len], max_ft_len, "<?xpacket ");
1053 goto missing_footer;
1055 GST_INFO ("xmp header okay");
1057 /* skip > and text until first xml-node */
1059 while (*xp1 != '<' && xp1 < xpe)
1062 /* no tag can be longer that the whole buffer */
1063 part = g_malloc (xp2 - xp1);
1064 list = gst_tag_list_new ();
1066 /* parse data into a list of nodes */
1067 /* data is between xp1..xp2 */
1074 while (ne < xp2 && *ne != '>' && *ne != '<') {
1075 if (*ne == '\n' || *ne == '\t' || *ne == ' ') {
1076 while (ne < xp2 && (*ne == '\n' || *ne == '\t' || *ne == ' '))
1087 /* {XML, ns, ne-ns} */
1089 gchar *as = strchr (part, ' ');
1090 /* only log start nodes */
1091 GST_INFO ("xml: %s", part);
1096 /* skip ' ' and scan the attributes */
1100 /* split attr=value pairs */
1101 while (*ae != '\0') {
1103 /* attr/value delimmiter */
1105 } else if (*ae == '"') {
1110 while (*ae != '\0' && *ae != '"')
1115 GST_INFO (" : [%s][%s]", as, v);
1116 if (!strncmp (as, "xmlns:", 6)) {
1118 /* we need to rewrite known namespaces to what we use in
1120 while (ns_match[i].ns_prefix) {
1121 if (!strcmp (ns_match[i].ns_uri, v))
1125 if (ns_match[i].ns_prefix) {
1126 if (strcmp (ns_map[i].original_ns, &as[6])) {
1127 ns_map[i].gstreamer_ns = g_strdup (&as[6]);
1131 const gchar *gst_tag;
1132 XmpTag *xmp_tag = NULL;
1133 /* FIXME: eventualy rewrite ns
1135 * check if ns before ':' is in ns_map and ns_map[i].gstreamer_ns!=NULL
1136 * do 2 stage filter in tag_matches
1138 gst_tag = _xmp_tag_get_mapping_reverse (as, &xmp_tag);
1140 PendingXmpTag *ptag;
1142 ptag = g_slice_new (PendingXmpTag);
1143 ptag->gst_tag = gst_tag;
1144 ptag->xmp_tag = xmp_tag;
1145 ptag->str = g_strdup (v);
1147 pending_tags = g_slist_append (pending_tags, ptag);
1150 /* restore chars overwritten by '\0' */
1153 } else if (*ae == '\0' || *ae == ' ') {
1154 /* end of attr/value pair */
1157 /* to next char if not eos */
1163 <dc:type><rdf:Bag><rdf:li>Image</rdf:li></rdf:Bag></dc:type>
1164 <dc:creator><rdf:Seq><rdf:li/></rdf:Seq></dc:creator>
1166 /* FIXME: eventualy rewrite ns */
1168 /* skip rdf tags for now */
1169 if (strncmp (part, "rdf:", 4)) {
1170 const gchar *parttag;
1172 parttag = _xmp_tag_get_mapping_reverse (part, &last_xmp_tag);
1188 while (ne < xp2 && *ne != '<') {
1194 /* {TXT, ns, (ne-ns)-1} */
1195 if (ns[0] != '\n' && &ns[1] <= ne) {
1196 /* only log non-newline nodes, we still have to parse them */
1197 GST_INFO ("txt: %s", part);
1199 PendingXmpTag *ptag;
1201 ptag = g_slice_new (PendingXmpTag);
1202 ptag->gst_tag = last_tag;
1203 ptag->xmp_tag = last_xmp_tag;
1204 ptag->str = g_strdup (part);
1206 pending_tags = g_slist_append (pending_tags, ptag);
1216 while (pending_tags) {
1217 PendingXmpTag *ptag = (PendingXmpTag *) pending_tags->data;
1219 pending_tags = g_slist_delete_link (pending_tags, pending_tags);
1221 read_one_tag (list, ptag->gst_tag, ptag->xmp_tag, ptag->str, &pending_tags);
1224 g_slice_free (PendingXmpTag, ptag);
1227 GST_INFO ("xmp packet parsed, %d entries",
1228 gst_structure_n_fields ((GstStructure *) list));
1230 /* free resources */
1232 while (ns_map[i].original_ns) {
1233 g_free (ns_map[i].gstreamer_ns);
1242 GST_WARNING ("malformed xmp packet header");
1245 GST_WARNING ("malformed xmp packet footer");
1248 GST_WARNING ("malformed xml tag: %s", part);
1256 string_open_tag (GString * string, const char *tag)
1258 g_string_append_c (string, '<');
1259 g_string_append (string, tag);
1260 g_string_append_c (string, '>');
1264 string_close_tag (GString * string, const char *tag)
1266 g_string_append (string, "</");
1267 g_string_append (string, tag);
1268 g_string_append (string, ">\n");
1272 gst_value_serialize_xmp (const GValue * value)
1274 switch (G_VALUE_TYPE (value)) {
1276 return g_markup_escape_text (g_value_get_string (value), -1);
1278 return g_strdup_printf ("%d", g_value_get_int (value));
1280 return g_strdup_printf ("%u", g_value_get_uint (value));
1284 /* put non-switchable types here */
1285 if (G_VALUE_TYPE (value) == GST_TYPE_DATE) {
1286 const GDate *date = gst_value_get_date (value);
1288 return g_strdup_printf ("%04d-%02d-%02d",
1289 (gint) g_date_get_year (date), (gint) g_date_get_month (date),
1290 (gint) g_date_get_day (date));
1291 } else if (G_VALUE_TYPE (value) == GST_TYPE_DATE_TIME) {
1292 gint year, month, day, hour, min, sec, microsec;
1293 gint gmt_offset = 0;
1294 gint gmt_offset_hour, gmt_offset_min;
1295 GstDateTime *datetime = (GstDateTime *) g_value_get_boxed (value);
1297 year = gst_date_time_get_year (datetime);
1298 month = gst_date_time_get_month (datetime);
1299 day = gst_date_time_get_day (datetime);
1300 hour = gst_date_time_get_hour (datetime);
1301 min = gst_date_time_get_minute (datetime);
1302 sec = gst_date_time_get_second (datetime);
1303 microsec = gst_date_time_get_microsecond (datetime);
1304 gmt_offset = (gint) (60 * gst_date_time_get_time_zone_offset (datetime));
1305 if (gmt_offset == 0) {
1307 return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d.%06dZ",
1308 year, month, day, hour, min, sec, microsec);
1310 gmt_offset_hour = ABS (gmt_offset) / 60;
1311 gmt_offset_min = ABS (gmt_offset) % 60;
1313 return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d.%06d%c%02d:%02d",
1314 year, month, day, hour, min, sec, microsec,
1315 gmt_offset >= 0 ? '+' : '-', gmt_offset_hour, gmt_offset_min);
1323 write_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
1325 guint i = 0, ct = gst_tag_list_get_tag_size (list, tag), tag_index;
1326 GString *data = user_data;
1327 GPtrArray *xmp_tag_array = NULL;
1331 /* map gst-tag to xmp tag */
1332 xmptaglist = _xmp_tag_get_mapping (tag);
1334 /* FIXME - we are always chosing the first tag mapped on the list */
1335 xmp_tag_array = (GPtrArray *) xmptaglist->data;
1338 if (!xmp_tag_array) {
1339 GST_WARNING ("no mapping for %s to xmp", tag);
1343 for (tag_index = 0; tag_index < xmp_tag_array->len; tag_index++) {
1346 xmp_tag = g_ptr_array_index (xmp_tag_array, tag_index);
1347 string_open_tag (data, xmp_tag->tag_name);
1349 /* fast path for single valued tag */
1351 if (xmp_tag->serialize) {
1352 s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, 0));
1354 s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
1358 g_string_append (data, s);
1361 GST_WARNING ("unhandled type for %s to xmp", tag);
1364 string_open_tag (data, "rdf:Bag");
1365 for (i = 0; i < ct; i++) {
1366 GST_DEBUG ("mapping %s[%u/%u] to xmp", tag, i, ct);
1367 if (xmp_tag->serialize) {
1368 s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, i));
1370 s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
1374 string_open_tag (data, "rdf:li");
1375 g_string_append (data, s);
1376 string_close_tag (data, "rdf:li");
1379 GST_WARNING ("unhandled type for %s to xmp", tag);
1382 string_close_tag (data, "rdf:Bag");
1385 string_close_tag (data, xmp_tag->tag_name);
1390 * gst_tag_list_to_xmp_buffer:
1392 * @read_only: does the container forbid inplace editing
1394 * Formats a taglist as a xmp packet.
1396 * Returns: new buffer or %NULL, unref the buffer when done
1401 gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
1403 GstBuffer *buffer = NULL;
1404 GString *data = g_string_sized_new (4096);
1407 xmp_tags_initialize ();
1409 g_return_val_if_fail (GST_IS_TAG_LIST (list), NULL);
1412 g_string_append (data,
1413 "<?xpacket begin=\"\xEF\xBB\xBF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n");
1414 g_string_append (data,
1415 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"GStreamer\">\n");
1416 g_string_append (data,
1417 "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"");
1419 while (ns_match[i].ns_prefix) {
1420 g_string_append_printf (data, " xmlns:%s=\"%s\"", ns_match[i].ns_prefix,
1421 ns_match[i].ns_uri);
1424 g_string_append (data, ">\n");
1425 g_string_append (data, "<rdf:Description rdf:about=\"\">\n");
1427 /* iterate the taglist */
1428 gst_tag_list_foreach (list, write_one_tag, data);
1431 g_string_append (data, "</rdf:Description>\n");
1432 g_string_append (data, "</rdf:RDF>\n");
1433 g_string_append (data, "</x:xmpmeta>\n");
1436 /* the xmp spec recommand to add 2-4KB padding for in-place editable xmp */
1439 for (i = 0; i < 32; i++) {
1440 g_string_append (data, " " " "
1444 g_string_append_printf (data, "<?xpacket end=\"%c\"?>\n",
1445 (read_only ? 'r' : 'w'));
1447 buffer = gst_buffer_new ();
1448 GST_BUFFER_SIZE (buffer) = data->len + 1;
1449 GST_BUFFER_DATA (buffer) = (guint8 *) g_string_free (data, FALSE);
1450 GST_BUFFER_MALLOCDATA (buffer) = GST_BUFFER_DATA (buffer);