Image metadata library and tools

Example 5: xmpsample.cpp

Sample (test) usage of high level XMP classes. This example shows various aspects of setting XMP metadata, including complex types. See also addmoddel.cpp

// ***************************************************************** -*- C++ -*-
// xmpsample.cpp, $Rev: 1305 $
// Sample/test for high level XMP classes. See also addmoddel.cpp

#include <exiv2/xmp.hpp>
#include <exiv2/error.hpp>

#include <string>
#include <iostream>
#include <iomanip>

int main()
try {
    // The XMP property container
    Exiv2::XmpData xmpData;

    // -------------------------------------------------------------------------
    // Teaser: Setting XMP properties doesn't get much easier than this:

    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
    // 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
    // type can be assigned to an Xmpdatum, if they have an output operator. The
    // default XMP value type for unknown properties is a simple text value.

    xmpData["Xmp.dc.one"]     = -1;
    xmpData["Xmp.dc.two"]     = 3.1415;
    xmpData["Xmp.dc.three"]   = Exiv2::Rational(5, 7);
    xmpData["Xmp.dc.four"]    = uint16_t(255);
    xmpData["Xmp.dc.five"]    = int32_t(256);
    xmpData["Xmp.dc.six"]     = false;

    // In addition, there is a dedicated assignment operator for Exiv2::Value
    Exiv2::XmpTextValue val("Seven");
    xmpData["Xmp.dc.seven"]   = val;

    // -------------------------------------------------------------------------
    // 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(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(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(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());

    // According to the XMP specification, Xmp.tiff.ImageDescription is an
    // alias for Xmp.dc.description. Exiv2 treats an alias just like any
    // other property and leaves it to the application to implement specific
    // behaviour if desired.
    xmpData["Xmp.tiff.ImageDescription"] = "TIFF image description";
    xmpData["Xmp.tiff.ImageDescription"] = "lang=de-DE TIFF Bildbeschreibung";

    // -------------------------------------------------------------------------
    // 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 an 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 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");
    xmpData.add(Exiv2::XmpKey("Xmp.dc.creator[2]/?ns:role"), &tv);

    // Add an array of structures
    tv.read("");                                         // Clear the value
    tv.setXmpArrayType(Exiv2::XmpValue::xaBag);
    xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef"), &tv); // Set the array type.

    tv.setXmpArrayType(Exiv2::XmpValue::xaNone);
    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);

    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) {
        std::cout << std::setfill(' ') << std::left
                  << std::setw(44)
                  << md->key() << " "
                  << std::setw(9) << std::setfill(' ') << std::left
                  << md->typeName() << " "
                  << std::dec << std::setw(3)
                  << std::setfill(' ') << std::right
                  << md->count() << "  "
                  << std::dec << md->value()
                  << std::endl;
    }

    // -------------------------------------------------------------------------
    // Serialize the XMP data and output the XMP packet
    std::string xmpPacket;
    if (0 != Exiv2::XmpParser::encode(xmpPacket, xmpData)) {
        throw Exiv2::Error(1, "Failed to serialize XMP data");
    }
    std::cout << xmpPacket << "\n";

    // Cleanup
    Exiv2::XmpParser::terminate();

    return 0;
}
catch (Exiv2::AnyError& e) {
    std::cout << "Caught Exiv2 exception '" << e << "'\n";
    return -1;
}

The resulting XMP Exiv2 metadata and XMP packet is below. The same can be achieved with a set of commands to the exiv2 command line tool.

$ xmpsample

Xmp.dc.source                                XmpText    13  xmpsample.cpp
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.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.creator                               XmpSeq      3  1) The first creator, 2) The second creator, 3) And another one
Xmp.dc.description                           LangAlt     2  lang="x-default" Hello, World, lang="de-DE" Hallo, Welt
Xmp.tiff.ImageDescription                    LangAlt     2  lang="x-default" TIFF image description, lang="de-DE" TIFF Bildbeschreibung
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                             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  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

<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.1.1-Exiv2">
 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <rdf:Description rdf:about=""
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:ns="myNamespace/"
    xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
    xmlns:xmpDM="http://ns.adobe.com/xmp/1.0/DynamicMedia/"
    xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#"
    xmlns:xapBJ="http://ns.adobe.com/xap/1.0/bj/"
    xmlns:stJob="http://ns.adobe.com/xap/1.0/sType/Job#"
   dc:source="xmpsample.cpp"
   dc:one="-1"
   dc:two="3.1415"
   dc:three="5/7"
   dc:four="255"
   dc:five="256"
   dc:six="false"
   dc:seven="Seven"
   dc:format="image/jpeg">
   <dc:subject>
    <rdf:Bag>
     <rdf:li>Palmtree</rdf:li>
     <rdf:li>Rubbertree</rdf:li>
    </rdf:Bag>
   </dc:subject>
   <dc:title>
    <rdf:Alt>
     <rdf:li xml:lang="de-DE">Sonnenuntergang am Strand</rdf:li>
     <rdf:li xml:lang="en-US">Sunset on the beach</rdf:li>
    </rdf:Alt>
   </dc:title>
   <dc:creator>
    <rdf:Seq>
     <rdf:li>1) The first creator</rdf:li>
     <rdf:li rdf:parseType="Resource">
      <rdf:value>2) The second creator</rdf:value>
      <ns:role>programmer</ns:role>
     </rdf:li>
     <rdf:li>3) And another one</rdf:li>
    </rdf:Seq>
   </dc:creator>
   <dc:description>
    <rdf:Alt>
     <rdf:li xml:lang="x-default">Hello, World</rdf:li>
     <rdf:li xml:lang="de-DE">Hallo, Welt</rdf:li>
    </rdf:Alt>
   </dc:description>
   <dc:publisher>
    <rdf:Bag>
     <rdf:li rdf:parseType="Resource">
      <rdf:value>James Bond</rdf:value>
      <ns:role>secret agent</ns:role>
     </rdf:li>
    </rdf:Bag>
   </dc:publisher>
   <tiff:ImageDescription>
    <rdf:Alt>
     <rdf:li xml:lang="x-default">TIFF image description</rdf:li>
     <rdf:li xml:lang="de-DE">TIFF Bildbeschreibung</rdf:li>
    </rdf:Alt>
   </tiff:ImageDescription>
   <xmpDM:videoFrameSize
    stDim:w="16"
    stDim:h="9"
    stDim:unit="inch"/>
   <xapBJ:JobRef>
    <rdf:Bag>
     <rdf:li
      stJob:name="Birthday party"
      stJob:role="Photographer"/>
     <rdf:li
      stJob:name="Wedding ceremony"
      stJob:role="Best man"/>
    </rdf:Bag>
   </xapBJ:JobRef>
  </rdf:Description>
 </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>