-
Notifications
You must be signed in to change notification settings - Fork 286
/
Copy pathxmpsidecar.cpp
254 lines (229 loc) · 8.84 KB
/
xmpsidecar.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
// ***************************************************************** -*- 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.
*/
// *****************************************************************************
// included header files
#include "config.h"
#include "xmpsidecar.hpp"
#include "image.hpp"
#include "basicio.hpp"
#include "error.hpp"
#include "xmp_exiv2.hpp"
#include "futils.hpp"
#include "convert.hpp"
// + standard includes
#include <string>
#include <iostream>
#include <cassert>
// *****************************************************************************
namespace {
const char* xmlHeader = "<?xpacket begin=\"\xef\xbb\xbf\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n";
const long xmlHdrCnt = (long) std::strlen(xmlHeader); // without the trailing 0-character
const char* xmlFooter = "<?xpacket end=\"w\"?>";
}
// class member definitions
namespace Exiv2 {
XmpSidecar::XmpSidecar(BasicIo::AutoPtr io, bool create)
: Image(ImageType::xmp, mdXmp, io)
{
if (create) {
if (io_->open() == 0) {
IoCloser closer(*io_);
io_->write(reinterpret_cast<const byte*>(xmlHeader), xmlHdrCnt);
}
}
} // XmpSidecar::XmpSidecar
std::string XmpSidecar::mimeType() const
{
return "application/rdf+xml";
}
void XmpSidecar::setComment(const std::string& /*comment*/)
{
// not supported
throw(Error(kerInvalidSettingForImage, "Image comment", "XMP"));
}
void XmpSidecar::readMetadata()
{
#ifdef EXIV2_DEBUG_MESSAGES
std::cerr << "Reading XMP file " << io_->path() << "\n";
#endif
if (io_->open() != 0) {
throw Error(kerDataSourceOpenFailed, io_->path(), strError());
}
IoCloser closer(*io_);
// Ensure that this is the correct image type
if (!isXmpType(*io_, false)) {
if (io_->error() || io_->eof()) throw Error(kerFailedToReadImageData);
throw Error(kerNotAnImage, "XMP");
}
// Read the XMP packet from the IO stream
std::string xmpPacket;
const long len = 64 * 1024;
byte buf[len];
long l;
while ((l = io_->read(buf, len)) > 0) {
xmpPacket.append(reinterpret_cast<char*>(buf), l);
}
if (io_->error()) throw Error(kerFailedToReadImageData);
clearMetadata();
xmpPacket_ = xmpPacket;
if (xmpPacket_.size() > 0 && XmpParser::decode(xmpData_, xmpPacket_)) {
#ifndef SUPPRESS_WARNINGS
EXV_WARNING << "Failed to decode XMP metadata.\n";
#endif
}
// #1112 - store dates to deal with loss of TZ information during conversions
for (Exiv2::XmpData::const_iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) {
std::string key(it->key());
if ( key.find("Date") != std::string::npos ) {
std::string value(it->value().toString());
dates_[key] = value;
}
}
copyXmpToIptc(xmpData_, iptcData_);
copyXmpToExif(xmpData_, exifData_);
} // XmpSidecar::readMetadata
// lower case string
static std::string toLowerCase(std::string a)
{
for(size_t i=0 ; i < a.length() ; i++)
{
a[i]=tolower(a[i]);
}
return a;
}
static bool matchi(const std::string key,const char* substr)
{
return toLowerCase(key).find(substr) != std::string::npos;
}
void XmpSidecar::writeMetadata()
{
if (io_->open() != 0) {
throw Error(kerDataSourceOpenFailed, io_->path(), strError());
}
IoCloser closer(*io_);
if (writeXmpFromPacket() == false) {
// #589 copy XMP tags
Exiv2::XmpData copy ;
for (Exiv2::XmpData::const_iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) {
if ( !matchi(it->key(),"exif") && !matchi(it->key(),"iptc") ) {
copy[it->key()] = it->value();
}
}
// run the convertors
copyExifToXmp(exifData_, xmpData_);
copyIptcToXmp(iptcData_, xmpData_);
// #589 - restore tags which were modified by the convertors
for (Exiv2::XmpData::const_iterator it = copy.begin(); it != copy.end(); ++it) {
xmpData_[it->key()] = it->value() ;
}
// #1112 - restore dates if they lost their TZ info
for ( Exiv2::Dictionary_i it = dates_.begin() ; it != dates_.end() ; ++it ) {
std::string sKey = it->first;
Exiv2::XmpKey key(sKey);
if ( xmpData_.findKey(key) != xmpData_.end() ) {
std::string value_orig(it->second);
std::string value_now(xmpData_[sKey].value().toString());
// std::cout << key << " -> " << value_now << " => " << value_orig << std::endl;
if ( value_orig.find(value_now.substr(0,10)) != std::string::npos ) {
xmpData_[sKey] = value_orig ;
}
}
}
if (XmpParser::encode(xmpPacket_, xmpData_,
XmpParser::omitPacketWrapper|XmpParser::useCompactFormat) > 1) {
#ifndef SUPPRESS_WARNINGS
EXV_ERROR << "Failed to encode XMP metadata.\n";
#endif
}
}
if (xmpPacket_.size() > 0) {
if (xmpPacket_.substr(0, 5) != "<?xml") {
xmpPacket_ = xmlHeader + xmpPacket_ + xmlFooter;
}
BasicIo::AutoPtr tempIo(new MemIo);
assert(tempIo.get() != 0);
// Write XMP packet
if ( tempIo->write(reinterpret_cast<const byte*>(xmpPacket_.data()),
static_cast<long>(xmpPacket_.size()))
!= static_cast<long>(xmpPacket_.size())) throw Error(kerImageWriteFailed);
if (tempIo->error()) throw Error(kerImageWriteFailed);
io_->close();
io_->transfer(*tempIo); // may throw
}
} // XmpSidecar::writeMetadata
// *************************************************************************
// free functions
Image::AutoPtr newXmpInstance(BasicIo::AutoPtr io, bool create)
{
Image::AutoPtr image(new XmpSidecar(io, create));
if (!image->good()) {
image.reset();
}
return image;
}
bool isXmpType(BasicIo& iIo, bool advance)
{
/*
Check if the file starts with an optional XML declaration followed by
either an XMP header (<?xpacket ... ?>) or an <x:xmpmeta> element.
In addition, in order for empty XmpSidecar objects as created by
Exiv2 to pass the test, just an XML header is also considered ok.
*/
const int32_t len = 80;
byte buf[len];
iIo.read(buf, xmlHdrCnt + 1);
if ( iIo.eof()
&& 0 == strncmp(reinterpret_cast<const char*>(buf), xmlHeader, xmlHdrCnt)) {
return true;
}
if (iIo.error() || iIo.eof()) {
return false;
}
iIo.read(buf + xmlHdrCnt + 1, len - xmlHdrCnt - 1);
if (iIo.error() || iIo.eof()) {
return false;
}
// Skip leading BOM
int32_t start = 0;
if (0 == strncmp(reinterpret_cast<const char*>(buf), "\xef\xbb\xbf", 3)) {
start = 3;
}
bool rc = false;
std::string head(reinterpret_cast<const char*>(buf + start), len - start);
if (head.substr(0, 5) == "<?xml") {
// Forward to the next tag
for (unsigned i = 5; i < head.size(); ++i) {
if (head[i] == '<') {
head = head.substr(i);
break;
}
}
}
if ( head.size() > 9
&& ( head.substr(0, 9) == "<?xpacket"
|| head.substr(0, 10) == "<x:xmpmeta")) {
rc = true;
}
if (!advance || !rc) {
iIo.seek(-(len - start), BasicIo::cur); // Swallow the BOM
}
return rc;
}
} // namespace Exiv2