File indexing completed on 2024-04-21 15:22:24

0001 /*
0002     SPDX-FileCopyrightText: 2006-2015 Gilles Caulier <caulier dot gilles at gmail dot com>
0003     SPDX-FileCopyrightText: 2006-2013 Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "kexiv2.h"
0009 #include "kexiv2_p.h"
0010 
0011 // Local includes
0012 
0013 #include "libkexiv2_version.h"
0014 #include "libkexiv2_debug.h"
0015 
0016 namespace KExiv2Iface
0017 {
0018 
0019 KExiv2::KExiv2()
0020     : d(new KExiv2Private)
0021 {
0022 }
0023 
0024 KExiv2::KExiv2(const KExiv2& metadata)
0025     : d(new KExiv2Private)
0026 {
0027     d->copyPrivateData(metadata.d.get());
0028 }
0029 
0030 KExiv2::KExiv2(const KExiv2Data& data)
0031     : d(new KExiv2Private)
0032 {
0033     setData(data);
0034 }
0035 
0036 KExiv2::KExiv2(const QString& filePath)
0037     : d(new KExiv2Private)
0038 {
0039     load(filePath);
0040 }
0041 
0042 KExiv2::~KExiv2() = default;
0043 
0044 KExiv2& KExiv2::operator=(const KExiv2& metadata)
0045 {
0046     d->copyPrivateData(metadata.d.get());
0047 
0048     return *this;
0049 }
0050 
0051 //-- Statics methods ----------------------------------------------
0052 
0053 bool KExiv2::initializeExiv2()
0054 {
0055 #ifdef _XMP_SUPPORT_
0056 
0057     if (!Exiv2::XmpParser::initialize())
0058         return false;
0059 
0060     registerXmpNameSpace(QString::fromLatin1("http://ns.adobe.com/lightroom/1.0/"),  QString::fromLatin1("lr"));
0061     registerXmpNameSpace(QString::fromLatin1("http://www.digikam.org/ns/kipi/1.0/"), QString::fromLatin1("kipi"));
0062     registerXmpNameSpace(QString::fromLatin1("http://ns.microsoft.com/photo/1.2/"),  QString::fromLatin1("MP"));
0063     registerXmpNameSpace(QString::fromLatin1("http://ns.acdsee.com/iptc/1.0/"),      QString::fromLatin1("acdsee"));
0064     registerXmpNameSpace(QString::fromLatin1("http://www.video"),                    QString::fromLatin1("video"));
0065 
0066 #endif // _XMP_SUPPORT_
0067 
0068 #ifdef EXV_ENABLE_BMFF
0069     Exiv2::enableBMFF(true);
0070 #endif
0071 
0072     return true;
0073 }
0074 
0075 bool KExiv2::cleanupExiv2()
0076 {
0077     // Fix memory leak if Exiv2 support XMP.
0078 #ifdef _XMP_SUPPORT_
0079 
0080     unregisterXmpNameSpace(QString::fromLatin1("http://ns.adobe.com/lightroom/1.0/"));
0081     unregisterXmpNameSpace(QString::fromLatin1("http://www.digikam.org/ns/kipi/1.0/"));
0082     unregisterXmpNameSpace(QString::fromLatin1("http://ns.microsoft.com/photo/1.2/"));
0083     unregisterXmpNameSpace(QString::fromLatin1("http://ns.acdsee.com/iptc/1.0/"));
0084     unregisterXmpNameSpace(QString::fromLatin1("http://www.video"));
0085     
0086     Exiv2::XmpParser::terminate();
0087 
0088 #endif // _XMP_SUPPORT_
0089 
0090     return true;
0091 }
0092 
0093 bool KExiv2::supportXmp()
0094 {
0095 #ifdef _XMP_SUPPORT_
0096     return true;
0097 #else
0098     return false;
0099 #endif // _XMP_SUPPORT_
0100 }
0101 
0102 bool KExiv2::supportMetadataWritting(const QString& typeMime)
0103 {
0104     if (typeMime == QString::fromLatin1("image/jpeg"))
0105     {
0106         return true;
0107     }
0108     else if (typeMime == QString::fromLatin1("image/tiff"))
0109     {
0110         return true;
0111     }
0112     else if (typeMime == QString::fromLatin1("image/png"))
0113     {
0114         return true;
0115     }
0116     else if (typeMime == QString::fromLatin1("image/jp2"))
0117     {
0118         return true;
0119     }
0120     else if (typeMime == QString::fromLatin1("image/x-raw"))
0121     {
0122         return true;
0123     }
0124     else if (typeMime == QString::fromLatin1("image/pgf"))
0125     {
0126         return true;
0127     }
0128 
0129     return false;
0130 }
0131 
0132 QString KExiv2::Exiv2Version()
0133 {
0134     // Since 0.14.0 release, we can extract run-time version of Exiv2.
0135     // else we return make version.
0136 
0137     return QString::fromStdString(Exiv2::versionString());
0138 }
0139 
0140 QString KExiv2::version()
0141 {
0142     return QString::fromLatin1(KEXIV2_VERSION_STRING);
0143 }
0144 
0145 QString KExiv2::sidecarFilePathForFile(const QString& path)
0146 {
0147     QString ret;
0148 
0149     if (!path.isEmpty())
0150     {
0151         ret = path + QString::fromLatin1(".xmp");
0152     }
0153 
0154     return ret;
0155 }
0156 
0157 QUrl KExiv2::sidecarUrl(const QUrl& url)
0158 {
0159     QString sidecarPath = sidecarFilePathForFile(url.path());
0160     QUrl sidecarUrl(url);
0161     sidecarUrl.setPath(sidecarPath);
0162     return sidecarUrl;
0163 }
0164 
0165 QUrl KExiv2::sidecarUrl(const QString& path)
0166 {
0167     return QUrl::fromLocalFile(sidecarFilePathForFile(path));
0168 }
0169 
0170 QString KExiv2::sidecarPath(const QString& path)
0171 {
0172     return sidecarFilePathForFile(path);
0173 }
0174 
0175 bool KExiv2::hasSidecar(const QString& path)
0176 {
0177     return QFileInfo(sidecarFilePathForFile(path)).exists();
0178 }
0179 
0180 //-- General methods ----------------------------------------------
0181 
0182 KExiv2Data KExiv2::data() const
0183 {
0184     KExiv2Data data;
0185     data.d = d->data;
0186     return data;
0187 }
0188 
0189 void KExiv2::setData(const KExiv2Data& data)
0190 {
0191     if (data.d)
0192     {
0193         d->data = data.d;
0194     }
0195     else
0196     {
0197         // KExiv2Data can have a null pointer,
0198         // but we never want a null pointer in Private.
0199         d->data->clear();
0200     }
0201 }
0202 
0203 bool KExiv2::loadFromData(const QByteArray& imgData) const
0204 {
0205     if (imgData.isEmpty())
0206         return false;
0207 
0208     try
0209     {
0210 #if EXIV2_TEST_VERSION(0,28,0)
0211         Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size());
0212 #else
0213         Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size());
0214 #endif
0215 
0216         d->filePath.clear();
0217         image->readMetadata();
0218 
0219         // Size and mimetype ---------------------------------
0220 
0221         d->pixelSize = QSize(image->pixelWidth(), image->pixelHeight());
0222         d->mimeType  = QString::fromLatin1(image->mimeType().c_str());
0223 
0224         // Image comments ---------------------------------
0225 
0226         d->imageComments() = image->comment();
0227 
0228         // Exif metadata ----------------------------------
0229 
0230         d->exifMetadata() = image->exifData();
0231 
0232         // Iptc metadata ----------------------------------
0233 
0234         d->iptcMetadata() = image->iptcData();
0235 
0236 #ifdef _XMP_SUPPORT_
0237 
0238         // Xmp metadata -----------------------------------
0239 
0240         d->xmpMetadata() = image->xmpData();
0241 
0242 #endif // _XMP_SUPPORT_
0243 
0244         return true;
0245     }
0246     catch( Exiv2::Error& e )
0247     {
0248         d->printExiv2ExceptionError(QString::fromLatin1("Cannot load metadata using Exiv2 "), e);
0249     }
0250     catch(...)
0251     {
0252         qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
0253     }
0254 
0255     return false;
0256 }
0257 
0258 bool KExiv2::load(const QString& filePath) const
0259 {
0260     if (filePath.isEmpty())
0261     {
0262         return false;
0263     }
0264 
0265     d->filePath      = filePath;
0266     bool hasLoaded   = false;
0267 
0268     try
0269     {
0270 #if EXIV2_TEST_VERSION(0,28,0)
0271         Exiv2::Image::UniquePtr image;
0272 #else
0273         Exiv2::Image::AutoPtr image;
0274 #endif
0275 
0276         image        = Exiv2::ImageFactory::open((const char*)(QFile::encodeName(filePath)).constData());
0277 
0278         image->readMetadata();
0279 
0280         // Size and mimetype ---------------------------------
0281 
0282         d->pixelSize = QSize(image->pixelWidth(), image->pixelHeight());
0283         d->mimeType  = QString::fromLatin1(image->mimeType().c_str());
0284 
0285         // Image comments ---------------------------------
0286 
0287         d->imageComments() = image->comment();
0288 
0289         // Exif metadata ----------------------------------
0290 
0291         d->exifMetadata() = image->exifData();
0292 
0293         // Iptc metadata ----------------------------------
0294 
0295         d->iptcMetadata() = image->iptcData();
0296 
0297 #ifdef _XMP_SUPPORT_
0298 
0299         // Xmp metadata -----------------------------------
0300         d->xmpMetadata() = image->xmpData();
0301 
0302 #endif // _XMP_SUPPORT_
0303 
0304         hasLoaded = true;
0305     }
0306     catch( Exiv2::Error& e )
0307     {
0308         d->printExiv2ExceptionError(QString::fromLatin1("Cannot load metadata from file "), e);
0309     }
0310     catch(...)
0311     {
0312         qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
0313     }
0314 
0315 #ifdef _XMP_SUPPORT_
0316     try
0317     {
0318         if (d->useXMPSidecar4Reading)
0319         {
0320             QString xmpSidecarPath = sidecarFilePathForFile(filePath);
0321             QFileInfo xmpSidecarFileInfo(xmpSidecarPath);
0322 
0323 #if EXIV2_TEST_VERSION(0,28,0)
0324             Exiv2::Image::UniquePtr xmpsidecar;
0325 #else
0326             Exiv2::Image::AutoPtr xmpsidecar;
0327 #endif
0328 
0329             if (xmpSidecarFileInfo.exists() && xmpSidecarFileInfo.isReadable())
0330             {
0331                 // Read sidecar data
0332                 xmpsidecar = Exiv2::ImageFactory::open(QFile::encodeName(xmpSidecarPath).constData());
0333                 xmpsidecar->readMetadata();
0334 
0335                 // Merge
0336 #if EXIV2_TEST_VERSION(0,28,0)
0337                 d->loadSidecarData(std::move(xmpsidecar));
0338 #else
0339                 d->loadSidecarData(xmpsidecar);
0340 #endif
0341                 hasLoaded = true;
0342             }
0343         }
0344     }
0345     catch( Exiv2::Error& e )
0346     {
0347         d->printExiv2ExceptionError(QString::fromLatin1("Cannot load XMP sidecar"), e);
0348     }
0349     catch(...)
0350     {
0351         qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
0352     }
0353 
0354 #endif // _XMP_SUPPORT_
0355 
0356     return hasLoaded;
0357 }
0358 
0359 bool KExiv2::save(const QString& imageFilePath) const
0360 {
0361     // If our image is really a symlink, we should follow the symlink so that
0362     // when we delete the file and rewrite it, we are honoring the symlink
0363     // (rather than just deleting it and putting a file there).
0364 
0365     // However, this may be surprising to the user when they are writing sidecar
0366     // files.  They might expect them to show up where the symlink is.  So, we
0367     // shouldn't follow the link when figuring out what the filename for the
0368     // sidecar should be.
0369 
0370     // Note, we are not yet handling the case where the sidecar itself is a
0371     // symlink.
0372     QString regularFilePath = imageFilePath; // imageFilePath might be a
0373                                              // symlink.  Below we will change
0374                                              // regularFile to the pointed to
0375                                              // file if so.
0376     QFileInfo givenFileInfo(imageFilePath);
0377 
0378     if (givenFileInfo.isSymLink())
0379     {
0380         qCDebug(LIBKEXIV2_LOG) << "filePath" << imageFilePath << "is a symlink."
0381                                << "Using target" << givenFileInfo.canonicalPath();
0382 
0383         regularFilePath = givenFileInfo.canonicalPath();// Walk all the symlinks
0384     }
0385 
0386     // NOTE: see B.K.O #137770 & #138540 : never touch the file if is read only.
0387     QFileInfo finfo(regularFilePath);
0388     QFileInfo dinfo(finfo.path());
0389 
0390     if (!dinfo.isWritable())
0391     {
0392         qCDebug(LIBKEXIV2_LOG) << "Dir '" << dinfo.filePath() << "' is read-only. Metadata not saved.";
0393         return false;
0394     }
0395 
0396     bool writeToFile                     = false;
0397     bool writeToSidecar                  = false;
0398     bool writeToSidecarIfFileNotPossible = false;
0399     bool writtenToFile                   = false;
0400     bool writtenToSidecar                = false;
0401 
0402     qCDebug(LIBKEXIV2_LOG) << "KExiv2::metadataWritingMode" << d->metadataWritingMode;
0403 
0404     switch(d->metadataWritingMode)
0405     {
0406         case WRITETOSIDECARONLY:
0407             writeToSidecar = true;
0408             break;
0409         case WRITETOIMAGEONLY:
0410             writeToFile    = true;
0411             break;
0412         case WRITETOSIDECARANDIMAGE:
0413             writeToFile    = true;
0414             writeToSidecar = true;
0415             break;
0416         case WRITETOSIDECARONLY4READONLYFILES:
0417             writeToFile = true;
0418             writeToSidecarIfFileNotPossible = true;
0419             break;
0420     }
0421 
0422     if (writeToFile)
0423     {
0424         qCDebug(LIBKEXIV2_LOG) << "Will write Metadata to file" << finfo.absoluteFilePath();
0425         writtenToFile = d->saveToFile(finfo);
0426 
0427         if (writtenToFile)
0428         {
0429             qCDebug(LIBKEXIV2_LOG) << "Metadata for file" << finfo.fileName() << "written to file.";
0430         }
0431     }
0432 
0433     if (writeToSidecar || (writeToSidecarIfFileNotPossible && !writtenToFile))
0434     {
0435         qCDebug(LIBKEXIV2_LOG) << "Will write XMP sidecar for file" << givenFileInfo.fileName();
0436         writtenToSidecar = d->saveToXMPSidecar(QFileInfo(imageFilePath));
0437 
0438         if (writtenToSidecar)
0439         {
0440             qCDebug(LIBKEXIV2_LOG) << "Metadata for file '" << givenFileInfo.fileName() << "' written to XMP sidecar.";
0441         }
0442     }
0443 
0444     return writtenToFile || writtenToSidecar;
0445 }
0446 
0447 bool KExiv2::applyChanges() const
0448 {
0449     if (d->filePath.isEmpty())
0450     {
0451         qCDebug(LIBKEXIV2_LOG) << "Failed to apply changes: file path is empty!";
0452         return false;
0453     }
0454 
0455     return save(d->filePath);
0456 }
0457 
0458 bool KExiv2::isEmpty() const
0459 {
0460     if (!hasComments() && !hasExif() && !hasIptc() && !hasXmp())
0461         return true;
0462 
0463     return false;
0464 }
0465 
0466 void KExiv2::setFilePath(const QString& path)
0467 {
0468     d->filePath = path;
0469 }
0470 
0471 QString KExiv2::getFilePath() const
0472 {
0473     return d->filePath;
0474 }
0475 
0476 QSize KExiv2::getPixelSize() const
0477 {
0478     return d->pixelSize;
0479 }
0480 
0481 QString KExiv2::getMimeType() const
0482 {
0483     return d->mimeType;
0484 }
0485 
0486 void KExiv2::setWriteRawFiles(const bool on)
0487 {
0488     d->writeRawFiles = on;
0489 }
0490 
0491 bool KExiv2::writeRawFiles() const
0492 {
0493     return d->writeRawFiles;
0494 }
0495 
0496 void KExiv2::setUseXMPSidecar4Reading(const bool on)
0497 {
0498     d->useXMPSidecar4Reading = on;
0499 }
0500 
0501 bool KExiv2::useXMPSidecar4Reading() const
0502 {
0503     return d->useXMPSidecar4Reading;
0504 }
0505 
0506 void KExiv2::setMetadataWritingMode(const int mode)
0507 {
0508     d->metadataWritingMode = mode;
0509 }
0510 
0511 int KExiv2::metadataWritingMode() const
0512 {
0513     return d->metadataWritingMode;
0514 }
0515 
0516 void KExiv2::setUpdateFileTimeStamp(bool on)
0517 {
0518     d->updateFileTimeStamp = on;
0519 }
0520 
0521 bool KExiv2::updateFileTimeStamp() const
0522 {
0523     return d->updateFileTimeStamp;
0524 }
0525 
0526 bool KExiv2::setProgramId(bool /*on*/) const
0527 {
0528     return true;
0529 }
0530 
0531 }  // NameSpace KExiv2Iface