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 }