File indexing completed on 2024-05-26 04:33:48
0001 /* 0002 * SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 #include "kis_iptc_io.h" 0008 0009 #include <exiv2/iptc.hpp> 0010 0011 #include <kis_debug.h> 0012 #include <kis_exiv2_common.h> 0013 #include <kis_meta_data_entry.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_value.h> 0018 0019 const char photoshopMarker[] = "Photoshop 3.0\0"; 0020 const char photoshopBimId_[] = "8BIM"; 0021 const uint16_t photoshopIptc = 0x0404; 0022 const QByteArray photoshopIptc_((char *)&photoshopIptc, 2); 0023 0024 struct IPTCToKMD { 0025 QString exivTag; 0026 QString namespaceUri; 0027 QString name; 0028 }; 0029 0030 static const IPTCToKMD mappings[] = { 0031 {"Iptc.Application2.City", KisMetaData::Schema::PhotoshopSchemaUri, "City"}, 0032 {"Iptc.Application2.Copyright", KisMetaData::Schema::DublinCoreSchemaUri, "rights"}, 0033 {"Iptc.Application2.CountryName", KisMetaData::Schema::PhotoshopSchemaUri, "Country"}, 0034 {"Iptc.Application2.CountryCode", KisMetaData::Schema::IPTCSchemaUri, "CountryCode"}, 0035 {"Iptc.Application2.Byline", KisMetaData::Schema::DublinCoreSchemaUri, "creator"}, 0036 {"Iptc.Application2.BylineTitle", KisMetaData::Schema::PhotoshopSchemaUri, "AuthorsPosition"}, 0037 {"Iptc.Application2.DateCreated", KisMetaData::Schema::PhotoshopSchemaUri, "DateCreated"}, 0038 {"Iptc.Application2.Caption", KisMetaData::Schema::DublinCoreSchemaUri, "description"}, 0039 {"Iptc.Application2.Writer", KisMetaData::Schema::PhotoshopSchemaUri, "CaptionWriter"}, 0040 {"Iptc.Application2.Headline", KisMetaData::Schema::PhotoshopSchemaUri, "Headline"}, 0041 {"Iptc.Application2.SpecialInstructions", KisMetaData::Schema::PhotoshopSchemaUri, "Instructions"}, 0042 {"Iptc.Application2.ObjectAttribute", KisMetaData::Schema::IPTCSchemaUri, "IntellectualGenre"}, 0043 {"Iptc.Application2.TransmissionReference", KisMetaData::Schema::PhotoshopSchemaUri, "JobID"}, 0044 {"Iptc.Application2.Keywords", KisMetaData::Schema::DublinCoreSchemaUri, "subject"}, 0045 {"Iptc.Application2.SubLocation", KisMetaData::Schema::IPTCSchemaUri, "Location"}, 0046 {"Iptc.Application2.Credit", KisMetaData::Schema::PhotoshopSchemaUri, "Credit"}, 0047 {"Iptc.Application2.ProvinceState", KisMetaData::Schema::PhotoshopSchemaUri, "State"}, 0048 {"Iptc.Application2.Source", KisMetaData::Schema::PhotoshopSchemaUri, "Source"}, 0049 {"Iptc.Application2.Subject", KisMetaData::Schema::IPTCSchemaUri, "SubjectCode"}, 0050 {"Iptc.Application2.ObjectName", KisMetaData::Schema::DublinCoreSchemaUri, "title"}, 0051 {"Iptc.Application2.Urgency", KisMetaData::Schema::PhotoshopSchemaUri, "Urgency"}, 0052 {"Iptc.Application2.Category", KisMetaData::Schema::PhotoshopSchemaUri, "Category"}, 0053 {"Iptc.Application2.SuppCategory", KisMetaData::Schema::PhotoshopSchemaUri, "SupplementalCategory"}, 0054 {"", "", ""} // indicates the end of the array 0055 }; 0056 0057 struct KisIptcIO::Private { 0058 QHash<QString, IPTCToKMD> iptcToKMD; 0059 QHash<QString, IPTCToKMD> kmdToIPTC; 0060 }; 0061 0062 // ---- Implementation of KisIptcIO ----// 0063 KisIptcIO::KisIptcIO() 0064 : KisMetaData::IOBackend() 0065 , d(new Private) 0066 { 0067 } 0068 0069 KisIptcIO::~KisIptcIO() 0070 { 0071 delete d; 0072 } 0073 0074 void KisIptcIO::initMappingsTable() const 0075 { 0076 // For some reason, initializing the tables in the constructor makes the it crash 0077 if (d->iptcToKMD.size() == 0) { 0078 for (int i = 0; !mappings[i].exivTag.isEmpty(); i++) { 0079 dbgKrita << "mapping[i] = " << mappings[i].exivTag << " " << mappings[i].namespaceUri << " " 0080 << mappings[i].name; 0081 d->iptcToKMD[mappings[i].exivTag] = mappings[i]; 0082 d->kmdToIPTC[KisMetaData::SchemaRegistry::instance() 0083 ->schemaFromUri(mappings[i].namespaceUri) 0084 ->generateQualifiedName(mappings[i].name)] = mappings[i]; 0085 } 0086 } 0087 } 0088 0089 bool KisIptcIO::saveTo(const KisMetaData::Store *store, QIODevice *ioDevice, HeaderType headerType) const 0090 { 0091 QStringList blockedEntries = QStringList() << "photoshop:DateCreated"; 0092 0093 initMappingsTable(); 0094 ioDevice->open(QIODevice::WriteOnly); 0095 Exiv2::IptcData iptcData; 0096 for (const KisMetaData::Entry &entry : *store) { 0097 if (d->kmdToIPTC.contains(entry.qualifiedName())) { 0098 if (blockedEntries.contains(entry.qualifiedName())) { 0099 warnKrita << "skipping" << entry.qualifiedName() << entry.value(); 0100 continue; 0101 } 0102 try { 0103 QString iptcKeyStr = d->kmdToIPTC[entry.qualifiedName()].exivTag; 0104 Exiv2::IptcKey iptcKey(qPrintable(iptcKeyStr)); 0105 Exiv2::Value *v = 0106 kmdValueToExivValue(entry.value(), 0107 Exiv2::IptcDataSets::dataSetType(iptcKey.tag(), iptcKey.record())); 0108 0109 if (v && v->typeId() != Exiv2::invalidTypeId) { 0110 iptcData.add(iptcKey, v); 0111 } 0112 #if EXIV2_TEST_VERSION(0,28,0) 0113 } catch (Exiv2::Error &e) { 0114 #else 0115 } catch (Exiv2::AnyError &e) { 0116 #endif 0117 dbgMetaData << "exiv error " << e.what(); 0118 } 0119 } 0120 } 0121 #if !EXIV2_TEST_VERSION(0, 18, 0) 0122 Exiv2::DataBuf rawData = iptcData.copy(); 0123 #else 0124 Exiv2::DataBuf rawData = Exiv2::IptcParser::encode(iptcData); 0125 #endif 0126 0127 if (headerType == KisMetaData::IOBackend::JpegHeader) { 0128 QByteArray header; 0129 header.append(photoshopMarker); 0130 header.append(QByteArray(1, 0)); // Null terminated string 0131 header.append(photoshopBimId_); 0132 header.append(photoshopIptc_); 0133 header.append(QByteArray(2, 0)); 0134 #if EXIV2_TEST_VERSION(0, 28, 0) 0135 qint32 size = rawData.size(); 0136 #else 0137 qint32 size = rawData.size_; 0138 #endif 0139 QByteArray sizeArray(4, 0); 0140 sizeArray[0] = (char)((size & 0xff000000) >> 24); 0141 sizeArray[1] = (char)((size & 0x00ff0000) >> 16); 0142 sizeArray[2] = (char)((size & 0x0000ff00) >> 8); 0143 sizeArray[3] = (char)(size & 0x000000ff); 0144 header.append(sizeArray); 0145 ioDevice->write(header); 0146 } 0147 0148 #if EXIV2_TEST_VERSION(0, 28, 0) 0149 ioDevice->write((const char *)rawData.data(), rawData.size()); 0150 #else 0151 ioDevice->write((const char *)rawData.pData_, rawData.size_); 0152 #endif 0153 ioDevice->close(); 0154 return true; 0155 } 0156 0157 bool KisIptcIO::canSaveAllEntries(KisMetaData::Store *store) const 0158 { 0159 Q_UNUSED(store); 0160 return false; 0161 } 0162 0163 bool KisIptcIO::loadFrom(KisMetaData::Store *store, QIODevice *ioDevice) const 0164 { 0165 initMappingsTable(); 0166 dbgMetaData << "Loading IPTC Tags"; 0167 ioDevice->open(QIODevice::ReadOnly); 0168 QByteArray arr = ioDevice->readAll(); 0169 Exiv2::IptcData iptcData; 0170 #if !EXIV2_TEST_VERSION(0, 18, 0) 0171 iptcData.load((const Exiv2::byte *)arr.data(), arr.size()); 0172 #else 0173 Exiv2::IptcParser::decode(iptcData, (const Exiv2::byte *)arr.data(), arr.size()); 0174 #endif 0175 dbgMetaData << "There are" << iptcData.count() << " entries in the IPTC section"; 0176 for (Exiv2::IptcMetadata::const_iterator it = iptcData.begin(); it != iptcData.end(); ++it) { 0177 dbgMetaData << "Reading info for key" << it->key().c_str(); 0178 if (d->iptcToKMD.contains(it->key().c_str())) { 0179 const IPTCToKMD &iptcToKMd = d->iptcToKMD[it->key().c_str()]; 0180 const KisMetaData::Schema *schema = 0181 KisMetaData::SchemaRegistry::instance()->schemaFromUri(iptcToKMd.namespaceUri); 0182 KisMetaData::Value value; 0183 if (iptcToKMd.exivTag == "Iptc.Application2.Keywords") { 0184 Q_ASSERT(it->getValue()->typeId() == Exiv2::string); 0185 QString data = it->getValue()->toString().c_str(); 0186 0187 QStringList list = data.split(','); 0188 QList<KisMetaData::Value> values; 0189 Q_FOREACH (const QString &entry, list) { 0190 values.push_back(KisMetaData::Value(entry)); 0191 } 0192 value = KisMetaData::Value(values, KisMetaData::Value::UnorderedArray); 0193 } else { 0194 value = exivValueToKMDValue(it->getValue(), false); 0195 } 0196 store->addEntry(KisMetaData::Entry(schema, iptcToKMd.name, value)); 0197 } 0198 } 0199 return false; 0200 }