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 <QRegExp>
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) {
183 QRegExp regexp("([A-Za-z]\\w+)/([A-Za-z]\\w+):([A-Za-z]\\w+)");
184 if (regexp.indexIn(tagString) != -1) {
185 structName = regexp.capturedTexts()[1];
186 tagName = regexp.capturedTexts()[3];
187 *typeInfo = schema->propertyType(structName);
188
189 if (*typeInfo && (*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) {
190 *typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName);
191 }
192
193 return true;
194 }
195
196 QRegExp regexp2("([A-Za-z]\\w+)\\[(\\d+)\\]/([A-Za-z]\\w+):([A-Za-z]\\w+)");
197 if (regexp2.indexIn(tagString) != -1) {
198 structName = regexp2.capturedTexts()[1];
199 arrayIndex = regexp2.capturedTexts()[2].toInt() - 1;
200 tagName = regexp2.capturedTexts()[4];
201
202 if (schema->propertyType(structName)) {
203 *typeInfo = schema->propertyType(structName)->embeddedPropertyType();
204 Q_ASSERT(*typeInfo);
205
206 if ((*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) {
207 *typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName);
208 }
209 }
210
211 return true;
212 }
213 }
214
215 warnKrita << "WARNING: Unsupported tag. We do not yet support nested tags. The tag will be dropped!";
216 warnKrita << " Failing tag:" << tagString;
217 return false;
218}
219
220bool KisXMPIO::loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const
221{
222 ioDevice->open(QIODevice::ReadOnly);
223 dbgMetaData << "Load XMP Data";
224 std::string xmpPacket_;
225 QByteArray arr = ioDevice->readAll();
226 xmpPacket_.assign(arr.data(), arr.length());
227 dbgMetaData << xmpPacket_.length();
228 // dbgMetaData << xmpPacket_.c_str();
229 Exiv2::XmpData xmpData_;
230 if (Exiv2::XmpParser::decode(xmpData_, xmpPacket_) != 0) {
231 warnMetaData << "Failed to decode as XMP";
232 return false;
233 }
234 QMap<const KisMetaData::Schema *, QMap<QString, QMap<QString, KisMetaData::Value>>> structures;
235 QMap<const KisMetaData::Schema *, QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>> arraysOfStructures;
236 for (Exiv2::XmpData::iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) {
237 dbgMetaData << "Start iteration" << it->key().c_str();
238
239 Exiv2::XmpKey key(it->key());
240 dbgMetaData << key.groupName().c_str() << " " << key.tagName().c_str() << " " << key.ns().c_str();
241 if ((key.groupName() == "exif" || key.groupName() == "tiff")
242 && key.tagName() == "NativeDigest") { // TODO: someone who has time to lose can look in adding support for
243 // NativeDigest, it's undocumented use by the XMP SDK to check if exif
244 // data has been changed while XMP hasn't been updated
245 dbgMetaData << "dropped";
246 } else {
247 const KisMetaData::Schema *schema =
249 if (!schema) {
250 schema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(key.ns().c_str());
251 if (!schema) {
252 schema = KisMetaData::SchemaRegistry::instance()->create(key.ns().c_str(), key.groupName().c_str());
253 Q_ASSERT(schema);
254 }
255 }
256#if EXIV2_TEST_VERSION(0,28,0)
257 const Exiv2::Value::UniquePtr value = it->getValue();
258#else
259 const Exiv2::Value::AutoPtr value = it->getValue();
260#endif
261 QString structName;
262 int arrayIndex = -1;
263 QString tagName;
264 const KisMetaData::TypeInfo *typeInfo = 0;
265
266 if (!parseTagName(key.tagName().c_str(), structName, arrayIndex, tagName, &typeInfo, schema))
267 continue;
268
269 bool isStructureEntry = !structName.isEmpty() && arrayIndex == -1;
270 bool isStructureInArrayEntry = !structName.isEmpty() && arrayIndex != -1;
271 Q_ASSERT(isStructureEntry != isStructureInArrayEntry || !isStructureEntry);
272
274 bool ignoreValue = false;
275 // Compute the value
276 if (value->typeId() == Exiv2::xmpBag || value->typeId() == Exiv2::xmpSeq
277 || value->typeId() == Exiv2::xmpAlt) {
278 const KisMetaData::TypeInfo *embeddedTypeInfo = 0;
279 if (typeInfo) {
280 embeddedTypeInfo = typeInfo->embeddedPropertyType();
281 }
282 const KisMetaData::Parser *parser = 0;
283 if (embeddedTypeInfo) {
284 parser = embeddedTypeInfo->parser();
285 }
286 const Exiv2::XmpArrayValue *xav = dynamic_cast<const Exiv2::XmpArrayValue *>(value.get());
287 Q_ASSERT(xav);
289#if EXIV2_TEST_VERSION(0,28,0)
290 for (size_t i = 0; i < xav->count(); ++i) {
291#else
292 for (int i = 0; i < xav->count(); ++i) {
293#endif
294 QString value = QString::fromStdString(xav->toString(i));
295 if (parser) {
296 array.push_back(parser->parse(value));
297 } else {
298 dbgImage << "No parser " << tagName;
299 array.push_back(KisMetaData::Value(value));
300 }
301 }
303 switch (xav->xmpArrayType()) {
304 case Exiv2::XmpValue::xaNone:
305 warnKrita << "KisXMPIO: Unsupported array";
306 break;
307 case Exiv2::XmpValue::xaAlt:
309 break;
310 case Exiv2::XmpValue::xaBag:
312 break;
313 case Exiv2::XmpValue::xaSeq:
315 break;
316 }
317 v = KisMetaData::Value(array, vt);
318 } else if (value->typeId() == Exiv2::langAlt) {
319 const Exiv2::LangAltValue *xav = dynamic_cast<const Exiv2::LangAltValue *>(value.get());
320 KIS_ASSERT(xav);
321
323 for (std::map<std::string, std::string>::const_iterator it = xav->value_.begin();
324 it != xav->value_.end();
325 ++it) {
326 KisMetaData::Value valt(it->second.c_str());
327 valt.addPropertyQualifier("xml:lang", KisMetaData::Value(it->first.c_str()));
328 alt.push_back(valt);
329 }
331 } else {
332 QString valTxt = value->toString().c_str();
333 if (typeInfo && typeInfo->parser()) {
334 v = typeInfo->parser()->parse(valTxt);
335 } else {
336 dbgMetaData << "No parser " << tagName;
337 v = KisMetaData::Value(valTxt);
338 }
339 if (valTxt == "type=\"Struct\"") {
340 if (!typeInfo || typeInfo->propertyType() == KisMetaData::TypeInfo::StructureType) {
341 ignoreValue = true;
342 }
343 }
344 }
345
346 // set the value
347 if (isStructureEntry) {
348 structures[schema][structName][tagName] = v;
349 } else if (isStructureInArrayEntry) {
350 if (arraysOfStructures[schema][structName].size() <= arrayIndex) {
351 arraysOfStructures[schema][structName].resize(arrayIndex + 1);
352 }
353
354 if (!arraysOfStructures[schema][structName][arrayIndex].contains(tagName)) {
355 arraysOfStructures[schema][structName][arrayIndex][tagName] = v;
356 } else {
357 warnKrita << "WARNING: trying to overwrite tag" << tagName << "in" << structName << arrayIndex;
358 }
359 } else {
360 if (!ignoreValue) {
361 store->addEntry(KisMetaData::Entry(schema, tagName, v));
362 } else {
363 dbgMetaData << "Ignoring value for " << tagName << " " << v;
364 }
365 }
366 }
367 }
368
369 for (QMap<const KisMetaData::Schema *, QMap<QString, QMap<QString, KisMetaData::Value>>>::iterator it =
370 structures.begin();
371 it != structures.end();
372 ++it) {
373 const KisMetaData::Schema *schema = it.key();
374 for (QMap<QString, QMap<QString, KisMetaData::Value>>::iterator it2 = it.value().begin();
375 it2 != it.value().end();
376 ++it2) {
377 store->addEntry(KisMetaData::Entry(schema, it2.key(), KisMetaData::Value(it2.value())));
378 }
379 }
380 for (QMap<const KisMetaData::Schema *, QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>>::iterator it =
381 arraysOfStructures.begin();
382 it != arraysOfStructures.end();
383 ++it) {
384 const KisMetaData::Schema *schema = it.key();
385 for (QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>::iterator it2 = it.value().begin();
386 it2 != it.value().end();
387 ++it2) {
389 QString entryName = it2.key();
390 if (schema->propertyType(entryName)) {
391 switch (schema->propertyType(entryName)->propertyType()) {
394 break;
397 break;
400 break;
401 default:
403 break;
404 }
405 } else if (store->containsEntry(schema, entryName)) {
406 KisMetaData::Value value = store->getEntry(schema, entryName).value();
407 if (value.isArray()) {
408 type = value.type();
409 }
410 }
411 store->removeEntry(schema, entryName);
414 for (int i = 0; i < it2.value().size(); ++i) {
415 valueList.append(it2.value()[i]);
416 }
417 store->addEntry(KisMetaData::Entry(schema, entryName, KisMetaData::Value(valueList, type)));
418 }
419 }
420 }
421
422 return true;
423}
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)