1 // ***************************************************************** -*- C++ -*-
2 /*
3  * Copyright (C) 2004-2021 Exiv2 authors
4  * This program is part of the Exiv2 distribution.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
19  */
20 // xmpsample.cpp
21 // Sample/test for high level XMP classes. See also addmoddel.cpp
22 
23 #include <exiv2/exiv2.hpp>
24 #include "unused.h"
25 
26 #include <string>
27 #include <iostream>
28 #include <iomanip>
29 #include <cassert>
30 #include <cmath>
31 
isEqual(float a,float b)32 bool isEqual(float a, float b)
33 {
34     double d = std::fabs(a - b);
35     return d < 0.00001;
36 }
37 
main()38 int main()
39 try {
40     Exiv2::XmpParser::initialize();
41     ::atexit(Exiv2::XmpParser::terminate);
42 #ifdef EXV_ENABLE_BMFF
43     Exiv2::enableBMFF();
44 #endif
45 
46     // The XMP property container
47     Exiv2::XmpData xmpData;
48 
49     // -------------------------------------------------------------------------
50     // Teaser: Setting XMP properties doesn't get much easier than this:
51 
52     xmpData["Xmp.dc.source"]  = "xmpsample.cpp";    // a simple text value
53     xmpData["Xmp.dc.subject"] = "Palmtree";         // an array item
54     xmpData["Xmp.dc.subject"] = "Rubbertree";       // add a 2nd array item
55     // a language alternative with two entries and without default
56     xmpData["Xmp.dc.title"]   = "lang=de-DE Sonnenuntergang am Strand";
57     xmpData["Xmp.dc.title"]   = "lang=en-US Sunset on the beach";
58 
59     // -------------------------------------------------------------------------
60     // Any properties can be set provided the namespace is known. Values of any
61     // type can be assigned to an Xmpdatum, if they have an output operator. The
62     // default XMP value type for unknown properties is a simple text value.
63 
64     xmpData["Xmp.dc.one"]     = -1;
65     xmpData["Xmp.dc.two"]     = 3.1415;
66     xmpData["Xmp.dc.three"]   = Exiv2::Rational(5, 7);
67     xmpData["Xmp.dc.four"]    = uint16_t(255);
68     xmpData["Xmp.dc.five"]    = 256;
69     xmpData["Xmp.dc.six"]     = false;
70 
71     // In addition, there is a dedicated assignment operator for Exiv2::Value
72     Exiv2::XmpTextValue val("Seven");
73     xmpData["Xmp.dc.seven"]   = val;
74     xmpData["Xmp.dc.eight"]   = true;
75 
76     // Extracting values
77     assert(xmpData["Xmp.dc.one"].toLong() == -1);
78     assert(xmpData["Xmp.dc.one"].value().ok());
79 
80     const Exiv2::Value &getv1 = xmpData["Xmp.dc.one"].value();
81     UNUSED(getv1);
82     assert(isEqual(getv1.toFloat(), -1));
83     assert(getv1.ok());
84     assert(getv1.toRational() == Exiv2::Rational(-1, 1));
85     assert(getv1.ok());
86 
87     const Exiv2::Value &getv2 = xmpData["Xmp.dc.two"].value();
88     UNUSED(getv2);
89     assert(isEqual(getv2.toFloat(), 3.1415f));
90     assert(getv2.ok());
91     assert(getv2.toLong() == 3);
92     assert(getv2.ok());
93     Exiv2::Rational R = getv2.toRational();
94     UNUSED(R);
95     assert(getv2.ok());
96     assert(isEqual(static_cast<float>(R.first) / R.second, 3.1415f ));
97 
98     const Exiv2::Value &getv3 = xmpData["Xmp.dc.three"].value();
99     UNUSED(getv3);
100     assert(isEqual(getv3.toFloat(), 5.0f/7.0f));
101     assert(getv3.ok());
102     assert(getv3.toLong() == 0);  // long(5.0 / 7.0)
103     assert(getv3.ok());
104     assert(getv3.toRational() == Exiv2::Rational(5, 7));
105     assert(getv3.ok());
106 
107     const Exiv2::Value &getv6 = xmpData["Xmp.dc.six"].value();
108     UNUSED(getv6);
109     assert(getv6.toLong() == 0);
110     assert(getv6.ok());
111     assert(getv6.toFloat() == 0.0f);
112     assert(getv6.ok());
113     assert(getv6.toRational() == Exiv2::Rational(0, 1));
114     assert(getv6.ok());
115 
116     const Exiv2::Value &getv7 = xmpData["Xmp.dc.seven"].value();
117     getv7.toLong(); // this should fail
118     assert(!getv7.ok());
119 
120     const Exiv2::Value &getv8 = xmpData["Xmp.dc.eight"].value();
121     UNUSED(getv8);
122     assert(getv8.toLong() == 1);
123     assert(getv8.ok());
124     assert(getv8.toFloat() == 1.0f);
125     assert(getv8.ok());
126     assert(getv8.toRational() == Exiv2::Rational(1, 1));
127     assert(getv8.ok());
128 
129     // Deleting an XMP property
130     Exiv2::XmpData::iterator pos = xmpData.findKey(Exiv2::XmpKey("Xmp.dc.eight"));
131     if (pos == xmpData.end()) throw Exiv2::Error(Exiv2::kerErrorMessage, "Key not found");
132     xmpData.erase(pos);
133 
134     // -------------------------------------------------------------------------
135     // Exiv2 has specialized values for simple XMP properties, arrays of simple
136     // properties and language alternatives.
137 
138     // Add a simple XMP property in a known namespace
139     Exiv2::Value::AutoPtr v = Exiv2::Value::create(Exiv2::xmpText);
140     v->read("image/jpeg");
141     xmpData.add(Exiv2::XmpKey("Xmp.dc.format"), v.get());
142 
143     // Add an ordered array of text values.
144     v = Exiv2::Value::create(Exiv2::xmpSeq); // or xmpBag or xmpAlt.
145     v->read("1) The first creator");         // The sequence in which the array
146     v->read("2) The second creator");        // elements are added is their
147     v->read("3) And another one");           // order in the array.
148     xmpData.add(Exiv2::XmpKey("Xmp.dc.creator"), v.get());
149 
150     // Add a language alternative property
151     v = Exiv2::Value::create(Exiv2::langAlt);
152     v->read("lang=de-DE Hallo, Welt");       // The default doesn't need a
153     v->read("Hello, World");                 // qualifier
154     xmpData.add(Exiv2::XmpKey("Xmp.dc.description"), v.get());
155 
156     // According to the XMP specification, Xmp.tiff.ImageDescription is an
157     // alias for Xmp.dc.description. Exiv2 treats an alias just like any
158     // other property and leaves it to the application to implement specific
159     // behaviour if desired.
160     xmpData["Xmp.tiff.ImageDescription"] = "TIFF image description";
161     xmpData["Xmp.tiff.ImageDescription"] = "lang=de-DE TIFF Bildbeschreibung";
162 
163     // -------------------------------------------------------------------------
164     // Register a namespace which Exiv2 doesn't know yet. This is only needed
165     // when properties are added manually. If the XMP metadata is read from an
166     // image, namespaces are decoded and registered at the same time.
167     Exiv2::XmpProperties::registerNs("myNamespace/", "ns");
168 
169     // -------------------------------------------------------------------------
170     // Add a property in the new custom namespace.
171     xmpData["Xmp.ns.myProperty"] = "myValue";
172 
173     // -------------------------------------------------------------------------
174     // There are no specialized values for structures, qualifiers and nested
175     // types. However, these can be added by using an XmpTextValue and a path as
176     // the key.
177 
178     // Add a structure
179     Exiv2::XmpTextValue tv("16");
180     xmpData.add(Exiv2::XmpKey("Xmp.xmpDM.videoFrameSize/stDim:w"), &tv);
181     tv.read("9");
182     xmpData.add(Exiv2::XmpKey("Xmp.xmpDM.videoFrameSize/stDim:h"), &tv);
183     tv.read("inch");
184     xmpData.add(Exiv2::XmpKey("Xmp.xmpDM.videoFrameSize/stDim:unit"), &tv);
185 
186     // Add an element with a qualifier (using the namespace registered above)
187     xmpData["Xmp.dc.publisher"] = "James Bond";  // creates an unordered array
188     xmpData["Xmp.dc.publisher[1]/?ns:role"] = "secret agent";
189 
190     // Add a qualifer to an array element of Xmp.dc.creator (added above)
191     tv.read("programmer");
192     xmpData.add(Exiv2::XmpKey("Xmp.dc.creator[2]/?ns:role"), &tv);
193 
194     // Add an array of structures
195     tv.read("");                                         // Clear the value
196     tv.setXmpArrayType(Exiv2::XmpValue::xaBag);
197     xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef"), &tv); // Set the array type.
198 
199     tv.setXmpArrayType(Exiv2::XmpValue::xaNone);
200     tv.read("Birthday party");
201     xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[1]/stJob:name"), &tv);
202     tv.read("Photographer");
203     xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[1]/stJob:role"), &tv);
204 
205     tv.read("Wedding ceremony");
206     xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[2]/stJob:name"), &tv);
207     tv.read("Best man");
208     xmpData.add(Exiv2::XmpKey("Xmp.xmpBJ.JobRef[2]/stJob:role"), &tv);
209 
210     // Add a creator contact info structure
211     xmpData["Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity"] = "Kuala Lumpur";
212     xmpData["Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry"] = "Malaysia";
213     xmpData["Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork"] = "http://www.exiv2.org";
214 
215     // -------------------------------------------------------------------------
216     // Output XMP properties
217     for (Exiv2::XmpData::const_iterator md = xmpData.begin();
218         md != xmpData.end(); ++md) {
219         std::cout << std::setfill(' ') << std::left
220                   << std::setw(44)
221                   << md->key() << " "
222                   << std::setw(9) << std::setfill(' ') << std::left
223                   << md->typeName() << " "
224                   << std::dec << std::setw(3)
225                   << std::setfill(' ') << std::right
226                   << md->count() << "  "
227                   << std::dec << md->value()
228                   << std::endl;
229     }
230 
231     // -------------------------------------------------------------------------
232     // Serialize the XMP data and output the XMP packet
233     std::string xmpPacket;
234     if (0 != Exiv2::XmpParser::encode(xmpPacket, xmpData)) {
235         throw Exiv2::Error(Exiv2::kerErrorMessage, "Failed to serialize XMP data");
236     }
237     std::cout << xmpPacket << "\n";
238 
239     // Cleanup
240     Exiv2::XmpParser::terminate();
241 
242     return 0;
243 }
244 catch (Exiv2::AnyError& e) {
245     std::cout << "Caught Exiv2 exception '" << e << "'\n";
246     return -1;
247 }
248