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 }