Project

General

Profile

using XML elements instead of attributes when writing XMP tags

Added by Linxiao Geng over 5 years ago

I was trying to write some customized XMP tags into a JPG file and noticed that, with the following code:

        Exiv2::XmpData xmpData;
        Exiv2::XmpProperties::registerNs("http://ns.exampledomainn.com/Camera/1.0/","Camera");
        xmpData["Xmp.Camera.GPSXYAccuracy"]="2.0001";
        xmpData["Xmp.Camera.GPSZAccuracy"]="2.0001";

        Exiv2::XmpTextValue s;
        s.read("2.0");
        xmpData.add(Exiv2::XmpKey("Xmp.Camera.GPSXYAccuracy"), &s);
        xmpData.add(Exiv2::XmpKey("Xmp.Camera.GPSZAccuracy"), &s);

        image->setXmpData(xmpData);
        image->writeMetadata();

I got XMP written in the JPG like this:

<rdf:Description rdf:about="" xmlns:Camera="http://ns.exampledomainn.com/" Camera:GPSXYAccuracy="2.0001" Camera:GPSZAccuracy="2.0001" />
</rdf:RDF>

But what I wanted to achieve is:

<rdf:Description rdf:about="" xmlns:Camera="http://ns.exampledomainn.com/">
        <Camera:GPSXYAccuracy>"2.0001"</Camera:GPSXYAccuracy>
        <Camera:GPSZAccuracy>"2.0001"</Camera:GPSZAccuracy>
</rdf:RDF>

That is, instead of using XML attributes, using XML elements for my tags.

I googled the web and didn't find anything, maybe someone here can help me out? Thanks!


Replies (4)

RE: using XML elements instead of attributes when writing XMP tags - Added by Robin Mills over 5 years ago

Alan's our XMP expert. I've been able to change the behaviour from an attribute to a tag using XMPseq:

693 rmills@rmillsmbp:~/gnu/exiv2/trunk $ curl -O --silent http://clanmills.com/Stonehenge.jpg
694 rmills@rmillsmbp:~/gnu/exiv2/trunk $ exiv2 -M"reg Camera http://clanmills.com/Camera/1.0/" \
    -M"add Xmp.Camera.Accuracy XmpSeq 2.001" Stonehenge.jpg
695 rmills@rmillsmbp:~/gnu/exiv2/trunk $ exiv2 -pX Stonehenge.jpg | xmllint --pretty 1 - 
<?xml version="1.0"?>
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      xmlns:Camera="http://clanmills.com/Camera/1.0/" 
      rdf:about="" xmp:Rating="0" xmp:ModifyDate="2015-07-16T20:25:28+01:00">
      <dc:description>
        <rdf:Alt>
          <rdf:li xml:lang="x-default">Classic View</rdf:li>
        </rdf:Alt>
      </dc:description>
      <Camera:Accuracy>
        <rdf:Seq>
          <rdf:li>2.001</rdf:li>
        </rdf:Seq>
      </Camera:Accuracy>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
696 rmills@rmillsmbp:~/gnu/exiv2/trunk $ 
Of course it's gratuitously embellished with embedded tags: <rdf:Seq><rdf:li> and encoded within <rdf:Description>

An alternative approach is to extract the XMP to a sidecar with

exiv2 -pX Stonehenge.jpg > Stonehenge.xmp
Edit the sidecar with an external application and replace the XMP with
exiv2 -iX Stonehenge.jpg

RE: using XML elements instead of attributes when writing XMP tags - Added by Linxiao Geng over 5 years ago

Robin, thanks for responding! That looks promising. However, for performance considerations, I'm looking for achieving this through C++ code instead of command line. Any idea?

RE: using XML elements instead of attributes when writing XMP tags - Added by Robin Mills over 5 years ago

Two answers:

1) The exiv2 command-line app is a thin wrapper over the API. So, your C++ probably becomes something like: warning, not compiled or tested in any way:

        Exiv2::XmpData xmpData;
        Exiv2::XmpProperties::registerNs("http://ns.exampledomainn.com/Camera/1.0/","Camera");

        XmpArrayValue  s(xmpSeq);
        s.read("2.0");
        xmpData.add(Exiv2::XmpKey("Xmp.Camera.GPSXYAccuracy"), &s);
        xmpData.add(Exiv2::XmpKey("Xmp.Camera.GPSZAccuracy"), &s);

        image->setXmpData(xmpData);
        image->writeMetadata();

2) You can read and write streams using the API. exiv2 -pR uses the API image->printStructure(....);

I can't remember off-hand how -iX is implemented - you'll have to read the code. I implemented -pR. I don't know who wrote -iX. However I recommend that you discover a solution with the exiv2 application and then convert it into C++. The debugger will be your best friend to discover the APIs being used.

RE: using XML elements instead of attributes when writing XMP tags - Added by Robin Mills over 5 years ago

I've discovered something else about this. I've used the type XmpAlt to add Camera.Accuracy.

$ curl --silent -O http://clanmills.com/Stonehenge.jpg
$ exiv2 -M"reg Camera http://clanmills.com/Camera/1.0/" \ 
-M"set Xmp.Camera.Accuracy XmpAlt   9.11" Stonehenge.jpg
$ exiv2 -pX Stonehenge.jpg | xmllint --pretty 1 -
<?xml version="1.0"?>
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" 
   xmlns:dc="http://purl.org/dc/elements/1.1/" 
   xmlns:Camera="http://clanmills.com/Camera/1.0/" 
   rdf:about="" xmp:Rating="0" xmp:ModifyDate="2015-07-16T20:25:28+01:00">
      <dc:description>
        <rdf:Alt>
          <rdf:li xml:lang="x-default">Classic View</rdf:li>
        </rdf:Alt>
      </dc:description>
      <Camera:Accuracy>
        <rdf:Alt>
          <rdf:li>9.11</rdf:li>
        </rdf:Alt>
      </Camera:Accuracy>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>$ 
The tag <Camera::Accuracy> is beautiful enclosed by tags <rdf:Alt><rdf:li> This seems to perfectly match the pattern of XMP/xml in the original image which was written by Google's Picasa application.

I'm not an expert in metadata and know very little about XMP. However I'm wondering if your target code is valid XMP:

<rdf:Description rdf:about="" xmlns:Camera="http://ns.exampledomainn.com/">
        <Camera:GPSXYAccuracy>"2.0001"</Camera:GPSXYAccuracy>
        <Camera:GPSZAccuracy>"2.0001"</Camera:GPSZAccuracy>
</rdf:RDF>
For certain this isn't valid XML, however I thing that's just a typo and the closing tag should be </rdf:Description>.

I'd like to reiterate my recommendation that you use the exiv2 application to obtain the result you desire and then figure out the C++ API to generate that from your code.

I've also investigated the behaviour of the test application xmpparser-test. It uses a side car which does use XMP/xml similar to your pattern.

600 rmills@rmillsmbp:~/gnu/exiv2/trunk $ xmllint --pretty 1 test/data/xmpsdk.xmp
<?xml version="1.0"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <rdf:Description xmlns:ns1="ns:test1/" xmlns:ns2="ns:test2/" rdf:about="">
    <ns1:SimpleProp1>Simple1 value</ns1:SimpleProp1>
    <ns1:SimpleProp2 xml:lang="x-default">Simple2 value</ns1:SimpleProp2>
    <ns1:ArrayProp1>
      <rdf:Bag>
        <rdf:li>Item1.1 value</rdf:li>
        <rdf:li>Item1.2 value</rdf:li>
      </rdf:Bag>
    </ns1:ArrayProp1>
    <ns1:ArrayProp2>
      <rdf:Alt>
        <rdf:li xml:lang="x-one">Item2.1 value</rdf:li>
        <rdf:li xml:lang="x-two">Item2.2 value</rdf:li>
      </rdf:Alt>
    </ns1:ArrayProp2>
    <ns1:StructProp rdf:parseType="Resource">
      <ns2:Field1>Field1 value</ns2:Field1>
      <ns2:Field2>Field2 value</ns2:Field2>
    </ns1:StructProp>
    <ns1:QualProp1 rdf:parseType="Resource">
      <rdf:value>Prop value</rdf:value>
      <ns2:Qual>Qual value</ns2:Qual>
    </ns1:QualProp1>
    <ns1:QualProp2 rdf:parseType="Resource">
      <rdf:value xml:lang="x-default">Prop value</rdf:value>
      <ns2:Qual>Qual value</ns2:Qual>
    </ns1:QualProp2>
    <ns1:NestedStructProp rdf:parseType="Resource">
      <ns2:Outer rdf:parseType="Resource">
        <ns2:Middle rdf:parseType="Resource">
          <ns2:Inner rdf:parseType="Resource">
            <ns2:Field1>Field1 value</ns2:Field1>
            <ns2:Field2>Field2 value</ns2:Field2>
          </ns2:Inner>
        </ns2:Middle>
      </ns2:Outer>
    </ns1:NestedStructProp>
  </rdf:Description>
</rdf:RDF>
601 rmills@rmillsmbp:~/gnu/exiv2/trunk $
The test application bin/xmpparser-test reads test/data/xmpsdk.xmp into memory then write the XMP as the sidecar test/data/xmpsdk.xmp-new.
602 rmills@rmillsmbp:~/gnu/exiv2/trunk $ bin/xmpparser-test test/data/xmpsdk.xmp
-----> Decoding XMP data read from test/data/xmpsdk.xmp <-----
Xmp.ns1.SimpleProp1                          XmpText    13  Simple1 value
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-two" Item2.2 value, lang="x-one" Item2.1 value
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
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  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 test/data/xmpsdk.xmp-new <-----
603 rmills@rmillsmbp:~/gnu/exiv2/trunk $ ls -alt test/data/xmpsdk.xml-new
ls: test/data/xmpsdk.xml-new: No such file or directory
604 rmills@rmillsmbp:~/gnu/exiv2/trunk $ ls -alt test/data/xmpsdk.xmp-new
-rw-r--r--+ 1 rmills  staff  3474 25 Jun 08:08 test/data/xmpsdk.xmp-new
605 rmills@rmillsmbp:~/gnu/exiv2/trunk $ xmllint --pretty 1 test/data/xmpsdk.xmp-new 
<?xml version="1.0"?>
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description xmlns:ns1="ns:test1/" xmlns:ns2="ns:test2/" rdf:about="" ns1:SimpleProp1="Simple1 value">
      <ns1:SimpleProp2 xml:lang="x-default">Simple2 value</ns1:SimpleProp2>
      <ns1:ArrayProp1>
        <rdf:Bag>
          <rdf:li>Item1.1 value</rdf:li>
          <rdf:li>Item1.2 value</rdf:li>
        </rdf:Bag>
      </ns1:ArrayProp1>
      <ns1:ArrayProp2>
        <rdf:Alt>
          <rdf:li xml:lang="x-two">Item2.2 value</rdf:li>
          <rdf:li xml:lang="x-one">Item2.1 value</rdf:li>
        </rdf:Alt>
      </ns1:ArrayProp2>
      <ns1:StructProp ns2:Field1="Field1 value" ns2:Field2="Field2 value"/>
      <ns1:QualProp1 rdf:parseType="Resource">
        <rdf:value>Prop value</rdf:value>
        <ns2:Qual>Qual value</ns2:Qual>
      </ns1:QualProp1>
      <ns1:QualProp2 xml:lang="x-default" rdf:parseType="Resource">
        <rdf:value>Prop value</rdf:value>
        <ns2:Qual>Qual value</ns2:Qual>
      </ns1:QualProp2>
      <ns1:NestedStructProp rdf:parseType="Resource">
        <ns2:Outer rdf:parseType="Resource">
          <ns2:Middle rdf:parseType="Resource">
            <ns2:Inner ns2:Field1="Field1 value" ns2:Field2="Field2 value"/>
          </ns2:Middle>
        </ns2:Outer>
      </ns1:NestedStructProp>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
606 rmills@rmillsmbp:~/gnu/exiv2/trunk $
As you can see ns1:SimpleProp1 is now an attribute and this matches your original report.

As you can also see, ns1:SimpleProp2 is written as a tag. The key difference between SimpleProp1 and SimpleProp2 is xml:lang="x-default". I haven't yet discovered how to use the exiv2 application to generate that pattern of XML in Stonehenge.jpg. I've run out of time at present for further investigation. Here's my current best shot. XmpAlt and LangAlt seem to behave identically.

$ curl --silent -O http://clanmills.com/Stonehenge.jpg
$ exiv2 -M"reg Camera http://clanmills.com/Camera/1.0/" -M"set Xmp.Camera.Accuracy XmpAlt lang=x-default 9.11" Stonehenge.jpg
$ exiv2 -pX Stonehenge.jpg | xmllint --pretty 1 -
<?xml version="1.0"?>
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description
      xmlns:xmp="http://ns.adobe.com/xap/1.0/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      xmlns:Camera="http://clanmills.com/Camera/1.0/" 
      rdf:about="" xmp:Rating="0" xmp:ModifyDate="2015-07-16T20:25:28+01:00">
      <dc:description>
        <rdf:Alt>
          <rdf:li xml:lang="x-default">Classic View</rdf:li>
        </rdf:Alt>
      </dc:description>
      <Camera:Accuracy>
        <rdf:Alt>
          <rdf:li>lang=x-default 9.11</rdf:li>
        </rdf:Alt>
      </Camera:Accuracy>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
$

    (1-4/4)