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