|
|
|
// ***************************************************************** -*- C++ -*-
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2004-2021 Exiv2 authors
|
|
|
|
* This program is part of the Exiv2 distribution.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
|
|
|
// xmpsample.cpp
|
|
|
|
// Sample/test for high level XMP classes. See also addmoddel.cpp
|
|
|
|
|
|
|
|
#include <exiv2/exiv2.hpp>
|
|
|
|
#include "unused.h"
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
#include <iostream>
|
|
|
|
#include <iomanip>
|
|
|
|
#include <cassert>
|
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
bool isEqual(float a, float b)
|
|
|
|
{
|
|
|
|
double d = std::fabs(a - b);
|
|
|
|
return d < 0.00001;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main()
|
|
|
|
try {
|
|
|
|
Exiv2::XmpParser::initialize();
|
|
|
|
::atexit(Exiv2::XmpParser::terminate);
|
|
|
|
#ifdef EXV_ENABLE_BMFF
|
|
|
|
Exiv2::enableBMFF();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// 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"] = 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;
|
|
|
|
xmpData["Xmp.dc.eight"] = true;
|
|
|
|
|
|
|
|
// Extracting values
|
|
|
|
assert(xmpData["Xmp.dc.one"].toLong() == -1);
|
|
|
|
assert(xmpData["Xmp.dc.one"].value().ok());
|
|
|
|
|
|
|
|
const Exiv2::Value &getv1 = xmpData["Xmp.dc.one"].value();
|
|
|
|
UNUSED(getv1);
|
|
|
|
assert(isEqual(getv1.toFloat(), -1));
|
|
|
|
assert(getv1.ok());
|
|
|
|
assert(getv1.toRational() == Exiv2::Rational(-1, 1));
|
|
|
|
assert(getv1.ok());
|
|
|
|
|
|
|
|
const Exiv2::Value &getv2 = xmpData["Xmp.dc.two"].value();
|
|
|
|
UNUSED(getv2);
|
|
|
|
assert(isEqual(getv2.toFloat(), 3.1415f));
|
|
|
|
assert(getv2.ok());
|
|
|
|
assert(getv2.toLong() == 3);
|
|
|
|
assert(getv2.ok());
|
|
|
|
Exiv2::Rational R = getv2.toRational();
|
|
|
|
UNUSED(R);
|
|
|
|
assert(getv2.ok());
|
|
|
|
assert(isEqual(static_cast<float>(R.first) / R.second, 3.1415f ));
|
|
|
|
|
|
|
|
const Exiv2::Value &getv3 = xmpData["Xmp.dc.three"].value();
|
|
|
|
UNUSED(getv3);
|
|
|
|
assert(isEqual(getv3.toFloat(), 5.0f/7.0f));
|
|
|
|
assert(getv3.ok());
|
|
|
|
assert(getv3.toLong() == 0); // long(5.0 / 7.0)
|
|
|
|
assert(getv3.ok());
|
|
|
|
assert(getv3.toRational() == Exiv2::Rational(5, 7));
|
|
|
|
assert(getv3.ok());
|
|
|
|
|
|
|
|
const Exiv2::Value &getv6 = xmpData["Xmp.dc.six"].value();
|
|
|
|
UNUSED(getv6);
|
|
|
|
assert(getv6.toLong() == 0);
|
|
|
|
assert(getv6.ok());
|
|
|
|
assert(getv6.toFloat() == 0.0f);
|
|
|
|
assert(getv6.ok());
|
|
|
|
assert(getv6.toRational() == Exiv2::Rational(0, 1));
|
|
|
|
assert(getv6.ok());
|
|
|
|
|
|
|
|
const Exiv2::Value &getv7 = xmpData["Xmp.dc.seven"].value();
|
|
|
|
getv7.toLong(); // this should fail
|
|
|
|
assert(!getv7.ok());
|
|
|
|
|
|
|
|
const Exiv2::Value &getv8 = xmpData["Xmp.dc.eight"].value();
|
|
|
|
UNUSED(getv8);
|
|
|
|
assert(getv8.toLong() == 1);
|
|
|
|
assert(getv8.ok());
|
|
|
|
assert(getv8.toFloat() == 1.0f);
|
|
|
|
assert(getv8.ok());
|
|
|
|
assert(getv8.toRational() == Exiv2::Rational(1, 1));
|
|
|
|
assert(getv8.ok());
|
|
|
|
|
|
|
|
// Deleting an XMP property
|
|
|
|
auto pos = xmpData.findKey(Exiv2::XmpKey("Xmp.dc.eight"));
|
|
|
|
if (pos == xmpData.end()) throw Exiv2::Error(Exiv2::kerErrorMessage, "Key not found");
|
|
|
|
xmpData.erase(pos);
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// 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::UniquePtr 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");
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// Add a property in the new custom namespace.
|
|
|
|
xmpData["Xmp.ns.myProperty"] = "myValue";
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
// Add a creator contact info structure
|
|
|
|
xmpData["Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity"] = "Kuala Lumpur";
|
|
|
|
xmpData["Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry"] = "Malaysia";
|
|
|
|
xmpData["Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork"] = "http://www.exiv2.org";
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
// Output XMP properties
|
|
|
|
for (auto &&md : xmpData) {
|
|
|
|
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(Exiv2::kerErrorMessage, "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;
|
|
|
|
}
|