diff --git a/samples/xmpsample.cpp b/samples/xmpsample.cpp index 2d13ef81..279f67f2 100644 --- a/samples/xmpsample.cpp +++ b/samples/xmpsample.cpp @@ -20,7 +20,9 @@ try { xmpData["Xmp.dc.source"] = "xmpsample.cpp"; // a simple text value xmpData["Xmp.dc.subject"] = "Palmtree"; // an array item xmpData["Xmp.dc.subject"] = "Rubbertree"; // add a 2nd array item - xmpData["Xmp.dc.title"] = "lang=en-US Beach"; // a language alternative + // a language alternative with two entries and without default + xmpData["Xmp.dc.title"] = "lang=de-DE Sonnenuntergang am Strand"; + xmpData["Xmp.dc.title"] = "lang=en-US Sunset on the beach"; // ------------------------------------------------------------------------- // Any properties can be set provided the namespace is known. Values of any @@ -86,11 +88,9 @@ try { 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 an element with a qualifier (using the namespace registered above) + xmpData["Xmp.dc.publisher"] = "James Bond"; // creates an unordered array + xmpData["Xmp.dc.publisher[1]/?ns:role"] = "secret agent"; // Add a qualifer to an array element of Xmp.dc.creator (added above) tv.read("programmer"); @@ -102,7 +102,7 @@ try { xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef"), &tv); // Set the array type. tv.setXmpArrayType(Exiv2::XmpValue::xaNone); - tv.read("Birtday party"); + tv.read("Birthday 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); diff --git a/src/error.cpp b/src/error.cpp index a64e428c..cb4621af 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -90,6 +90,7 @@ namespace Exiv2 { 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_("Aliases are not supported. Please send this XMP packet to ahuggel@gmx.net `%1', `%2', `%3'")), // %1=namespace, %2=property path, %3=value + ErrMsg( 48, N_("Invalid XmpText type `%1'")), // %1=type // Last error message (message is not used) ErrMsg( -2, N_("(Unknown Error)")) diff --git a/src/value.cpp b/src/value.cpp index 38d4ce24..490a4aad 100644 --- a/src/value.cpp +++ b/src/value.cpp @@ -495,7 +495,36 @@ namespace Exiv2 { int XmpTextValue::read(const std::string& buf) { - value_ = buf; + // support a type=Alt,Bag,Seq,Struct indicator + std::string b = buf; + std::string type; + if (buf.length() > 5 && buf.substr(0, 5) == "type=") { + std::string::size_type pos = buf.find_first_of(' '); + type = buf.substr(5, pos-5); + // Strip quotes (so you can also specify the type without quotes) + if (type[0] == '"') type = type.substr(1); + if (type[type.length()-1] == '"') type = type.substr(0, type.length()-1); + b.clear(); + if (pos != std::string::npos) b = buf.substr(pos+1); + } + if (!type.empty()) { + if (type == "Alt") { + setXmpArrayType(XmpValue::xaAlt); + } + else if (type == "Bag") { + setXmpArrayType(XmpValue::xaBag); + } + else if (type == "Seq") { + setXmpArrayType(XmpValue::xaSeq); + } + else if (type == "Struct") { + setXmpStruct(); + } + else { + throw Error(48, type); + } + } + value_ = b; return 0; } @@ -516,6 +545,24 @@ namespace Exiv2 { std::ostream& XmpTextValue::write(std::ostream& os) const { + bool del = false; + if (xmpArrayType() != XmpValue::xaNone) { + switch (xmpArrayType()) { + case XmpValue::xaAlt: os << "type=\"Alt\""; break; + case XmpValue::xaBag: os << "type=\"Bag\""; break; + case XmpValue::xaSeq: os << "type=\"Seq\""; break; + case XmpValue::xaNone: break; // just to suppress the warning + } + del = true; + } + else if (xmpStruct() != XmpValue::xsNone) { + switch (xmpStruct()) { + case XmpValue::xsStruct: os << "type=\"Struct\""; break; + case XmpValue::xsNone: break; // just to suppress the warning + } + del = true; + } + if (del && !value_.empty()) os << " "; return os << value_; } diff --git a/src/value.hpp b/src/value.hpp index c6f67882..98faa638 100644 --- a/src/value.hpp +++ b/src/value.hpp @@ -716,6 +716,21 @@ namespace Exiv2 { //! @name Manipulators //@{ + /*! + @brief Read a simple property value from \em buf to set the value. + + Sets the value to the contents of \em buf. A optional keyword, + \em type is supported to set the XMP value type. This is useful for + complex value types for which Exiv2 does not have direct support. + + The format of \em buf is: +
+ [type=["]Alt|Bag|Seq|Struct["] ]text +
+ + @return 0 if successful. + */ + virtual int read(const std::string& buf); //@} diff --git a/test/data/cmdxmp.txt b/test/data/cmdxmp.txt new file mode 100644 index 00000000..75bfa982 --- /dev/null +++ b/test/data/cmdxmp.txt @@ -0,0 +1,82 @@ +# Sample Exiv2 command file for XMP tags +# -------------------------------------- + +# Set basic properties. Exiv2 uses the value type of the XMP specification +# for the property, if it is not specified. The default XMP value type +# for unknown properties is a simple text value. + +# A simple text property. +set Xmp.dc.source xmpsample.cpp + +# An array item (unordered array). +set Xmp.dc.subject "Palmtree" + +# Add a 2nd array item +set Xmp.dc.subject "Rubbertree" + +# A language alternative (without a default) +set Xmp.dc.title lang=en-US Sunset on the beach +set Xmp.dc.title lang=de-DE Sonnenuntergang am Strand + +# Any properties can be set provided the namespace is known. +set Xmp.dc.one -1 +set Xmp.dc.two 3.1415 +set Xmp.dc.three 5/7 +set Xmp.dc.four 255 +set Xmp.dc.five 256 +set Xmp.dc.six false +set Xmp.dc.seven Seven + +# The value type can be specified. Exiv2 has support for a limited number +# of specific XMP types with built-in types: The basic XmpText, array +# types XmpAlt (alternative array), XmpBag (unordered array), XmpSeq +# (ordered array) and language alternatives LangAlt. + +# Simple text property with explicitly specified value type +set Xmp.dc.format XmpText "image/jpeg" + +# An ordered array +set Xmp.dc.creator XmpSeq "1) The first creator" +set Xmp.dc.creator "2) The second creator" +set Xmp.dc.creator "3) And another one" + +# A language alternative. The default entry of a langauge alternative +# doesn't need a language qualifier. +set Xmp.dc.description LangAlt lang=de-DE Hallo, Welt +set Xmp.dc.description LangAlt Hello, World + +# According to the XMP specification, Xmp.tiff.ImageDescription is an +# alias for Xmp.dc.description. Exiv2 treats an alias just like any +# other property. +set Xmp.tiff.ImageDescription TIFF image description +set Xmp.tiff.ImageDescription lang=de-DE TIFF Bildbeschreibung + +# Register a namespace which Exiv2 doesn't know yet with a prefix. +reg ns myNamespace/ + +# There are no built-in Exiv2 value types for structures, qualifiers and +# nested types. However, these can be added by using an XmpText value and a +# path as the key. + +# Add a structure +set Xmp.xmpDM.videoFrameSize/stDim:w 16 +set Xmp.xmpDM.videoFrameSize/stDim:h 9 +set Xmp.xmpDM.videoFrameSize/stDim:unit inch + +# Add an element with a qualifier (using the namespace registered earlier) +set Xmp.dc.publisher James Bond +set Xmp.dc.publisher/?ns:role secret agent + +# Add a qualifer to an array element of Xmp.dc.creator (added above) +set Xmp.dc.creator[2]/?ns:role programmer + +# Add an array of structures. First set a text property with just the +# array type. (Note: this is not the same as creating an XmpBag property.) +set Xmp.xmpBJ.JobRef XmpText type=Bag + +# Then set the array items. Each of them is a structure with two elements. +set Xmp.xmpBJ.JobRef[1]/stJob:name XmpText Birthday party +set Xmp.xmpBJ.JobRef[1]/stJob:role XmpText Photographer + +set Xmp.xmpBJ.JobRef[2]/stJob:name Wedding ceremony +set Xmp.xmpBJ.JobRef[2]/stJob:role Best man diff --git a/test/data/xmpparser-test.out b/test/data/xmpparser-test.out index 0e6ccdf6..36f1f2c1 100644 --- a/test/data/xmpparser-test.out +++ b/test/data/xmpparser-test.out @@ -9,7 +9,7 @@ Xmp.xmp.ModifyDate XmpText 25 2005-09-07T15:09:51- Xmp.xmp.MetadataDate XmpText 25 2006-04-10T13:37:10-07:00 Xmp.xmpMM.DocumentID XmpText 37 uuid:9A3B7F52214211DAB6308A7391270C13 Xmp.xmpMM.InstanceID XmpText 37 uuid:B59AC1B3214311DAB6308A7391270C13 -Xmp.xmpMM.DerivedFrom XmpText 0 +Xmp.xmpMM.DerivedFrom XmpText 0 type="Struct" Xmp.xmpMM.DerivedFrom/stRef:instanceID XmpText 37 uuid:9A3B7F4F214211DAB6308A7391270C13 Xmp.xmpMM.DerivedFrom/stRef:documentID XmpText 37 uuid:9A3B7F4E214211DAB6308A7391270C13 Xmp.photoshop.ColorMode XmpText 1 3 @@ -40,7 +40,7 @@ Xmp.exif.NativeDigest XmpText 414 36864,40960,40961,37 Xmp.iptc.IntellectualGenre XmpText 7 Profile Xmp.iptc.Location XmpText 17 Moore family farm Xmp.iptc.CountryCode XmpText 2 US -Xmp.iptc.CreatorContactInfo XmpText 0 +Xmp.iptc.CreatorContactInfo XmpText 0 type="Struct" Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr XmpText 30 Big Newspaper, 123 Main Street Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity XmpText 6 Boston Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion XmpText 13 Massachusetts @@ -81,7 +81,7 @@ Xmp.xmp.ModifyDate XmpText 25 2005-03-13T02:01:44- Xmp.xmp.MetadataDate XmpText 25 2007-01-08T13:25:45+01:00 Xmp.xmp.CreatorTool XmpText 26 Adobe Photoshop CS Windows Xmp.xmpMM.DocumentID XmpText 58 adobe:docid:photoshop:0f410643-9396-11d9-bb8e-a67e6693b6e9 -Xmp.xmpMM.DerivedFrom XmpText 0 +Xmp.xmpMM.DerivedFrom XmpText 0 type="Struct" Xmp.xmpMM.DerivedFrom/stRef:instanceID XmpText 41 uuid:0f410640-9396-11d9-bb8e-a67e6693b6e9 Xmp.xmpMM.DerivedFrom/stRef:documentID XmpText 58 adobe:docid:photoshop:e4d002a0-9392-11d9-bb8e-a67e6693b6e9 Xmp.xmpMM.InstanceID XmpText 41 uuid:0f410644-9396-11d9-bb8e-a67e6693b6e9 @@ -148,7 +148,7 @@ Xmp.ns1.SimpleProp2 XmpText 13 Simple2 value Xmp.ns1.SimpleProp2/?xml:lang XmpText 9 x-default Xmp.ns1.ArrayProp1 XmpBag 2 Item1.1 value, Item1.2 value Xmp.ns1.ArrayProp2 LangAlt 2 lang="x-one" Item2.1 value, lang="x-two" Item2.2 value -Xmp.ns1.StructProp XmpText 0 +Xmp.ns1.StructProp XmpText 0 type="Struct" Xmp.ns1.StructProp/ns2:Field1 XmpText 12 Field1 value Xmp.ns1.StructProp/ns2:Field2 XmpText 12 Field2 value Xmp.ns1.QualProp1 XmpText 10 Prop value @@ -156,10 +156,10 @@ Xmp.ns1.QualProp1/?ns2:Qual XmpText 10 Qual value Xmp.ns1.QualProp2 XmpText 10 Prop value Xmp.ns1.QualProp2/?xml:lang XmpText 9 x-default Xmp.ns1.QualProp2/?ns2:Qual XmpText 10 Qual value -Xmp.ns1.NestedStructProp XmpText 0 -Xmp.ns1.NestedStructProp/ns2:Outer XmpText 0 -Xmp.ns1.NestedStructProp/ns2:Outer/ns2:Middle XmpText 0 -Xmp.ns1.NestedStructProp/ns2:Outer/ns2:Middle/ns2:Inner XmpText 0 +Xmp.ns1.NestedStructProp XmpText 0 type="Struct" +Xmp.ns1.NestedStructProp/ns2:Outer XmpText 0 type="Struct" +Xmp.ns1.NestedStructProp/ns2:Outer/ns2:Middle XmpText 0 type="Struct" +Xmp.ns1.NestedStructProp/ns2:Outer/ns2:Middle/ns2:Inner XmpText 0 type="Struct" Xmp.ns1.NestedStructProp/ns2:Outer/ns2:Middle/ns2:Inner/ns2:Field1 XmpText 12 Field1 value Xmp.ns1.NestedStructProp/ns2:Outer/ns2:Middle/ns2:Inner/ns2:Field2 XmpText 12 Field2 value -----> Encoding XMP data to write to xmpsdk.xmp-new <----- @@ -281,7 +281,7 @@ Xmp.ns1.NestedStructProp/ns2:Outer/ns2:Middle/ns2:Inner/ns2:Field2 XmpText 12 \ No newline at end of file Xmp.dc.source XmpText 13 xmpsample.cpp Xmp.dc.subject XmpBag 2 Palmtree, Rubbertree -Xmp.dc.title LangAlt 1 lang="en-US" Beach +Xmp.dc.title LangAlt 2 lang="de-DE" Sonnenuntergang am Strand, lang="en-US" Sunset on the beach Xmp.dc.one XmpText 2 -1 Xmp.dc.two XmpText 6 3.1415 Xmp.dc.three XmpText 3 5/7 @@ -296,11 +296,11 @@ Xmp.tiff.ImageDescription LangAlt 2 lang="x-default" TIF 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.publisher XmpBag 1 James Bond +Xmp.dc.publisher[1]/?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 XmpText 0 type="Bag" +Xmp.xmpBJ.JobRef[1]/stJob:name XmpText 14 Birthday 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 @@ -332,7 +332,8 @@ Xmp.xmpBJ.JobRef[2]/stJob:role XmpText 8 Best man - Beach + Sonnenuntergang am Strand + Sunset on the beach @@ -351,9 +352,13 @@ Xmp.xmpBJ.JobRef[2]/stJob:role XmpText 8 Best man Hallo, Welt - - James Bond - secret agent + + + + James Bond + secret agent + + @@ -368,7 +373,7 @@ Xmp.xmpBJ.JobRef[2]/stJob:role XmpText 8 Best man +File 1/1: exiv2-empty.jpg +Set Xmp.dc.source "xmpsample.cpp" (XmpText) +Set Xmp.dc.subject "Palmtree" (XmpBag) +Set Xmp.dc.subject "Rubbertree" (XmpBag) +Set Xmp.dc.title "lang=en-US Sunset on the beach" (LangAlt) +Set Xmp.dc.title "lang=de-DE Sonnenuntergang am Strand" (LangAlt) +Set Xmp.dc.one "-1" (XmpText) +Set Xmp.dc.two "3.1415" (XmpText) +Set Xmp.dc.three "5/7" (XmpText) +Set Xmp.dc.four "255" (XmpText) +Set Xmp.dc.five "256" (XmpText) +Set Xmp.dc.six "false" (XmpText) +Set Xmp.dc.seven "Seven" (XmpText) +Set Xmp.dc.format "image/jpeg" (XmpText) +Set Xmp.dc.creator "1) The first creator" (XmpSeq) +Set Xmp.dc.creator "2) The second creator" (XmpSeq) +Set Xmp.dc.creator "3) And another one" (XmpSeq) +Set Xmp.dc.description "lang=de-DE Hallo, Welt" (LangAlt) +Set Xmp.dc.description "Hello, World" (LangAlt) +Set Xmp.tiff.ImageDescription "TIFF image description" (LangAlt) +Set Xmp.tiff.ImageDescription "lang=de-DE TIFF Bildbeschreibung" (LangAlt) +Reg ns="myNamespace/" +Set Xmp.xmpDM.videoFrameSize/stDim:w "16" (XmpText) +Set Xmp.xmpDM.videoFrameSize/stDim:h "9" (XmpText) +Set Xmp.xmpDM.videoFrameSize/stDim:unit "inch" (XmpText) +Set Xmp.dc.publisher "James Bond" (XmpBag) +Set Xmp.dc.publisher/?ns:role "secret agent" (XmpText) +Set Xmp.dc.creator[2]/?ns:role "programmer" (XmpText) +Set Xmp.xmpBJ.JobRef "type=Bag" (XmpText) +Set Xmp.xmpBJ.JobRef[1]/stJob:name "Birthday party" (XmpText) +Set Xmp.xmpBJ.JobRef[1]/stJob:role "Photographer" (XmpText) +Set Xmp.xmpBJ.JobRef[2]/stJob:name "Wedding ceremony" (XmpText) +Set Xmp.xmpBJ.JobRef[2]/stJob:role "Best man" (XmpText) +File 1/1: exiv2-empty.jpg +Xmp.dc.source XmpText 13 xmpsample.cpp +Xmp.dc.one XmpText 2 -1 +Xmp.dc.two XmpText 6 3.1415 +Xmp.dc.three XmpText 3 5/7 +Xmp.dc.four XmpText 3 255 +Xmp.dc.five XmpText 3 256 +Xmp.dc.six XmpText 5 false +Xmp.dc.seven XmpText 5 Seven +Xmp.dc.format XmpText 10 image/jpeg +Xmp.dc.subject XmpBag 2 Palmtree, Rubbertree +Xmp.dc.title LangAlt 2 lang="de-DE" Sonnenuntergang am Strand, lang="en-US" Sunset on the beach +Xmp.dc.creator XmpText 0 type="Seq" +Xmp.dc.creator[1] XmpText 20 1) The first creator +Xmp.dc.creator[2] XmpText 21 2) The second creator +Xmp.dc.creator[2]/?ns:role XmpText 10 programmer +Xmp.dc.creator[3] XmpText 18 3) And another one +Xmp.dc.description LangAlt 2 lang="x-default" Hello, World, lang="de-DE" Hallo, Welt +Xmp.dc.publisher XmpText 0 type="Bag" +Xmp.dc.publisher/?ns:role XmpText 12 secret agent +Xmp.dc.publisher[1] XmpText 10 James Bond +Xmp.tiff.ImageDescription LangAlt 2 lang="x-default" TIFF image description, lang="de-DE" TIFF Bildbeschreibung +Xmp.xmpDM.videoFrameSize XmpText 0 type="Struct" +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.xmpBJ.JobRef XmpText 0 type="Bag" +Xmp.xmpBJ.JobRef[1] XmpText 0 type="Struct" +Xmp.xmpBJ.JobRef[1]/stJob:name XmpText 14 Birthday party +Xmp.xmpBJ.JobRef[1]/stJob:role XmpText 12 Photographer +Xmp.xmpBJ.JobRef[2] XmpText 0 type="Struct" +Xmp.xmpBJ.JobRef[2]/stJob:name XmpText 16 Wedding ceremony +Xmp.xmpBJ.JobRef[2]/stJob:role XmpText 8 Best man diff --git a/test/xmpparser-test.sh b/test/xmpparser-test.sh index 602a6d9a..59f64e7f 100755 --- a/test/xmpparser-test.sh +++ b/test/xmpparser-test.sh @@ -24,6 +24,7 @@ fi # Main routine ( binpath="$VALGRIND ../../samples" +exiv2="$VALGRIND ../../src/exiv2" cd ./tmp # ---------------------------------------------------------------------- @@ -54,6 +55,12 @@ diff t1 t2 # xmpsample $binpath/xmpsample +# ---------------------------------------------------------------------- +# XMP sample commands +cp -f ../data/exiv2-empty.jpg . +$exiv2 -v -m ../data/cmdxmp.txt exiv2-empty.jpg +$exiv2 -v -px exiv2-empty.jpg + ) > $results 2>&1 # ----------------------------------------------------------------------