diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/metadata/TODO | 13 | ||||
-rw-r--r-- | ext/metadata/metadataexif.c | 59 | ||||
-rw-r--r-- | ext/metadata/metadataxmp.c | 382 |
3 files changed, 373 insertions, 81 deletions
diff --git a/ext/metadata/TODO b/ext/metadata/TODO index 77245761..35c7eb8a 100644 --- a/ext/metadata/TODO +++ b/ext/metadata/TODO @@ -3,12 +3,11 @@ This file contains a list of things to be done as well some open issues (questio INFO: -1- I (Edgard Lima - alima - edgard.lima@indt.org.br) will be on vacation until 05-Jan-2008. After that I will be back on it. -2- to see what tags are mapped so far run 'grep -n GST_TAG *.[ch]' into this folder. +1- to see what tags are mapped so far run 'grep -n GST_TAG *.[ch]' into this folder. TODO: -1- Add individual XMP tags (and more for EXIF and IPTC) +1- Add more individual tags to XMP, EXIF and IPTC 2- Get properties like 'width' and 'height' from caps 3- Review the code (in order to move to gst-plugins-good) 4- Document how the plugin works (atchitecture and interaction beteween modules) @@ -19,10 +18,10 @@ OPEN ISSUES: 2- How to change metadata when the orignal image was modified. ex: file.jpeg has XMP, then we do filesrc ! metadataparse ! jpegdec ! pngenc ! metadatamux ! files is the metadata still valid? which fields are no valid anymore? -3- Add GST_TYPE_FRACTION support for GStreamer TAGS -4- currently, in JPEG files, if there is a Photoshop segment, everything inside it but IPTC will be lost. From the point of view of implementation it is easy, but I still don't now how to solve from the point of view of "designing". Anyway I think it is not so important. +3- In EXIF, how to make sure we are compliant with the specification when adding some tag? For example, we are not considerinb what are mandatory (or optional) IFDs for tags. +4- Add GST_TYPE_FRACTION support for GStreamer TAGS +5- currently, in JPEG files, if there is a Photoshop segment, everything inside it but IPTC will be lost. From the point of view of implementation it is easy, but I still don't now how to solve from the point of view of "designing". Anyway I think it is not so important. +6- language is not considered in XMP (How to do it with GStreamer?) KNOWN BUGS -1- exposure-time, exposure-program and fnumber can't be read from a file saved from scratch (whithout WHOLE_CHUNK from previous file) - I believe it is a bug in libexif diff --git a/ext/metadata/metadataexif.c b/ext/metadata/metadataexif.c index 5b745a7a..76dee2a7 100644 --- a/ext/metadata/metadataexif.c +++ b/ext/metadata/metadataexif.c @@ -88,6 +88,7 @@ typedef struct _tag_MEUserData typedef struct _tag_MapIntStr { ExifTag exif; + ExifIfd ifd; const gchar *str; } MapIntStr; @@ -98,21 +99,35 @@ static void exif_content_foreach_entry_func (ExifEntry * entry, void *); /* *INDENT-OFF* */ static MapIntStr mappedTags[] = { - {EXIF_TAG_MAKE, /*ASCII,*/ GST_TAG_DEVICE_MAKE, /*STRING*/}, - {EXIF_TAG_MODEL, /*ASCII,*/ GST_TAG_DEVICE_MODEL, /*STRING*/}, - {EXIF_TAG_SOFTWARE, /*ASCII,*/ GST_TAG_CREATOR_TOOL, /*STRING*/}, - {EXIF_TAG_X_RESOLUTION, /*RATIONAL,*/ GST_TAG_IMAGE_XRESOLUTION, /*FRACTION*/}, /* inches */ - {EXIF_TAG_Y_RESOLUTION, /*RATIONAL,*/ GST_TAG_IMAGE_YRESOLUTION, /*FRACTION*/}, /* inches */ - {EXIF_TAG_EXPOSURE_TIME, /*RATIONAL,*/ GST_TAG_CAPTURE_EXPOSURE_TIME, /*FRACTION*/}, - {EXIF_TAG_FNUMBER, /*RATIONAL,*/ GST_TAG_CAPTURE_FNUMBER, /*FRACTION*/}, - {EXIF_TAG_EXPOSURE_PROGRAM, /*SHORT,*/ GST_TAG_CAPTURE_EXPOSURE_PROGRAM, /*UINT*/}, - {EXIF_TAG_BRIGHTNESS_VALUE, /*SRATIONAL,*/ GST_TAG_CAPTURE_BRIGHTNESS, /*FRACTION*/}, - {EXIF_TAG_WHITE_BALANCE, /*SHORT,*/ GST_TAG_CAPTURE_WHITE_BALANCE, /*UINT*/}, - {EXIF_TAG_DIGITAL_ZOOM_RATIO, /*RATIONAL,*/ GST_TAG_CAPTURE_DIGITAL_ZOOM, /*FRACTION*/}, - {EXIF_TAG_GAIN_CONTROL, /*SHORT,*/ GST_TAG_CAPTURE_GAIN, /*UINT*/}, - {EXIF_TAG_CONTRAST, /*SHORT,*/ GST_TAG_CAPTURE_CONTRAST, /*INT*/}, - {EXIF_TAG_SATURATION, /*SHORT,*/ GST_TAG_CAPTURE_SATURATION, /*INT*/}, - {0, NULL} + {EXIF_TAG_MAKE, /*ASCII,*/ EXIF_IFD_0, + GST_TAG_DEVICE_MAKE, /*STRING*/}, + {EXIF_TAG_MODEL, /*ASCII,*/ EXIF_IFD_0, + GST_TAG_DEVICE_MODEL, /*STRING*/}, + {EXIF_TAG_SOFTWARE, /*ASCII,*/ EXIF_IFD_0, + GST_TAG_CREATOR_TOOL, /*STRING*/}, + {EXIF_TAG_X_RESOLUTION, /*RATIONAL,*/ EXIF_IFD_0, + GST_TAG_IMAGE_XRESOLUTION, /*FRACTION*/}, /* inches */ + {EXIF_TAG_Y_RESOLUTION, /*RATIONAL,*/ EXIF_IFD_0, + GST_TAG_IMAGE_YRESOLUTION, /*FRACTION*/}, /* inches */ + {EXIF_TAG_EXPOSURE_TIME, /*RATIONAL,*/ EXIF_IFD_EXIF, + GST_TAG_CAPTURE_EXPOSURE_TIME, /*FRACTION*/}, + {EXIF_TAG_FNUMBER, /*RATIONAL,*/ EXIF_IFD_EXIF, + GST_TAG_CAPTURE_FNUMBER, /*FRACTION*/}, + {EXIF_TAG_EXPOSURE_PROGRAM, /*SHORT,*/ EXIF_IFD_EXIF, + GST_TAG_CAPTURE_EXPOSURE_PROGRAM, /*UINT*/}, + {EXIF_TAG_BRIGHTNESS_VALUE, /*SRATIONAL,*/ EXIF_IFD_0, + GST_TAG_CAPTURE_BRIGHTNESS, /*FRACTION*/}, + {EXIF_TAG_WHITE_BALANCE, /*SHORT,*/ EXIF_IFD_0, + GST_TAG_CAPTURE_WHITE_BALANCE, /*UINT*/}, + {EXIF_TAG_DIGITAL_ZOOM_RATIO, /*RATIONAL,*/ EXIF_IFD_0, + GST_TAG_CAPTURE_DIGITAL_ZOOM, /*FRACTION*/}, + {EXIF_TAG_GAIN_CONTROL, /*SHORT,*/ EXIF_IFD_0, + GST_TAG_CAPTURE_GAIN, /*UINT*/}, + {EXIF_TAG_CONTRAST, /*SHORT,*/ EXIF_IFD_0, + GST_TAG_CAPTURE_CONTRAST, /*INT*/}, + {EXIF_TAG_SATURATION, /*SHORT,*/ EXIF_IFD_0, + GST_TAG_CAPTURE_SATURATION, /*INT*/}, + {0, EXIF_IFD_COUNT, NULL} }; /* *INDENT-ON* */ @@ -134,13 +149,15 @@ metadataparse_exif_get_tag_from_exif (ExifTag exif, GType * type) } static ExifTag -metadataparse_exif_get_exif_from_tag (const gchar * tag, GType * type) +metadataparse_exif_get_exif_from_tag (const gchar * tag, GType * type, + ExifIfd * ifd) { int i = 0; while (mappedTags[i].exif) { if (0 == strcmp (mappedTags[i].str, tag)) { *type = gst_tag_get_type (tag); + *ifd = mappedTags[i].ifd; break; } ++i; @@ -361,13 +378,12 @@ done: " Title: %s\n" " Description: %s\n", entry, - exif_tag_get_name_in_ifd (entry->tag, EXIF_IFD_0), + exif_tag_get_name (entry->tag), exif_format_get_name (entry->format), entry->size, (int) (entry->components), exif_entry_get_value (entry, buf, sizeof (buf)), - exif_tag_get_title_in_ifd (entry->tag, EXIF_IFD_0), - exif_tag_get_description_in_ifd (entry->tag, EXIF_IFD_0)); + exif_tag_get_title (entry->tag), exif_tag_get_description (entry->tag)); return; @@ -449,9 +465,10 @@ metadataexif_for_each_tag_in_list (const GstTagList * list, const gchar * tag, ExifTag exif_tag; GType type; ExifEntry *entry = NULL; + ExifIfd ifd; const ExifByteOrder byte_order = exif_data_get_byte_order (ed); - exif_tag = metadataparse_exif_get_exif_from_tag (tag, &type); + exif_tag = metadataparse_exif_get_exif_from_tag (tag, &type, &ifd); if (!exif_tag) goto done; @@ -462,7 +479,7 @@ metadataexif_for_each_tag_in_list (const GstTagList * list, const gchar * tag, exif_entry_ref (entry); else { entry = exif_entry_new (); - exif_content_add_entry (ed->ifd[EXIF_IFD_0], entry); + exif_content_add_entry (ed->ifd[ifd], entry); exif_entry_initialize (entry, exif_tag); } diff --git a/ext/metadata/metadataxmp.c b/ext/metadata/metadataxmp.c index 56c3ad87..cc8172ce 100644 --- a/ext/metadata/metadataxmp.c +++ b/ext/metadata/metadataxmp.c @@ -88,14 +88,64 @@ metadatamux_xmp_create_chunk_from_tag_list (guint8 ** buf, guint32 * size, #define XMP_SCHEMA_NODE 0x80000000UL -void -metadataparse_xmp_iter_array (XmpPtr xmp, const char *schema, const char *path); +typedef struct _tag_SchemaTagMap +{ + const gchar *xmp_tag; + const gchar *gst_tag; +} SchemaTagMap; + +typedef struct _tag_SchemaMap +{ + const gchar *schema; + const gchar *prefix; + const guint8 prefix_len; + const SchemaTagMap *tags_map; +} SchemaMap; + +static const SchemaTagMap schema_map_dublin_tags_map[] = { + {"description", GST_TAG_DESCRIPTION}, + {"title", GST_TAG_TITLE}, + {"rights", GST_TAG_COPYRIGHT}, + {NULL, NULL} +}; + +static const SchemaMap schema_map_dublin = { "http://purl.org/dc/elements/1.1/", + "dc:", + 3, + schema_map_dublin_tags_map +}; + +static const SchemaMap *schemas_map[] = { + &schema_map_dublin, + NULL +}; + +static void +metadataparse_xmp_iter_add_to_tag_list (GstTagList * taglist, + GstTagMergeMode mode, const char *path, const char *value, + const SchemaMap * schema_map, const uint32_t opt); + +static void +metadataparse_xmp_iter_array (GstTagList * taglist, GstTagMergeMode mode, + XmpPtr xmp, const char *schema, const char *path, + const SchemaMap * schema_map); + +static void +metadataparse_xmp_iter_node_schema (GstTagList * taglist, GstTagMergeMode mode, + XmpPtr xmp, const char *schema, const char *path); static void -metadataparse_xmp_iter_simple (const char *schema, const char *path, - const char *value); +metadataparse_xmp_iter_simple (GstTagList * taglist, GstTagMergeMode mode, + const char *schema, const char *path, const char *value, + const SchemaMap * schema_map); -static void metadataparse_xmp_iter (XmpPtr xmp, XmpIteratorPtr iter); +static void +metadataparse_xmp_iter_simple_qual (GstTagList * taglist, GstTagMergeMode mode, + const char *schema, const char *path, const char *value, + const SchemaMap * schema_map); + +static void +metadataparse_xmp_iter (GstTagList * taglist, GstTagMergeMode mode, XmpPtr xmp); gboolean metadataparse_xmp_init (void) @@ -116,7 +166,6 @@ metadataparse_xmp_tag_list_add (GstTagList * taglist, GstTagMergeMode mode, const guint8 *buf; guint32 size; XmpPtr xmp = NULL; - XmpIteratorPtr xmp_iter = NULL; if (adapter == NULL || (size = gst_adapter_available (adapter)) == 0) { goto done; @@ -135,116 +184,321 @@ metadataparse_xmp_tag_list_add (GstTagList * taglist, GstTagMergeMode mode, if (!xmp) goto done; - xmp_iter = xmp_iterator_new (xmp, NULL, NULL, XMP_ITER_JUSTCHILDREN); + metadataparse_xmp_iter (taglist, mode, xmp); + +done: + + if (xmp) { + xmp_free (xmp); + } + + return; + +} - if (!xmp_iter) +static const SchemaTagMap * +metadataparse_get_tagsmap_from_gsttag (const SchemaMap * schema_map, + const gchar * tag) +{ + SchemaTagMap *tags_map = NULL; + int i; + + if (NULL == schema_map) goto done; - metadataparse_xmp_iter (xmp, xmp_iter); + + for (i = 0; schema_map->tags_map[i].gst_tag; i++) { + if (0 == strcmp (schema_map->tags_map[i].gst_tag, tag)) { + tags_map = (SchemaTagMap *) & schema_map->tags_map[i]; + break; + } + } done: - if (xmp_iter) { - xmp_iterator_free (xmp_iter); + return tags_map; + +} + +static const SchemaTagMap * +metadataparse_get_tagsmap_from_path (const SchemaMap * schema_map, + const gchar * path, uint32_t opt) +{ + + GString *string = NULL; + gchar *ch; + SchemaTagMap *tags_map = NULL; + + if (NULL == schema_map) + goto done; + + tags_map = (SchemaTagMap *) schema_map->tags_map; + + if (XMP_HAS_PROP_QUALIFIERS (opt) || XMP_IS_ARRAY_ALTTEXT (opt)) { + + string = g_string_new (path); + + /* remove the language qualifier */ + ch = string->str + string->len - 3; + while (ch != string->str + schema_map->prefix_len) { + if (*ch == '[') { + *ch = '\0'; + } + --ch; + } + + } else { + ch = (gchar *) path + schema_map->prefix_len; } - if (xmp) { - xmp_free (xmp); + while (tags_map->xmp_tag) { + if (0 == strcmp (tags_map->xmp_tag, ch)) + break; + tags_map++; } +done: + + if (string) + g_string_free (string, TRUE); + + return tags_map; + +} + +static void +metadataparse_xmp_iter_add_to_tag_list (GstTagList * taglist, + GstTagMergeMode mode, const char *path, const char *value, + const SchemaMap * schema_map, const uint32_t opt) +{ + + const SchemaTagMap *smaptag = + metadataparse_get_tagsmap_from_path (schema_map, path, opt); + + if (NULL == smaptag) + goto done; + + if (NULL == smaptag->gst_tag) + goto done; + + GType type = gst_tag_get_type (smaptag->gst_tag); + + switch (type) { + case G_TYPE_STRING: + gst_tag_list_add (taglist, mode, smaptag->gst_tag, value, NULL); + break; + default: + break; + } + +done: + return; } void -metadataparse_xmp_iter_simple_qual (const char *schema, const char *path, - const char *value) +metadataparse_xmp_iter_simple_qual (GstTagList * taglist, GstTagMergeMode mode, + const char *schema, const char *path, const char *value, + const SchemaMap * schema_map) { GString *string = g_string_new (path); gchar *ch; + /* remove the language qualifier */ ch = string->str + string->len - 3; - while (ch != string->str) { + while (ch != string->str + schema_map->prefix_len) { if (*ch == '[') { *ch = '\0'; } --ch; } - GST_LOG (" %s = %s\n", string->str, value); + GST_LOG (" %s = %s", string->str, value); + + metadataparse_xmp_iter_add_to_tag_list (taglist, mode, path, value, + schema_map, XMP_PROP_HAS_QUALIFIERS); + g_string_free (string, TRUE); } -void -metadataparse_xmp_iter_simple (const char *schema, const char *path, - const char *value) -{ - GST_LOG (" %s = %s\n", path, value); -} void -metadataparse_xmp_iter_array (XmpPtr xmp, const char *schema, const char *path) +metadataparse_xmp_iter_simple (GstTagList * taglist, GstTagMergeMode mode, + const char *schema, const char *path, const char *value, + const SchemaMap * schema_map) { + GST_LOG (" %s = %s", path, value); - XmpIteratorPtr xmp_iter = NULL; - - xmp_iter = xmp_iterator_new (xmp, schema, path, XMP_ITER_JUSTCHILDREN); - - if (xmp_iter) { - metadataparse_xmp_iter (xmp, xmp_iter); - - xmp_iterator_free (xmp_iter); - } + metadataparse_xmp_iter_add_to_tag_list (taglist, mode, path, value, + schema_map, 0); } void -metadataparse_xmp_iter (XmpPtr xmp, XmpIteratorPtr iter) +metadataparse_xmp_iter_array (GstTagList * taglist, GstTagMergeMode mode, + XmpPtr xmp, const char *schema, const char *path, + const SchemaMap * schema_map) { XmpStringPtr xstr_schema = xmp_string_new (); XmpStringPtr xstr_path = xmp_string_new (); XmpStringPtr xstr_prop = xmp_string_new (); uint32_t opt = 0; + XmpIteratorPtr xmp_iter = NULL; + + xmp_iter = xmp_iterator_new (xmp, schema, path, XMP_ITER_JUSTCHILDREN); + + if (NULL == xmp_iter) + goto done; - while (xmp_iterator_next (iter, xstr_schema, xstr_path, xstr_prop, &opt)) { + while (xmp_iterator_next (xmp_iter, xstr_schema, xstr_path, xstr_prop, &opt)) { const char *schema = xmp_string_cstr (xstr_schema); const char *path = xmp_string_cstr (xstr_path); const char *value = xmp_string_cstr (xstr_prop); if (XMP_IS_NODE_SCHEMA (opt)) { - GST_LOG ("%s\n", schema); - metadataparse_xmp_iter_array (xmp, schema, path); + GST_LOG ("Unexpected iteraction"); } else if (XMP_IS_PROP_SIMPLE (opt)) { if (strcmp (path, "") != 0) { if (XMP_HAS_PROP_QUALIFIERS (opt)) { - metadataparse_xmp_iter_simple_qual (schema, path, value); + /* ignore language qualifier, just get the first */ + metadataparse_xmp_iter_simple_qual (taglist, mode, schema, path, + value, schema_map); } else { - metadataparse_xmp_iter_simple (schema, path, value); + metadataparse_xmp_iter_simple (taglist, mode, schema, path, value, + schema_map); } } } else if (XMP_IS_PROP_ARRAY (opt)) { + /* FIXME: array with merge mode */ + GstTagMergeMode new_mode = mode; + +#if 0 + //const gchar *tag = ; + if (mode == GST_TAG_MERGE_REPLACE) { + //gst_tag_list_remove_tag(taglist, ); + } +#endif if (XMP_IS_ARRAY_ALTTEXT (opt)) { - metadataparse_xmp_iter_array (xmp, schema, path); - xmp_iterator_skip (iter, XMP_ITER_SKIPSUBTREE); + metadataparse_xmp_iter_array (taglist, new_mode, xmp, schema, path, + schema_map); + xmp_iterator_skip (xmp_iter, XMP_ITER_SKIPSUBTREE); } else { - metadataparse_xmp_iter_array (xmp, schema, path); - xmp_iterator_skip (iter, XMP_ITER_SKIPSUBTREE); + metadataparse_xmp_iter_array (taglist, new_mode, xmp, schema, path, + schema_map); + xmp_iterator_skip (xmp_iter, XMP_ITER_SKIPSUBTREE); } } + } +done: - } + if (xmp_iter) + xmp_iterator_free (xmp_iter); - if (xstr_prop) { + if (xstr_prop) xmp_string_free (xstr_prop); - } - if (xstr_path) { + + if (xstr_path) xmp_string_free (xstr_path); + + if (xstr_schema) + xmp_string_free (xstr_schema); + +} + +void +metadataparse_xmp_iter_node_schema (GstTagList * taglist, GstTagMergeMode mode, + XmpPtr xmp, const char *schema, const char *path) +{ + SchemaMap *schema_map = NULL; + + if (0 == strcmp (schema, "http://purl.org/dc/elements/1.1/")) { + schema_map = (SchemaMap *) & schema_map_dublin; } - if (xstr_schema) { + + metadataparse_xmp_iter_array (taglist, mode, xmp, schema, path, schema_map); +} + +void +metadataparse_xmp_iter (GstTagList * taglist, GstTagMergeMode mode, XmpPtr xmp) +{ + XmpStringPtr xstr_schema = xmp_string_new (); + XmpStringPtr xstr_path = xmp_string_new (); + XmpStringPtr xstr_prop = xmp_string_new (); + uint32_t opt = 0; + XmpIteratorPtr xmp_iter = NULL; + + xmp_iter = xmp_iterator_new (xmp, NULL, NULL, XMP_ITER_JUSTCHILDREN); + + if (NULL == xmp_iter) + goto done; + + while (xmp_iterator_next (xmp_iter, xstr_schema, xstr_path, xstr_prop, &opt)) { + const char *schema = xmp_string_cstr (xstr_schema); + const char *path = xmp_string_cstr (xstr_path); + + if (XMP_IS_NODE_SCHEMA (opt)) { + GST_LOG ("%s", schema); + metadataparse_xmp_iter_node_schema (taglist, mode, xmp, schema, path); + } else { + GST_LOG ("Unexpected iteraction"); + } + } + +done: + + if (xmp_iter) + xmp_iterator_free (xmp_iter); + + if (xstr_prop) + xmp_string_free (xstr_prop); + + if (xstr_path) + xmp_string_free (xstr_path); + + if (xstr_schema) xmp_string_free (xstr_schema); +} + + +static void +metadataxmp_for_each_tag_in_list (const GstTagList * list, const gchar * tag, + gpointer user_data) +{ + XmpPtr xmp = (XmpPtr) user_data; + int i; + + for (i = 0; schemas_map[i]; i++) { + + const SchemaMap *smap = schemas_map[i]; + const SchemaTagMap *stagmap = + metadataparse_get_tagsmap_from_gsttag (smap, tag); + + if (stagmap) { + + gchar *value = NULL; + + GType type = gst_tag_get_type (tag); + + switch (type) { + case G_TYPE_STRING: + gst_tag_list_get_string (list, tag, &value); + break; + default: + break; + } + + if (value) { + xmp_set_property (xmp, smap->schema, stagmap->xmp_tag, value); + g_free (value); + } + + } + } + } void @@ -253,6 +507,8 @@ metadatamux_xmp_create_chunk_from_tag_list (guint8 ** buf, guint32 * size, { GstBuffer *xmp_chunk = NULL; const GValue *val = NULL; + XmpPtr xmp = NULL; + XmpStringPtr xmp_str_buf = xmp_string_new (); if (!(buf && size)) goto done; @@ -265,15 +521,35 @@ metadatamux_xmp_create_chunk_from_tag_list (guint8 ** buf, guint32 * size, val = gst_tag_list_get_value_index (taglist, GST_TAG_XMP, 0); if (val) { xmp_chunk = gst_value_get_buffer (val); - if (xmp_chunk) { - *size = GST_BUFFER_SIZE (xmp_chunk); - *buf = g_new (guint8, *size); - memcpy (*buf, GST_BUFFER_DATA (xmp_chunk), *size); - } + if (xmp_chunk) + xmp = xmp_new (GST_BUFFER_DATA (xmp_chunk), GST_BUFFER_SIZE (xmp_chunk)); + } + + if (NULL == xmp) + xmp = xmp_new_empty (); + + gst_tag_list_foreach (taglist, metadataxmp_for_each_tag_in_list, xmp); + + if (!xmp_serialize (xmp, xmp_str_buf, 0, 2)) { + GST_ERROR ("failed to serialize xmp into chunk\n"); + } else if (xmp_str_buf) { + unsigned int len = strlen (xmp_string_cstr (xmp_str_buf)); + + *size = len + 1; + *buf = malloc (*size); + memcpy (*buf, xmp_string_cstr (xmp_str_buf), *size); + } else { + GST_ERROR ("failed to serialize xmp into chunk\n"); } done: + if (xmp_str_buf) + xmp_string_free (xmp_str_buf); + + if (xmp) + xmp_free (xmp); + return; } |