diff --git a/core/libs/metadataengine/engine/metaengine_mergehelper.h b/core/libs/metadataengine/engine/metaengine_mergehelper.h index 5250c93eef..23ae033a6a 100644 --- a/core/libs/metadataengine/engine/metaengine_mergehelper.h +++ b/core/libs/metadataengine/engine/metaengine_mergehelper.h @@ -1,157 +1,160 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Internal merge helper. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_META_ENGINE_MERGE_HELPER_H #define DIGIKAM_META_ENGINE_MERGE_HELPER_H #include "metaengine_p.h" namespace Digikam { template > class Q_DECL_HIDDEN MetaEngineMergeHelper { public: KeyStringList keys; public: MetaEngineMergeHelper& operator<<(const KeyString& key) { keys << key; + return *this; } /** * Merge two (Exif,IPTC,Xmp) Data packages, where the result is stored in dest * and fields from src take precedence over existing data from dest. */ void mergeAll(const Data& src, Data& dest) { for (typename Data::const_iterator it = src.begin() ; it != src.end() ; ++it) { typename Data::iterator destIt = dest.findKey(Key(it->key())); if (destIt == dest.end()) { dest.add(*it); } else { *destIt = *it; } } } /** * Merge two (Exif,IPTC,Xmp) Data packages, the result is stored in dest. * Only keys in keys are considered for merging. * Fields from src take precedence over existing data from dest. */ void mergeFields(const Data& src, Data& dest) { foreach (const KeyString& keyString, keys) { Key key(keyString.latin1()); typename Data::const_iterator it = src.findKey(key); if (it == src.end()) { continue; } typename Data::iterator destIt = dest.findKey(key); if (destIt == dest.end()) { dest.add(*it); } else { *destIt = *it; } } } /** * Merge two (Exif,IPTC,Xmp) Data packages, the result is stored in dest. * The following steps apply only to keys in "keys": * The result is determined by src. * Keys must exist in src to kept in dest. * Fields from src take precedence over existing data from dest. */ void exclusiveMerge(const Data& src, Data& dest) { foreach (const KeyString& keyString, keys) { Key key(keyString.latin1()); typename Data::const_iterator it = src.findKey(key); typename Data::iterator destIt = dest.findKey(key); if (destIt == dest.end()) { if (it != src.end()) { dest.add(*it); } } else { if (it == src.end()) { dest.erase(destIt); } else { *destIt = *it; } } } } }; class Q_DECL_HIDDEN ExifMetaEngineMergeHelper : public MetaEngineMergeHelper { }; class Q_DECL_HIDDEN IptcMetaEngineMergeHelper : public MetaEngineMergeHelper { }; #ifdef _XMP_SUPPORT_ + class Q_DECL_HIDDEN XmpMetaEngineMergeHelper : public MetaEngineMergeHelper { }; + #endif } // namespace Digikam #endif // DIGIKAM_META_ENGINE_MERGE_HELPER_H diff --git a/core/libs/metadataengine/engine/metaengine_p.cpp b/core/libs/metadataengine/engine/metaengine_p.cpp index fdc60cdc59..0aff37fe21 100644 --- a/core/libs/metadataengine/engine/metaengine_p.cpp +++ b/core/libs/metadataengine/engine/metaengine_p.cpp @@ -1,836 +1,878 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Internal private container. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "metaengine_p.h" // C ANSI includes extern "C" { #include #ifndef Q_CC_MSVC # include #else # include #endif } // Qt includes #include #include // Local includes #include "digikam_debug.h" #include "metaengine_data_p.h" // Pragma directives to reduce warnings from Exiv2. #if defined(Q_CC_GNU) && !defined(Q_CC_CLANG) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif namespace Digikam { -/** This mutex is used to protect all Exiv2 API calls when MetaEngine is used with multi-threads. +/** + * This mutex is used to protect all Exiv2 API calls when MetaEngine is used with multi-threads. */ QMutex s_metaEngineMutex(QMutex::Recursive); MetaEngine::Private::Private() : writeRawFiles(false), updateFileTimeStamp(false), useXMPSidecar4Reading(false), useCompatibleFileName(false), metadataWritingMode(WRITE_TO_FILE_ONLY), loadedFromSidecar(false), data(new MetaEngineData::Private) { QMutexLocker lock(&s_metaEngineMutex); Exiv2::LogMsg::setHandler(MetaEngine::Private::printExiv2MessageHandler); } MetaEngine::Private::~Private() { } const Exiv2::ExifData& MetaEngine::Private::exifMetadata() const { return data.constData()->exifMetadata; } const Exiv2::IptcData& MetaEngine::Private::iptcMetadata() const { return data.constData()->iptcMetadata; } const std::string& MetaEngine::Private::itemComments() const { return data.constData()->imageComments; } Exiv2::ExifData& MetaEngine::Private::exifMetadata() { return data.data()->exifMetadata; } Exiv2::IptcData& MetaEngine::Private::iptcMetadata() { return data.data()->iptcMetadata; } std::string& MetaEngine::Private::itemComments() { return data.data()->imageComments; } #ifdef _XMP_SUPPORT_ + const Exiv2::XmpData& MetaEngine::Private::xmpMetadata() const { return data.constData()->xmpMetadata; } Exiv2::XmpData& MetaEngine::Private::xmpMetadata() { return data.data()->xmpMetadata; } + #endif void MetaEngine::Private::copyPrivateData(const Private* const other) { QMutexLocker lock(&s_metaEngineMutex); data = other->data; filePath = other->filePath; writeRawFiles = other->writeRawFiles; updateFileTimeStamp = other->updateFileTimeStamp; useXMPSidecar4Reading = other->useXMPSidecar4Reading; useCompatibleFileName = other->useCompatibleFileName; metadataWritingMode = other->metadataWritingMode; } bool MetaEngine::Private::saveToXMPSidecar(const QFileInfo& finfo) const { QString filePath = MetaEngine::sidecarFilePathForFile(finfo.filePath(), useCompatibleFileName); if (filePath.isEmpty()) { return false; } QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::Image::AutoPtr image; image = Exiv2::ImageFactory::create(Exiv2::ImageType::xmp, (const char*)(QFile::encodeName(filePath).constData())); #if EXIV2_TEST_VERSION(0,27,99) + return saveOperations(finfo, std::move(image)); + #else + return saveOperations(finfo, image); + #endif + } catch(Exiv2::AnyError& e) { printExiv2ExceptionError(QLatin1String("Cannot save metadata to XMP sidecar using Exiv2 "), e); + return false; } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; + return false; } } bool MetaEngine::Private::saveToFile(const QFileInfo& finfo) const { if (!finfo.isWritable()) { qCDebug(DIGIKAM_METAENGINE_LOG) << "File" << finfo.fileName() << "is read only. Metadata not written."; return false; } QStringList rawTiffBasedSupported, rawTiffBasedNotSupported; // Raw files supported by Exiv2 0.26 + rawTiffBasedSupported << QLatin1String("cr2") << QLatin1String("crw") << QLatin1String("dng") << QLatin1String("nef") << QLatin1String("pef") << QLatin1String("orf") << QLatin1String("srw"); // Raw files not supported by Exiv2 0.26 + rawTiffBasedNotSupported << QLatin1String("3fr") << QLatin1String("arw") << QLatin1String("dcr") << QLatin1String("erf") << QLatin1String("k25") << QLatin1String("kdc") << QLatin1String("mos") << QLatin1String("raf") << QLatin1String("raw") << QLatin1String("sr2") << QLatin1String("srf") << QLatin1String("rw2"); QString ext = finfo.suffix().toLower(); if (rawTiffBasedNotSupported.contains(ext) || (!writeRawFiles && rawTiffBasedSupported.contains(ext))) { qCDebug(DIGIKAM_METAENGINE_LOG) << finfo.fileName() << "is a TIFF based RAW file, " << "writing to such a file is disabled by current settings."; return false; } /* if (rawTiffBasedNotSupported.contains(ext)) { qCDebug(DIGIKAM_METAENGINE_LOG) << finfo.fileName() - << "is TIFF based RAW file not yet supported. Metadata not saved."; + << "is TIFF based RAW file not yet supported. Metadata not saved."; return false; } if (rawTiffBasedSupported.contains(ext) && !writeRawFiles) { qCDebug(DIGIKAM_METAENGINE_LOG) << finfo.fileName() - << "is TIFF based RAW file supported but writing mode is disabled. " - << "Metadata not saved."; + << "is TIFF based RAW file supported but writing mode is disabled. " + << "Metadata not saved."; return false; } qCDebug(DIGIKAM_METAENGINE_LOG) << "File Extension: " << ext << " is supported for writing mode"; bool ret = false; */ QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::Image::AutoPtr image; image = Exiv2::ImageFactory::open((const char*)(QFile::encodeName(finfo.filePath()).constData())); #if EXIV2_TEST_VERSION(0,27,99) + return saveOperations(finfo, std::move(image)); + #else + return saveOperations(finfo, image); + #endif + } catch(Exiv2::AnyError& e) { printExiv2ExceptionError(QLatin1String("Cannot save metadata to image using Exiv2 "), e); + return false; } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; + return false; } } bool MetaEngine::Private::saveOperations(const QFileInfo& finfo, Exiv2::Image::AutoPtr image) const { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::AccessMode mode; bool wroteComment = false; bool wroteEXIF = false; bool wroteIPTC = false; bool wroteXMP = false; // We need to load target file metadata to merge with new one. It's mandatory with TIFF format: // like all tiff file structure is based on Exif. + image->readMetadata(); // Image Comments --------------------------------- mode = image->checkMode(Exiv2::mdComment); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { image->setComment(itemComments()); wroteComment = true; } qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteComment: " << wroteComment; // Exif metadata ---------------------------------- mode = image->checkMode(Exiv2::mdExif); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { if (image->mimeType() == "image/tiff") { Exiv2::ExifData orgExif = image->exifData(); Exiv2::ExifData newExif; QStringList untouchedTags; // With tiff image we cannot overwrite whole Exif data as well, because // image data are stored in Exif container. We need to take a care about // to not lost image data. + untouchedTags << QLatin1String("Exif.Image.ImageWidth"); untouchedTags << QLatin1String("Exif.Image.ImageLength"); untouchedTags << QLatin1String("Exif.Image.BitsPerSample"); untouchedTags << QLatin1String("Exif.Image.Compression"); untouchedTags << QLatin1String("Exif.Image.PhotometricInterpretation"); untouchedTags << QLatin1String("Exif.Image.FillOrder"); untouchedTags << QLatin1String("Exif.Image.SamplesPerPixel"); untouchedTags << QLatin1String("Exif.Image.StripOffsets"); untouchedTags << QLatin1String("Exif.Image.RowsPerStrip"); untouchedTags << QLatin1String("Exif.Image.StripByteCounts"); untouchedTags << QLatin1String("Exif.Image.XResolution"); untouchedTags << QLatin1String("Exif.Image.YResolution"); untouchedTags << QLatin1String("Exif.Image.PlanarConfiguration"); untouchedTags << QLatin1String("Exif.Image.ResolutionUnit"); for (Exiv2::ExifData::const_iterator it = orgExif.begin() ; it != orgExif.end() ; ++it) { if (untouchedTags.contains(QLatin1String(it->key().c_str()))) { newExif[it->key().c_str()] = orgExif[it->key().c_str()]; } } Exiv2::ExifData readedExif = exifMetadata(); for (Exiv2::ExifData::const_iterator it = readedExif.begin() ; it != readedExif.end() ; ++it) { if (!untouchedTags.contains(QLatin1String(it->key().c_str()))) { newExif[it->key().c_str()] = readedExif[it->key().c_str()]; } } image->setExifData(newExif); } else { image->setExifData(exifMetadata()); } wroteEXIF = true; } qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteEXIF: " << wroteEXIF; // Iptc metadata ---------------------------------- mode = image->checkMode(Exiv2::mdIptc); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { image->setIptcData(iptcMetadata()); wroteIPTC = true; } qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteIPTC: " << wroteIPTC; // Xmp metadata ----------------------------------- mode = image->checkMode(Exiv2::mdXmp); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { + #ifdef _XMP_SUPPORT_ + image->setXmpData(xmpMetadata()); wroteXMP = true; + #endif + } qCDebug(DIGIKAM_METAENGINE_LOG) << "wroteXMP: " << wroteXMP; - if (!wroteComment && !wroteEXIF && !wroteIPTC && !wroteXMP) + if (!wroteComment && !wroteEXIF && !wroteIPTC && !wroteXMP) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Writing metadata is not supported for file" << finfo.fileName(); + return false; } else if (!wroteEXIF || !wroteIPTC || !wroteXMP) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Support for writing metadata is limited for file" << finfo.fileName(); } if (!updateFileTimeStamp) { // Don't touch access and modification timestamp of file. + QT_STATBUF st; struct utimbuf ut; int ret = QT_STAT(QFile::encodeName(filePath).constData(), &st); if (ret == 0) { ut.modtime = st.st_mtime; ut.actime = st.st_atime; } image->writeMetadata(); if (ret == 0) { ::utime(QFile::encodeName(filePath).constData(), &ut); } qCDebug(DIGIKAM_METAENGINE_LOG) << "File time stamp restored"; } else { image->writeMetadata(); } return true; } catch(Exiv2::AnyError& e) { printExiv2ExceptionError(QLatin1String("Cannot save metadata using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return false; } void MetaEngine::Private::printExiv2ExceptionError(const QString& msg, Exiv2::AnyError& e) { std::string s(e.what()); qCCritical(DIGIKAM_METAENGINE_LOG) << msg.toLatin1().constData() << " (Error #" << e.code() << ": " << s.c_str(); } void MetaEngine::Private::printExiv2MessageHandler(int lvl, const char* msg) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Exiv2 (" << lvl << ") : " << msg; } QString MetaEngine::Private::convertCommentValue(const Exiv2::Exifdatum& exifDatum) const { QMutexLocker lock(&s_metaEngineMutex); try { std::string comment; std::string charset; comment = exifDatum.toString(); // libexiv2 will prepend "charset=\"SomeCharset\" " if charset is specified // Before conversion to QString, we must know the charset, so we stay with std::string for a while if ((comment.length() > 8) && (comment.substr(0, 8) == "charset=")) { // the prepended charset specification is followed by a blank std::string::size_type pos = comment.find_first_of(' '); if (pos != std::string::npos) { // extract string between the = and the blank charset = comment.substr(8, pos-8); // get the rest of the string after the charset specification comment = comment.substr(pos+1); } } if (charset == "\"Unicode\"") { return QString::fromUtf8(comment.data()); } else if (charset == "\"Jis\"") { QTextCodec* const codec = QTextCodec::codecForName("JIS7"); if (codec) { const char* tmp = comment.c_str(); if (tmp) { return codec->toUnicode(tmp); } } return QLatin1String(""); } else if (charset == "\"Ascii\"") { return QLatin1String(comment.c_str()); } else { return detectEncodingAndDecode(comment); } } catch(Exiv2::AnyError& e) { printExiv2ExceptionError(QLatin1String("Cannot convert Comment using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } return QString(); } QString MetaEngine::Private::detectEncodingAndDecode(const std::string& value) const { // For charset autodetection, we could use sophisticated code // (Mozilla chardet, KHTML's autodetection, QTextCodec::codecForContent), // but that is probably too much. // We check for UTF8, Local encoding and ASCII. // Look like KEncodingDetector class can provide a full implementation for encoding detection. if (value.empty()) { return QString(); } if (isUtf8(value.c_str())) { return QString::fromUtf8(value.c_str()); } // Utf8 has a pretty unique byte pattern. // Thats not true for ASCII, it is not possible // to reliably autodetect different ISO-8859 charsets. // So we can use either local encoding, or latin1. QString localString = QString::fromLocal8Bit(value.c_str()); // To fix broken JFIF comment, replace non printable // characters from the string. See bug 39617 for (int i = 0 ; i < localString.size() ; ++i) { if (!localString.at(i).isPrint() && (localString.at(i) != QLatin1Char('\n')) && (localString.at(i) != QLatin1Char('\r'))) { localString[i] = QLatin1Char('_'); } } return localString; } bool MetaEngine::Private::isUtf8(const char* const buffer) const { int i, n; unsigned char c; bool gotone = false; if (!buffer) { return true; } // character never appears in text + #define F 0 + // character appears in plain ASCII text + #define T 1 + // character appears in ISO-8859 text + #define I 2 + // character appears in non-ISO extended ASCII (Mac, IBM PC) + #define X 3 static const unsigned char text_chars[256] = { // BEL BS HT LF FF CR F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F, // 0x0X // ESC F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F, // 0x1X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x2X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x3X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x4X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x5X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x6X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F, // 0x7X // NEL X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, // 0x8X X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, // 0x9X I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xaX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xbX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xcX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xdX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xeX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I // 0xfX }; for (i = 0 ; (c = buffer[i]) ; ++i) { if ((c & 0x80) == 0) { // 0xxxxxxx is plain ASCII // Even if the whole file is valid UTF-8 sequences, // still reject it if it uses weird control characters. if (text_chars[c] != T) { return false; } } else if ((c & 0x40) == 0) { // 10xxxxxx never 1st byte + return false; } else { // 11xxxxxx begins UTF-8 + int following = 0; if ((c & 0x20) == 0) { // 110xxxxx + following = 1; } else if ((c & 0x10) == 0) { // 1110xxxx + following = 2; } else if ((c & 0x08) == 0) { // 11110xxx + following = 3; } else if ((c & 0x04) == 0) { // 111110xx + following = 4; } else if ((c & 0x02) == 0) { // 1111110x + following = 5; } else { return false; } for (n = 0 ; n < following ; ++n) { i++; if (!(c = buffer[i])) { goto done; } if (((c & 0x80) == 0) || (c & 0x40)) { return false; } } gotone = true; } } done: return gotone; // don't claim it's UTF-8 if it's all 7-bit. } #undef F #undef T #undef I #undef X int MetaEngine::Private::getXMPTagsListFromPrefix(const QString& pf, MetaEngine::TagsMap& tagsMap) const { int i = 0; #ifdef _XMP_SUPPORT_ QMutexLocker lock(&s_metaEngineMutex); try { QList tags; tags << Exiv2::XmpProperties::propertyList(pf.toLatin1().data()); for (QList::iterator it = tags.begin() ; it != tags.end() ; ++it) { while ( (*it) && !QString::fromLatin1((*it)->name_).isNull() ) { QString key = QLatin1String( Exiv2::XmpKey( pf.toLatin1().data(), (*it)->name_ ).key().c_str() ); QStringList values; values << QLatin1String((*it)->name_) << QLatin1String((*it)->title_) << QLatin1String((*it)->desc_); tagsMap.insert(key, values); ++(*it); i++; } } } catch(Exiv2::AnyError& e) { printExiv2ExceptionError(QLatin1String("Cannot get Xmp tags list using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(pf); Q_UNUSED(tagsMap); #endif // _XMP_SUPPORT_ return i; } #ifdef _XMP_SUPPORT_ void MetaEngine::Private::loadSidecarData(Exiv2::Image::AutoPtr xmpsidecar) { // Having a sidecar is a special situation. // The sidecar data often "dominates", see in particular bug 309058 for important aspects: // If a field is removed from the sidecar, we must ignore (older) data for this field in the file. // First: Ignore file XMP, only use sidecar XMP xmpMetadata() = xmpsidecar->xmpData(); loadedFromSidecar = true; // EXIF // Four groups of properties are mapped between EXIF and XMP: // Date/Time, Description, Copyright, Creator // A few more tags are defined "writeback" tags in the XMP specification, the sidecar value therefore overrides the Exif value. // The rest is kept side-by-side. // (to understand, remember that the xmpsidecar's Exif data is actually XMP data mapped back to Exif) // Description, Copyright and Creator is dominated by the sidecar: Remove file Exif fields, if field not in XMP. ExifMetaEngineMergeHelper exifDominatedHelper; exifDominatedHelper << QLatin1String("Exif.Image.ImageDescription") << QLatin1String("Exif.Photo.UserComment") << QLatin1String("Exif.Image.Copyright") << QLatin1String("Exif.Image.Artist"); exifDominatedHelper.exclusiveMerge(xmpsidecar->exifData(), exifMetadata()); // Date/Time and "the few more" from the XMP spec are handled as writeback // Note that Date/Time mapping is slightly contradictory in latest specs. ExifMetaEngineMergeHelper exifWritebackHelper; exifWritebackHelper << QLatin1String("Exif.Image.DateTime") << QLatin1String("Exif.Image.DateTime") << QLatin1String("Exif.Photo.DateTimeOriginal") << QLatin1String("Exif.Photo.DateTimeDigitized") << QLatin1String("Exif.Image.Orientation") << QLatin1String("Exif.Image.XResolution") << QLatin1String("Exif.Image.YResolution") << QLatin1String("Exif.Image.ResolutionUnit") << QLatin1String("Exif.Image.Software") << QLatin1String("Exif.Photo.RelatedSoundFile"); exifWritebackHelper.mergeFields(xmpsidecar->exifData(), exifMetadata()); // IPTC // These fields cover almost all relevant IPTC data and are defined in the XMP specification for reconciliation. IptcMetaEngineMergeHelper iptcDominatedHelper; iptcDominatedHelper << QLatin1String("Iptc.Application2.ObjectName") << QLatin1String("Iptc.Application2.Urgency") << QLatin1String("Iptc.Application2.Category") << QLatin1String("Iptc.Application2.SuppCategory") << QLatin1String("Iptc.Application2.Keywords") << QLatin1String("Iptc.Application2.SubLocation") << QLatin1String("Iptc.Application2.SpecialInstructions") << QLatin1String("Iptc.Application2.Byline") << QLatin1String("Iptc.Application2.BylineTitle") << QLatin1String("Iptc.Application2.City") << QLatin1String("Iptc.Application2.ProvinceState") << QLatin1String("Iptc.Application2.CountryCode") << QLatin1String("Iptc.Application2.CountryName") << QLatin1String("Iptc.Application2.TransmissionReference") << QLatin1String("Iptc.Application2.Headline") << QLatin1String("Iptc.Application2.Credit") << QLatin1String("Iptc.Application2.Source") << QLatin1String("Iptc.Application2.Copyright") << QLatin1String("Iptc.Application2.Caption") << QLatin1String("Iptc.Application2.Writer"); iptcDominatedHelper.exclusiveMerge(xmpsidecar->iptcData(), iptcMetadata()); IptcMetaEngineMergeHelper iptcWritebackHelper; iptcWritebackHelper << QLatin1String("Iptc.Application2.DateCreated") << QLatin1String("Iptc.Application2.TimeCreated") << QLatin1String("Iptc.Application2.DigitizationDate") << QLatin1String("Iptc.Application2.DigitizationTime"); iptcWritebackHelper.mergeFields(xmpsidecar->iptcData(), iptcMetadata()); /* * TODO: Exiv2 (referring to 0.23) does not correctly synchronize all times values as given below. * Time values and their synchronization: * Original Date/Time – Creation date of the intellectual content (e.g. the photograph), * rather than the creatio*n date of the content being shown * Exif DateTimeOriginal (36867, 0x9003) and SubSecTimeOriginal (37521, 0x9291) * IPTC DateCreated (IIM 2:55, 0x0237) and TimeCreated (IIM 2:60, 0x023C) * XMP (photoshop:DateCreated) * Digitized Date/Time – Creation date of the digital representation * Exif DateTimeDigitized (36868, 0x9004) and SubSecTimeDigitized (37522, 0x9292) * IPTC DigitalCreationDate (IIM 2:62, 0x023E) and DigitalCreationTime (IIM 2:63, 0x023F) * XMP (xmp:CreateDate) * Modification Date/Time – Modification date of the digital image file * Exif DateTime (306, 0x132) and SubSecTime (37520, 0x9290) * XMP (xmp:ModifyDate) */ } + #endif // _XMP_SUPPORT_ } // namespace Digikam // Restore warnings #if defined(Q_CC_GNU) && !defined(Q_CC_CLANG) # pragma GCC diagnostic pop #endif #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif diff --git a/core/libs/metadataengine/engine/metaengine_p.h b/core/libs/metadataengine/engine/metaengine_p.h index 4d6cc86128..389481034a 100644 --- a/core/libs/metadataengine/engine/metaengine_p.h +++ b/core/libs/metadataengine/engine/metaengine_p.h @@ -1,204 +1,206 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Internal private container. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_META_ENGINE_PRIVATE_H #define DIGIKAM_META_ENGINE_PRIVATE_H #include "metaengine.h" // C++ includes #include #include #include #include #include #include #include #include // Qt includes #include #include #include #include #include #include // Exiv2 includes ------------------------------------------------------- // NOTE: All Exiv2 header must be stay there to not expose external source code to Exiv2 API // and reduce Exiv2 dependency to client code. #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif // The pragmas are required to be able to catch exceptions thrown by libexiv2: // See http://gcc.gnu.org/wiki/Visibility, the section about c++ exceptions. // They are needed for all libexiv2 versions that do not care about visibility. #ifdef Q_CC_GNU # pragma GCC visibility push(default) #endif #include #include #include #include #include #include #include #include #include #include #include #include // Check if Exiv2 support XMP #ifdef EXV_HAVE_XMP_TOOLKIT # define _XMP_SUPPORT_ 1 #endif #ifndef EXIV2_TEST_VERSION # define EXIV2_TEST_VERSION(major,minor,patch) \ ( EXIV2_VERSION >= EXIV2_MAKE_VERSION(major,minor,patch) ) #endif #if EXIV2_TEST_VERSION(0,27,99) # define AutoPtr UniquePtr #endif // With exiv2 > 0.20.0, all makernote header files have been removed to increase binary compatibility. // See Exiv2 bugzilla entry http://dev.exiv2.org/issues/719 // and wiki topic http://dev.exiv2.org/boards/3/topics/583 + #ifdef Q_CC_GNU # pragma GCC visibility pop #endif // End of Exiv2 headers ------------------------------------------------------ // Local includes #include "metaengine_mergehelper.h" namespace Digikam { extern QMutex s_metaEngineMutex; // -------------------------------------------------------------------------- class Q_DECL_HIDDEN MetaEngine::Private { public: explicit Private(); virtual ~Private(); void copyPrivateData(const Private* const other); bool saveToXMPSidecar(const QFileInfo& finfo) const; bool saveToFile(const QFileInfo& finfo) const; bool saveOperations(const QFileInfo& finfo, Exiv2::Image::AutoPtr image) const; /** * Wrapper method to convert a Comments content to a QString. */ QString convertCommentValue(const Exiv2::Exifdatum& exifDatum) const; /** * Charset autodetection to convert a string to a QString. */ QString detectEncodingAndDecode(const std::string& value) const; /** * UTF8 autodetection from a string. */ bool isUtf8(const char* const buffer) const; int getXMPTagsListFromPrefix(const QString& pf, MetaEngine::TagsMap& tagsMap) const; const Exiv2::ExifData& exifMetadata() const; const Exiv2::IptcData& iptcMetadata() const; const std::string& itemComments() const; Exiv2::ExifData& exifMetadata(); Exiv2::IptcData& iptcMetadata(); std::string& itemComments(); #ifdef _XMP_SUPPORT_ const Exiv2::XmpData& xmpMetadata() const; Exiv2::XmpData& xmpMetadata(); void loadSidecarData(Exiv2::Image::AutoPtr xmpsidecar); + #endif public: /** * Generic method to print the Exiv2 C++ Exception error message from 'e'. * 'msg' string is printed using qDebug rules. */ static void printExiv2ExceptionError(const QString& msg, Exiv2::AnyError& e); /** * Generic method to print debug message from Exiv2. * 'msg' string is printed using qDebug rules. 'lvl' is the debug level of Exiv2 message. */ static void printExiv2MessageHandler(int lvl, const char* msg); public: bool writeRawFiles; bool updateFileTimeStamp; bool useXMPSidecar4Reading; bool useCompatibleFileName; /// A mode from #MetadataWritingMode enum. int metadataWritingMode; /// XMP, and parts of EXIF/IPTC, were loaded from an XMP sidecar file bool loadedFromSidecar; QString filePath; QSize pixelSize; QString mimeType; QExplicitlySharedDataPointer data; }; } // namespace Digikam #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif #endif // DIGIKAM_META_ENGINE_PRIVATE_H diff --git a/core/libs/metadataengine/engine/metaengine_previews.cpp b/core/libs/metadataengine/engine/metaengine_previews.cpp index 8e6f243910..26f90ed264 100644 --- a/core/libs/metadataengine/engine/metaengine_previews.cpp +++ b/core/libs/metadataengine/engine/metaengine_previews.cpp @@ -1,250 +1,295 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Embedded preview loading. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // Local includes #include "metaengine_previews.h" #include "metaengine_p.h" #include "digikam_debug.h" #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif namespace Digikam { class Q_DECL_HIDDEN MetaEnginePreviews::Private { public: explicit Private() { manager = nullptr; } ~Private() { delete manager; } void load(Exiv2::Image::AutoPtr image_) { QMutexLocker lock(&s_metaEngineMutex); try { + #if EXIV2_TEST_VERSION(0,27,99) + image = std::move(image_); + #else + image = image_; + #endif image->readMetadata(); manager = new Exiv2::PreviewManager(*image); Exiv2::PreviewPropertiesList props = manager->getPreviewProperties(); // reverse order of list, which is smallest-first + Exiv2::PreviewPropertiesList::reverse_iterator it; for (it = props.rbegin() ; it != props.rend() ; ++it) { properties << *it; } } catch(Exiv2::AnyError& e) { MetaEngine::Private::printExiv2ExceptionError(QLatin1String("Cannot load preview data using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } } public: Exiv2::Image::AutoPtr image; Exiv2::PreviewManager* manager; QList properties; }; MetaEnginePreviews::MetaEnginePreviews(const QString& filePath) : d(new Private) { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)(QFile::encodeName(filePath).constData())); + #if EXIV2_TEST_VERSION(0,27,99) + d->load(std::move(image)); + #else + d->load(image); + #endif + } catch(Exiv2::AnyError& e) { MetaEngine::Private::printExiv2ExceptionError(QLatin1String("Cannot load metadata using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } } MetaEnginePreviews::MetaEnginePreviews(const QByteArray& imgData) : d(new Private) { QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size()); + #if EXIV2_TEST_VERSION(0,27,99) + d->load(std::move(image)); + #else + d->load(image); + #endif + } catch(Exiv2::AnyError& e) { MetaEngine::Private::printExiv2ExceptionError(QLatin1String("Cannot load metadata using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } } MetaEnginePreviews::~MetaEnginePreviews() { delete d; } bool MetaEnginePreviews::isEmpty() { return d->properties.isEmpty(); } QSize MetaEnginePreviews::originalSize() const { if (d->image.get()) + { return QSize(d->image->pixelWidth(), d->image->pixelHeight()); + } return QSize(); } QString MetaEnginePreviews::originalMimeType() const { if (d->image.get()) - return QLatin1String(d->image->mimeType().c_str()); + { + return QLatin1String(d->image->mimeType().c_str()); + } return QString(); } int MetaEnginePreviews::count() { return d->properties.size(); } int MetaEnginePreviews::dataSize(int index) { - if (index < 0 || index >= size()) return 0; + if ((index < 0) || (index >= size())) + { + return 0; + } return d->properties[index].size_; } int MetaEnginePreviews::width(int index) { - if (index < 0 || index >= size()) return 0; + if ((index < 0) || (index >= size())) + { + return 0; + } return d->properties[index].width_; } int MetaEnginePreviews::height(int index) { - if (index < 0 || index >= size()) return 0; + if ((index < 0) || (index >= size())) + { + return 0; + } return d->properties[index].height_; } QString MetaEnginePreviews::mimeType(int index) { - if (index < 0 || index >= size()) return QString(); + if ((index < 0) || (index >= size())) + { + return QString(); + } return QLatin1String(d->properties[index].mimeType_.c_str()); } QString MetaEnginePreviews::fileExtension(int index) { - if (index < 0 || index >= size()) return QString(); + if ((index < 0) || (index >= size())) + { + return QString(); + } return QLatin1String(d->properties[index].extension_.c_str()); } QByteArray MetaEnginePreviews::data(int index) { - if (index < 0 || index >= size()) return QByteArray(); + if ((index < 0) || (index >= size())) + { + return QByteArray(); + } qCDebug(DIGIKAM_METAENGINE_LOG) << "index : " << index; qCDebug(DIGIKAM_METAENGINE_LOG) << "properties: " << count(); QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::PreviewImage image = d->manager->getPreviewImage(d->properties[index]); + return QByteArray((const char*)image.pData(), image.size()); } catch(Exiv2::AnyError& e) { MetaEngine::Private::printExiv2ExceptionError(QLatin1String("Cannot load metadata using Exiv2 "), e); + return QByteArray(); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; + return QByteArray(); } } QImage MetaEnginePreviews::image(int index) { QByteArray previewData = data(index); QImage image; if (!image.loadFromData(previewData)) + { return QImage(); + } return image; } } // namespace Digikam #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif diff --git a/core/libs/metadataengine/engine/metaengine_rotation.cpp b/core/libs/metadataengine/engine/metaengine_rotation.cpp index 1163029533..86599837a6 100644 --- a/core/libs/metadataengine/engine/metaengine_rotation.cpp +++ b/core/libs/metadataengine/engine/metaengine_rotation.cpp @@ -1,355 +1,355 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Tools for combining rotation operations. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // Local includes #include "metaengine_rotation.h" namespace Digikam { /** - If the picture is displayed according to the exif orientation tag, - the user will request rotating operations relative to what he sees, - and that is the picture rotated according to the EXIF tag. - So the operation requested and the given EXIF angle must be combined. - E.g. if orientation is "6" (rotate 90 clockwiseto show correctly) - and the user selects 180 clockwise, the operation is 270. - If the user selected 270, the operation would be None (and clearing the exif tag). - - This requires to describe the transformations in a model which - cares for both composing (180+90=270) and eliminating (180+180=no action), - as well as the non-commutative nature of the operations (vflip+90 is not 90+vflip) - - All 2D transformations can be described by a 2x3 matrix, see QWMetaEngineRotation. - All transformations needed here - rotate 90, 180, 270, flipV, flipH - - can be described in a 2x2 matrix with the values 0,1,-1 - (because flipping is expressed by changing the sign only, - and sine and cosine of 90, 180 and 270 are either 0,1 or -1). - - x' = m11 x + m12 y - y' = m21 x + m22 y - - Moreover, all combinations of these rotate/flip operations result in one of the eight - matrices defined below. - (I did not proof that mathematically, but empirically) - - static const MetaEngineRotation identity; //( 1, 0, 0, 1) - static const MetaEngineRotation rotate90; //( 0, 1, -1, 0) - static const MetaEngineRotation rotate180; //(-1, 0, 0, -1) - static const MetaEngineRotation rotate270; //( 0, -1, 1, 0) - static const MetaEngineRotation flipHorizontal; //(-1, 0, 0, 1) - static const MetaEngineRotation flipVertical; //( 1, 0, 0, -1) - static const MetaEngineRotation rotate90flipHorizontal; //( 0, 1, 1, 0), first rotate, then flip - static const MetaEngineRotation rotate90flipVertical; //( 0, -1, -1, 0), first rotate, then flip - -*/ + * If the picture is displayed according to the exif orientation tag, + * the user will request rotating operations relative to what he sees, + * and that is the picture rotated according to the EXIF tag. + * So the operation requested and the given EXIF angle must be combined. + * E.g. if orientation is "6" (rotate 90 clockwiseto show correctly) + * and the user selects 180 clockwise, the operation is 270. + * If the user selected 270, the operation would be None (and clearing the exif tag). + * + * This requires to describe the transformations in a model which + * cares for both composing (180+90=270) and eliminating (180+180=no action), + * as well as the non-commutative nature of the operations (vflip+90 is not 90+vflip) + * + * All 2D transformations can be described by a 2x3 matrix, see QWMetaEngineRotation. + * All transformations needed here - rotate 90, 180, 270, flipV, flipH - + * can be described in a 2x2 matrix with the values 0,1,-1 + * (because flipping is expressed by changing the sign only, + * and sine and cosine of 90, 180 and 270 are either 0,1 or -1). + * + * x' = m11 x + m12 y + * y' = m21 x + m22 y + * + * Moreover, all combinations of these rotate/flip operations result in one of the eight + * matrices defined below. + * (I did not proof that mathematically, but empirically) + * + * static const MetaEngineRotation identity; //( 1, 0, 0, 1) + * static const MetaEngineRotation rotate90; //( 0, 1, -1, 0) + * static const MetaEngineRotation rotate180; //(-1, 0, 0, -1) + * static const MetaEngineRotation rotate270; //( 0, -1, 1, 0) + * static const MetaEngineRotation flipHorizontal; //(-1, 0, 0, 1) + * static const MetaEngineRotation flipVertical; //( 1, 0, 0, -1) + * static const MetaEngineRotation rotate90flipHorizontal; //( 0, 1, 1, 0), first rotate, then flip + * static const MetaEngineRotation rotate90flipVertical; //( 0, -1, -1, 0), first rotate, then flip + * + */ namespace Matrix { static const MetaEngineRotation identity ( 1, 0, 0, 1); static const MetaEngineRotation rotate90 ( 0, 1, -1, 0); static const MetaEngineRotation rotate180 (-1, 0, 0, -1); static const MetaEngineRotation rotate270 ( 0, -1, 1, 0); static const MetaEngineRotation flipHorizontal (-1, 0, 0, 1); static const MetaEngineRotation flipVertical ( 1, 0, 0, -1); static const MetaEngineRotation rotate90flipHorizontal ( 0, 1, 1, 0); static const MetaEngineRotation rotate90flipVertical ( 0, -1, -1, 0); MetaEngineRotation matrix(MetaEngineRotation::TransformationAction action) { switch (action) { case MetaEngineRotation::NoTransformation: return identity; case MetaEngineRotation::FlipHorizontal: return flipHorizontal; case MetaEngineRotation::FlipVertical: return flipVertical; case MetaEngineRotation::Rotate90: return rotate90; case MetaEngineRotation::Rotate180: return rotate180; case MetaEngineRotation::Rotate270: return rotate270; } return identity; } MetaEngineRotation matrix(MetaEngine::ImageOrientation exifOrientation) { switch (exifOrientation) { case MetaEngine::ORIENTATION_NORMAL: return identity; case MetaEngine::ORIENTATION_HFLIP: return flipHorizontal; case MetaEngine::ORIENTATION_ROT_180: return rotate180; case MetaEngine::ORIENTATION_VFLIP: return flipVertical; case MetaEngine::ORIENTATION_ROT_90_HFLIP: return rotate90flipHorizontal; case MetaEngine::ORIENTATION_ROT_90: return rotate90; case MetaEngine::ORIENTATION_ROT_90_VFLIP: return rotate90flipVertical; case MetaEngine::ORIENTATION_ROT_270: return rotate270; case MetaEngine::ORIENTATION_UNSPECIFIED: return identity; } return identity; } } // namespace Matrix MetaEngineRotation::MetaEngineRotation() { - set( 1, 0, 0, 1 ); + set(1, 0, 0, 1); } MetaEngineRotation::MetaEngineRotation(TransformationAction action) { *this = Matrix::matrix(action); } MetaEngineRotation::MetaEngineRotation(MetaEngine::ImageOrientation exifOrientation) { *this = Matrix::matrix(exifOrientation); } MetaEngineRotation::MetaEngineRotation(int m11, int m12, int m21, int m22) { set(m11, m12, m21, m22); } void MetaEngineRotation::set(int m11, int m12, int m21, int m22) { m[0][0]=m11; m[0][1]=m12; m[1][0]=m21; m[1][1]=m22; } bool MetaEngineRotation::isNoTransform() const { return (*this == Matrix::identity); } MetaEngineRotation& MetaEngineRotation::operator*=(const MetaEngineRotation& ma) { set(ma.m[0][0]*m[0][0] + ma.m[0][1]*m[1][0], ma.m[0][0]*m[0][1] + ma.m[0][1]*m[1][1], ma.m[1][0]*m[0][0] + ma.m[1][1]*m[1][0], ma.m[1][0]*m[0][1] + ma.m[1][1]*m[1][1]); return *this; } bool MetaEngineRotation::operator==(const MetaEngineRotation& ma) const { return ( (m[0][0] == ma.m[0][0]) && (m[0][1] == ma.m[0][1]) && (m[1][0] == ma.m[1][0]) && (m[1][1] == ma.m[1][1]) ); } bool MetaEngineRotation::operator!=(const MetaEngineRotation& ma) const { return !(*this==ma); } MetaEngineRotation& MetaEngineRotation::operator*=(TransformationAction action) { return (*this *= Matrix::matrix(action)); } MetaEngineRotation& MetaEngineRotation::operator*=(QList actions) { foreach (const TransformationAction& action, actions) { *this *= Matrix::matrix(action); } return *this; } MetaEngineRotation& MetaEngineRotation::operator*=(MetaEngine::ImageOrientation exifOrientation) { return (*this *= Matrix::matrix(exifOrientation)); } /** * Converts the mathematically correct description * into the primitive operations that can be carried out losslessly. */ QList MetaEngineRotation::transformations() const { QList transforms; if (*this == Matrix::rotate90) { transforms << Rotate90; } else if (*this == Matrix::rotate180) { transforms << Rotate180; } else if (*this == Matrix::rotate270) { transforms << Rotate270; } else if (*this == Matrix::flipHorizontal) { transforms << FlipHorizontal; } else if (*this == Matrix::flipVertical) { transforms << FlipVertical; } else if (*this == Matrix::rotate90flipHorizontal) { // first rotate, then flip! transforms << Rotate90; transforms << FlipHorizontal; } else if (*this == Matrix::rotate90flipVertical) { // first rotate, then flip! transforms << Rotate90; transforms << FlipVertical; } return transforms; } MetaEngine::ImageOrientation MetaEngineRotation::exifOrientation() const { if (*this == Matrix::identity) { return MetaEngine::ORIENTATION_NORMAL; } if (*this == Matrix::rotate90) { return MetaEngine::ORIENTATION_ROT_90; } else if (*this == Matrix::rotate180) { return MetaEngine::ORIENTATION_ROT_180; } else if (*this == Matrix::rotate270) { return MetaEngine::ORIENTATION_ROT_270; } else if (*this == Matrix::flipHorizontal) { return MetaEngine::ORIENTATION_HFLIP; } else if (*this == Matrix::flipVertical) { return MetaEngine::ORIENTATION_VFLIP; } else if (*this == Matrix::rotate90flipHorizontal) { return MetaEngine::ORIENTATION_ROT_90_HFLIP; } else if (*this == Matrix::rotate90flipVertical) { return MetaEngine::ORIENTATION_ROT_90_VFLIP; } return MetaEngine::ORIENTATION_UNSPECIFIED; } QMatrix MetaEngineRotation::toMatrix() const { return toMatrix(exifOrientation()); } QMatrix MetaEngineRotation::toMatrix(MetaEngine::ImageOrientation orientation) { QMatrix matrix; switch (orientation) { case MetaEngine::ORIENTATION_NORMAL: case MetaEngine::ORIENTATION_UNSPECIFIED: break; case MetaEngine::ORIENTATION_HFLIP: matrix.scale(-1, 1); break; case MetaEngine::ORIENTATION_ROT_180: matrix.rotate(180); break; case MetaEngine::ORIENTATION_VFLIP: matrix.scale(1, -1); break; case MetaEngine::ORIENTATION_ROT_90_HFLIP: matrix.scale(-1, 1); matrix.rotate(90); break; case MetaEngine::ORIENTATION_ROT_90: matrix.rotate(90); break; case MetaEngine::ORIENTATION_ROT_90_VFLIP: matrix.scale(1, -1); matrix.rotate(90); break; case MetaEngine::ORIENTATION_ROT_270: matrix.rotate(270); break; } return matrix; } } // namespace Digikam diff --git a/core/libs/metadataengine/engine/metaengine_xmp.cpp b/core/libs/metadataengine/engine/metaengine_xmp.cpp index 26a61e74f0..8c99a7dee9 100644 --- a/core/libs/metadataengine/engine/metaengine_xmp.cpp +++ b/core/libs/metadataengine/engine/metaengine_xmp.cpp @@ -1,1217 +1,1325 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Xmp manipulation methods. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ // Local includes #include "metaengine_p.h" #include "digikam_debug.h" #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif namespace Digikam { bool MetaEngine::canWriteXmp(const QString& filePath) { + #ifdef _XMP_SUPPORT_ + QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*) (QFile::encodeName(filePath).constData())); Exiv2::AccessMode mode = image->checkMode(Exiv2::mdXmp); - return (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite); + return ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)); } catch(Exiv2::AnyError& e) { std::string s(e.what()); qCCritical(DIGIKAM_METAENGINE_LOG) << "Cannot check Xmp access mode using Exiv2 (Error #" << e.code() << ": " << s.c_str() << ")"; } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(filePath); #endif // _XMP_SUPPORT_ return false; } bool MetaEngine::hasXmp() const { + #ifdef _XMP_SUPPORT_ return !d->xmpMetadata().empty(); #else return false; #endif // _XMP_SUPPORT_ + } bool MetaEngine::clearXmp() const { + #ifdef _XMP_SUPPORT_ + QMutexLocker lock(&s_metaEngineMutex); try { d->xmpMetadata().clear(); + return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot clear Xmp data using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #endif // _XMP_SUPPORT_ return false; } QByteArray MetaEngine::getXmp() const { + #ifdef _XMP_SUPPORT_ + QMutexLocker lock(&s_metaEngineMutex); try { if (!d->xmpMetadata().empty()) { std::string xmpPacket; Exiv2::XmpParser::encode(xmpPacket, d->xmpMetadata()); QByteArray data(xmpPacket.data(), xmpPacket.size()); return data; } } catch(Exiv2::AnyError& e) { if (!d->filePath.isEmpty()) + { d->printExiv2ExceptionError(QLatin1String("Cannot get Xmp data using Exiv2 "), e); + } } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #endif // _XMP_SUPPORT_ return QByteArray(); } bool MetaEngine::setXmp(const QByteArray& data) const { + #ifdef _XMP_SUPPORT_ + QMutexLocker lock(&s_metaEngineMutex); try { if (!data.isEmpty()) { std::string xmpPacket; xmpPacket.assign(data.data(), data.size()); if (Exiv2::XmpParser::decode(d->xmpMetadata(), xmpPacket) != 0) + { return false; + } else + { return true; + } } } catch(Exiv2::AnyError& e) { if (!d->filePath.isEmpty()) + { qCCritical(DIGIKAM_METAENGINE_LOG) << "From file " << d->filePath.toLatin1().constData(); + } d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp data using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(data); #endif // _XMP_SUPPORT_ return false; } MetaEngine::MetaDataMap MetaEngine::getXmpTagsDataList(const QStringList& xmpKeysFilter, bool invertSelection) const { + #ifdef _XMP_SUPPORT_ if (d->xmpMetadata().empty()) + { return MetaDataMap(); + } QMutexLocker lock(&s_metaEngineMutex); try { Exiv2::XmpData xmpData = d->xmpMetadata(); xmpData.sortByKey(); QString ifDItemName; MetaDataMap metaDataMap; for (Exiv2::XmpData::const_iterator md = xmpData.begin() ; md != xmpData.end() ; ++md) { QString key = QLatin1String(md->key().c_str()); // Decode the tag value with a user friendly output. + std::ostringstream os; os << *md; QString value = QString::fromUtf8(os.str().c_str()); // If the tag is a language alternative type, parse content to detect language. + if (md->typeId() == Exiv2::langAlt) { QString lang; value = detectLanguageAlt(value, lang); } else { value = QString::fromUtf8(os.str().c_str()); } // To make a string just on one line. + value.replace(QLatin1Char('\n'), QLatin1String(" ")); // Some XMP key are redondancy. check if already one exist... + MetaDataMap::const_iterator it = metaDataMap.constFind(key); // We apply a filter to get only the XMP tags that we need. if (!xmpKeysFilter.isEmpty()) { if (!invertSelection) { if (xmpKeysFilter.contains(key.section(QLatin1Char('.'), 1, 1))) { if (it == metaDataMap.constEnd()) { metaDataMap.insert(key, value); } else { QString v = *it; v.append(QLatin1String(", ")); v.append(value); metaDataMap.insert(key, v); } } } else { if (!xmpKeysFilter.contains(key.section(QLatin1Char('.'), 1, 1))) { if (it == metaDataMap.constEnd()) { metaDataMap.insert(key, value); } else { QString v = *it; v.append(QLatin1String(", ")); v.append(value); metaDataMap.insert(key, v); } } } } else // else no filter at all. { if (it == metaDataMap.constEnd()) { metaDataMap.insert(key, value); } else { QString v = *it; v.append(QLatin1String(", ")); v.append(value); metaDataMap.insert(key, v); } } } return metaDataMap; } - catch (Exiv2::AnyError& e) + catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot parse Xmp metadata using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpKeysFilter); Q_UNUSED(invertSelection); #endif // _XMP_SUPPORT_ return MetaDataMap(); } QString MetaEngine::getXmpTagTitle(const char* xmpTagName) { + #ifdef _XMP_SUPPORT_ + QMutexLocker lock(&s_metaEngineMutex); try { std::string xmpkey(xmpTagName); Exiv2::XmpKey xk(xmpkey); - return QString::fromLocal8Bit( Exiv2::XmpProperties::propertyTitle(xk) ); + return QString::fromLocal8Bit(Exiv2::XmpProperties::propertyTitle(xk)); } - catch (Exiv2::AnyError& e) + catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot get Xmp metadata tag title using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); #endif // _XMP_SUPPORT_ return QString(); } QString MetaEngine::getXmpTagDescription(const char* xmpTagName) { + #ifdef _XMP_SUPPORT_ + try { std::string xmpkey(xmpTagName); Exiv2::XmpKey xk(xmpkey); return QString::fromLocal8Bit( Exiv2::XmpProperties::propertyDesc(xk) ); } - catch (Exiv2::AnyError& e) + catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot get Xmp metadata tag description using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); #endif // _XMP_SUPPORT_ return QString(); } QString MetaEngine::getXmpTagString(const char* xmpTagName, bool escapeCR) const { + #ifdef _XMP_SUPPORT_ try { Exiv2::XmpData xmpData(d->xmpMetadata()); Exiv2::XmpKey key(xmpTagName); Exiv2::XmpData::const_iterator it = xmpData.findKey(key); if (it != xmpData.end()) { std::ostringstream os; os << *it; QString tagValue = QString::fromUtf8(os.str().c_str()); if (escapeCR) + { tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); + } return tagValue; } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image using Exiv2 ") .arg(QLatin1String(xmpTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(escapeCR); #endif // _XMP_SUPPORT_ return QString(); } bool MetaEngine::setXmpTagString(const char* xmpTagName, const QString& value) const { + #ifdef _XMP_SUPPORT_ try { const std::string& txt(value.toUtf8().constData()); Exiv2::Value::AutoPtr xmpTxtVal = Exiv2::Value::create(Exiv2::xmpText); xmpTxtVal->read(txt); d->xmpMetadata()[xmpTagName].setValue(xmpTxtVal.get()); + return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(value); #endif // _XMP_SUPPORT_ return false; } bool MetaEngine::setXmpTagString(const char* xmpTagName, const QString& value, MetaEngine::XmpTagType type) const { #ifdef _XMP_SUPPORT_ try { const std::string &txt(value.toUtf8().constData()); Exiv2::XmpTextValue xmpTxtVal(""); - if (type == MetaEngine::NormalTag) // normal type + if (type == MetaEngine::NormalTag) // normal type { xmpTxtVal.read(txt); d->xmpMetadata().add(Exiv2::XmpKey(xmpTagName), &xmpTxtVal); + return true; } - if (type == MetaEngine::ArrayBagTag) // xmp type = bag + if (type == MetaEngine::ArrayBagTag) // xmp type = bag { xmpTxtVal.setXmpArrayType(Exiv2::XmpValue::xaBag); xmpTxtVal.read(""); d->xmpMetadata().add(Exiv2::XmpKey(xmpTagName), &xmpTxtVal); + return true; } - if (type == MetaEngine::StructureTag) // xmp type = struct + if (type == MetaEngine::StructureTag) // xmp type = struct { xmpTxtVal.setXmpStruct(); d->xmpMetadata().add(Exiv2::XmpKey(xmpTagName), &xmpTxtVal); + return true; } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(value); #endif // _XMP_SUPPORT_ return false; } MetaEngine::AltLangMap MetaEngine::getXmpTagStringListLangAlt(const char* xmpTagName, bool escapeCR) const { + #ifdef _XMP_SUPPORT_ try { Exiv2::XmpData xmpData = d->xmpMetadata(); for (Exiv2::XmpData::const_iterator it = xmpData.begin() ; it != xmpData.end() ; ++it) { - if (it->key() == xmpTagName && it->typeId() == Exiv2::langAlt) + if ((it->key() == xmpTagName) && (it->typeId() == Exiv2::langAlt)) { AltLangMap map; const Exiv2::LangAltValue &value = static_cast(it->value()); for (Exiv2::LangAltValue::ValueType::const_iterator it2 = value.value_.begin() ; it2 != value.value_.end() ; ++it2) { QString lang = QString::fromUtf8(it2->first.c_str()); QString text = QString::fromUtf8(it2->second.c_str()); if (escapeCR) + { text.replace(QLatin1Char('\n'), QLatin1String(" ")); + } map.insert(lang, text); } return map; } } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image using Exiv2 ") .arg(QLatin1String(xmpTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(escapeCR); #endif // _XMP_SUPPORT_ return AltLangMap(); } bool MetaEngine::setXmpTagStringListLangAlt(const char* xmpTagName, const MetaEngine::AltLangMap& values) const { + #ifdef _XMP_SUPPORT_ try { // Remove old XMP alternative Language tag. + removeXmpTag(xmpTagName); if (!values.isEmpty()) { Exiv2::Value::AutoPtr xmpTxtVal = Exiv2::Value::create(Exiv2::langAlt); for (AltLangMap::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it) { QString lang = it.key(); QString text = it.value(); QString txtLangAlt = QString::fromLatin1("lang=%1 %2").arg(lang).arg(text); const std::string &txt(txtLangAlt.toUtf8().constData()); xmpTxtVal->read(txt); } // ...and add the new one instead. + d->xmpMetadata().add(Exiv2::XmpKey(xmpTagName), xmpTxtVal.get()); } + return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string lang-alt into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(values); #endif // _XMP_SUPPORT_ return false; } QString MetaEngine::getXmpTagStringLangAlt(const char* xmpTagName, const QString& langAlt, bool escapeCR) const { + #ifdef _XMP_SUPPORT_ try { Exiv2::XmpData xmpData(d->xmpMetadata()); Exiv2::XmpKey key(xmpTagName); for (Exiv2::XmpData::const_iterator it = xmpData.begin() ; it != xmpData.end() ; ++it) { - if (it->key() == xmpTagName && it->typeId() == Exiv2::langAlt) + if ((it->key() == xmpTagName) && (it->typeId() == Exiv2::langAlt)) { for (int i = 0 ; i < (int)it->count() ; ++i) { std::ostringstream os; os << it->toString(i); QString lang; QString tagValue = QString::fromUtf8(os.str().c_str()); tagValue = detectLanguageAlt(tagValue, lang); if (langAlt == lang) { if (escapeCR) + { tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); + } return tagValue; } } } } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image using Exiv2 ") .arg(QLatin1String(xmpTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(langAlt); Q_UNUSED(escapeCR); #endif // _XMP_SUPPORT_ return QString(); } bool MetaEngine::setXmpTagStringLangAlt(const char* xmpTagName, const QString& value, const QString& langAlt) const { + #ifdef _XMP_SUPPORT_ try { QString language(QLatin1String("x-default")); // default alternative language. if (!langAlt.isEmpty()) + { language = langAlt; + } QString txtLangAlt = QString::fromLatin1("lang=%1 %2").arg(language).arg(value); const std::string& txt(txtLangAlt.toUtf8().constData()); Exiv2::Value::AutoPtr xmpTxtVal = Exiv2::Value::create(Exiv2::langAlt); // Search if an Xmp tag already exist. AltLangMap map = getXmpTagStringListLangAlt(xmpTagName, false); if (!map.isEmpty()) { for (AltLangMap::const_iterator it = map.constBegin() ; it != map.constEnd() ; ++it) { if (it.key() != langAlt) { const std::string &val((*it).toUtf8().constData()); xmpTxtVal->read(val); qCDebug(DIGIKAM_METAENGINE_LOG) << *it; } } } xmpTxtVal->read(txt); removeXmpTag(xmpTagName); d->xmpMetadata().add(Exiv2::XmpKey(xmpTagName), xmpTxtVal.get()); + return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string lang-alt into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(value); Q_UNUSED(langAlt); #endif // _XMP_SUPPORT_ return false; } QStringList MetaEngine::getXmpTagStringSeq(const char* xmpTagName, bool escapeCR) const { + #ifdef _XMP_SUPPORT_ try { Exiv2::XmpData xmpData(d->xmpMetadata()); Exiv2::XmpKey key(xmpTagName); Exiv2::XmpData::const_iterator it = xmpData.findKey(key); if (it != xmpData.end()) { if (it->typeId() == Exiv2::xmpSeq) { QStringList seq; for (int i = 0 ; i < (int)it->count() ; ++i) { std::ostringstream os; os << it->toString(i); QString seqValue = QString::fromUtf8(os.str().c_str()); if (escapeCR) + { seqValue.replace(QLatin1Char('\n'), QLatin1String(" ")); + } seq.append(seqValue); } + qCDebug(DIGIKAM_METAENGINE_LOG) << "XMP String Seq (" << xmpTagName << "): " << seq; return seq; } } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image using Exiv2 ") .arg(QLatin1String(xmpTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(escapeCR); #endif // _XMP_SUPPORT_ return QStringList(); } bool MetaEngine::setXmpTagStringSeq(const char* xmpTagName, const QStringList& seq) const { #ifdef _XMP_SUPPORT_ try { if (seq.isEmpty()) { removeXmpTag(xmpTagName); } else { const QStringList list = seq; Exiv2::Value::AutoPtr xmpTxtSeq = Exiv2::Value::create(Exiv2::xmpSeq); for (QStringList::const_iterator it = list.constBegin() ; it != list.constEnd() ; ++it) { const std::string &txt((*it).toUtf8().constData()); xmpTxtSeq->read(txt); } d->xmpMetadata()[xmpTagName].setValue(xmpTxtSeq.get()); } + return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string Seq into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(seq); #endif // _XMP_SUPPORT_ return false; } QStringList MetaEngine::getXmpTagStringBag(const char* xmpTagName, bool escapeCR) const { + #ifdef _XMP_SUPPORT_ try { Exiv2::XmpData xmpData(d->xmpMetadata()); Exiv2::XmpKey key(xmpTagName); Exiv2::XmpData::const_iterator it = xmpData.findKey(key); if (it != xmpData.end()) { if (it->typeId() == Exiv2::xmpBag) { QStringList bag; for (int i = 0 ; i < (int)it->count() ; ++i) { std::ostringstream os; os << it->toString(i); QString bagValue = QString::fromUtf8(os.str().c_str()); if (escapeCR) + { bagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); + } bag.append(bagValue); } return bag; } } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image using Exiv2 ") .arg(QLatin1String(xmpTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(escapeCR); #endif // _XMP_SUPPORT_ return QStringList(); } bool MetaEngine::setXmpTagStringBag(const char* xmpTagName, const QStringList& bag) const { + #ifdef _XMP_SUPPORT_ try { if (bag.isEmpty()) { removeXmpTag(xmpTagName); } else { QStringList list = bag; Exiv2::Value::AutoPtr xmpTxtBag = Exiv2::Value::create(Exiv2::xmpBag); for (QStringList::const_iterator it = list.constBegin() ; it != list.constEnd() ; ++it) { const std::string &txt((*it).toUtf8().constData()); xmpTxtBag->read(txt); } d->xmpMetadata()[xmpTagName].setValue(xmpTxtBag.get()); } + return true; } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot set Xmp tag string Bag into image using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(bag); #endif // _XMP_SUPPORT_ return false; } bool MetaEngine::addToXmpTagStringBag(const char* xmpTagName, const QStringList& entriesToAdd) const { QStringList oldEntries = getXmpTagStringBag(xmpTagName, false); QStringList newEntries = entriesToAdd; // Create a list of keywords including old one which already exists. + for (QStringList::const_iterator it = oldEntries.constBegin() ; it != oldEntries.constEnd() ; ++it) { if (!newEntries.contains(*it)) + { newEntries.append(*it); + } } if (setXmpTagStringBag(xmpTagName, newEntries)) + { return true; + } return false; } bool MetaEngine::removeFromXmpTagStringBag(const char* xmpTagName, const QStringList& entriesToRemove) const { QStringList currentEntries = getXmpTagStringBag(xmpTagName, false); QStringList newEntries; // Create a list of current keywords except those that shall be removed + for (QStringList::const_iterator it = currentEntries.constBegin() ; it != currentEntries.constEnd() ; ++it) { if (!entriesToRemove.contains(*it)) + { newEntries.append(*it); + } } if (setXmpTagStringBag(xmpTagName, newEntries)) + { return true; + } return false; } QVariant MetaEngine::getXmpTagVariant(const char* xmpTagName, bool rationalAsListOfInts, bool stringEscapeCR) const { + #ifdef _XMP_SUPPORT_ + try { Exiv2::XmpData xmpData(d->xmpMetadata()); Exiv2::XmpKey key(xmpTagName); Exiv2::XmpData::const_iterator it = xmpData.findKey(key); if (it != xmpData.end()) { switch (it->typeId()) { case Exiv2::unsignedByte: case Exiv2::unsignedShort: case Exiv2::unsignedLong: case Exiv2::signedShort: case Exiv2::signedLong: + { return QVariant((int)it->toLong()); + } + case Exiv2::unsignedRational: case Exiv2::signedRational: + { if (rationalAsListOfInts) { QList list; list << (*it).toRational().first; list << (*it).toRational().second; + return QVariant(list); } else { // prefer double precision + double num = (*it).toRational().first; double den = (*it).toRational().second; if (den == 0.0) + { return QVariant(QVariant::Double); + } return QVariant(num / den); } + } + case Exiv2::date: case Exiv2::time: { QDateTime dateTime = QDateTime::fromString(QLatin1String(it->toString().c_str()), Qt::ISODate); + return QVariant(dateTime); } + case Exiv2::asciiString: case Exiv2::comment: case Exiv2::string: { std::ostringstream os; os << *it; QString tagValue = QString::fromLocal8Bit(os.str().c_str()); if (stringEscapeCR) + { tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); + } return QVariant(tagValue); } + case Exiv2::xmpText: { std::ostringstream os; os << *it; QString tagValue = QString::fromUtf8(os.str().c_str()); if (stringEscapeCR) + { tagValue.replace(QLatin1Char('\n'), QLatin1String(" ")); + } return tagValue; } + case Exiv2::xmpBag: case Exiv2::xmpSeq: case Exiv2::xmpAlt: { QStringList list; for (int i = 0 ; i < (int)it->count() ; ++i) { list << QString::fromUtf8(it->toString(i).c_str()); } return list; } + case Exiv2::langAlt: { // access the value directly + const Exiv2::LangAltValue &value = static_cast(it->value()); QMap map; + // access the ValueType std::map< std::string, std::string> + Exiv2::LangAltValue::ValueType::const_iterator i; for (i = value.value_.begin() ; i != value.value_.end() ; ++i) { map[QString::fromUtf8(i->first.c_str())] = QString::fromUtf8(i->second.c_str()); } return map; } + default: break; } } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Xmp key '%1' into image using Exiv2 ") .arg(QLatin1String(xmpTagName)), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); Q_UNUSED(rationalAsListOfInts); Q_UNUSED(stringEscapeCR); #endif // _XMP_SUPPORT_ return QVariant(); } bool MetaEngine::registerXmpNameSpace(const QString& uri, const QString& prefix) { + #ifdef _XMP_SUPPORT_ try { QString ns = uri; if (!uri.endsWith(QLatin1Char('/'))) + { ns.append(QLatin1Char('/')); + } Exiv2::XmpProperties::registerNs(ns.toLatin1().constData(), prefix.toLatin1().constData()); + return true; } catch(Exiv2::AnyError& e) { Private::printExiv2ExceptionError(QLatin1String("Cannot register a new Xmp namespace using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(uri); Q_UNUSED(prefix); #endif // _XMP_SUPPORT_ return false; } bool MetaEngine::unregisterXmpNameSpace(const QString& uri) { + #ifdef _XMP_SUPPORT_ try { QString ns = uri; if (!uri.endsWith(QLatin1Char('/'))) + { ns.append(QLatin1Char('/')); + } Exiv2::XmpProperties::unregisterNs(ns.toLatin1().constData()); + return true; } catch(Exiv2::AnyError& e) { Private::printExiv2ExceptionError(QLatin1String("Cannot unregister a new Xmp namespace using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(uri); #endif // _XMP_SUPPORT_ return false; } bool MetaEngine::removeXmpTag(const char* xmpTagName) const { + #ifdef _XMP_SUPPORT_ try { Exiv2::XmpKey xmpKey(xmpTagName); Exiv2::XmpData::iterator it = d->xmpMetadata().findKey(xmpKey); if (it != d->xmpMetadata().end()) { d->xmpMetadata().erase(it); + return true; } } catch(Exiv2::AnyError& e) { d->printExiv2ExceptionError(QLatin1String("Cannot remove Xmp tag using Exiv2 "), e); } catch(...) { qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(xmpTagName); #endif // _XMP_SUPPORT_ return false; } QStringList MetaEngine::getXmpKeywords() const { return (getXmpTagStringBag("Xmp.dc.subject", false)); } bool MetaEngine::setXmpKeywords(const QStringList& newKeywords) const { return addToXmpTagStringBag("Xmp.dc.subject", newKeywords); } bool MetaEngine::removeXmpKeywords(const QStringList& keywordsToRemove) { return removeFromXmpTagStringBag("Xmp.dc.subject", keywordsToRemove); } QStringList MetaEngine::getXmpSubCategories() const { return (getXmpTagStringBag("Xmp.photoshop.SupplementalCategories", false)); } bool MetaEngine::setXmpSubCategories(const QStringList& newSubCategories) const { return addToXmpTagStringBag("Xmp.photoshop.SupplementalCategories", newSubCategories); } bool MetaEngine::removeXmpSubCategories(const QStringList& subCategoriesToRemove) { return removeFromXmpTagStringBag("Xmp.photoshop.SupplementalCategories", subCategoriesToRemove); } QStringList MetaEngine::getXmpSubjects() const { return (getXmpTagStringBag("Xmp.iptc.SubjectCode", false)); } bool MetaEngine::setXmpSubjects(const QStringList& newSubjects) const { return addToXmpTagStringBag("Xmp.iptc.SubjectCode", newSubjects); } bool MetaEngine::removeXmpSubjects(const QStringList& subjectsToRemove) { return removeFromXmpTagStringBag("Xmp.iptc.SubjectCode", subjectsToRemove); } MetaEngine::TagsMap MetaEngine::getXmpTagsList() const { TagsMap tagsMap; d->getXMPTagsListFromPrefix(QLatin1String("dc"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("digiKam"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("xmp"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("xmpRights"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("xmpMM"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("xmpBJ"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("xmpTPg"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("xmpDM"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("MicrosoftPhoto"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("pdf"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("photoshop"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("crs"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("tiff"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("exif"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("aux"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("iptc"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("iptcExt"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("plus"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("mwg-rs"), tagsMap); d->getXMPTagsListFromPrefix(QLatin1String("dwc"), tagsMap); + return tagsMap; } } // namespace Digikam #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif diff --git a/core/libs/metadataengine/engine/metaenginesettings.cpp b/core/libs/metadataengine/engine/metaenginesettings.cpp index 062ee6efa1..e35113a948 100644 --- a/core/libs/metadataengine/engine/metaenginesettings.cpp +++ b/core/libs/metadataengine/engine/metaenginesettings.cpp @@ -1,151 +1,152 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-08-20 * Description : central place for MetaEngine settings * * Copyright (C) 2010-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "metaenginesettings.h" // Qt includes #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN MetaEngineSettings::Private { public: explicit Private() : mutex(), configGroup(QLatin1String("Metadata Settings")) { } MetaEngineSettingsContainer settings; QMutex mutex; const QString configGroup; public: MetaEngineSettingsContainer readFromConfig() const; void writeToConfig() const; MetaEngineSettingsContainer setSettings(const MetaEngineSettingsContainer& s); }; MetaEngineSettingsContainer MetaEngineSettings::Private::readFromConfig() const { MetaEngineSettingsContainer s; KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroup); s.readFromConfig(group); + return s; } void MetaEngineSettings::Private::writeToConfig() const { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroup); settings.writeToConfig(group); } MetaEngineSettingsContainer MetaEngineSettings::Private::setSettings(const MetaEngineSettingsContainer& s) { QMutexLocker lock(&mutex); MetaEngineSettingsContainer old; old = settings; settings = s; return old; } // ----------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN MetaEngineSettingsCreator { public: MetaEngineSettings object; }; Q_GLOBAL_STATIC(MetaEngineSettingsCreator, metaEngineSettingsCreator) // ----------------------------------------------------------------------------------------------- MetaEngineSettings* MetaEngineSettings::instance() { return &metaEngineSettingsCreator->object; } MetaEngineSettings::MetaEngineSettings() : d(new Private) { readFromConfig(); qRegisterMetaType("MetaEngineSettingsContainer"); } MetaEngineSettings::~MetaEngineSettings() { delete d; } MetaEngineSettingsContainer MetaEngineSettings::settings() const { QMutexLocker lock(&d->mutex); MetaEngineSettingsContainer s(d->settings); return s; } bool MetaEngineSettings::exifRotate() const { return d->settings.exifRotate; } void MetaEngineSettings::setSettings(const MetaEngineSettingsContainer& settings) { MetaEngineSettingsContainer old = d->setSettings(settings); emit settingsChanged(); emit settingsChanged(settings, old); d->writeToConfig(); } void MetaEngineSettings::readFromConfig() { MetaEngineSettingsContainer s = d->readFromConfig(); MetaEngineSettingsContainer old = d->setSettings(s); emit settingsChanged(); emit settingsChanged(s, old); } } // namespace Digikam