File indexing completed on 2024-12-22 04:16:02
0001 /* 0002 * SPDX-FileCopyrightText: 2008-2010 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 #include "kis_xmp_io.h" 0008 0009 #include <string> 0010 0011 #include <kis_exiv2_common.h> 0012 #include <kis_meta_data_entry.h> 0013 #include <kis_meta_data_parser.h> 0014 #include <kis_meta_data_schema.h> 0015 #include <kis_meta_data_schema_registry.h> 0016 #include <kis_meta_data_store.h> 0017 #include <kis_meta_data_type_info.h> 0018 #include <kis_meta_data_value.h> 0019 0020 #include <kis_debug.h> 0021 0022 KisXMPIO::KisXMPIO() 0023 : KisMetaData::IOBackend() 0024 { 0025 } 0026 0027 KisXMPIO::~KisXMPIO() 0028 { 0029 } 0030 0031 inline std::string exiv2Prefix(const KisMetaData::Schema *_schema) 0032 { 0033 const QByteArray latin1SchemaUri = _schema->uri().toLatin1(); 0034 std::string prefix = Exiv2::XmpProperties::prefix(latin1SchemaUri.constData()); 0035 if (prefix.empty()) { 0036 dbgMetaData << "Unknown namespace " << ppVar(_schema->uri()) << ppVar(_schema->prefix()); 0037 prefix = _schema->prefix().toLatin1().constData(); 0038 Exiv2::XmpProperties::registerNs(latin1SchemaUri.constData(), prefix); 0039 } 0040 return prefix; 0041 } 0042 0043 namespace 0044 { 0045 void saveStructure(Exiv2::XmpData &xmpData_, 0046 const QString &name, 0047 const std::string &prefix, 0048 const QMap<QString, KisMetaData::Value> &structure, 0049 const KisMetaData::Schema *structureSchema) 0050 { 0051 std::string structPrefix = exiv2Prefix(structureSchema); 0052 for (QMap<QString, KisMetaData::Value>::const_iterator it = structure.begin(); it != structure.end(); ++it) { 0053 Q_ASSERT(it.value().type() != KisMetaData::Value::Structure); // Can't nest structure 0054 QString key = QString("%1/%2:%3").arg(name).arg(structPrefix.c_str()).arg(it.key()); 0055 Exiv2::XmpKey ekey(prefix, key.toLatin1().constData()); 0056 dbgMetaData << ppVar(key) << ppVar(ekey.key().c_str()); 0057 Exiv2::Value *v = kmdValueToExivXmpValue(it.value()); 0058 if (v) { 0059 xmpData_.add(ekey, v); 0060 } 0061 } 0062 } 0063 } 0064 0065 bool KisXMPIO::saveTo(const KisMetaData::Store *store, QIODevice *ioDevice, HeaderType headerType) const 0066 { 0067 dbgMetaData << "Save XMP Data"; 0068 Exiv2::XmpData xmpData_; 0069 0070 for (const KisMetaData::Entry &entry : *store) { 0071 // Check whether the prefix and namespace are know to exiv2 0072 std::string prefix = exiv2Prefix(entry.schema()); 0073 dbgMetaData << "Saving " << entry.name(); 0074 0075 const KisMetaData::Value &value = entry.value(); 0076 0077 const KisMetaData::TypeInfo *typeInfo = entry.schema()->propertyType(entry.name()); 0078 if (value.type() == KisMetaData::Value::Structure) { 0079 QMap<QString, KisMetaData::Value> structure = value.asStructure(); 0080 const KisMetaData::Schema *structureSchema = 0; 0081 if (typeInfo) { 0082 structureSchema = typeInfo->structureSchema(); 0083 } 0084 if (!structureSchema) { 0085 dbgMetaData << "Unknown schema for " << entry.name(); 0086 structureSchema = entry.schema(); 0087 } 0088 Q_ASSERT(structureSchema); 0089 saveStructure(xmpData_, entry.name(), prefix, structure, structureSchema); 0090 } else { 0091 Exiv2::XmpKey key(prefix, entry.name().toLatin1().constData()); 0092 if (typeInfo 0093 && (typeInfo->propertyType() == KisMetaData::TypeInfo::OrderedArrayType 0094 || typeInfo->propertyType() == KisMetaData::TypeInfo::UnorderedArrayType 0095 || typeInfo->propertyType() == KisMetaData::TypeInfo::AlternativeArrayType) 0096 && typeInfo->embeddedPropertyType()->propertyType() == KisMetaData::TypeInfo::StructureType) { 0097 // Here is the bad part, again we need to do it by hand 0098 Exiv2::XmpTextValue tv; 0099 switch (typeInfo->propertyType()) { 0100 case KisMetaData::TypeInfo::OrderedArrayType: 0101 tv.setXmpArrayType(Exiv2::XmpValue::xaSeq); 0102 break; 0103 case KisMetaData::TypeInfo::UnorderedArrayType: 0104 tv.setXmpArrayType(Exiv2::XmpValue::xaBag); 0105 break; 0106 case KisMetaData::TypeInfo::AlternativeArrayType: 0107 tv.setXmpArrayType(Exiv2::XmpValue::xaAlt); 0108 break; 0109 default: 0110 // Cannot happen 0111 ; 0112 } 0113 xmpData_.add(key, &tv); // set the array type 0114 const KisMetaData::TypeInfo *structureTypeInfo = typeInfo->embeddedPropertyType(); 0115 const KisMetaData::Schema *structureSchema = 0; 0116 if (structureTypeInfo) { 0117 structureSchema = structureTypeInfo->structureSchema(); 0118 } 0119 if (!structureSchema) { 0120 dbgMetaData << "Unknown schema for " << entry.name(); 0121 structureSchema = entry.schema(); 0122 } 0123 Q_ASSERT(structureSchema); 0124 QList<KisMetaData::Value> array = value.asArray(); 0125 for (int idx = 0; idx < array.size(); ++idx) { 0126 saveStructure(xmpData_, 0127 QString("%1[%2]").arg(entry.name()).arg(idx + 1), 0128 prefix, 0129 array[idx].asStructure(), 0130 structureSchema); 0131 } 0132 } else { 0133 dbgMetaData << ppVar(key.key().c_str()); 0134 Exiv2::Value *v = kmdValueToExivXmpValue(value); 0135 if (v) { 0136 xmpData_.add(key, v); 0137 } 0138 } 0139 } 0140 // TODO property qualifier 0141 } 0142 // Serialize data 0143 std::string xmpPacket_; 0144 try { 0145 Exiv2::XmpParser::encode(xmpPacket_, xmpData_); 0146 } catch (std::exception &e) { 0147 warnMetaData << "Couldn't encode the data, error =" << e.what(); 0148 return false; 0149 } 0150 // Save data into the IO device 0151 ioDevice->open(QIODevice::WriteOnly); 0152 if (headerType == KisMetaData::IOBackend::JpegHeader) { 0153 xmpPacket_ = "http://ns.adobe.com/xap/1.0/\0" + xmpPacket_; 0154 } 0155 ioDevice->write(xmpPacket_.c_str(), xmpPacket_.length()); 0156 return true; 0157 } 0158 0159 bool parseTagName(const QString &tagString, 0160 QString &structName, 0161 int &arrayIndex, 0162 QString &tagName, 0163 const KisMetaData::TypeInfo **typeInfo, 0164 const KisMetaData::Schema *schema) 0165 { 0166 arrayIndex = -1; 0167 *typeInfo = 0; 0168 0169 int numSubNames = tagString.count('/') + 1; 0170 0171 if (numSubNames == 1) { 0172 structName.clear(); 0173 tagName = tagString; 0174 *typeInfo = schema->propertyType(tagName); 0175 return true; 0176 } 0177 0178 if (numSubNames == 2) { 0179 QRegExp regexp("([A-Za-z]\\w+)/([A-Za-z]\\w+):([A-Za-z]\\w+)"); 0180 if (regexp.indexIn(tagString) != -1) { 0181 structName = regexp.capturedTexts()[1]; 0182 tagName = regexp.capturedTexts()[3]; 0183 *typeInfo = schema->propertyType(structName); 0184 0185 if (*typeInfo && (*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) { 0186 *typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName); 0187 } 0188 0189 return true; 0190 } 0191 0192 QRegExp regexp2("([A-Za-z]\\w+)\\[(\\d+)\\]/([A-Za-z]\\w+):([A-Za-z]\\w+)"); 0193 if (regexp2.indexIn(tagString) != -1) { 0194 structName = regexp2.capturedTexts()[1]; 0195 arrayIndex = regexp2.capturedTexts()[2].toInt() - 1; 0196 tagName = regexp2.capturedTexts()[4]; 0197 0198 if (schema->propertyType(structName)) { 0199 *typeInfo = schema->propertyType(structName)->embeddedPropertyType(); 0200 Q_ASSERT(*typeInfo); 0201 0202 if ((*typeInfo)->propertyType() == KisMetaData::TypeInfo::StructureType) { 0203 *typeInfo = (*typeInfo)->structureSchema()->propertyType(tagName); 0204 } 0205 } 0206 0207 return true; 0208 } 0209 } 0210 0211 warnKrita << "WARNING: Unsupported tag. We do not yet support nested tags. The tag will be dropped!"; 0212 warnKrita << " Failing tag:" << tagString; 0213 return false; 0214 } 0215 0216 bool KisXMPIO::loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const 0217 { 0218 ioDevice->open(QIODevice::ReadOnly); 0219 dbgMetaData << "Load XMP Data"; 0220 std::string xmpPacket_; 0221 QByteArray arr = ioDevice->readAll(); 0222 xmpPacket_.assign(arr.data(), arr.length()); 0223 dbgMetaData << xmpPacket_.length(); 0224 // dbgMetaData << xmpPacket_.c_str(); 0225 Exiv2::XmpData xmpData_; 0226 if (Exiv2::XmpParser::decode(xmpData_, xmpPacket_) != 0) { 0227 warnMetaData << "Failed to decode as XMP"; 0228 return false; 0229 } 0230 QMap<const KisMetaData::Schema *, QMap<QString, QMap<QString, KisMetaData::Value>>> structures; 0231 QMap<const KisMetaData::Schema *, QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>> arraysOfStructures; 0232 for (Exiv2::XmpData::iterator it = xmpData_.begin(); it != xmpData_.end(); ++it) { 0233 dbgMetaData << "Start iteration" << it->key().c_str(); 0234 0235 Exiv2::XmpKey key(it->key()); 0236 dbgMetaData << key.groupName().c_str() << " " << key.tagName().c_str() << " " << key.ns().c_str(); 0237 if ((key.groupName() == "exif" || key.groupName() == "tiff") 0238 && key.tagName() == "NativeDigest") { // TODO: someone who has time to lose can look in adding support for 0239 // NativeDigest, it's undocumented use by the XMP SDK to check if exif 0240 // data has been changed while XMP hasn't been updated 0241 dbgMetaData << "dropped"; 0242 } else { 0243 const KisMetaData::Schema *schema = 0244 KisMetaData::SchemaRegistry::instance()->schemaFromPrefix(key.groupName().c_str()); 0245 if (!schema) { 0246 schema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(key.ns().c_str()); 0247 if (!schema) { 0248 schema = KisMetaData::SchemaRegistry::instance()->create(key.ns().c_str(), key.groupName().c_str()); 0249 Q_ASSERT(schema); 0250 } 0251 } 0252 #if EXIV2_TEST_VERSION(0,28,0) 0253 const Exiv2::Value::UniquePtr value = it->getValue(); 0254 #else 0255 const Exiv2::Value::AutoPtr value = it->getValue(); 0256 #endif 0257 QString structName; 0258 int arrayIndex = -1; 0259 QString tagName; 0260 const KisMetaData::TypeInfo *typeInfo = 0; 0261 0262 if (!parseTagName(key.tagName().c_str(), structName, arrayIndex, tagName, &typeInfo, schema)) 0263 continue; 0264 0265 bool isStructureEntry = !structName.isEmpty() && arrayIndex == -1; 0266 bool isStructureInArrayEntry = !structName.isEmpty() && arrayIndex != -1; 0267 Q_ASSERT(isStructureEntry != isStructureInArrayEntry || !isStructureEntry); 0268 0269 KisMetaData::Value v; 0270 bool ignoreValue = false; 0271 // Compute the value 0272 if (value->typeId() == Exiv2::xmpBag || value->typeId() == Exiv2::xmpSeq 0273 || value->typeId() == Exiv2::xmpAlt) { 0274 const KisMetaData::TypeInfo *embeddedTypeInfo = 0; 0275 if (typeInfo) { 0276 embeddedTypeInfo = typeInfo->embeddedPropertyType(); 0277 } 0278 const KisMetaData::Parser *parser = 0; 0279 if (embeddedTypeInfo) { 0280 parser = embeddedTypeInfo->parser(); 0281 } 0282 const Exiv2::XmpArrayValue *xav = dynamic_cast<const Exiv2::XmpArrayValue *>(value.get()); 0283 Q_ASSERT(xav); 0284 QList<KisMetaData::Value> array; 0285 #if EXIV2_TEST_VERSION(0,28,0) 0286 for (size_t i = 0; i < xav->count(); ++i) { 0287 #else 0288 for (int i = 0; i < xav->count(); ++i) { 0289 #endif 0290 QString value = QString::fromStdString(xav->toString(i)); 0291 if (parser) { 0292 array.push_back(parser->parse(value)); 0293 } else { 0294 dbgImage << "No parser " << tagName; 0295 array.push_back(KisMetaData::Value(value)); 0296 } 0297 } 0298 KisMetaData::Value::ValueType vt = KisMetaData::Value::Invalid; 0299 switch (xav->xmpArrayType()) { 0300 case Exiv2::XmpValue::xaNone: 0301 warnKrita << "KisXMPIO: Unsupported array"; 0302 break; 0303 case Exiv2::XmpValue::xaAlt: 0304 vt = KisMetaData::Value::AlternativeArray; 0305 break; 0306 case Exiv2::XmpValue::xaBag: 0307 vt = KisMetaData::Value::UnorderedArray; 0308 break; 0309 case Exiv2::XmpValue::xaSeq: 0310 vt = KisMetaData::Value::OrderedArray; 0311 break; 0312 } 0313 v = KisMetaData::Value(array, vt); 0314 } else if (value->typeId() == Exiv2::langAlt) { 0315 const Exiv2::LangAltValue *xav = dynamic_cast<const Exiv2::LangAltValue *>(value.get()); 0316 KIS_ASSERT(xav); 0317 0318 QList<KisMetaData::Value> alt; 0319 for (std::map<std::string, std::string>::const_iterator it = xav->value_.begin(); 0320 it != xav->value_.end(); 0321 ++it) { 0322 KisMetaData::Value valt(it->second.c_str()); 0323 valt.addPropertyQualifier("xml:lang", KisMetaData::Value(it->first.c_str())); 0324 alt.push_back(valt); 0325 } 0326 v = KisMetaData::Value(alt, KisMetaData::Value::LangArray); 0327 } else { 0328 QString valTxt = value->toString().c_str(); 0329 if (typeInfo && typeInfo->parser()) { 0330 v = typeInfo->parser()->parse(valTxt); 0331 } else { 0332 dbgMetaData << "No parser " << tagName; 0333 v = KisMetaData::Value(valTxt); 0334 } 0335 if (valTxt == "type=\"Struct\"") { 0336 if (!typeInfo || typeInfo->propertyType() == KisMetaData::TypeInfo::StructureType) { 0337 ignoreValue = true; 0338 } 0339 } 0340 } 0341 0342 // set the value 0343 if (isStructureEntry) { 0344 structures[schema][structName][tagName] = v; 0345 } else if (isStructureInArrayEntry) { 0346 if (arraysOfStructures[schema][structName].size() <= arrayIndex) { 0347 arraysOfStructures[schema][structName].resize(arrayIndex + 1); 0348 } 0349 0350 if (!arraysOfStructures[schema][structName][arrayIndex].contains(tagName)) { 0351 arraysOfStructures[schema][structName][arrayIndex][tagName] = v; 0352 } else { 0353 warnKrita << "WARNING: trying to overwrite tag" << tagName << "in" << structName << arrayIndex; 0354 } 0355 } else { 0356 if (!ignoreValue) { 0357 store->addEntry(KisMetaData::Entry(schema, tagName, v)); 0358 } else { 0359 dbgMetaData << "Ignoring value for " << tagName << " " << v; 0360 } 0361 } 0362 } 0363 } 0364 0365 for (QMap<const KisMetaData::Schema *, QMap<QString, QMap<QString, KisMetaData::Value>>>::iterator it = 0366 structures.begin(); 0367 it != structures.end(); 0368 ++it) { 0369 const KisMetaData::Schema *schema = it.key(); 0370 for (QMap<QString, QMap<QString, KisMetaData::Value>>::iterator it2 = it.value().begin(); 0371 it2 != it.value().end(); 0372 ++it2) { 0373 store->addEntry(KisMetaData::Entry(schema, it2.key(), KisMetaData::Value(it2.value()))); 0374 } 0375 } 0376 for (QMap<const KisMetaData::Schema *, QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>>::iterator it = 0377 arraysOfStructures.begin(); 0378 it != arraysOfStructures.end(); 0379 ++it) { 0380 const KisMetaData::Schema *schema = it.key(); 0381 for (QMap<QString, QVector<QMap<QString, KisMetaData::Value>>>::iterator it2 = it.value().begin(); 0382 it2 != it.value().end(); 0383 ++it2) { 0384 KisMetaData::Value::ValueType type = KisMetaData::Value::OrderedArray; 0385 QString entryName = it2.key(); 0386 if (schema->propertyType(entryName)) { 0387 switch (schema->propertyType(entryName)->propertyType()) { 0388 case KisMetaData::TypeInfo::OrderedArrayType: 0389 type = KisMetaData::Value::OrderedArray; 0390 break; 0391 case KisMetaData::TypeInfo::UnorderedArrayType: 0392 type = KisMetaData::Value::OrderedArray; 0393 break; 0394 case KisMetaData::TypeInfo::AlternativeArrayType: 0395 type = KisMetaData::Value::AlternativeArray; 0396 break; 0397 default: 0398 type = KisMetaData::Value::Invalid; 0399 break; 0400 } 0401 } else if (store->containsEntry(schema, entryName)) { 0402 KisMetaData::Value value = store->getEntry(schema, entryName).value(); 0403 if (value.isArray()) { 0404 type = value.type(); 0405 } 0406 } 0407 store->removeEntry(schema, entryName); 0408 if (type != KisMetaData::Value::Invalid) { 0409 QList<KisMetaData::Value> valueList; 0410 for (int i = 0; i < it2.value().size(); ++i) { 0411 valueList.append(it2.value()[i]); 0412 } 0413 store->addEntry(KisMetaData::Entry(schema, entryName, KisMetaData::Value(valueList, type))); 0414 } 0415 } 0416 } 0417 0418 return true; 0419 }