[gexiv2] Add function: if tag supports multiple values
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gexiv2] Add function: if tag supports multiple values
- Date: Sun, 20 Jun 2021 10:08:22 +0000 (UTC)
commit 2f78f647b5f0d3f0b9ae11bebdf2acab26518fc6
Author: postscript-dev <43813-postscript-dev users noreply gitlab gnome org>
Date: Wed Mar 17 14:53:24 2021 +0000
Add function: if tag supports multiple values
+ The function works on all tags. For Iptc tags, this is the only way
to find out if it is Repeatable or Non-repeatable.
+ Only added `_try_` public version
+ Added Python tests
gexiv2/gexiv2-metadata-exif.cpp | 14 ++++++++++++++
gexiv2/gexiv2-metadata-iptc.cpp | 14 ++++++++++++++
gexiv2/gexiv2-metadata-private.h | 5 +++++
gexiv2/gexiv2-metadata-xmp.cpp | 41 ++++++++++++++++++++++++++++++++++++++++
gexiv2/gexiv2-metadata.cpp | 23 ++++++++++++++++++++++
gexiv2/gexiv2-metadata.h | 15 +++++++++++++++
test/python/test_metadata.py | 38 ++++++++++++++++++++++++++++++++++++-
7 files changed, 149 insertions(+), 1 deletion(-)
---
diff --git a/gexiv2/gexiv2-metadata-exif.cpp b/gexiv2/gexiv2-metadata-exif.cpp
index 7b800fc..89749bf 100644
--- a/gexiv2/gexiv2-metadata-exif.cpp
+++ b/gexiv2/gexiv2-metadata-exif.cpp
@@ -432,6 +432,20 @@ const gchar* gexiv2_metadata_get_exif_tag_type (const gchar* tag, GError **error
return NULL;
}
+gboolean gexiv2_metadata_exif_tag_supports_multiple_values (const gchar* tag, GError **error) {
+ g_return_val_if_fail(tag != nullptr, FALSE);
+ g_return_val_if_fail(error == nullptr || *error == nullptr, FALSE);
+
+ try {
+ // Exif does not support multiple values, but still check if @tag is valid
+ const Exiv2::ExifKey key(tag);
+ } catch (Exiv2::Error& e) {
+ g_set_error_literal(error, g_quark_from_string("GExiv2"), e.code(), e.what());
+ }
+
+ return FALSE;
+}
+
GBytes* gexiv2_metadata_get_exif_tag_raw (GExiv2Metadata *self, const gchar* tag, GError **error) {
g_return_val_if_fail(GEXIV2_IS_METADATA (self), NULL);
g_return_val_if_fail(tag != NULL, NULL);
diff --git a/gexiv2/gexiv2-metadata-iptc.cpp b/gexiv2/gexiv2-metadata-iptc.cpp
index 165d1e6..afeec29 100644
--- a/gexiv2/gexiv2-metadata-iptc.cpp
+++ b/gexiv2/gexiv2-metadata-iptc.cpp
@@ -288,6 +288,20 @@ const gchar* gexiv2_metadata_get_iptc_tag_type (const gchar* tag, GError **error
return NULL;
}
+gboolean gexiv2_metadata_iptc_tag_supports_multiple_values(const gchar* tag, GError** error) {
+ g_return_val_if_fail(tag != nullptr, FALSE);
+ g_return_val_if_fail(error == nullptr || *error == nullptr, FALSE);
+
+ try {
+ const Exiv2::IptcKey key(tag); // Check to see if @tag is valid
+ return (Exiv2::IptcDataSets::dataSetRepeatable(key.tag(), key.record()) ? TRUE : FALSE);
+ } catch (Exiv2::Error& e) {
+ g_set_error_literal(error, g_quark_from_string("GExiv2"), e.code(), e.what());
+ }
+
+ return FALSE;
+}
+
GBytes* gexiv2_metadata_get_iptc_tag_raw (GExiv2Metadata *self, const gchar* tag, GError **error) {
g_return_val_if_fail(GEXIV2_IS_METADATA (self), NULL);
g_return_val_if_fail(tag != NULL, NULL);
diff --git a/gexiv2/gexiv2-metadata-private.h b/gexiv2/gexiv2-metadata-private.h
index b5482ed..d36ac50 100644
--- a/gexiv2/gexiv2-metadata-private.h
+++ b/gexiv2/gexiv2-metadata-private.h
@@ -63,9 +63,12 @@ G_GNUC_INTERNAL gchar*
gexiv2_metadata_get_exif_tag_interpreted_string (GExiv2
G_GNUC_INTERNAL glong gexiv2_metadata_get_exif_tag_long (GExiv2Metadata *self, const
gchar* tag, GError **error);
G_GNUC_INTERNAL gboolean gexiv2_metadata_set_exif_tag_long (GExiv2Metadata *self, const
gchar* tag, glong value, GError **error);
G_GNUC_INTERNAL gdouble gexiv2_metadata_get_exif_tag_rational_as_double
(GExiv2Metadata *self, const gchar* tag, gdouble def);
+
G_GNUC_INTERNAL const gchar* gexiv2_metadata_get_exif_tag_label (const gchar* tag, GError **error);
G_GNUC_INTERNAL const gchar* gexiv2_metadata_get_exif_tag_description (const gchar* tag, GError **error);
G_GNUC_INTERNAL const gchar* gexiv2_metadata_get_exif_tag_type (const gchar* tag, GError **error);
+G_GNUC_INTERNAL gboolean gexiv2_metadata_exif_tag_supports_multiple_values(const gchar* tag, GError**
error);
+
G_GNUC_INTERNAL GBytes* gexiv2_metadata_get_exif_tag_raw (GExiv2Metadata
*self, const gchar* tag, GError **error);
/* private XMP functions */
@@ -84,6 +87,7 @@ G_GNUC_INTERNAL gboolean gexiv2_metadata_set_xmp_tag_multiple (GExiv2Metadata *
G_GNUC_INTERNAL const gchar* gexiv2_metadata_get_xmp_tag_label (const gchar* tag, GError
**error);
G_GNUC_INTERNAL const gchar* gexiv2_metadata_get_xmp_tag_description (const gchar* tag, GError **error);
G_GNUC_INTERNAL const gchar* gexiv2_metadata_get_xmp_tag_type (const gchar* tag, GError **error);
+G_GNUC_INTERNAL gboolean gexiv2_metadata_xmp_tag_supports_multiple_values(GExiv2Metadata* self, const
gchar* tag, GError** error);
G_GNUC_INTERNAL GBytes* gexiv2_metadata_get_xmp_tag_raw (GExiv2Metadata
*self, const gchar* tag, GError **error);
@@ -100,6 +104,7 @@ G_GNUC_INTERNAL gboolean gexiv2_metadata_set_iptc_tag_multiple
(GExiv2Metadata
G_GNUC_INTERNAL const gchar* gexiv2_metadata_get_iptc_tag_label (const gchar* tag, GError **error);
G_GNUC_INTERNAL const gchar* gexiv2_metadata_get_iptc_tag_description (const gchar* tag, GError
**error);
G_GNUC_INTERNAL const gchar* gexiv2_metadata_get_iptc_tag_type (const gchar* tag, GError **error);
+G_GNUC_INTERNAL gboolean gexiv2_metadata_iptc_tag_supports_multiple_values(const gchar* tag, GError**
error);
G_GNUC_INTERNAL GBytes* gexiv2_metadata_get_iptc_tag_raw (GExiv2Metadata
*self, const gchar* tag, GError **error);
diff --git a/gexiv2/gexiv2-metadata-xmp.cpp b/gexiv2/gexiv2-metadata-xmp.cpp
index f0de00f..777d624 100644
--- a/gexiv2/gexiv2-metadata-xmp.cpp
+++ b/gexiv2/gexiv2-metadata-xmp.cpp
@@ -525,6 +525,47 @@ const gchar* gexiv2_metadata_get_xmp_tag_type (const gchar* tag, GError **error)
return NULL;
}
+gboolean gexiv2_metadata_xmp_tag_supports_multiple_values(GExiv2Metadata* self, const gchar* tag, GError**
error) {
+ g_return_val_if_fail(GEXIV2_IS_METADATA(self), FALSE);
+ g_return_val_if_fail(self->priv != nullptr, FALSE);
+ g_return_val_if_fail(self->priv->image.get() != nullptr, FALSE);
+ g_return_val_if_fail(tag != nullptr, FALSE);
+ g_return_val_if_fail(error == nullptr || *error == nullptr, FALSE);
+
+ try {
+ const Exiv2::XmpKey key(tag); // Check tag is in correct format
+ const gchar* type = gexiv2_metadata_get_xmp_tag_type(tag, error);
+
+ if (error != nullptr && *error != nullptr) {
+ g_set_error_literal(error, g_quark_from_string("GExiv2"), (*error)->code, (*error)->message);
+ return FALSE;
+ }
+
+ if (type == nullptr)
+ throw Exiv2::Error(Exiv2::ErrorCode::kerInvalidKey, tag);
+
+ // If @tag has a valid familyName and groupName, Exiv2 will return
+ // "XmpText" even if the tagName has never been added (e.g.
+ // "Xmp.dc.TagDoesNotExist").
+ // For consistency with the `_supports_multiple_values` Exif and Iptc functions,
+ // check if @tag exists - Note: all built-in tags have a label.
+ const auto& xmp_data = self->priv->image->xmpData();
+
+ if (g_ascii_strcasecmp(type, "XmpText") == 0 && gexiv2_metadata_get_xmp_tag_label(tag, error) ==
nullptr &&
+ xmp_data.findKey(key) == xmp_data.end()) {
+ throw Exiv2::Error(Exiv2::ErrorCode::kerInvalidKey, tag);
+ }
+
+ if (g_ascii_strcasecmp(type, "XmpAlt") == 0 || g_ascii_strcasecmp(type, "XmpBag") == 0 ||
+ g_ascii_strcasecmp(type, "XmpSeq") == 0 || g_ascii_strcasecmp(type, "LangAlt") == 0) {
+ return TRUE;
+ }
+ } catch (Exiv2::Error& e) {
+ g_set_error_literal(error, g_quark_from_string("GExiv2"), e.code(), e.what());
+ }
+ return FALSE;
+}
+
GBytes* gexiv2_metadata_get_xmp_tag_raw (GExiv2Metadata *self, const gchar* tag, GError **error) {
g_return_val_if_fail(GEXIV2_IS_METADATA (self), NULL);
g_return_val_if_fail(tag != NULL, NULL);
diff --git a/gexiv2/gexiv2-metadata.cpp b/gexiv2/gexiv2-metadata.cpp
index 6448c2b..d51977b 100644
--- a/gexiv2/gexiv2-metadata.cpp
+++ b/gexiv2/gexiv2-metadata.cpp
@@ -1438,6 +1438,29 @@ const gchar* gexiv2_metadata_get_tag_type (const gchar *tag) {
return value;
}
+gboolean gexiv2_metadata_try_tag_supports_multiple_values(GExiv2Metadata* self, const gchar* tag, GError**
error) {
+ g_return_val_if_fail(GEXIV2_IS_METADATA(self), FALSE);
+ g_return_val_if_fail(self->priv != nullptr, FALSE);
+ g_return_val_if_fail(self->priv->image.get() != nullptr, FALSE);
+ g_return_val_if_fail(tag != nullptr, FALSE);
+ g_return_val_if_fail(error == nullptr || *error == nullptr, FALSE);
+
+ if (gexiv2_metadata_is_iptc_tag(tag) == TRUE)
+ return gexiv2_metadata_iptc_tag_supports_multiple_values(tag, error);
+
+ if (gexiv2_metadata_is_xmp_tag(tag) == TRUE)
+ return gexiv2_metadata_xmp_tag_supports_multiple_values(self, tag, error);
+
+ if (gexiv2_metadata_is_exif_tag(tag) == TRUE)
+ return gexiv2_metadata_exif_tag_supports_multiple_values(tag, error);
+
+ // Invalid tag (Family name)
+ Exiv2::Error e(Exiv2::ErrorCode::kerInvalidKey, tag);
+ g_set_error_literal(error, g_quark_from_string("GExiv2"), e.code(), e.what());
+
+ return FALSE;
+}
+
GBytes* gexiv2_metadata_try_get_tag_raw(GExiv2Metadata *self, const gchar* tag, GError **error) {
g_return_val_if_fail(GEXIV2_IS_METADATA (self), NULL);
g_return_val_if_fail(tag != NULL, NULL);
diff --git a/gexiv2/gexiv2-metadata.h b/gexiv2/gexiv2-metadata.h
index 6b413de..a247ec4 100644
--- a/gexiv2/gexiv2-metadata.h
+++ b/gexiv2/gexiv2-metadata.h
@@ -535,6 +535,21 @@ const gchar* gexiv2_metadata_try_get_tag_type (const gchar *tag, GError
**error)
G_DEPRECATED_FOR(gexiv2_metadata_try_get_tag_type)
const gchar* gexiv2_metadata_get_tag_type (const gchar *tag);
+/**
+ * gexiv2_metadata_try_tag_supports_multiple_values:
+ * @self: An instance of #GExiv2Metadata
+ * @tag: An Exiv2 tag
+ * @error: (allow-none): A return location for a #GError or %NULL
+ *
+ * The Exiv2 Tag Reference can be found at <ulink url="https://www.exiv2.org/metadata.html"></ulink>
+ *
+ * Returns: Whether @tag is capable of storing multiple values or not. If @tag is undefined
+ * (i.e. not built-in and not added to @self), then @error is set and %FALSE is returned.
+ *
+ * Since: 0.12.2
+ */
+gboolean gexiv2_metadata_try_tag_supports_multiple_values(GExiv2Metadata* self, const gchar* tag, GError**
error);
+
/**
* gexiv2_metadata_get_supports_exif:
* @self: An instance of #GExiv2Metadata
diff --git a/test/python/test_metadata.py b/test/python/test_metadata.py
index 4361dc5..9d325df 100644
--- a/test/python/test_metadata.py
+++ b/test/python/test_metadata.py
@@ -32,7 +32,7 @@ PY3K = sys.version_info[0] == 3
import gi
gi.require_version('GExiv2', '0.10')
-from gi.repository import GExiv2
+from gi.repository import GExiv2, GLib
from fractions import Fraction
@@ -185,6 +185,7 @@ class TestMetadata(unittest.TestCase):
'stop_emission',
'stop_emission_by_name',
'thaw_notify',
+ 'try_tag_supports_multiple_values',
'try_delete_gps_info',
'try_generate_xmp_packet',
'try_get_exif_tag_rational',
@@ -722,6 +723,41 @@ generated the image. When the field is left blank, it is treated as unknown.""")
self.assertEqual(
self.metadata.get_iptc_tags(), ['Iptc.Application2.City'])
+ def test_try_tag_supports_multiple_values(self):
+ # Test different types of built-in/added tags for Exif/Iptc/Xmp
+
+ # Add a new xmpText string
+ self.metadata.set_tag_string('Xmp.dc.NewXmpTextValue', 'New Value')
+
+ ### Test Exif tags
+ self.assertFalse(self.metadata.try_tag_supports_multiple_values('Exif.Image.Model'))
+
self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Exif.Madeup.Orientation')
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Exif.Image.MadeUp')
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Exif.MadeUp.MadeUp')
+
+ ### Test Iptc tags
+ self.assertTrue(self.metadata.try_tag_supports_multiple_values('Iptc.Application2.Keywords'))
+ self.assertFalse(self.metadata.try_tag_supports_multiple_values('Iptc.Envelope.ServiceId'))
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Iptc.Madeup.Keywords')
+
self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Iptc.Application2.Madeup')
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Iptc.Madeup.MadeUp')
+
+ ### Test Xmp tags
+ self.assertFalse(self.metadata.try_tag_supports_multiple_values('Xmp.xmpMM.InstanceID'))
+ self.assertTrue(self.metadata.try_tag_supports_multiple_values('Xmp.dc.subject'))
+ self.assertTrue(self.metadata.try_tag_supports_multiple_values('Xmp.photoshop.TextLayers'))
+ self.assertTrue(self.metadata.try_tag_supports_multiple_values('Xmp.xmpRights.UsageTerms'))
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Xmp.MadeUp.subject')
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Xmp.dc.MadeUp')
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'Xmp.MadeUp.MadeUp')
+ self.assertFalse(self.metadata.try_tag_supports_multiple_values('Xmp.dc.NewXmpTextValue'))
+
+ ### Test Other tags
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values, '')
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'MadeUp.dc.subject')
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'MadeUp.MadeUp.subject')
+ self.assertRaises(GLib.GError,self.metadata.try_tag_supports_multiple_values,'MadeUp.MadeUp.MadeUp')
+
def test_delete_gps_info(self):
# Longitude, latitude, altitude
self.metadata.set_gps_info(-123.35, 48.43, 10)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]