Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_xmp_io.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008-2010 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7#include "kis_xmp_io.h"
8
9#include <string>
10
11#include <QIODevice>
12#include <QRegularExpression>
13
14#include <kis_exiv2_common.h>
15#include <kis_meta_data_entry.h>
19#include <kis_meta_data_store.h>
21#include <kis_meta_data_value.h>
22#include <KisUsageLogger.h>
23
24
25#include <kis_debug.h>
26
28 : KisMetaData::IOBackend()
29{
30}
31
35
36inline std::string exiv2Prefix(const KisMetaData::Schema *_schema)
37{
38 const QByteArray latin1SchemaUri = _schema->uri().toLatin1();
39 std::string prefix = Exiv2::XmpProperties::prefix(latin1SchemaUri.constData());
40 if (prefix.empty()) {
41 dbgMetaData << "Unknown namespace " << ppVar(_schema->uri()) << ppVar(_schema->prefix());
42 prefix = _schema->prefix().toLatin1().constData();
43 Exiv2::XmpProperties::registerNs(latin1SchemaUri.constData(), prefix);
44 }
45 return prefix;
46}
47
48namespace
49{
50void saveStructure(Exiv2::XmpData &xmpData_,
51 const QString &name,
52 const std::string &prefix,
53 const QMap<QString, KisMetaData::Value> &structure,
54 const KisMetaData::Schema *structureSchema)
55{
56 std::string structPrefix = exiv2Prefix(structureSchema);
57 for (QMap<QString, KisMetaData::Value>::const_iterator it = structure.begin(); it != structure.end(); ++it) {
58 Q_ASSERT(it.value().type() != KisMetaData::Value::Structure); // Can't nest structure
59 QString key = QString("%1/%2:%3").arg(name).arg(structPrefix.c_str()).arg(it.key());
60 Exiv2::XmpKey ekey(prefix, key.toLatin1().constData());
61 dbgMetaData << ppVar(key) << ppVar(ekey.key().c_str());
62 Exiv2::Value *v = kmdValueToExivXmpValue(it.value());
63 if (v) {
64 xmpData_.add(ekey, v);
65 }
66 }
67}
68}
69
70bool KisXMPIO::saveTo(const KisMetaData::Store *store, QIODevice *ioDevice, HeaderType headerType) const
71{
72 dbgMetaData << "Save XMP Data";
73 Exiv2::XmpData xmpData_;
74
75 for (const KisMetaData::Entry &entry : *store) {
76 // Check whether the prefix and namespace are know to exiv2
77 std::string prefix = exiv2Prefix(entry.schema());
78 dbgMetaData << "Saving " << entry.name();
79
80 const KisMetaData::Value &value = entry.value();
81
82 const KisMetaData::TypeInfo *typeInfo = entry.schema()->propertyType(entry.name());
84 QMap<QString, KisMetaData::Value> structure = value.asStructure();
85 const KisMetaData::Schema *structureSchema = 0;
86 if (typeInfo) {
87 structureSchema = typeInfo->structureSchema();
88 }
89 if (!structureSchema) {
90 dbgMetaData << "Unknown schema for " << entry.name();
91 structureSchema = entry.schema();
92 }
93 Q_ASSERT(structureSchema);
94 saveStructure(xmpData_, entry.name(), prefix, structure, structureSchema);
95 } else {
96 Exiv2::XmpKey key(prefix, entry.name().toLatin1().constData());
97 if (typeInfo
102 // Here is the bad part, again we need to do it by hand
103 Exiv2::XmpTextValue tv;
104 switch (typeInfo->propertyType()) {
106 tv.setXmpArrayType(Exiv2::XmpValue::xaSeq);
107 break;
109 tv.setXmpArrayType(Exiv2::XmpValue::xaBag);
110 break;
112 tv.setXmpArrayType(Exiv2::XmpValue::xaAlt);
113 break;
114 default:
115 // Cannot happen
116 ;
117 }
118 xmpData_.add(key, &tv); // set the array type
119 const KisMetaData::TypeInfo *structureTypeInfo = typeInfo->embeddedPropertyType();
120 const KisMetaData::Schema *structureSchema = 0;
121 if (structureTypeInfo) {
122 structureSchema = structureTypeInfo->structureSchema();
123 }
124 if (!structureSchema) {
125 dbgMetaData << "Unknown schema for " << entry.name();
126 structureSchema = entry.schema();
127 }
128 Q_ASSERT(structureSchema);
129 QList<KisMetaData::Value> array = value.asArray();
130 for (int idx = 0; idx < array.size(); ++idx) {
131 saveStructure(xmpData_,
132 QString("%1[%2]").arg(entry.name()).arg(idx + 1),
133 prefix,
134 array[idx].asStructure(),
135 structureSchema);
136 }
137 } else {
138 dbgMetaData << ppVar(key.key().c_str());
139 Exiv2::Value *v = kmdValueToExivXmpValue(value);
140 if (v && v->typeId() != Exiv2::TypeId::invalidTypeId) {
141 xmpData_.add(key, v);
142 } else {
143 warnMetaData << "Invalid metadata value " << value << "for: " << entry.name();
144 KisUsageLogger::log(QString("Invalid metadata value \"%1\" for key %2. The metadata key is therefore skipped during saving.").arg(value.toString(), entry.name()));
145 }
146 }
147 }
148 // TODO property qualifier
149 }
150 // Serialize data
151 std::string xmpPacket_;
152 try {
153 Exiv2::XmpParser::encode(xmpPacket_, xmpData_);
154 } catch (std::exception &e) {
155 warnMetaData << "Couldn't encode the data, error =" << e.what();
156 return false;
157 }
158 // Save data into the IO device
159 ioDevice->open(QIODevice::WriteOnly);
160 if (headerType == KisMetaData::IOBackend::JpegHeader) {
161 xmpPacket_ = "http://ns.adobe.com/xap/1.0/\0" + xmpPacket_;
162 }
163 ioDevice->write(xmpPacket_.c_str(), xmpPacket_.length());
164 return true;
165}
166
167bool parseTagName(const QString &tagString,
168 QString &structName,
169 int &arrayIndex,
170 QString &tagName,
171 const KisMetaData::TypeInfo **typeInfo,
172 const KisMetaData::Schema *schema)
173{
174 arrayIndex = -1;
175 *typeInfo = 0;
176
177 int numSubNames = tagString.count('/') + 1;
178
179 if (numSubNames == 1) {
180 structName.clear();
181 tagName = tagString;
182 *typeInfo = schema->propertyType(tagName);
183 return true;
184 }
185
186 if (numSubNames == 2) {
191 QRegularExpression regexp("([A-Za-z]\\w+)/([A-Za-z]\\w+):([A-Za-z]\\w+)");
192 regexp.setPatternOptions(QRegularExpression::UseUnicodePropertiesOption);
193
194 QRegularExpressionMatch match;
195 if (tagString.contains(regexp, &match)) {
196 structName = match.captured(1);
197 tagName = match.captured(3);
198 *typeInfo = schema->propertyType(structName);
199
200 if (*typeInfo && (*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) {
201 *typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName);
202 }
203
204 return true;
205 }
206
207 QRegularExpression regexp2("([A-Za-z]\\w+)\\[(\\d+)\\]/([A-Za-z]\\w+):([A-Za-z]\\w+)");
208 regexp2.setPatternOptions(QRegularExpression::UseUnicodePropertiesOption);
209
210 QRegularExpressionMatch match2;
211 if (tagString.contains(regexp2, &match2)) {
212 structName = match2.captured(1);
213 arrayIndex = match2.captured(2).toInt() - 1;
214 tagName = match2.captured(4);
215
216 if (schema->propertyType(structName)) {
217 *typeInfo = schema->propertyType(structName)->embeddedPropertyType();
218 Q_ASSERT(*typeInfo);
219
220 if ((*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) {
221 *typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName);
222 }
223 }
224
225 return true;
226 }
227 }
228
229 warnKrita << "WARNING: Unsupported tag. We do not yet support nested tags. The tag will be dropped!";
230 warnKrita << " Failing tag:" << tagString;
231 return false;
232}
233
234bool KisXMPIO::loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const
235{
236 ioDevice->open(QIODevice::ReadOnly);
237 dbgMetaData << "Load XMP Data";
238 std::string xmpPacket_;
239 QByteArray arr = ioDevice->readAll();
240 xmpPacket_.assign(arr.data(), arr.length());
241 dbgMetaData << xmpPacket_.length();
242 // dbgMetaData << xmpPacket_.c_str();
243 Exiv2::XmpData xmpData_;
244 if (Exiv2::XmpParser::decode(xmpData_, xmpPacket_) != 0) {
245 warnMetaData << "Failed to decode as XMP";
246 return false;
247 }
248 QMap<const KisMetaData::Schema *, QMap<QString, QMap<QString, KisMetaData::Value>>> structures;
249 QMap<const KisMetaData::Schema *, QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>> arraysOfStructures;
250 for (Exiv2::XmpData::iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) {
251 dbgMetaData << "Start iteration" << it->key().c_str();
252
253 Exiv2::XmpKey key(it->key());
254 dbgMetaData << key.groupName().c_str() << " " << key.tagName().c_str() << " " << key.ns().c_str();
255 if ((key.groupName() == "exif" || key.groupName() == "tiff")
256 && key.tagName() == "NativeDigest") { // TODO: someone who has time to lose can look in adding support for
257 // NativeDigest, it's undocumented use by the XMP SDK to check if exif
258 // data has been changed while XMP hasn't been updated
259 dbgMetaData << "dropped";
260 } else {
261 const KisMetaData::Schema *schema =
263 if (!schema) {
264 schema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(key.ns().c_str());
265 if (!schema) {
266 schema = KisMetaData::SchemaRegistry::instance()->create(key.ns().c_str(), key.groupName().c_str());
267 Q_ASSERT(schema);
268 }
269 }
270#if EXIV2_TEST_VERSION(0,28,0)
271 const Exiv2::Value::UniquePtr value = it->getValue();
272#else
273 const Exiv2::Value::AutoPtr value = it->getValue();
274#endif
275 QString structName;
276 int arrayIndex = -1;
277 QString tagName;
278 const KisMetaData::TypeInfo *typeInfo = 0;
279
280 if (!parseTagName(key.tagName().c_str(), structName, arrayIndex, tagName, &typeInfo, schema))
281 continue;
282
283 bool isStructureEntry = !structName.isEmpty() && arrayIndex == -1;
284 bool isStructureInArrayEntry = !structName.isEmpty() && arrayIndex != -1;
285 Q_ASSERT(isStructureEntry != isStructureInArrayEntry || !isStructureEntry);
286
288 bool ignoreValue = false;
289 // Compute the value
290 if (value->typeId() == Exiv2::xmpBag || value->typeId() == Exiv2::xmpSeq
291 || value->typeId() == Exiv2::xmpAlt) {
292 const KisMetaData::TypeInfo *embeddedTypeInfo = 0;
293 if (typeInfo) {
294 embeddedTypeInfo = typeInfo->embeddedPropertyType();
295 }
296 const KisMetaData::Parser *parser = 0;
297 if (embeddedTypeInfo) {
298 parser = embeddedTypeInfo->parser();
299 }
300 const Exiv2::XmpArrayValue *xav = dynamic_cast<const Exiv2::XmpArrayValue *>(value.get());
301 Q_ASSERT(xav);
303#if EXIV2_TEST_VERSION(0,28,0)
304 for (size_t i = 0; i < xav->count(); ++i) {
305#else
306 for (int i = 0; i < xav->count(); ++i) {
307#endif
308 QString value = QString::fromStdString(xav->toString(i));
309 if (parser) {
310 KisMetaData::Value parsed = parser->parse(value);
311 if (parsed.type() == KisMetaData::Value::Invalid) {
312 KisUsageLogger::log(QString("Cannot parse metadata value \"%1\" for %2. Assigning invalid value.").arg(value, tagName));
313 }
314 array.push_back(parsed);
315
316 } else {
317 dbgImage << "No parser " << tagName;
318 array.push_back(KisMetaData::Value(value));
319 }
320 }
322 switch (xav->xmpArrayType()) {
323 case Exiv2::XmpValue::xaNone:
324 warnKrita << "KisXMPIO: Unsupported array";
325 break;
326 case Exiv2::XmpValue::xaAlt:
328 break;
329 case Exiv2::XmpValue::xaBag:
331 break;
332 case Exiv2::XmpValue::xaSeq:
334 break;
335 }
336 v = KisMetaData::Value(array, vt);
337 } else if (value->typeId() == Exiv2::langAlt) {
338 const Exiv2::LangAltValue *xav = dynamic_cast<const Exiv2::LangAltValue *>(value.get());
339 KIS_ASSERT(xav);
340
342 for (std::map<std::string, std::string>::const_iterator it = xav->value_.begin();
343 it != xav->value_.end();
344 ++it) {
345 KisMetaData::Value valt(it->second.c_str());
346 valt.addPropertyQualifier("xml:lang", KisMetaData::Value(it->first.c_str()));
347 alt.push_back(valt);
348 }
350 } else {
351 QString valTxt = value->toString().c_str();
352 if (typeInfo && typeInfo->parser()) {
353 v = typeInfo->parser()->parse(valTxt);
354 if (v.type() == KisMetaData::Value::Invalid) {
355 KisUsageLogger::log(QString("Cannot parse metadata value \"%1\" for %2. Assigning invalid value.").arg(valTxt, tagName));
356 }
357 } else {
358 dbgMetaData << "No parser " << tagName;
359 v = KisMetaData::Value(valTxt);
360 }
361 if (valTxt == "type=\"Struct\"") {
362 if (!typeInfo || typeInfo->propertyType() == KisMetaData::TypeInfo::StructureType) {
363 ignoreValue = true;
364 }
365 }
366 }
367
368 // set the value
369 if (isStructureEntry) {
370 structures[schema][structName][tagName] = v;
371 } else if (isStructureInArrayEntry) {
372 if (arraysOfStructures[schema][structName].size() <= arrayIndex) {
373 arraysOfStructures[schema][structName].resize(arrayIndex + 1);
374 }
375
376 if (!arraysOfStructures[schema][structName][arrayIndex].contains(tagName)) {
377 arraysOfStructures[schema][structName][arrayIndex][tagName] = v;
378 } else {
379 warnKrita << "WARNING: trying to overwrite tag" << tagName << "in" << structName << arrayIndex;
380 }
381 } else {
382 if (!ignoreValue) {
383 store->addEntry(KisMetaData::Entry(schema, tagName, v));
384 } else {
385 dbgMetaData << "Ignoring value for " << tagName << " " << v;
386 }
387 }
388 }
389 }
390
391 for (QMap<const KisMetaData::Schema *, QMap<QString, QMap<QString, KisMetaData::Value>>>::iterator it =
392 structures.begin();
393 it != structures.end();
394 ++it) {
395 const KisMetaData::Schema *schema = it.key();
396 for (QMap<QString, QMap<QString, KisMetaData::Value>>::iterator it2 = it.value().begin();
397 it2 != it.value().end();
398 ++it2) {
399 store->addEntry(KisMetaData::Entry(schema, it2.key(), KisMetaData::Value(it2.value())));
400 }
401 }
402 for (QMap<const KisMetaData::Schema *, QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>>::iterator it =
403 arraysOfStructures.begin();
404 it != arraysOfStructures.end();
405 ++it) {
406 const KisMetaData::Schema *schema = it.key();
407 for (QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>::iterator it2 = it.value().begin();
408 it2 != it.value().end();
409 ++it2) {
411 QString entryName = it2.key();
412 if (schema->propertyType(entryName)) {
413 switch (schema->propertyType(entryName)->propertyType()) {
416 break;
419 break;
422 break;
423 default:
425 break;
426 }
427 } else if (store->containsEntry(schema, entryName)) {
428 KisMetaData::Value value = store->getEntry(schema, entryName).value();
429 if (value.isArray()) {
430 type = value.type();
431 }
432 }
433 store->removeEntry(schema, entryName);
436 for (int i = 0; i < it2.value().size(); ++i) {
437 valueList.append(it2.value()[i]);
438 }
439 store->addEntry(KisMetaData::Entry(schema, entryName, KisMetaData::Value(valueList, type)));
440 }
441 }
442 }
443
444 return true;
445}
float value(const T *src, size_t ch)
qreal v
const KisMetaData::Value & value() const
@ JpegHeader
Append Jpeg-style header.
virtual Value parse(const QString &) const =0
static KisMetaData::SchemaRegistry * instance()
const Schema * schemaFromPrefix(const QString &prefix) const
const Schema * schemaFromUri(const QString &uri) const
const KisMetaData::Schema * create(const QString &uri, const QString &prefix)
const TypeInfo * propertyType(const QString &_propertyName) const
void removeEntry(const QString &entryKey)
bool addEntry(const Entry &entry)
bool containsEntry(const QString &entryKey) const
Entry & getEntry(const QString &entryKey)
PropertyType propertyType() const
const Parser * parser() const
const TypeInfo * embeddedPropertyType() const
ValueType
Define the possible value type.
void addPropertyQualifier(const QString &_name, const Value &)
ValueType type() const
static void log(const QString &message)
Logs with date/time.
bool loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const override
BackendType type() const override
Definition kis_xmp_io.h:28
bool saveTo(const KisMetaData::Store *store, QIODevice *ioDevice, HeaderType headerType=NoHeader) const override
~KisXMPIO() override
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define warnMetaData
Definition kis_debug.h:103
#define warnKrita
Definition kis_debug.h:87
#define ppVar(var)
Definition kis_debug.h:155
#define dbgMetaData
Definition kis_debug.h:61
#define dbgImage
Definition kis_debug.h:46
Exiv2::Value * kmdValueToExivXmpValue(const KisMetaData::Value &value)
std::string exiv2Prefix(const KisMetaData::Schema *_schema)
bool parseTagName(const QString &tagString, QString &structName, int &arrayIndex, QString &tagName, const KisMetaData::TypeInfo **typeInfo, const KisMetaData::Schema *schema)