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