File indexing completed on 2025-01-05 03:56:26

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2006-09-15
0007  * Description : Exiv2 library interface
0008  *               Exiv2: www.exiv2.org
0009  *               Exif : www.exif.org/Exif2-2.PDF
0010  *               Iptc : www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
0011  *               Xmp  : www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf
0012  *                      www.iptc.org/std/Iptc4xmpCore/1.0/specification/Iptc4xmpCore_1.0-spec-XMPSchema_8.pdf
0013  *               Paper: www.metadataworkinggroup.com/pdf/mwg_guidance.pdf
0014  *
0015  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0016  * SPDX-FileCopyrightText: 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0017  *
0018  * SPDX-License-Identifier: GPL-2.0-or-later
0019  *
0020  * ============================================================ */
0021 
0022 #include "metaengine_p.h"
0023 #include "metaengine_data_p.h"
0024 
0025 // Local includes
0026 
0027 #include "digikam_debug.h"
0028 #include "digikam_version.h"
0029 
0030 #if defined(Q_CC_CLANG)
0031 #   pragma clang diagnostic push
0032 #   pragma clang diagnostic ignored "-Wdeprecated-declarations"
0033 #endif
0034 
0035 namespace Digikam
0036 {
0037 
0038 MetaEngine::MetaEngine()
0039     : d(new Private(this))
0040 {
0041 }
0042 
0043 MetaEngine::MetaEngine(const MetaEngine& metadata)
0044     : d(new Private(this))
0045 {
0046     d->copyPrivateData(metadata.d);
0047 }
0048 
0049 MetaEngine::MetaEngine(const MetaEngineData& data)
0050     : d(new Private(this))
0051 {
0052     setData(data);
0053 }
0054 
0055 MetaEngine::MetaEngine(const QString& filePath)
0056     : d(new Private(this))
0057 {
0058     // NOTE: use dynamic binding as this virtual method can be re-implemented in derived classes.
0059 
0060     this->load(filePath);
0061 }
0062 
0063 MetaEngine::~MetaEngine()
0064 {
0065     delete d;
0066 }
0067 
0068 MetaEngine& MetaEngine::operator=(const MetaEngine& metadata)
0069 {
0070     d->copyPrivateData(metadata.d);
0071 
0072     return *this;
0073 }
0074 
0075 //-- Statics methods ----------------------------------------------
0076 
0077 bool MetaEngine::initializeExiv2()
0078 {
0079 
0080 #ifdef _XMP_SUPPORT_
0081 
0082     if (!Exiv2::XmpParser::initialize())
0083     {
0084         return false;
0085     }
0086 
0087     registerXmpNameSpace(QLatin1String("http://ns.google.com/photos/1.0/container/item_1_/"),
0088                          QLatin1String("Item_1_"));     // krazy:exclude=insecurenet
0089     registerXmpNameSpace(QLatin1String("http://ns.google.com/photos/1.0/container/item/"),
0090                          QLatin1String("Item"));        // krazy:exclude=insecurenet
0091     registerXmpNameSpace(QLatin1String("http://ns.apple.com/faceinfo/1.0/"),
0092                          QLatin1String("apple-fi"));    // krazy:exclude=insecurenet
0093     registerXmpNameSpace(QLatin1String("http://ns.excire.com/foto/1.0/"),
0094                          QLatin1String("excire"));      // krazy:exclude=insecurenet
0095 
0096     /**
0097      * It cleans up memory used by Adobe XMP SDK automatically at application exit.
0098      * See Bug #166424 for details.
0099      */
0100     ::atexit(Exiv2::XmpParser::terminate);
0101 
0102 #endif // _XMP_SUPPORT_
0103 
0104 #if EXIV2_TEST_VERSION(0,27,4)
0105 
0106     // For Base Media File Format (aka CR3, HEIF, HEIC, and AVIF)
0107 
0108     s_metaEngineSupportBmff = Exiv2::enableBMFF(true);
0109 
0110 #endif
0111 
0112     return true;
0113 }
0114 
0115 bool MetaEngine::supportBmff()
0116 {
0117     return s_metaEngineSupportBmff;
0118 }
0119 
0120 bool MetaEngine::supportXmp()
0121 {
0122 
0123 #ifdef _XMP_SUPPORT_
0124 
0125     return true;
0126 
0127 #else
0128 
0129     return false;
0130 
0131 #endif // _XMP_SUPPORT_
0132 
0133 }
0134 
0135 bool MetaEngine::supportJpegXL()
0136 {
0137 
0138 #ifdef EXV_HAVE_BROTLI
0139 
0140     return true;
0141 
0142 #else
0143 
0144     return false;
0145 
0146 #endif // EXV_HAVE_BROTLI
0147 
0148 }
0149 
0150 bool MetaEngine::supportMetadataWriting(const QString& /*typeMime*/)
0151 {
0152 /* FIXME : use Exiv2 API to return right writings support
0153     if      (typeMime == QLatin1String("image/jpeg"))
0154     {
0155         return true;
0156     }
0157     else if (typeMime == QLatin1String("image/tiff"))
0158     {
0159         return true;
0160     }
0161     else if (typeMime == QLatin1String("image/png"))
0162     {
0163         return true;
0164     }
0165     else if (typeMime == QLatin1String("image/jp2"))
0166     {
0167         return true;
0168     }
0169     else if (typeMime == QLatin1String("image/x-raw"))
0170     {
0171         return true;
0172     }
0173     else if (typeMime == QLatin1String("image/pgf"))
0174     {
0175         return true;
0176     }
0177 */
0178     return true;
0179 }
0180 
0181 QString MetaEngine::Exiv2Version()
0182 {
0183 
0184 #if EXIV2_TEST_VERSION(0,27,0)
0185 
0186     return QString::fromStdString(Exiv2::versionString());
0187 
0188 #else
0189 
0190     return QLatin1String(Exiv2::version());
0191 
0192 #endif
0193 
0194 }
0195 
0196 //-- General methods ----------------------------------------------
0197 
0198 MetaEngineData MetaEngine::data() const
0199 {
0200     MetaEngineData data;
0201     data.d = d->data;
0202 
0203     return data;
0204 }
0205 
0206 void MetaEngine::setData(const MetaEngineData& data)
0207 {
0208     if (data.d)
0209     {
0210         d->data = data.d;
0211     }
0212     else
0213     {
0214         // MetaEngineData can have a null pointer,
0215         // but we never want a null pointer in Private.
0216         d->data->clear();
0217     }
0218 }
0219 
0220 bool MetaEngine::loadFromData(const QByteArray& imgData)
0221 {
0222     if (imgData.isEmpty())
0223     {
0224         return false;
0225     }
0226 
0227     QMutexLocker lock(&s_metaEngineMutex);
0228 
0229     try
0230     {
0231         Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size());
0232 
0233         d->filePath.clear();
0234         image->readMetadata();
0235 
0236         // Size and mimetype ---------------------------------
0237 
0238         d->pixelSize = QSize(image->pixelWidth(), image->pixelHeight());
0239         d->mimeType  = QString::fromStdString(image->mimeType());
0240 
0241         // Image comments ---------------------------------
0242 
0243         d->itemComments() = image->comment();
0244 
0245         // Exif metadata ----------------------------------
0246 
0247         d->exifMetadata() = image->exifData();
0248 
0249         // Iptc metadata ----------------------------------
0250 
0251         d->iptcMetadata() = image->iptcData();
0252 
0253 #ifdef _XMP_SUPPORT_
0254 
0255         // Xmp metadata -----------------------------------
0256 
0257         d->xmpMetadata() = image->xmpData();
0258 
0259 #endif // _XMP_SUPPORT_
0260 
0261         return true;
0262     }
0263     catch (Exiv2::AnyError& e)
0264     {
0265         d->printExiv2ExceptionError(QLatin1String("Cannot load metadata with Exiv2:"), e);
0266     }
0267     catch (...)
0268     {
0269         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0270     }
0271 
0272     return false;
0273 }
0274 
0275 bool MetaEngine::loadFromDataAndMerge(const QByteArray& imgData, const QStringList& exclude)
0276 {
0277     if (imgData.isEmpty())
0278     {
0279         return false;
0280     }
0281 
0282     QMutexLocker lock(&s_metaEngineMutex);
0283 
0284     try
0285     {
0286         Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size());
0287 
0288         d->filePath.clear();
0289         image->readMetadata();
0290 
0291         // Size and mimetype ---------------------------------
0292 
0293         d->pixelSize = QSize(image->pixelWidth(), image->pixelHeight());
0294         d->mimeType  = QString::fromStdString(image->mimeType());
0295 
0296         // Exif metadata ----------------------------------
0297 
0298         Q_FOREACH (const QString& exTag, exclude)
0299         {
0300             Exiv2::ExifKey exifKey(exTag.toLatin1().constData());
0301             Exiv2::ExifData::iterator it = image->exifData().findKey(exifKey);
0302 
0303             if (it != image->exifData().end())
0304             {
0305                 image->exifData().erase(it);
0306             }
0307         }
0308 
0309         ExifMetaEngineMergeHelper exifHelper;
0310         exifHelper.mergeAll(image->exifData(), d->exifMetadata());
0311 
0312         // Iptc metadata ----------------------------------
0313 
0314         IptcMetaEngineMergeHelper iptcHelper;
0315         iptcHelper.mergeAll(image->iptcData(), d->iptcMetadata());
0316 
0317 #ifdef _XMP_SUPPORT_
0318 
0319         // Xmp metadata -----------------------------------
0320 
0321         XmpMetaEngineMergeHelper xmpHelper;
0322         xmpHelper.mergeAll(image->xmpData(), d->xmpMetadata());
0323 
0324 #endif // _XMP_SUPPORT_
0325 
0326         return true;
0327     }
0328     catch (Exiv2::AnyError& e)
0329     {
0330         d->printExiv2ExceptionError(QLatin1String("Cannot load and merge metadata with Exiv2:"), e);
0331     }
0332     catch (...)
0333     {
0334         qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
0335     }
0336 
0337     return false;
0338 }
0339 
0340 bool MetaEngine::isEmpty() const
0341 {
0342     if (!hasComments() && !hasExif() && !hasIptc() && !hasXmp())
0343     {
0344         return true;
0345     }
0346 
0347     return false;
0348 }
0349 
0350 QSize MetaEngine::getPixelSize() const
0351 {
0352     return d->pixelSize;
0353 }
0354 
0355 QString MetaEngine::getMimeType() const
0356 {
0357     return d->mimeType;
0358 }
0359 
0360 void MetaEngine::setWriteWithExifTool(const bool on)
0361 {
0362     d->writeWithExifTool = on;
0363 }
0364 
0365 bool MetaEngine::writeWithExifTool() const
0366 {
0367     return d->writeWithExifTool;
0368 }
0369 
0370 void MetaEngine::setWriteRawFiles(const bool on)
0371 {
0372     d->writeRawFiles = on;
0373 }
0374 
0375 bool MetaEngine::writeRawFiles() const
0376 {
0377     return d->writeRawFiles;
0378 }
0379 
0380 void MetaEngine::setWriteDngFiles(const bool on)
0381 {
0382     d->writeDngFiles = on;
0383 }
0384 
0385 bool MetaEngine::writeDngFiles() const
0386 {
0387     return d->writeDngFiles;
0388 }
0389 
0390 void MetaEngine::setUseXMPSidecar4Reading(const bool on)
0391 {
0392     d->useXMPSidecar4Reading = on;
0393 }
0394 
0395 bool MetaEngine::useXMPSidecar4Reading() const
0396 {
0397     return d->useXMPSidecar4Reading;
0398 }
0399 
0400 void MetaEngine::setUseCompatibleFileName(const bool on)
0401 {
0402     d->useCompatibleFileName = on;
0403 }
0404 
0405 bool MetaEngine::useCompatibleFileName() const
0406 {
0407     return d->useCompatibleFileName;
0408 }
0409 
0410 void MetaEngine::setMetadataWritingMode(const int mode)
0411 {
0412     d->metadataWritingMode = mode;
0413 }
0414 
0415 int MetaEngine::metadataWritingMode() const
0416 {
0417     return d->metadataWritingMode;
0418 }
0419 
0420 void MetaEngine::setUpdateFileTimeStamp(bool on)
0421 {
0422     d->updateFileTimeStamp = on;
0423 }
0424 
0425 bool MetaEngine::updateFileTimeStamp() const
0426 {
0427     return d->updateFileTimeStamp;
0428 }
0429 
0430 bool MetaEngine::setProgramId() const
0431 {
0432     QString version(digiKamVersion());
0433     QLatin1String software("digiKam");
0434 
0435     return setItemProgramId(software, version);
0436 }
0437 
0438 } // namespace Digikam
0439 
0440 #if defined(Q_CC_CLANG)
0441 #   pragma clang diagnostic pop
0442 #endif