/** =========================================================== * @file * * This file is a part of digiKam project * http://www.digikam.org * * @date 2006-09-15 * @brief Exiv2 library interface for KDE * * @author Copyright (C) 2006-2014 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2013 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * * 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 "kexiv2.h" #include "kexiv2_p.h" // Local includes #include "version.h" namespace KExiv2Iface { KExiv2Data::KExiv2Data() : d(0) { } KExiv2Data::KExiv2Data(const KExiv2Data& other) { d = other.d; } KExiv2Data::~KExiv2Data() { } KExiv2Data& KExiv2Data::operator=(const KExiv2Data& other) { d = other.d; return *this; } // ------------------------------------------------------------------------------------------- KExiv2::KExiv2() : d(new Private) { } KExiv2::KExiv2(const KExiv2& metadata) : d(new Private) { d->copyPrivateData(metadata.d); } KExiv2::KExiv2(const KExiv2Data& data) : d(new Private) { setData(data); } KExiv2::KExiv2(const QString& filePath) : d(new Private) { load(filePath); } KExiv2::~KExiv2() { delete d; } KExiv2& KExiv2::operator=(const KExiv2& metadata) { d->copyPrivateData(metadata.d); return *this; } //-- Statics methods ---------------------------------------------- bool KExiv2::initializeExiv2() { #ifdef _XMP_SUPPORT_ if (!Exiv2::XmpParser::initialize()) return false; registerXmpNameSpace(QString("http://ns.adobe.com/lightroom/1.0/"), QString("lr")); registerXmpNameSpace(QString("http://www.digikam.org/ns/kipi/1.0/"), QString("kipi")); registerXmpNameSpace(QString("http://ns.microsoft.com/photo/1.2/"), QString("MP")); #endif // _XMP_SUPPORT_ return true; } bool KExiv2::cleanupExiv2() { // Fix memory leak if Exiv2 support XMP. #ifdef _XMP_SUPPORT_ unregisterXmpNameSpace(QString("http://ns.adobe.com/lightroom/1.0/")); unregisterXmpNameSpace(QString("http://www.digikam.org/ns/kipi/1.0/")); unregisterXmpNameSpace(QString("http://ns.microsoft.com/photo/1.2/")); Exiv2::XmpParser::terminate(); #endif // _XMP_SUPPORT_ return true; } bool KExiv2::supportXmp() { #ifdef _XMP_SUPPORT_ return true; #else return false; #endif // _XMP_SUPPORT_ } bool KExiv2::supportMetadataWritting(const QString& typeMime) { if (typeMime == QString("image/jpeg")) { return true; } else if (typeMime == QString("image/tiff")) { return true; } else if (typeMime == QString("image/png")) { return true; } else if (typeMime == QString("image/jp2")) { return true; } else if (typeMime == QString("image/x-raw")) { return true; } else if (typeMime == QString("image/pgf")) { return true; } return false; } QString KExiv2::Exiv2Version() { // Since 0.14.0 release, we can extract run-time version of Exiv2. // else we return make version. return QString(Exiv2::version()); } QString KExiv2::version() { return QString(kexiv2_version); } QString KExiv2::sidecarFilePathForFile(const QString& path) { QString ret; if (!path.isEmpty()) { ret = path + QString(".xmp"); } return ret; } KUrl KExiv2::sidecarUrl(const KUrl& url) { QString sidecarPath = sidecarFilePathForFile(url.path()); KUrl sidecarUrl(url); sidecarUrl.setPath(sidecarPath); return sidecarUrl; } KUrl KExiv2::sidecarUrl(const QString& path) { return KUrl::fromPath(sidecarFilePathForFile(path)); } QString KExiv2::sidecarPath(const QString& path) { return sidecarFilePathForFile(path); } bool KExiv2::hasSidecar(const QString& path) { return QFileInfo(sidecarFilePathForFile(path)).exists(); } //-- General methods ---------------------------------------------- KExiv2Data KExiv2::data() const { KExiv2Data data; data.d = d->data; return data; } void KExiv2::setData(const KExiv2Data& data) { if (data.d) { d->data = data.d; } else { // KExiv2Data can have a null pointer, // but we never want a null pointer in Private. d->data->clear(); } } bool KExiv2::loadFromData(const QByteArray& imgData) const { if (imgData.isEmpty()) return false; try { Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size()); d->filePath.clear(); image->readMetadata(); // Size and mimetype --------------------------------- d->pixelSize = QSize(image->pixelWidth(), image->pixelHeight()); d->mimeType = image->mimeType().c_str(); // Image comments --------------------------------- d->imageComments() = image->comment(); // Exif metadata ---------------------------------- d->exifMetadata() = image->exifData(); // Iptc metadata ---------------------------------- d->iptcMetadata() = image->iptcData(); #ifdef _XMP_SUPPORT_ // Xmp metadata ----------------------------------- d->xmpMetadata() = image->xmpData(); #endif // _XMP_SUPPORT_ return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError("Cannot load metadata using Exiv2 ", e); } catch(...) { kError() << "Default exception from Exiv2"; } return false; } bool KExiv2::load(const QString& filePath) const { if (filePath.isEmpty()) { return false; } d->filePath = filePath; bool hasLoaded = false; try { Exiv2::Image::AutoPtr image; image = Exiv2::ImageFactory::open((const char*)(QFile::encodeName(filePath))); image->readMetadata(); // Size and mimetype --------------------------------- d->pixelSize = QSize(image->pixelWidth(), image->pixelHeight()); d->mimeType = image->mimeType().c_str(); // Image comments --------------------------------- d->imageComments() = image->comment(); // Exif metadata ---------------------------------- d->exifMetadata() = image->exifData(); // Iptc metadata ---------------------------------- d->iptcMetadata() = image->iptcData(); #ifdef _XMP_SUPPORT_ // Xmp metadata ----------------------------------- d->xmpMetadata() = image->xmpData(); #endif // _XMP_SUPPORT_ hasLoaded = true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError("Cannot load metadata from file ", e); } catch(...) { kError() << "Default exception from Exiv2"; } #ifdef _XMP_SUPPORT_ try { if (d->useXMPSidecar4Reading) { QString xmpSidecarPath = sidecarFilePathForFile(filePath); QFileInfo xmpSidecarFileInfo(xmpSidecarPath); Exiv2::Image::AutoPtr xmpsidecar; if (xmpSidecarFileInfo.exists() && xmpSidecarFileInfo.isReadable()) { // Read sidecar data xmpsidecar = Exiv2::ImageFactory::open((const char*)QFile::encodeName(xmpSidecarPath)); xmpsidecar->readMetadata(); // Merge d->loadSidecarData(xmpsidecar); hasLoaded = true; } } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError("Cannot load XMP sidecar", e); } catch(...) { kError() << "Default exception from Exiv2"; } #endif // _XMP_SUPPORT_ return hasLoaded; } bool KExiv2::save(const QString& imageFilePath) const { // If our image is really a symlink, we should follow the symlink so that // when we delete the file and rewrite it, we are honoring the symlink // (rather than just deleting it and putting a file there). // However, this may be surprising to the user when they are writing sidecar // files. They might expect them to show up where the symlink is. So, we // shouldn't follow the link when figuring out what the filename for the // sidecar should be. // Note, we are not yet handling the case where the sidecar itself is a // symlink. QString regularFilePath = imageFilePath; // imageFilePath might be a // symlink. Below we will change // regularFile to the pointed to // file if so. QFileInfo givenFileInfo(imageFilePath); if (givenFileInfo.isSymLink()) { kDebug() << "filePath" << imageFilePath << "is a symlink." << "Using target" << givenFileInfo.canonicalPath(); regularFilePath = givenFileInfo.canonicalPath();// Walk all the symlinks } // NOTE: see B.K.O #137770 & #138540 : never touch the file if is read only. QFileInfo finfo(regularFilePath); QFileInfo dinfo(finfo.path()); if (!dinfo.isWritable()) { kDebug() << "Dir '" << dinfo.filePath() << "' is read-only. Metadata not saved."; return false; } bool writeToFile = false; bool writeToSidecar = false; bool writeToSidecarIfFileNotPossible = false; bool writtenToFile = false; bool writtenToSidecar = false; kDebug() << "KExiv2::metadataWritingMode" << d->metadataWritingMode; switch(d->metadataWritingMode) { case WRITETOSIDECARONLY: writeToSidecar = true; break; case WRITETOIMAGEONLY: writeToFile = true; break; case WRITETOSIDECARANDIMAGE: writeToFile = true; writeToSidecar = true; break; case WRITETOSIDECARONLY4READONLYFILES: writeToFile = true; writeToSidecarIfFileNotPossible = true; break; } if (writeToFile) { kDebug() << "Will write Metadata to file" << finfo.fileName(); writtenToFile = d->saveToFile(finfo); if (writeToFile) { kDebug() << "Metadata for file" << finfo.fileName() << "written to file."; } } if (writeToSidecar || (writeToSidecarIfFileNotPossible && !writtenToFile)) { kDebug() << "Will write XMP sidecar for file" << givenFileInfo.fileName(); writtenToSidecar = d->saveToXMPSidecar(imageFilePath); if (writtenToSidecar) { kDebug() << "Metadata for file '" << givenFileInfo.fileName() << "' written to XMP sidecar."; } } return writtenToFile || writtenToSidecar; } bool KExiv2::applyChanges() const { if (d->filePath.isEmpty()) { kDebug() << "Failed to apply changes: file path is empty!"; return false; } return save(d->filePath); } bool KExiv2::isEmpty() const { if (!hasComments() && !hasExif() && !hasIptc() && !hasXmp()) return true; return false; } void KExiv2::setFilePath(const QString& path) { d->filePath = path; } QString KExiv2::getFilePath() const { return d->filePath; } QSize KExiv2::getPixelSize() const { return d->pixelSize; } QString KExiv2::getMimeType() const { return d->mimeType; } void KExiv2::setWriteRawFiles(const bool on) { d->writeRawFiles = on; } bool KExiv2::writeRawFiles() const { return d->writeRawFiles; } void KExiv2::setUseXMPSidecar4Reading(const bool on) { d->useXMPSidecar4Reading = on; } bool KExiv2::useXMPSidecar4Reading() const { return d->useXMPSidecar4Reading; } void KExiv2::setMetadataWritingMode(const int mode) { d->metadataWritingMode = mode; } int KExiv2::metadataWritingMode() const { return d->metadataWritingMode; } void KExiv2::setUpdateFileTimeStamp(bool on) { d->updateFileTimeStamp = on; } bool KExiv2::updateFileTimeStamp() const { return d->updateFileTimeStamp; } bool KExiv2::setProgramId(bool /*on*/) const { return true; } } // NameSpace KExiv2Iface