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.
39 #include <gst/gsttagsetter.h>
40 #include "gsttageditingprivate.h"
47 static const gchar *schema_list[] = {
58 * gst_tag_xmp_list_schemas:
60 * Gets the list of supported schemas in the xmp lib
62 * Returns: a %NULL terminated array of strings with the schema names
67 gst_tag_xmp_list_schemas (void)
72 typedef struct _XmpSerializationData XmpSerializationData;
73 typedef struct _XmpTag XmpTag;
76 * Serializes a GValue into a string.
78 typedef gchar *(*XmpSerializationFunc) (const GValue * value);
81 * Deserializes @str that is the gstreamer tag @gst_tag represented in
82 * XMP as the @xmp_tag_value and adds the result to the @taglist.
84 * @pending_tags is passed so that compound xmp tags can search for its
85 * complements on the list and use them. Note that used complements should
86 * be freed and removed from the list.
87 * The list is of PendingXmpTag
89 typedef void (*XmpDeserializationFunc) (XmpTag * xmptag, GstTagList * taglist,
90 const gchar * gst_tag, const gchar * xmp_tag_value,
91 const gchar * str, GSList ** pending_tags);
93 struct _XmpSerializationData
96 const gchar **schemas;
100 xmp_serialization_data_use_schema (XmpSerializationData * serdata,
101 const gchar * schemaname)
104 if (serdata->schemas == NULL)
107 while (serdata->schemas[i] != NULL) {
108 if (strcmp (serdata->schemas[i], schemaname) == 0)
116 #define GST_XMP_TAG_TYPE_SIMPLE 0
117 #define GST_XMP_TAG_TYPE_BAG 1
118 #define GST_XMP_TAG_TYPE_SEQ 2
121 const gchar *tag_name;
124 XmpSerializationFunc serialize;
125 XmpDeserializationFunc deserialize;
128 static GstTagMergeMode
129 xmp_tag_get_merge_mode (XmpTag * xmptag)
131 switch (xmptag->type) {
132 case GST_XMP_TAG_TYPE_BAG:
133 case GST_XMP_TAG_TYPE_SEQ:
134 return GST_TAG_MERGE_APPEND;
135 case GST_XMP_TAG_TYPE_SIMPLE:
137 return GST_TAG_MERGE_KEEP;
142 xmp_tag_get_type_name (XmpTag * xmptag)
144 switch (xmptag->type) {
145 case GST_XMP_TAG_TYPE_SEQ:
148 g_assert_not_reached ();
149 case GST_XMP_TAG_TYPE_BAG:
154 struct _PendingXmpTag
156 const gchar *gst_tag;
160 typedef struct _PendingXmpTag PendingXmpTag;
164 * A schema is a mapping of strings (the tag name in gstreamer) to a list of
165 * tags in xmp (XmpTag). We need a list because some tags are split into 2
166 * when serialized into xmp.
167 * e.g. GST_TAG_GEO_LOCATION_ELEVATION needs to be mapped into 2 complementary
168 * tags in the exif's schema. One of them stores the absolute elevation,
169 * and the other one stores if it is above of below sea level.
171 typedef GHashTable GstXmpSchema;
172 #define gst_xmp_schema_lookup g_hash_table_lookup
173 #define gst_xmp_schema_insert g_hash_table_insert
174 static GstXmpSchema *
175 gst_xmp_schema_new ()
177 return g_hash_table_new (g_direct_hash, g_direct_equal);
181 * Mappings from schema names into the schema group of tags (GstXmpSchema)
183 static GHashTable *__xmp_schemas;
186 _gst_xmp_add_schema (const gchar * name, GstXmpSchema * schema)
190 key = g_quark_from_string (name);
192 if (g_hash_table_lookup (__xmp_schemas, GUINT_TO_POINTER (key))) {
193 GST_WARNING ("Schema %s already exists, ignoring", name);
194 g_assert_not_reached ();
198 g_hash_table_insert (__xmp_schemas, GUINT_TO_POINTER (key), schema);
202 _gst_xmp_schema_add_mapping (GstXmpSchema * schema, const gchar * gst_tag,
207 key = g_quark_from_string (gst_tag);
209 if (gst_xmp_schema_lookup (schema, GUINT_TO_POINTER (key))) {
210 GST_WARNING ("Tag %s already present for the schema", gst_tag);
211 g_assert_not_reached ();
214 gst_xmp_schema_insert (schema, GUINT_TO_POINTER (key), array);
218 _gst_xmp_schema_add_simple_mapping (GstXmpSchema * schema,
219 const gchar * gst_tag, const gchar * xmp_tag, gint xmp_type,
220 XmpSerializationFunc serialization_func,
221 XmpDeserializationFunc deserialization_func)
226 xmpinfo = g_slice_new (XmpTag);
227 xmpinfo->tag_name = xmp_tag;
228 xmpinfo->type = xmp_type;
229 xmpinfo->serialize = serialization_func;
230 xmpinfo->deserialize = deserialization_func;
232 array = g_ptr_array_sized_new (1);
233 g_ptr_array_add (array, xmpinfo);
235 _gst_xmp_schema_add_mapping (schema, gst_tag, array);
239 * We do not return a copy here because elements are
240 * appended, and the API is not public, so we shouldn't
241 * have our lists modified during usage
244 _xmp_tag_get_mapping (const gchar * gst_tag, XmpSerializationData * serdata)
246 GPtrArray *ret = NULL;
248 GQuark key = g_quark_from_string (gst_tag);
249 gpointer iterkey, value;
250 const gchar *schemaname;
252 g_hash_table_iter_init (&iter, __xmp_schemas);
253 while (!ret && g_hash_table_iter_next (&iter, &iterkey, &value)) {
254 GstXmpSchema *schema = (GstXmpSchema *) value;
256 schemaname = g_quark_to_string (GPOINTER_TO_UINT (iterkey));
257 if (xmp_serialization_data_use_schema (serdata, schemaname))
259 (GPtrArray *) gst_xmp_schema_lookup (schema, GUINT_TO_POINTER (key));
264 /* finds the gst tag that maps to this xmp tag in this schema */
266 _gst_xmp_schema_get_mapping_reverse (GstXmpSchema * schema,
267 const gchar * xmp_tag, XmpTag ** _xmp_tag)
271 const gchar *ret = NULL;
274 /* Iterate over the hashtable */
275 g_hash_table_iter_init (&iter, schema);
276 while (!ret && g_hash_table_iter_next (&iter, &key, &value)) {
277 GPtrArray *array = (GPtrArray *) value;
279 /* each mapping might contain complementary tags */
280 for (index = 0; index < array->len; index++) {
281 XmpTag *xmpinfo = (XmpTag *) g_ptr_array_index (array, index);
283 if (strcmp (xmpinfo->tag_name, xmp_tag) == 0) {
285 ret = g_quark_to_string (GPOINTER_TO_UINT (key));
295 /* finds the gst tag that maps to this xmp tag (searches on all schemas) */
297 _gst_xmp_tag_get_mapping_reverse (const gchar * xmp_tag, XmpTag ** _xmp_tag)
301 const gchar *ret = NULL;
303 /* Iterate over the hashtable */
304 g_hash_table_iter_init (&iter, __xmp_schemas);
305 while (!ret && g_hash_table_iter_next (&iter, &key, &value)) {
306 ret = _gst_xmp_schema_get_mapping_reverse ((GstXmpSchema *) value, xmp_tag,
312 /* utility functions/macros */
314 #define METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR (3.6)
315 #define KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND (1/3.6)
316 #define MILES_PER_HOUR_TO_METERS_PER_SECOND (0.44704)
317 #define KNOTS_TO_METERS_PER_SECOND (0.514444)
320 double_to_fraction_string (gdouble num)
325 gst_util_double_to_fraction (num, &frac_n, &frac_d);
326 return g_strdup_printf ("%d/%d", frac_n, frac_d);
329 /* (de)serialize functions */
331 serialize_exif_gps_coordinate (const GValue * value, gchar pos, gchar neg)
336 gchar fraction[G_ASCII_DTOSTR_BUF_SIZE];
338 g_return_val_if_fail (G_VALUE_TYPE (value) == G_TYPE_DOUBLE, NULL);
340 num = g_value_get_double (value);
347 integer = (gint) num;
349 g_ascii_dtostr (fraction, sizeof (fraction), (num - integer) * 60);
351 /* FIXME review GPSCoordinate serialization spec for the .mm or ,ss
352 * decision. Couldn't understand it clearly */
353 return g_strdup_printf ("%d,%s%c", integer, fraction, c);
357 serialize_exif_latitude (const GValue * value)
359 return serialize_exif_gps_coordinate (value, 'N', 'S');
363 serialize_exif_longitude (const GValue * value)
365 return serialize_exif_gps_coordinate (value, 'E', 'W');
369 deserialize_exif_gps_coordinate (XmpTag * xmptag, GstTagList * taglist,
370 const gchar * gst_tag, const gchar * str, gchar pos, gchar neg)
373 gint d = 0, m = 0, s = 0;
376 const gchar *current;
378 /* get the degrees */
379 if (sscanf (str, "%d", &d) != 1)
382 /* find the beginning of the minutes */
383 current = strchr (str, ',');
388 /* check if it uses ,SS or .mm */
389 if (strchr (current, ',') != NULL) {
390 sscanf (current, "%d,%d%c", &m, &s, &c);
392 gchar *copy = g_strdup (current);
393 gint len = strlen (copy);
396 /* check the last letter */
397 for (i = len - 1; len >= 0; len--) {
398 if (g_ascii_isspace (copy[i]))
401 if (g_ascii_isalpha (copy[i])) {
408 /* something is wrong */
414 /* use a copy so we can change the last letter as E can cause
416 m2 = g_ascii_strtod (copy, NULL);
421 /* we can add them all as those that aren't parsed are 0 */
422 value = d + (m / 60.0) + (s / (60.0 * 60.0)) + (m2 / 60.0);
426 } else if (c == neg) {
432 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag, value,
437 GST_WARNING ("Failed to deserialize gps coordinate: %s", str);
441 deserialize_exif_latitude (XmpTag * xmptag, GstTagList * taglist,
442 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
443 GSList ** pending_tags)
445 deserialize_exif_gps_coordinate (xmptag, taglist, gst_tag, str, 'N', 'S');
449 deserialize_exif_longitude (XmpTag * xmptag, GstTagList * taglist,
450 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
451 GSList ** pending_tags)
453 deserialize_exif_gps_coordinate (xmptag, taglist, gst_tag, str, 'E', 'W');
457 serialize_exif_altitude (const GValue * value)
461 num = g_value_get_double (value);
466 return double_to_fraction_string (num);
470 serialize_exif_altituderef (const GValue * value)
474 num = g_value_get_double (value);
476 /* 0 means above sea level, 1 means below */
478 return g_strdup ("0");
479 return g_strdup ("1");
483 deserialize_exif_altitude (XmpTag * xmptag, GstTagList * taglist,
484 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
485 GSList ** pending_tags)
487 const gchar *altitude_str = NULL;
488 const gchar *altituderef_str = NULL;
494 PendingXmpTag *ptag = NULL;
496 /* find the other missing part */
497 if (strcmp (xmp_tag, "exif:GPSAltitude") == 0) {
500 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
501 ptag = (PendingXmpTag *) entry->data;
503 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitudeRef") == 0) {
504 altituderef_str = ptag->str;
509 } else if (strcmp (xmp_tag, "exif:GPSAltitudeRef") == 0) {
510 altituderef_str = str;
512 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
513 ptag = (PendingXmpTag *) entry->data;
515 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSAltitude") == 0) {
516 altitude_str = ptag->str;
522 GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
527 GST_WARNING ("Missing exif:GPSAltitude tag");
530 if (!altituderef_str) {
531 GST_WARNING ("Missing exif:GPSAltitudeRef tag");
535 if (sscanf (altitude_str, "%d/%d", &frac_n, &frac_d) != 2) {
536 GST_WARNING ("Failed to parse fraction: %s", altitude_str);
540 gst_util_fraction_to_double (frac_n, frac_d, &value);
542 if (altituderef_str[0] == '0') {
544 } else if (altituderef_str[0] == '1') {
547 GST_WARNING ("Unexpected exif:AltitudeRef value: %s", altituderef_str);
551 /* add to the taglist */
552 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag),
553 GST_TAG_GEO_LOCATION_ELEVATION, value, NULL);
557 g_slice_free (PendingXmpTag, ptag);
558 *pending_tags = g_slist_delete_link (*pending_tags, entry);
562 serialize_exif_gps_speed (const GValue * value)
564 return double_to_fraction_string (g_value_get_double (value) *
565 METERS_PER_SECOND_TO_KILOMETERS_PER_HOUR);
569 serialize_exif_gps_speedref (const GValue * value)
571 /* we always use km/h */
572 return g_strdup ("K");
576 deserialize_exif_gps_speed (XmpTag * xmptag, GstTagList * taglist,
577 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
578 GSList ** pending_tags)
580 const gchar *speed_str = NULL;
581 const gchar *speedref_str = NULL;
587 PendingXmpTag *ptag = NULL;
589 /* find the other missing part */
590 if (strcmp (xmp_tag, "exif:GPSSpeed") == 0) {
593 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
594 ptag = (PendingXmpTag *) entry->data;
596 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSSpeedRef") == 0) {
597 speedref_str = ptag->str;
602 } else if (strcmp (xmp_tag, "exif:GPSSpeedRef") == 0) {
605 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
606 ptag = (PendingXmpTag *) entry->data;
608 if (strcmp (ptag->xmp_tag->tag_name, "exif:GPSSpeed") == 0) {
609 speed_str = ptag->str;
615 GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
620 GST_WARNING ("Missing exif:GPSSpeed tag");
624 GST_WARNING ("Missing exif:GPSSpeedRef tag");
628 if (sscanf (speed_str, "%d/%d", &frac_n, &frac_d) != 2) {
629 GST_WARNING ("Failed to parse fraction: %s", speed_str);
633 gst_util_fraction_to_double (frac_n, frac_d, &value);
635 if (speedref_str[0] == 'K') {
636 value *= KILOMETERS_PER_HOUR_TO_METERS_PER_SECOND;
637 } else if (speedref_str[0] == 'M') {
638 value *= MILES_PER_HOUR_TO_METERS_PER_SECOND;
639 } else if (speedref_str[0] == 'N') {
640 value *= KNOTS_TO_METERS_PER_SECOND;
642 GST_WARNING ("Unexpected exif:SpeedRef value: %s", speedref_str);
646 /* add to the taglist */
647 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag),
648 GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, value, NULL);
652 g_slice_free (PendingXmpTag, ptag);
653 *pending_tags = g_slist_delete_link (*pending_tags, entry);
657 serialize_exif_gps_direction (const GValue * value)
659 return double_to_fraction_string (g_value_get_double (value));
663 serialize_exif_gps_directionref (const GValue * value)
665 /* T for true geographic direction (M would mean magnetic) */
666 return g_strdup ("T");
670 deserialize_exif_gps_direction (XmpTag * xmptag, GstTagList * taglist,
671 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
672 GSList ** pending_tags, const gchar * direction_tag,
673 const gchar * directionref_tag)
675 const gchar *dir_str = NULL;
676 const gchar *dirref_str = NULL;
682 PendingXmpTag *ptag = NULL;
684 /* find the other missing part */
685 if (strcmp (xmp_tag, direction_tag) == 0) {
688 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
689 ptag = (PendingXmpTag *) entry->data;
691 if (strcmp (ptag->xmp_tag->tag_name, directionref_tag) == 0) {
692 dirref_str = ptag->str;
697 } else if (strcmp (xmp_tag, directionref_tag) == 0) {
700 for (entry = *pending_tags; entry; entry = g_slist_next (entry)) {
701 ptag = (PendingXmpTag *) entry->data;
703 if (strcmp (ptag->xmp_tag->tag_name, direction_tag) == 0) {
710 GST_WARNING ("Unexpected xmp tag %s", xmp_tag);
715 GST_WARNING ("Missing %s tag", dir_str);
719 GST_WARNING ("Missing %s tag", dirref_str);
723 if (sscanf (dir_str, "%d/%d", &frac_n, &frac_d) != 2) {
724 GST_WARNING ("Failed to parse fraction: %s", dir_str);
728 gst_util_fraction_to_double (frac_n, frac_d, &value);
730 if (dirref_str[0] == 'T') {
732 } else if (dirref_str[0] == 'M') {
733 GST_WARNING ("Magnetic direction tags aren't supported yet");
736 GST_WARNING ("Unexpected %s value: %s", directionref_tag, dirref_str);
740 /* add to the taglist */
741 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag, value,
746 g_slice_free (PendingXmpTag, ptag);
747 *pending_tags = g_slist_delete_link (*pending_tags, entry);
751 deserialize_exif_gps_track (XmpTag * xmptag, GstTagList * taglist,
752 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
753 GSList ** pending_tags)
755 deserialize_exif_gps_direction (xmptag, taglist, gst_tag, xmp_tag, str,
756 pending_tags, "exif:GPSTrack", "exif:GPSTrackRef");
760 deserialize_exif_gps_img_direction (XmpTag * xmptag, GstTagList * taglist,
761 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
762 GSList ** pending_tags)
764 deserialize_exif_gps_direction (xmptag, taglist, gst_tag, xmp_tag, str,
765 pending_tags, "exif:GPSImgDirection", "exif:GPSImgDirectionRef");
769 deserialize_xmp_rating (XmpTag * xmptag, GstTagList * taglist,
770 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
771 GSList ** pending_tags)
775 if (sscanf (str, "%u", &value) != 1) {
776 GST_WARNING ("Failed to parse xmp:Rating %s", str);
780 if (value < 0 || value > 100) {
781 GST_WARNING ("Unsupported Rating tag %u (should be from 0 to 100), "
786 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag, value,
791 serialize_tiff_orientation (const GValue * value)
796 str = g_value_get_string (value);
798 GST_WARNING ("Failed to get image orientation tag value");
802 num = __exif_tag_image_orientation_to_exif_value (str);
806 return g_strdup_printf ("%d", num);
810 deserialize_tiff_orientation (XmpTag * xmptag, GstTagList * taglist,
811 const gchar * gst_tag, const gchar * xmp_tag, const gchar * str,
812 GSList ** pending_tags)
815 const gchar *orientation = NULL;
817 if (sscanf (str, "%u", &value) != 1) {
818 GST_WARNING ("Failed to parse tiff:Orientation %s", str);
822 if (value < 1 || value > 8) {
823 GST_WARNING ("Invalid tiff:Orientation tag %u (should be from 1 to 8), "
828 orientation = __exif_tag_image_orientation_from_exif_value (value);
829 if (orientation == NULL)
831 gst_tag_list_add (taglist, xmp_tag_get_merge_mode (xmptag), gst_tag,
836 /* look at this page for addtional schemas
837 * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/XMP.html
840 _init_xmp_tag_map (gpointer user_data)
844 GstXmpSchema *schema;
846 __xmp_schemas = g_hash_table_new (g_direct_hash, g_direct_equal);
849 /* dublic code metadata
850 * http://dublincore.org/documents/dces/
852 schema = gst_xmp_schema_new ();
853 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_ARTIST,
854 "dc:creator", GST_XMP_TAG_TYPE_SEQ, NULL, NULL);
855 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_COPYRIGHT,
856 "dc:rights", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
857 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DATE, "dc:date",
858 GST_XMP_TAG_TYPE_SEQ, NULL, NULL);
859 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DESCRIPTION,
860 "dc:description", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
861 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_KEYWORDS,
862 "dc:subject", GST_XMP_TAG_TYPE_BAG, NULL, NULL);
863 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_TITLE, "dc:title",
864 GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
865 /* FIXME: we probably want GST_TAG_{,AUDIO_,VIDEO_}MIME_TYPE */
866 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_VIDEO_CODEC,
867 "dc:format", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
868 _gst_xmp_add_schema ("dc", schema);
870 /* xap (xmp) schema */
871 schema = gst_xmp_schema_new ();
872 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_USER_RATING,
873 "xmp:Rating", GST_XMP_TAG_TYPE_SIMPLE, NULL, deserialize_xmp_rating);
874 _gst_xmp_add_schema ("xap", schema);
877 schema = gst_xmp_schema_new ();
878 _gst_xmp_schema_add_simple_mapping (schema,
879 GST_TAG_DEVICE_MANUFACTURER, "tiff:Make", GST_XMP_TAG_TYPE_SIMPLE, NULL,
881 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DEVICE_MODEL,
882 "tiff:Model", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
883 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_APPLICATION_NAME,
884 "tiff:Software", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
885 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_IMAGE_ORIENTATION,
886 "tiff:Orientation", GST_XMP_TAG_TYPE_SIMPLE, serialize_tiff_orientation,
887 deserialize_tiff_orientation);
888 _gst_xmp_add_schema ("tiff", schema);
891 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_DATE_TIME,
892 "exif:DateTimeOriginal", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
893 _gst_xmp_schema_add_simple_mapping (schema,
894 GST_TAG_GEO_LOCATION_LATITUDE, "exif:GPSLatitude",
895 GST_XMP_TAG_TYPE_SIMPLE, serialize_exif_latitude,
896 deserialize_exif_latitude);
897 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_GEO_LOCATION_LONGITUDE,
898 "exif:GPSLongitude", GST_XMP_TAG_TYPE_SIMPLE, serialize_exif_longitude,
899 deserialize_exif_longitude);
900 _gst_xmp_schema_add_simple_mapping (schema,
901 GST_TAG_CAPTURING_EXPOSURE_COMPENSATION, "exif:ExposureBiasValue",
902 GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
904 /* compound exif tags */
905 array = g_ptr_array_sized_new (2);
906 xmpinfo = g_slice_new (XmpTag);
907 xmpinfo->tag_name = "exif:GPSAltitude";
908 xmpinfo->serialize = serialize_exif_altitude;
909 xmpinfo->deserialize = deserialize_exif_altitude;
910 xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
911 g_ptr_array_add (array, xmpinfo);
912 xmpinfo = g_slice_new (XmpTag);
913 xmpinfo->tag_name = "exif:GPSAltitudeRef";
914 xmpinfo->serialize = serialize_exif_altituderef;
915 xmpinfo->deserialize = deserialize_exif_altitude;
916 xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
917 g_ptr_array_add (array, xmpinfo);
918 _gst_xmp_schema_add_mapping (schema, GST_TAG_GEO_LOCATION_ELEVATION, array);
920 array = g_ptr_array_sized_new (2);
921 xmpinfo = g_slice_new (XmpTag);
922 xmpinfo->tag_name = "exif:GPSSpeed";
923 xmpinfo->serialize = serialize_exif_gps_speed;
924 xmpinfo->deserialize = deserialize_exif_gps_speed;
925 xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
926 g_ptr_array_add (array, xmpinfo);
927 xmpinfo = g_slice_new (XmpTag);
928 xmpinfo->tag_name = "exif:GPSSpeedRef";
929 xmpinfo->serialize = serialize_exif_gps_speedref;
930 xmpinfo->deserialize = deserialize_exif_gps_speed;
931 xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
932 g_ptr_array_add (array, xmpinfo);
933 _gst_xmp_schema_add_mapping (schema,
934 GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, array);
936 array = g_ptr_array_sized_new (2);
937 xmpinfo = g_slice_new (XmpTag);
938 xmpinfo->tag_name = "exif:GPSTrack";
939 xmpinfo->serialize = serialize_exif_gps_direction;
940 xmpinfo->deserialize = deserialize_exif_gps_track;
941 xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
942 g_ptr_array_add (array, xmpinfo);
943 xmpinfo = g_slice_new (XmpTag);
944 xmpinfo->tag_name = "exif:GPSTrackRef";
945 xmpinfo->serialize = serialize_exif_gps_directionref;
946 xmpinfo->deserialize = deserialize_exif_gps_track;
947 xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
948 g_ptr_array_add (array, xmpinfo);
949 _gst_xmp_schema_add_mapping (schema,
950 GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION, array);
952 array = g_ptr_array_sized_new (2);
953 xmpinfo = g_slice_new (XmpTag);
954 xmpinfo->tag_name = "exif:GPSImgDirection";
955 xmpinfo->serialize = serialize_exif_gps_direction;
956 xmpinfo->deserialize = deserialize_exif_gps_img_direction;
957 xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
958 g_ptr_array_add (array, xmpinfo);
959 xmpinfo = g_slice_new (XmpTag);
960 xmpinfo->tag_name = "exif:GPSImgDirectionRef";
961 xmpinfo->serialize = serialize_exif_gps_directionref;
962 xmpinfo->deserialize = deserialize_exif_gps_img_direction;
963 xmpinfo->type = GST_XMP_TAG_TYPE_SIMPLE;
964 g_ptr_array_add (array, xmpinfo);
965 _gst_xmp_schema_add_mapping (schema,
966 GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION, array);
967 _gst_xmp_add_schema ("exif", schema);
969 /* photoshop schema */
970 _gst_xmp_schema_add_simple_mapping (schema,
971 GST_TAG_GEO_LOCATION_COUNTRY, "photoshop:Country",
972 GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
973 _gst_xmp_schema_add_simple_mapping (schema, GST_TAG_GEO_LOCATION_CITY,
974 "photoshop:City", GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
975 _gst_xmp_add_schema ("photoshop", schema);
977 /* iptc4xmpcore schema */
978 _gst_xmp_schema_add_simple_mapping (schema,
979 GST_TAG_GEO_LOCATION_SUBLOCATION, "Iptc4xmpCore:Location",
980 GST_XMP_TAG_TYPE_SIMPLE, NULL, NULL);
981 _gst_xmp_add_schema ("Iptc4xmpCore", schema);
987 xmp_tags_initialize ()
989 static GOnce my_once = G_ONCE_INIT;
990 g_once (&my_once, (GThreadFunc) _init_xmp_tag_map, NULL);
993 typedef struct _GstXmpNamespaceMatch GstXmpNamespaceMatch;
994 struct _GstXmpNamespaceMatch
996 const gchar *ns_prefix;
1000 static const GstXmpNamespaceMatch ns_match[] = {
1001 {"dc", "http://purl.org/dc/elements/1.1/"},
1002 {"exif", "http://ns.adobe.com/exif/1.0/"},
1003 {"tiff", "http://ns.adobe.com/tiff/1.0/"},
1004 {"xap", "http://ns.adobe.com/xap/1.0/"},
1005 {"photoshop", "http://ns.adobe.com/photoshop/1.0/"},
1006 {"Iptc4xmpCore", "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"},
1010 typedef struct _GstXmpNamespaceMap GstXmpNamespaceMap;
1011 struct _GstXmpNamespaceMap
1013 const gchar *original_ns;
1014 gchar *gstreamer_ns;
1020 read_one_tag (GstTagList * list, const gchar * tag, XmpTag * xmptag,
1021 const gchar * v, GSList ** pending_tags)
1024 GstTagMergeMode merge_mode;
1026 if (xmptag && xmptag->deserialize) {
1027 xmptag->deserialize (xmptag, list, tag, xmptag->tag_name, v, pending_tags);
1031 merge_mode = xmp_tag_get_merge_mode (xmptag);
1032 tag_type = gst_tag_get_type (tag);
1034 /* add gstreamer tag depending on type */
1036 case G_TYPE_STRING:{
1037 gst_tag_list_add (list, merge_mode, tag, v, NULL);
1040 case G_TYPE_DOUBLE:{
1042 gint frac_n, frac_d;
1044 if (sscanf (v, "%d/%d", &frac_n, &frac_d) == 2) {
1045 gst_util_fraction_to_double (frac_n, frac_d, &value);
1046 gst_tag_list_add (list, merge_mode, tag, value, NULL);
1048 GST_WARNING ("Failed to parse fraction: %s", v);
1053 if (tag_type == GST_TYPE_DATE_TIME) {
1054 GstDateTime *datetime = NULL;
1055 gint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
1057 gint gmt_offset_hour = -1, gmt_offset_min = -1, gmt_offset = -1;
1064 GST_WARNING ("Empty string for datetime parsing");
1068 GST_DEBUG ("Parsing %s into a datetime", v);
1070 ret = sscanf (v, "%04d-%02d-%02dT%02d:%02d:%02d.%15s",
1071 &year, &month, &day, &hour, &minute, &second, usec_str);
1073 /* FIXME theoretically, xmp can express datetimes with only year
1074 * or year and month, but gstdatetime doesn't support it */
1075 GST_WARNING ("Invalid datetime value: %s", v);
1078 /* parse the usecs */
1080 gint num_digits = 0;
1082 /* find the number of digits */
1083 while (isdigit ((gint) usec_str[num_digits++]) && num_digits < 6);
1085 if (num_digits > 0) {
1086 /* fill up to 6 digits with 0 */
1087 while (num_digits < 6) {
1088 usec_str[num_digits++] = 0;
1091 g_assert (num_digits == 6);
1093 usec_str[num_digits] = '\0';
1094 usecs = atoi (usec_str);
1098 /* parse the timezone info */
1099 if (v[len - 1] == 'Z') {
1100 GST_LOG ("UTC timezone");
1102 /* Having a Z at the end means UTC */
1103 datetime = gst_date_time_new (0, year, month, day, hour, minute,
1104 second + usecs / 1000000.0);
1106 gchar *plus_pos = NULL;
1107 gchar *neg_pos = NULL;
1110 GST_LOG ("Checking for timezone information");
1112 /* check if there is timezone info */
1113 plus_pos = strrchr (v, '+');
1114 neg_pos = strrchr (v, '-');
1117 } else if (neg_pos) {
1122 gint ret_tz = sscanf (pos, "%d:%d", &gmt_offset_hour,
1125 GST_DEBUG ("Parsing timezone: %s", pos);
1128 gmt_offset = gmt_offset_hour * 60 + gmt_offset_min;
1129 if (neg_pos != NULL && neg_pos + 1 == pos)
1132 GST_LOG ("Timezone offset: %f (%d minutes)", gmt_offset / 60.0,
1135 /* no way to know if it is DST or not */
1137 gst_date_time_new (gmt_offset / 60.0,
1138 year, month, day, hour, minute,
1139 second + usecs / ((gdouble) G_USEC_PER_SEC));
1141 GST_WARNING ("Failed to parse timezone information");
1144 GST_WARNING ("No timezone signal found");
1149 gst_tag_list_add (list, merge_mode, tag, datetime, NULL);
1150 gst_date_time_unref (datetime);
1153 } else if (tag_type == GST_TYPE_DATE) {
1157 /* this is ISO 8601 Date and Time Format
1158 * %F Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)
1159 * %T The time in 24-hour notation (%H:%M:%S). (SU)
1160 * e.g. 2009-05-30T18:26:14+03:00 */
1162 /* FIXME: this would be the proper way, but needs
1163 #define _XOPEN_SOURCE before #include <time.h>
1165 date = g_date_new ();
1167 strptime (dts, "%FT%TZ", &tm);
1168 g_date_set_time_t (date, mktime(&tm));
1170 /* FIXME: this cannot parse the date
1171 date = g_date_new ();
1172 g_date_set_parse (date, v);
1173 if (g_date_valid (date)) {
1174 gst_tag_list_add (list, merge_mode, tag,
1177 GST_WARNING ("unparsable date: '%s'", v);
1180 /* poor mans straw */
1181 sscanf (v, "%04d-%02d-%02dT", &y, &m, &d);
1182 date = g_date_new_dmy (d, m, y);
1183 gst_tag_list_add (list, merge_mode, tag, date, NULL);
1186 GST_WARNING ("unhandled type for %s from xmp", tag);
1193 * gst_tag_list_from_xmp_buffer:
1196 * Parse a xmp packet into a taglist.
1198 * Returns: new taglist or %NULL, free the list when done
1203 gst_tag_list_from_xmp_buffer (const GstBuffer * buffer)
1205 GstTagList *list = NULL;
1206 const gchar *xps, *xp1, *xp2, *xpe, *ns, *ne;
1207 guint len, max_ft_len;
1211 const gchar *last_tag = NULL;
1212 XmpTag *last_xmp_tag = NULL;
1213 GSList *pending_tags = NULL;
1215 GstXmpNamespaceMap ns_map[] = {
1220 {"photoshop", NULL},
1221 {"Iptc4xmpCore", NULL},
1225 xmp_tags_initialize ();
1227 g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
1228 g_return_val_if_fail (GST_BUFFER_SIZE (buffer) > 0, NULL);
1230 xps = (const gchar *) GST_BUFFER_DATA (buffer);
1231 len = GST_BUFFER_SIZE (buffer);
1232 xpe = &xps[len + 1];
1234 /* check header and footer */
1235 xp1 = g_strstr_len (xps, len, "<?xpacket begin");
1237 goto missing_header;
1238 xp1 = &xp1[strlen ("<?xpacket begin")];
1239 while (*xp1 != '>' && *xp1 != '<' && xp1 < xpe)
1242 goto missing_header;
1244 max_ft_len = 1 + strlen ("<?xpacket end=\".\"?>\n");
1245 if (len < max_ft_len)
1246 goto missing_footer;
1248 GST_DEBUG ("checking footer: [%s]", &xps[len - max_ft_len]);
1249 xp2 = g_strstr_len (&xps[len - max_ft_len], max_ft_len, "<?xpacket ");
1251 goto missing_footer;
1253 GST_INFO ("xmp header okay");
1255 /* skip > and text until first xml-node */
1257 while (*xp1 != '<' && xp1 < xpe)
1260 /* no tag can be longer that the whole buffer */
1261 part = g_malloc (xp2 - xp1);
1262 list = gst_tag_list_new ();
1264 /* parse data into a list of nodes */
1265 /* data is between xp1..xp2 */
1272 while (ne < xp2 && *ne != '>' && *ne != '<') {
1273 if (*ne == '\n' || *ne == '\t' || *ne == ' ') {
1274 while (ne < xp2 && (*ne == '\n' || *ne == '\t' || *ne == ' '))
1285 /* {XML, ns, ne-ns} */
1287 gchar *as = strchr (part, ' ');
1288 /* only log start nodes */
1289 GST_INFO ("xml: %s", part);
1294 /* skip ' ' and scan the attributes */
1298 /* split attr=value pairs */
1299 while (*ae != '\0') {
1301 /* attr/value delimmiter */
1303 } else if (*ae == '"') {
1308 while (*ae != '\0' && *ae != '"')
1313 GST_INFO (" : [%s][%s]", as, v);
1314 if (!strncmp (as, "xmlns:", 6)) {
1316 /* we need to rewrite known namespaces to what we use in
1318 while (ns_match[i].ns_prefix) {
1319 if (!strcmp (ns_match[i].ns_uri, v))
1323 if (ns_match[i].ns_prefix) {
1324 if (strcmp (ns_map[i].original_ns, &as[6])) {
1325 ns_map[i].gstreamer_ns = g_strdup (&as[6]);
1329 const gchar *gst_tag;
1330 XmpTag *xmp_tag = NULL;
1331 /* FIXME: eventualy rewrite ns
1333 * check if ns before ':' is in ns_map and ns_map[i].gstreamer_ns!=NULL
1334 * do 2 stage filter in tag_matches
1336 gst_tag = _gst_xmp_tag_get_mapping_reverse (as, &xmp_tag);
1338 PendingXmpTag *ptag;
1340 ptag = g_slice_new (PendingXmpTag);
1341 ptag->gst_tag = gst_tag;
1342 ptag->xmp_tag = xmp_tag;
1343 ptag->str = g_strdup (v);
1345 pending_tags = g_slist_append (pending_tags, ptag);
1348 /* restore chars overwritten by '\0' */
1351 } else if (*ae == '\0' || *ae == ' ') {
1352 /* end of attr/value pair */
1355 /* to next char if not eos */
1361 <dc:type><rdf:Bag><rdf:li>Image</rdf:li></rdf:Bag></dc:type>
1362 <dc:creator><rdf:Seq><rdf:li/></rdf:Seq></dc:creator>
1364 /* FIXME: eventualy rewrite ns */
1366 /* skip rdf tags for now */
1367 if (strncmp (part, "rdf:", 4)) {
1368 const gchar *parttag;
1370 parttag = _gst_xmp_tag_get_mapping_reverse (part, &last_xmp_tag);
1386 while (ne < xp2 && *ne != '<') {
1392 /* {TXT, ns, (ne-ns)-1} */
1393 if (ns[0] != '\n' && &ns[1] <= ne) {
1394 /* only log non-newline nodes, we still have to parse them */
1395 GST_INFO ("txt: %s", part);
1397 PendingXmpTag *ptag;
1399 ptag = g_slice_new (PendingXmpTag);
1400 ptag->gst_tag = last_tag;
1401 ptag->xmp_tag = last_xmp_tag;
1402 ptag->str = g_strdup (part);
1404 pending_tags = g_slist_append (pending_tags, ptag);
1414 while (pending_tags) {
1415 PendingXmpTag *ptag = (PendingXmpTag *) pending_tags->data;
1417 pending_tags = g_slist_delete_link (pending_tags, pending_tags);
1419 read_one_tag (list, ptag->gst_tag, ptag->xmp_tag, ptag->str, &pending_tags);
1422 g_slice_free (PendingXmpTag, ptag);
1425 GST_INFO ("xmp packet parsed, %d entries",
1426 gst_structure_n_fields ((GstStructure *) list));
1428 /* free resources */
1430 while (ns_map[i].original_ns) {
1431 g_free (ns_map[i].gstreamer_ns);
1440 GST_WARNING ("malformed xmp packet header");
1443 GST_WARNING ("malformed xmp packet footer");
1446 GST_WARNING ("malformed xml tag: %s", part);
1454 string_open_tag (GString * string, const char *tag)
1456 g_string_append_c (string, '<');
1457 g_string_append (string, tag);
1458 g_string_append_c (string, '>');
1462 string_close_tag (GString * string, const char *tag)
1464 g_string_append (string, "</");
1465 g_string_append (string, tag);
1466 g_string_append (string, ">\n");
1470 gst_value_serialize_xmp (const GValue * value)
1472 switch (G_VALUE_TYPE (value)) {
1474 return g_markup_escape_text (g_value_get_string (value), -1);
1476 return g_strdup_printf ("%d", g_value_get_int (value));
1478 return g_strdup_printf ("%u", g_value_get_uint (value));
1480 return double_to_fraction_string (g_value_get_double (value));
1484 /* put non-switchable types here */
1485 if (G_VALUE_TYPE (value) == GST_TYPE_DATE) {
1486 const GDate *date = gst_value_get_date (value);
1488 return g_strdup_printf ("%04d-%02d-%02d",
1489 (gint) g_date_get_year (date), (gint) g_date_get_month (date),
1490 (gint) g_date_get_day (date));
1491 } else if (G_VALUE_TYPE (value) == GST_TYPE_DATE_TIME) {
1492 gint year, month, day, hour, min, sec, microsec;
1493 gfloat gmt_offset = 0;
1494 gint gmt_offset_hour, gmt_offset_min;
1495 GstDateTime *datetime = (GstDateTime *) g_value_get_boxed (value);
1497 year = gst_date_time_get_year (datetime);
1498 month = gst_date_time_get_month (datetime);
1499 day = gst_date_time_get_day (datetime);
1500 hour = gst_date_time_get_hour (datetime);
1501 min = gst_date_time_get_minute (datetime);
1502 sec = gst_date_time_get_second (datetime);
1503 microsec = gst_date_time_get_microsecond (datetime);
1504 gmt_offset = gst_date_time_get_time_zone_offset (datetime);
1505 if (gmt_offset == 0) {
1507 return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d.%06dZ",
1508 year, month, day, hour, min, sec, microsec);
1510 gmt_offset_hour = ABS (gmt_offset);
1511 gmt_offset_min = (ABS (gmt_offset) - gmt_offset_hour) * 60;
1513 return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d.%06d%c%02d:%02d",
1514 year, month, day, hour, min, sec, microsec,
1515 gmt_offset >= 0 ? '+' : '-', gmt_offset_hour, gmt_offset_min);
1523 write_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
1525 guint i = 0, ct = gst_tag_list_get_tag_size (list, tag), tag_index;
1526 XmpSerializationData *serialization_data = user_data;
1527 GString *data = serialization_data->data;
1528 GPtrArray *xmp_tag_array = NULL;
1531 /* map gst-tag to xmp tag */
1532 xmp_tag_array = _xmp_tag_get_mapping (tag, serialization_data);
1534 if (!xmp_tag_array) {
1535 GST_WARNING ("no mapping for %s to xmp", tag);
1539 for (tag_index = 0; tag_index < xmp_tag_array->len; tag_index++) {
1542 xmp_tag = g_ptr_array_index (xmp_tag_array, tag_index);
1543 string_open_tag (data, xmp_tag->tag_name);
1545 /* fast path for single valued tag */
1546 if (ct == 1 || xmp_tag->type == GST_XMP_TAG_TYPE_SIMPLE) {
1547 if (xmp_tag->serialize) {
1548 s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, 0));
1550 s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
1554 g_string_append (data, s);
1557 GST_WARNING ("unhandled type for %s to xmp", tag);
1560 const gchar *typename;
1562 typename = xmp_tag_get_type_name (xmp_tag);
1564 string_open_tag (data, typename);
1565 for (i = 0; i < ct; i++) {
1566 GST_DEBUG ("mapping %s[%u/%u] to xmp", tag, i, ct);
1567 if (xmp_tag->serialize) {
1568 s = xmp_tag->serialize (gst_tag_list_get_value_index (list, tag, i));
1570 s = gst_value_serialize_xmp (gst_tag_list_get_value_index (list, tag,
1574 string_open_tag (data, "rdf:li");
1575 g_string_append (data, s);
1576 string_close_tag (data, "rdf:li");
1579 GST_WARNING ("unhandled type for %s to xmp", tag);
1582 string_close_tag (data, typename);
1585 string_close_tag (data, xmp_tag->tag_name);
1590 * gst_tag_list_to_xmp_buffer_full:
1592 * @read_only: does the container forbid inplace editing
1593 * @schemas: %NULL terminated array of schemas to be used on serialization
1595 * Formats a taglist as a xmp packet using only the selected
1596 * schemas. An empty list (%NULL) means that all schemas should
1599 * Returns: new buffer or %NULL, unref the buffer when done
1604 gst_tag_list_to_xmp_buffer_full (const GstTagList * list, gboolean read_only,
1605 const gchar ** schemas)
1607 GstBuffer *buffer = NULL;
1608 XmpSerializationData serialization_data;
1612 serialization_data.data = g_string_sized_new (4096);
1613 serialization_data.schemas = schemas;
1614 data = serialization_data.data;
1616 xmp_tags_initialize ();
1618 g_return_val_if_fail (GST_IS_TAG_LIST (list), NULL);
1621 g_string_append (data,
1622 "<?xpacket begin=\"\xEF\xBB\xBF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n");
1623 g_string_append (data,
1624 "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"GStreamer\">\n");
1625 g_string_append (data,
1626 "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"");
1628 while (ns_match[i].ns_prefix) {
1629 if (xmp_serialization_data_use_schema (&serialization_data,
1630 ns_match[i].ns_prefix))
1631 g_string_append_printf (data, " xmlns:%s=\"%s\"",
1632 ns_match[i].ns_prefix, ns_match[i].ns_uri);
1635 g_string_append (data, ">\n");
1636 g_string_append (data, "<rdf:Description rdf:about=\"\">\n");
1638 /* iterate the taglist */
1639 gst_tag_list_foreach (list, write_one_tag, &serialization_data);
1642 g_string_append (data, "</rdf:Description>\n");
1643 g_string_append (data, "</rdf:RDF>\n");
1644 g_string_append (data, "</x:xmpmeta>\n");
1647 /* the xmp spec recommand to add 2-4KB padding for in-place editable xmp */
1650 for (i = 0; i < 32; i++) {
1651 g_string_append (data, " " " "
1655 g_string_append_printf (data, "<?xpacket end=\"%c\"?>\n",
1656 (read_only ? 'r' : 'w'));
1658 buffer = gst_buffer_new ();
1659 GST_BUFFER_SIZE (buffer) = data->len + 1;
1660 GST_BUFFER_DATA (buffer) = (guint8 *) g_string_free (data, FALSE);
1661 GST_BUFFER_MALLOCDATA (buffer) = GST_BUFFER_DATA (buffer);
1667 * gst_tag_list_to_xmp_buffer:
1669 * @read_only: does the container forbid inplace editing
1671 * Formats a taglist as a xmp packet.
1673 * Returns: new buffer or %NULL, unref the buffer when done
1678 gst_tag_list_to_xmp_buffer (const GstTagList * list, gboolean read_only)
1680 return gst_tag_list_to_xmp_buffer_full (list, read_only, NULL);
1683 #undef gst_xmp_schema_lookup
1684 #undef gst_xmp_schema_insert