From 16c95f0fab5efe24f428bbc0a1a32cff394b92aa Mon Sep 17 00:00:00 2001 From: Andreas Huggel Date: Sun, 23 Sep 2007 16:15:49 +0000 Subject: [PATCH] Extended xmpsample.cpp, related bugfixes and tweaks. --- README-XMP | 27 ++++++------ src/doxygen.hpp.in | 4 ++ src/error.cpp | 5 +-- src/properties.cpp | 22 +++++----- src/properties.hpp | 27 +++++------- src/value.hpp | 2 +- src/xmp.cpp | 26 +++++++---- src/xmp.hpp | 16 ++++++- src/xmpsample.cpp | 75 ++++++++++++++++++++++++++------ test/data/xmpparser-test.out | 84 ++++++++++++++++++++++++++++++++++++ test/xmpparser-test.sh | 4 ++ 11 files changed, 224 insertions(+), 68 deletions(-) diff --git a/README-XMP b/README-XMP index 64968324..9abb5cba 100644 --- a/README-XMP +++ b/README-XMP @@ -6,17 +6,6 @@ Exif and IPTC classes. The property-repository is XmpProperties. In addition to the expected new members, class Image also has a new interface to access the raw XMP packet. -Todo: XMP support is controlled with the ENABLE_XMP directive -in config/config.mk.in. This will be a configure option -eventually. For now, comment the line "ENABLE_XMP = 1" either -in config.mk.in before running ./configure or in config.mk to -build without XMP support. - -Exiv2 uses the Adobe XMP toolkit (XMP SDK) to parse -and serialize XMP packets (only the XMPCore component). - -Supported XMP value types -------------------------- All XMP value types are supported: Simple types, structures, arrays, property qualifiers and language alternatives. @@ -35,16 +24,28 @@ namespaces can be registered. The specialized Exiv2 values XmpArrayValue and LangAltValue are provided to simplify the use of XMP properties. +See xmpsample.cpp for examples of how to set various types of +XMP properties. + Note: Unlike Exif and IPTC tags, XMP properties do not have a tag number. Todo: Conversion between XMP and Exif/IPTC metadata. -XMP toolkit installation +Todo: XMP support is controlled with the ENABLE_XMP directive +in config/config.mk.in. This will be a configure option +eventually. For now, comment the line "ENABLE_XMP = 1" either +in config.mk.in before running ./configure or in config.mk to +build without XMP support. + +Exiv2 uses the Adobe XMP Toolkit (XMP SDK) to parse +and serialize XMP packets (only the XMPCore component). + +XMP Toolkit installation ======================== This is what worked for me on a Debian GNU/Linux (testing) system. Your mileage may vary. Please check with Adobe if -you encounter problems with the XMP toolkit installation. +you encounter problems with the XMP Toolkit installation. Installation directory ---------------------- diff --git a/src/doxygen.hpp.in b/src/doxygen.hpp.in index bacadeb1..e3c97e77 100644 --- a/src/doxygen.hpp.in +++ b/src/doxygen.hpp.in @@ -133,3 +133,7 @@ bug tracking system.

@example iptceasy.cpp The quickest way to access, set or modify IPTC metadata */ +/*! + @example xmpsample.cpp + Sample usage of high-level XMP classes. + */ diff --git a/src/error.cpp b/src/error.cpp index a6b701cc..83d7d212 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -78,9 +78,9 @@ namespace Exiv2 { ErrMsg( 33, N_("This does not look like a CRW image")), ErrMsg( 34, N_("%1: Not supported")), // %1=function ErrMsg( 35, N_("No namespace info available for XMP prefix `%1'")), // %1=prefix - ErrMsg( 36, N_("No XMP property list for prefix `%1'")), // %1=prefix + ErrMsg( 36, N_("No prefix registered for namespace `%1'")), // %1=namespace ErrMsg( 37, N_("Size of %1 JPEG segment is larger than 65535 bytes")), // %1=type of metadata (Exif, IPTC, JPEG comment) - ErrMsg( 38, N_("Unknown XMP property `%1:%2'")), // %1=prefix, %2=property name + ErrMsg( 38, N_("Unhandled Xmpdatum %1 of type %2")), // %1=key, %2=value type ErrMsg( 39, N_("Unhandled XMP node %1 with opt=%2")), // %1=key, %2=XMP Toolkit option flags ErrMsg( 40, N_("XMP Toolkit error %1: %2")), // %1=XMP_Error::GetID(), %2=XMP_Error::GetErrMsg() ErrMsg( 41, N_("Failed to decode Lang Alt property %1 with opt=%2")), // %1=property path, %3=XMP Toolkit option flags @@ -89,7 +89,6 @@ namespace Exiv2 { ErrMsg( 44, N_("Failed to determine property name from path %1, namespace %2")), // %1=property path, %2=namespace ErrMsg( 45, N_("Schema namespace %1 is not registered with the XMP Toolkit")), // %1=namespace ErrMsg( 46, N_("No namespace registered for prefix `%1'")), // %1=prefix - ErrMsg( 47, N_("No prefix registered for namespace `%1'")), // %1=namespace // Last error message (message is not used) ErrMsg( -2, N_("(Unknown Error)")) diff --git a/src/properties.cpp b/src/properties.cpp index e16bd778..424ad677 100644 --- a/src/properties.cpp +++ b/src/properties.cpp @@ -37,6 +37,7 @@ EXIV2_RCSID("@(#) $Id$") #include "value.hpp" #include "metadatum.hpp" #include "i18n.h" // NLS support. +#include "xmp.hpp" #include #include @@ -793,8 +794,10 @@ namespace Exiv2 { const std::string& prefix) { std::string ns2 = ns; - if (ns2.substr(ns2.size() - 1, 1) != "/") ns2 += "/"; + if ( ns2.substr(ns2.size() - 1, 1) != "/" + && ns2.substr(ns2.size() - 1, 1) != "#") ns2 += "/"; nsRegistry_[ns2] = prefix; + XmpParser::registerNs(ns2, prefix); } std::string XmpProperties::prefix(const std::string& ns) @@ -827,28 +830,26 @@ namespace Exiv2 { const char* XmpProperties::propertyTitle(const XmpKey& key) { - return propertyInfo(key)->title_; + const XmpPropertyInfo* pi = propertyInfo(key); + return pi ? pi->title_ : 0; } const char* XmpProperties::propertyDesc(const XmpKey& key) { - return propertyInfo(key)->desc_; + const XmpPropertyInfo* pi = propertyInfo(key); + return pi ? pi->desc_ : 0; } TypeId XmpProperties::propertyType(const XmpKey& key) { - const XmpPropertyInfo* pi = propertyInfo(key, false); + const XmpPropertyInfo* pi = propertyInfo(key); return pi ? pi->typeId_ : xmpText; } - const XmpPropertyInfo* XmpProperties::propertyInfo(const XmpKey& key, - bool doThrow) + const XmpPropertyInfo* XmpProperties::propertyInfo(const XmpKey& key) { const XmpPropertyInfo* pl = propertyList(key.groupName()); - if (!pl) { - if (doThrow) throw Error(36, key.groupName()); - else return 0; - } + if (!pl) return 0; const XmpPropertyInfo* pi = 0; for (int i = 0; pl[i].name_ != 0; ++i) { if (std::string(pl[i].name_) == key.tagName()) { @@ -856,7 +857,6 @@ namespace Exiv2 { break; } } - if (!pi && doThrow) throw Error(38, key.groupName(), key.tagName()); return pi; } diff --git a/src/properties.hpp b/src/properties.hpp index 1260fbdf..ba533ecd 100644 --- a/src/properties.hpp +++ b/src/properties.hpp @@ -108,15 +108,15 @@ namespace Exiv2 { /*! @brief Return the title (label) of the property. @param key The property key - @return The title (label) of the property - @throw Error if the key is invalid. + @return The title (label) of the property, 0 if the + key is of an unknown property. */ static const char* propertyTitle(const XmpKey& key); /*! @brief Return the description of the property. @param key The property key - @return The description of the property - @throw Error if the key is invalid. + @return The description of the property, 0 if the + key is of an unknown property. */ static const char* propertyDesc(const XmpKey& key); /*! @@ -128,21 +128,16 @@ namespace Exiv2 { static TypeId propertyType(const XmpKey& key); /*! @brief Return information for the property for key. - Always returns a valid pointer. @param key The property key - @param doThrow Flag indicating whether to throw an Error or - return 0 if the key is not valid or unknown. - @return a pointer to the property information - @throw Error if the key is unknown and the \em doThrow - flag is true. + @return A pointer to the property information, 0 if the + key is of an unknown property. */ - static const XmpPropertyInfo* propertyInfo(const XmpKey& key, - bool doThrow =true); + static const XmpPropertyInfo* propertyInfo(const XmpKey& key); /*! @brief Return the namespace name for the schema associated with \em prefix. @param prefix Prefix - @return the namespace name + @return The namespace name @throw Error if no namespace is registered with \em prefix. */ static std::string ns(const std::string& prefix); @@ -150,7 +145,7 @@ namespace Exiv2 { @brief Return the namespace description for the schema associated with \em prefix. @param prefix Prefix - @return the namespace description + @return The namespace description @throw Error if no namespace is registered with \em prefix. */ static const char* nsDesc(const std::string& prefix); @@ -166,14 +161,14 @@ namespace Exiv2 { @brief Return information about a schema namespace for \em prefix. Always returns a valid pointer. @param prefix The prefix - @return a pointer to the related information + @return A pointer to the related information @throw Error if no namespace is registered with \em prefix. */ static const XmpNsInfo* nsInfo(const std::string& prefix); /*! @brief Return the (preferred) prefix for schema namespace \em ns. @param ns Schema namespace - @return the prefix or an empty string if namespace \em ns is not + @return The prefix or an empty string if namespace \em ns is not registered. */ static std::string prefix(const std::string& ns); diff --git a/src/value.hpp b/src/value.hpp index e4db4aa4..70e0d029 100644 --- a/src/value.hpp +++ b/src/value.hpp @@ -652,7 +652,7 @@ namespace Exiv2 { //! Set the XMP array type to indicate that an XMP value is an array. void setXmpArrayType(XmpArrayType xmpArrayType); //! Set the XMP struct type to indicate that an XMP value is a structure. - void setXmpStruct(XmpStruct xmpStruct); + void setXmpStruct(XmpStruct xmpStruct =xsStruct); /*! @brief Read the value from a character buffer. diff --git a/src/xmp.cpp b/src/xmp.cpp index bfa7dfc9..9242755f 100644 --- a/src/xmp.cpp +++ b/src/xmp.cpp @@ -387,6 +387,17 @@ namespace Exiv2 { } } + bool XmpParser::registerNs(const std::string& ns, + const std::string& prefix) + { + initialize(); +#ifdef EXV_HAVE_XMP_TOOLKIT + return SXMPMeta::RegisterNamespace(ns.c_str(), prefix.c_str(), 0); +#else + return true; +#endif + } + #ifdef EXV_HAVE_XMP_TOOLKIT int XmpParser::decode( XmpData& xmpData, const std::string& xmpPacket) @@ -549,15 +560,8 @@ namespace Exiv2 { } SXMPMeta meta; - for (XmpData::const_iterator i = xmpData.begin(); i != xmpData.end(); ++i) { - - std::string ns = XmpProperties::ns(i->groupName()); - - // Todo: Make sure the namespace is registered with XMP-SDK - - // Todo: What about structure namespaces? - + const std::string ns = XmpProperties::ns(i->groupName()); XMP_OptionBits options = 0; if (i->typeId() == langAlt) { @@ -605,6 +609,7 @@ namespace Exiv2 { #endif meta.SetProperty(ns.c_str(), item.c_str(), i->toString(idx).c_str()); } + continue; } if (i->typeId() == xmpText) { if (i->count() == 0) { @@ -619,7 +624,10 @@ namespace Exiv2 { #endif meta.SetProperty(ns.c_str(), i->tagName().c_str(), i->toString(0).c_str(), options); } + continue; } + // Don't let any Xmpdatum go by unnoticed + throw Error(38, i->tagName(), TypeInfo::typeName(i->typeId())); } meta.SerializeToBuffer(&xmpPacket, kXMP_UseCompactFormat); @@ -771,7 +779,7 @@ namespace { property = propPath.substr(idx + 1); std::string prefix = Exiv2::XmpProperties::prefix(schemaNs); if (prefix.empty()) { - throw Exiv2::Error(47, propPath, schemaNs); + throw Exiv2::Error(36, propPath, schemaNs); } return Exiv2::XmpKey::AutoPtr(new Exiv2::XmpKey(prefix, property)); } // makeXmpKey diff --git a/src/xmp.hpp b/src/xmp.hpp index bc9e4983..b61aeceb 100644 --- a/src/xmp.hpp +++ b/src/xmp.hpp @@ -32,9 +32,9 @@ // ***************************************************************************** // included header files #include "metadatum.hpp" -#include "types.hpp" -#include "value.hpp" #include "properties.hpp" +#include "value.hpp" +#include "types.hpp" // + standard includes #include @@ -245,6 +245,7 @@ namespace Exiv2 { the XMP toolkit to do the job. */ class XmpParser { + friend void XmpProperties::registerNs(const std::string&, const std::string&); public: /*! @brief Decode XMP metadata from an XMP packet \em xmpPacket into @@ -295,6 +296,17 @@ namespace Exiv2 { static void terminate(); private: + /*! + @brief Register a namespace with the XMP Toolkit. + + XmpProperties::registerNs calls this to synchronize namespaces. + + @return \em true if the registered prefix matches the suggested prefix. + */ + static bool registerNs(const std::string& ns, + const std::string& prefix); + + // DATA static bool initialized_; //! Indicates if the XMP Toolkit has been initialized }; // class XmpParser diff --git a/src/xmpsample.cpp b/src/xmpsample.cpp index 4dc1adfc..7e193be1 100644 --- a/src/xmpsample.cpp +++ b/src/xmpsample.cpp @@ -1,8 +1,7 @@ // ***************************************************************** -*- C++ -*- // xmpsample.cpp, $Rev$ -// Sample/test for high level XMP classes +// Sample/test for high level XMP classes. See also addmoddel.cpp -#include "value.hpp" #include "xmp.hpp" #include "error.hpp" @@ -10,31 +9,79 @@ #include #include -using namespace Exiv2; - int main() try { // The XMP property container Exiv2::XmpData xmpData; + // ------------------------------------------------------------------------- + // Exiv2 has specialized values for simple XMP properties, arrays of simple + // properties and language alternatives. + // Add a simple XMP property in a known namespace - Exiv2::Value::AutoPtr v = Exiv2::Value::create(xmpText); + Exiv2::Value::AutoPtr v = Exiv2::Value::create(Exiv2::xmpText); v->read("image/jpeg"); xmpData.add(Exiv2::XmpKey("Xmp.dc.format"), v.get()); - // Add an ordered array of text values - v = Exiv2::Value::create(xmpSeq); - v->read("1) The first creator"); // the sequence in which the array elements - v->read("2) The second creator"); // are added is relevant - v->read("3) And another one"); + // Add an ordered array of text values. + v = Exiv2::Value::create(Exiv2::xmpSeq); // or xmpBag or xmpAlt. + v->read("1) The first creator"); // The sequence in which the array + v->read("2) The second creator"); // elements are added is their + v->read("3) And another one"); // order in the array. xmpData.add(Exiv2::XmpKey("Xmp.dc.creator"), v.get()); // Add a language alternative property - v = Exiv2::Value::create(langAlt); - v->read("lang=de-DE Hallo, Welt"); // the default doesn't need a qualifier - v->read("Hello, World"); // and it will become the first element + v = Exiv2::Value::create(Exiv2::langAlt); + v->read("lang=de-DE Hallo, Welt"); // The default doesn't need a + v->read("Hello, World"); // qualifier xmpData.add(Exiv2::XmpKey("Xmp.dc.description"), v.get()); + // ------------------------------------------------------------------------- + // Register a namespace which Exiv2 doesn't know yet. This is only needed + // when properties are added manually. If the XMP metadata is read from an + // image, namespaces are decoded and registered at the same time. + Exiv2::XmpProperties::registerNs("myNamespace/", "ns"); + + // ------------------------------------------------------------------------- + // There are no specialized values for structures, qualifiers and nested + // types. However, these can be added by using a XmpTextValue and a path as + // the key. + + // Add a structure + Exiv2::XmpTextValue tv("16"); + xmpData.add(Exiv2::XmpKey("Xmp.xmpDM.videoFrameSize/stDim:w"), &tv); + tv.read("9"); + xmpData.add(Exiv2::XmpKey("Xmp.xmpDM.videoFrameSize/stDim:h"), &tv); + tv.read("inch"); + xmpData.add(Exiv2::XmpKey("Xmp.xmpDM.videoFrameSize/stDim:unit"), &tv); + + // Add an element with a qualifier (using the namespace registered earlier) + tv.read("James Bond"); + xmpData.add(Exiv2::XmpKey("Xmp.dc.publisher"), &tv); + tv.read("secret agent"); + xmpData.add(Exiv2::XmpKey("Xmp.dc.publisher/?ns:role"), &tv); + + // Add a qualifer to an array element of Xmp.dc.creator (added above) + tv.read("programmer"); + xmpData.add(Exiv2::XmpKey("Xmp.dc.creator[2]/?ns:role"), &tv); + + // Add an array of structures + tv.read(""); + tv.setXmpArrayType(Exiv2::XmpValue::xaBag); + xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef"), &tv); // Set the array type. + tv.setXmpArrayType(Exiv2::XmpValue::xaNone); + + tv.read("Birtday party"); + xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[1]/stJob:name"), &tv); + tv.read("Photographer"); + xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[1]/stJob:role"), &tv); + + tv.read("Wedding ceremony"); + xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[2]/stJob:name"), &tv); + tv.read("Best man"); + xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[2]/stJob:role"), &tv); + + // ------------------------------------------------------------------------- // Output XMP properties for (Exiv2::XmpData::const_iterator md = xmpData.begin(); md != xmpData.end(); ++md) { @@ -50,6 +97,7 @@ try { << std::endl; } + // ------------------------------------------------------------------------- // Serialize the XMP data and output the XMP packet std::string xmpPacket; if (0 != Exiv2::XmpParser::encode(xmpPacket, xmpData)) { @@ -59,6 +107,7 @@ try { // Cleanup Exiv2::XmpParser::terminate(); + return 0; } catch (Exiv2::AnyError& e) { diff --git a/test/data/xmpparser-test.out b/test/data/xmpparser-test.out index 330b9a01..dc84e59e 100644 --- a/test/data/xmpparser-test.out +++ b/test/data/xmpparser-test.out @@ -279,3 +279,87 @@ Xmp.ns1.NestedStructProp/ns2:Outer/ns2:Middle/ns2:Inner/ns2:Field2 XmpText 12 > > \ No newline at end of file +Xmp.dc.format XmpText 10 image/jpeg +Xmp.dc.creator XmpSeq 3 1) The first creator, 2) The second creator, 3) And another one +Xmp.dc.description LangAlt 2 lang="de-DE" Hallo, Welt, lang="x-default" Hello, World +Xmp.xmpDM.videoFrameSize/stDim:w XmpText 2 16 +Xmp.xmpDM.videoFrameSize/stDim:h XmpText 1 9 +Xmp.xmpDM.videoFrameSize/stDim:unit XmpText 4 inch +Xmp.dc.publisher XmpText 10 James Bond +Xmp.dc.publisher/?ns:role XmpText 12 secret agent +Xmp.dc.creator[2]/?ns:role XmpText 10 programmer +Xmp.xmpBJ.JobRef XmpText 0 +Xmp.xmpBJ.JobRef[1]/stJob:name XmpText 13 Birtday party +Xmp.xmpBJ.JobRef[1]/stJob:role XmpText 12 Photographer +Xmp.xmpBJ.JobRef[2]/stJob:name XmpText 16 Wedding ceremony +Xmp.xmpBJ.JobRef[2]/stJob:role XmpText 8 Best man + + + + + + + 1) The first creator + + 2) The second creator + programmer + + 3) And another one + + + + + Hello, World + Hallo, Welt + + + + James Bond + secret agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/xmpparser-test.sh b/test/xmpparser-test.sh index 705d4ce1..9ddba930 100755 --- a/test/xmpparser-test.sh +++ b/test/xmpparser-test.sh @@ -52,6 +52,10 @@ $binpath/xmpparse ${testfile} > t1 2>&1 $binpath/xmpparse ${testfile}-new > t2 2>&1 diff t1 t2 +# ---------------------------------------------------------------------- +# xmpsample +$binpath/xmpsample + ) > $results 2>&1 # ----------------------------------------------------------------------