diff --git a/core/libs/metadataengine/dmetadata/dmetadata.h b/core/libs/metadataengine/dmetadata/dmetadata.h index c029749315..6f708876b4 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata.h +++ b/core/libs/metadataengine/dmetadata/dmetadata.h @@ -1,387 +1,385 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2013 by Veaceslav Munteanu * * 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_DMETA_DATA_H #define DIGIKAM_DMETA_DATA_H // Qt includes #include #include // Local includes #include "metaengine.h" #include "metaengine_data.h" #include "metaenginesettingscontainer.h" #include "metadatainfo.h" #include "captionvalues.h" #include "photoinfocontainer.h" #include "videoinfocontainer.h" #include "digikam_export.h" #include "dmetadatasettings.h" namespace Digikam { class Template; class IccProfile; class DIGIKAM_EXPORT DMetadata : public MetaEngine { public: /** * Video color model reported by FFMPEG following XMP DM Spec from Adobe. * These values are stored in DB as Image color model properties (extension of DImg::ColorModel) */ enum VIDEOCOLORMODEL { VIDEOCOLORMODEL_UNKNOWN=1000, VIDEOCOLORMODEL_OTHER, VIDEOCOLORMODEL_SRGB, VIDEOCOLORMODEL_BT709, VIDEOCOLORMODEL_BT601 }; public: typedef QMap CountryCodeMap; public: DMetadata(); explicit DMetadata(const QString& filePath); explicit DMetadata(const MetaEngineData& data); ~DMetadata(); public: // Settings helpers void registerMetadataSettings(); void setSettings(const MetaEngineSettingsContainer& settings); public: // File I/O helpers /** * Re-implemented from libMetaEngine to use libraw identify and * ffmpeg probe methods if Exiv2 failed. */ bool load(const QString& filePath) override; bool save(const QString& filePath, bool setVersion = false) const; bool applyChanges(bool setVersion = false) const; /** * Try to extract metadata using Raw Engine identify method (libraw). */ bool loadUsingRawEngine(const QString& filePath); public: // History helpers QString getItemHistory() const; bool setItemHistory(QString& imageHistoryXml) const; bool hasItemHistoryTag() const; QString getItemUniqueId() const; bool setItemUniqueId(const QString& uuid) const; public: // Faces helpers /** * Get Images Face Map based on tags stored in Picassa/Metadatagroup * format. */ bool getItemFacesMap(QMultiMap& facesPath) const; /** * Set Images Face Map tags in Picassa/Metadatagroup format. * * @param write : if true all faces will be written, else update mode: * search if at least a face tag exist and write if true. */ bool setItemFacesMap(QMultiMap& facesPath, bool write) const; /** * Remove Images Face Map tags from Picassa/Metadatagroup format. */ void removeItemFacesMap(); public: // Tags helpers bool getItemTagsPath(QStringList& tagsPath, const DMetadataSettingsContainer& settings = DMetadataSettings::instance()->settings()) const; bool setItemTagsPath(const QStringList& tagsPath, const DMetadataSettingsContainer& settings = DMetadataSettings::instance()->settings()) const; bool getACDSeeTagsPath(QStringList& tagsPath) const; bool setACDSeeTagsPath(const QStringList& tagsPath) const; public: // Comments helpers CaptionsMap getItemComments(const DMetadataSettingsContainer& settings = DMetadataSettings::instance()->settings()) const; bool setItemComments(const CaptionsMap& comments, const DMetadataSettingsContainer& settings = DMetadataSettings::instance()->settings()) const; CaptionsMap getItemTitles() const; bool setItemTitles(const CaptionsMap& title) const; static MetaEngine::AltLangMap toAltLangMap(const QVariant& var); public: // Labels helpers int getItemRating(const DMetadataSettingsContainer& settings = DMetadataSettings::instance()->settings()) const; bool setItemRating(int rating, const DMetadataSettingsContainer& settings = DMetadataSettings::instance()->settings()) const; int getItemPickLabel() const; bool setItemPickLabel(int pickId) const; int getItemColorLabel() const; bool setItemColorLabel(int colorId) const; public: // Template helpers bool setMetadataTemplate(const Template& t) const; Template getMetadataTemplate() const; bool removeMetadataTemplate() const; /** * Fills only the copyright values in the template. Use getMetadataTemplate() usually. * Returns true if valid fields were read. */ bool getCopyrightInformation(Template& t) const; public: // EXIF helpers /** * Reads an IccProfile that is described or embedded in the metadata. * This method does not retrieve profiles embedded in the image but from the Exif metadata, * e.g. embedded profiles in JPEG images. * Returns a null profile if no profile is found. */ IccProfile getIccProfile() const; /** * Sets the IccProfile embedded in the Exif metadata. */ bool setIccProfile(const IccProfile& profile); /** * Remove the Exif color space identification from the image. */ bool removeExifColorSpace() const; /** * Returns millisecond time-stamp from Exif tags or 0 if not found. */ int getMSecsInfo() const; /** * Extract milliseconds time-stamp of photo from an Exif tag and store it to 'ms'. * Returns true if data are extracted. */ bool mSecTimeStamp(const char* const exifTagName, int& ms) const; bool removeExifTags(const QStringList& tagFilters); private: QString getExifTagStringFromTagsList(const QStringList& tagsList) const; public: // IPTC helpers IptcCoreContactInfo getCreatorContactInfo() const; bool setCreatorContactInfo(const IptcCoreContactInfo& info) const; IptcCoreLocationInfo getIptcCoreLocation() const; bool setIptcCoreLocation(const IptcCoreLocationInfo& location) const; QStringList getIptcCoreSubjects() const; bool removeIptcTags(const QStringList& tagFilters); static CountryCodeMap countryCodeMap(); private: bool setIptcTag(const QString& text, int maxLength, const char* const debugLabel, const char* const tagKey) const; QVariant fromIptcEmulateList(const char* const iptcTagName) const; QVariant fromIptcEmulateLangAlt(const char* const iptcTagName) const; public: // XMP helpers - // These methods have been factored to libMetaEngine 2.3.0. Remove it after KDE 4.8.2 - /** * Set an Xmp tag content using a list of strings defined by the 'entriesToAdd' parameter. * The existing entries are preserved. The method will compare * all new with all already existing entries to prevent duplicates in the image. * Return true if the entries have been added to metadata. */ bool addToXmpTagStringBag(const char* const xmpTagName, const QStringList& entriesToAdd) const; /** * Remove those Xmp tag entries that are listed in entriesToRemove from the entries in metadata. * Return true if tag entries are no longer contained in metadata. * All other entries are preserved. */ bool removeFromXmpTagStringBag(const char* const xmpTagName, const QStringList& entriesToRemove) const; /** * Return a strings list of Xmp keywords from image. Return an empty list if no keyword are set. */ QStringList getXmpKeywords() const; /** * Set Xmp keywords using a list of strings defined by 'newKeywords' parameter. * The existing keywords from image are preserved. The method will compare * all new keywords with all already existing keywords to prevent duplicate entries in image. * Return true if keywords have been changed in metadata. */ bool setXmpKeywords(const QStringList& newKeywords) const; /** * Remove those Xmp keywords that are listed in keywordsToRemove from the keywords in metadata. * Return true if keywords are no longer contained in metadata. */ bool removeXmpKeywords(const QStringList& keywordsToRemove); /** * Return a strings list of Xmp subjects from image. Return an empty list if no subject are set. */ QStringList getXmpSubjects() const; /** * Set Xmp subjects using a list of strings defined by 'newSubjects' parameter. * The existing subjects from image are preserved. The method will compare * all new subject with all already existing subject to prevent duplicate entries in image. * Return true if subjects have been changed in metadata. */ bool setXmpSubjects(const QStringList& newSubjects) const; /** * Remove those Xmp subjects that are listed in subjectsToRemove from the subjects in metadata. * Return true if subjects are no longer contained in metadata. */ bool removeXmpSubjects(const QStringList& subjectsToRemove); /** * Return a strings list of Xmp sub-categories from image. Return an empty list if no sub-category * are set. */ QStringList getXmpSubCategories() const; /** * Set Xmp sub-categories using a list of strings defined by 'newSubCategories' parameter. * The existing sub-categories from image are preserved. The method will compare * all new sub-categories with all already existing sub-categories to prevent duplicate entries in image. * Return true if sub-categories have been changed in metadata. */ bool setXmpSubCategories(const QStringList& newSubCategories) const; /** * Remove those Xmp sub-categories that are listed in categoriesToRemove from the sub-categories in metadata. * Return true if subjects are no longer contained in metadata. */ bool removeXmpSubCategories(const QStringList& categoriesToRemove); bool removeXmpTags(const QStringList& tagFilters); private: QVariant fromXmpList(const char* const xmpTagName) const; QVariant fromXmpLangAlt(const char* const xmpTagName) const; public: // Video helpers /** * Try to extract metadata using FFMpeg probe method (libav). */ bool loadUsingFFmpeg(const QString& filePath); /** * Returns video metadata from Xmp tags. */ VideoInfoContainer getVideoInformation() const; /** * Helper method to translate enum values to user presentable strings */ static QString videoColorModelToString(VIDEOCOLORMODEL videoColorModel); public: // Photo helpers /** * Return a string with Lens mounted on the front of camera. * There no standard Exif tag for Lens information. * Camera makernotes and Xmp tags are parsed. * Take a care : lens information are not standardized and string content is not homogeneous between * camera model/maker. */ QString getLensDescription() const; PhotoInfoContainer getPhotographInformation() const; static double apexApertureToFNumber(double aperture); static double apexShutterSpeedToExposureTime(double shutterSpeed); public: // Generic helpers /** * Returns the requested metadata field as a QVariant. See metadatainfo.h for a specification * of the format of the QVariant. */ QVariant getMetadataField(MetadataInfo::Field field) const; QVariantList getMetadataFields(const MetadataFields& fields) const; /** * Convert a QVariant value of the specified field to a user-presentable, i18n'ed string. * The QVariant must be of the type as specified in metadatainfo.h and as obtained by getMetadataField. */ static QString valueToString (const QVariant& value, MetadataInfo::Field field); static QStringList valuesToString(const QVariantList& list, const MetadataFields& fields); /** * Returns a map of possible enum values and their user-presentable, i18n'ed representation. * Valid fields are those which are described as "enum from" or "bit mask from" in metadatainfo.h. */ static QMap possibleValuesForEnumField(MetadataInfo::Field field); private: QVariant fromExifOrXmp(const char* const exifTagName, const char* const xmpTagName) const; QVariant fromIptcOrXmp(const char* const iptcTagName, const char* const xmpTagName) const; bool hasValidField(const QVariantList& list) const; QVariant toStringListVariant(const QStringList& list) const; }; } // namespace Digikam #endif // DIGIKAM_DMETA_DATA_H diff --git a/core/libs/metadataengine/dmetadata/dmetadata_comments.cpp b/core/libs/metadataengine/dmetadata/dmetadata_comments.cpp index 4a0aa3f4a3..ba540dd664 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_comments.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_comments.cpp @@ -1,427 +1,514 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - comments helpers. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Qt includes #include // Local includes #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { CaptionsMap DMetadata::getItemComments(const DMetadataSettingsContainer& settings) const { if (getFilePath().isEmpty()) { return CaptionsMap(); } CaptionsMap captionsMap; MetaEngine::AltLangMap authorsMap; MetaEngine::AltLangMap datesMap; MetaEngine::AltLangMap commentsMap; QString commonAuthor; // In first try to get captions properties from digiKam XMP namespace if (supportXmp()) { authorsMap = getXmpTagStringListLangAlt("Xmp.digiKam.CaptionsAuthorNames", false); datesMap = getXmpTagStringListLangAlt("Xmp.digiKam.CaptionsDateTimeStamps", false); if (authorsMap.isEmpty() && commonAuthor.isEmpty()) { QString xmpAuthors = getXmpTagString("Xmp.acdsee.author", false); if (!xmpAuthors.isEmpty()) { authorsMap.insert(QLatin1String("x-default"), xmpAuthors); } } } // Get author name from IPTC DescriptionWriter. Private namespace above gets precedence. + QVariant descriptionWriter = getMetadataField(MetadataInfo::DescriptionWriter); if (!descriptionWriter.isNull()) { commonAuthor = descriptionWriter.toString(); } // In first, we check XMP alternative language tags to create map of values. bool xmpSupported = hasXmp(); bool iptcSupported = hasIptc(); bool exivSupported = hasExif(); for (NamespaceEntry entry : settings.getReadMapping(NamespaceEntry::DM_COMMENT_CONTAINER())) { if (entry.isDisabled) + { continue; + } QString commentString; const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); switch(entry.subspace) { case NamespaceEntry::XMP: + { switch(entry.specialOpts) { case NamespaceEntry::COMMENT_ALTLANG: + { if (xmpSupported) + { commentString = getXmpTagStringLangAlt(nameSpace, QString(), false); + } + break; + } + case NamespaceEntry::COMMENT_ATLLANGLIST: + { if (xmpSupported) + { commentsMap = getXmpTagStringListLangAlt(nameSpace, false); + } + break; + } + case NamespaceEntry::COMMENT_XMP: + { if (xmpSupported) + { commentString = getXmpTagString("Xmp.acdsee.notes", false); + } + break; + } + case NamespaceEntry::COMMENT_JPEG: + { // Now, we trying to get image comments, outside of XMP. // For JPEG, string is extracted from JFIF Comments section. // For PNG, string is extracted from iTXt chunk. + commentString = getCommentsDecoded(); + } + default: break; } + break; + } + case NamespaceEntry::IPTC: + { if (iptcSupported) + { commentString = getIptcTagString(nameSpace, false); + } + break; + } + case NamespaceEntry::EXIF: + { if (exivSupported) + { commentString = getExifComment(); + } + break; + } + default: break; } - if (!commentString.isEmpty() &&!commentString.trimmed().isEmpty()) + if (!commentString.isEmpty() && !commentString.trimmed().isEmpty()) { commentsMap.insert(QLatin1String("x-default"), commentString); captionsMap.setData(commentsMap, authorsMap, commonAuthor, datesMap); + return captionsMap; } if (!commentsMap.isEmpty()) { captionsMap.setData(commentsMap, authorsMap, commonAuthor, datesMap); + return captionsMap; } } return captionsMap; } bool DMetadata::setItemComments(const CaptionsMap& comments, const DMetadataSettingsContainer& settings) const { /* // See bug #139313: An empty string is also a valid value + if (comments.isEmpty()) + { return false; + } */ //qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Comment: " << comments; // In first, set captions properties to digiKam XMP namespace if (supportXmp()) { if (!setXmpTagStringListLangAlt("Xmp.digiKam.CaptionsAuthorNames", comments.authorsList())) { return false; } QString defaultAuthor = comments.value(QLatin1String("x-default")).author; removeXmpTag("Xmp.acdsee.author"); if (!defaultAuthor.isNull()) { if (!setXmpTagString("Xmp.acdsee.author", defaultAuthor)) { return false; } } if (!setXmpTagStringListLangAlt("Xmp.digiKam.CaptionsDateTimeStamps", comments.datesList())) { return false; } } QString defaultComment = comments.value(QLatin1String("x-default")).caption; QList toWrite = settings.getReadMapping(NamespaceEntry::DM_COMMENT_CONTAINER()); if (!settings.unifyReadWrite()) { toWrite = settings.getWriteMapping(NamespaceEntry::DM_COMMENT_CONTAINER()); } for (NamespaceEntry entry : toWrite) { if (entry.isDisabled) + { continue; + } const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); - switch(entry.subspace) + switch (entry.subspace) { case NamespaceEntry::XMP: + { if (entry.namespaceName.contains(QLatin1String("Xmp."))) + { removeXmpTag(nameSpace); + } switch(entry.specialOpts) { case NamespaceEntry::COMMENT_ALTLANG: + { if (!defaultComment.isNull()) { if (!setXmpTagStringLangAlt(nameSpace, defaultComment, QString())) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image comment failed" << nameSpace; return false; } } + break; + } case NamespaceEntry::COMMENT_ATLLANGLIST: + { if (!setXmpTagStringListLangAlt(nameSpace, comments.toAltLangMap())) { return false; } + break; + } case NamespaceEntry::COMMENT_XMP: + { if (!defaultComment.isNull()) { if (!setXmpTagString(nameSpace, defaultComment)) { return false; } } + break; + } case NamespaceEntry::COMMENT_JPEG: + { // In first we set image comments, outside of Exif, XMP, and IPTC. + if (!setComments(defaultComment.toUtf8())) { return false; } + break; + } default: break; } + break; + } case NamespaceEntry::IPTC: + { removeIptcTag(nameSpace); if (!defaultComment.isNull()) { defaultComment.truncate(2000); if (!setIptcTagString(nameSpace, defaultComment)) { return false; } } + break; + } case NamespaceEntry::EXIF: + { if (!setExifComment(defaultComment)) { return false; } + break; + } default: break; } } return true; } CaptionsMap DMetadata::getItemTitles() const { if (getFilePath().isEmpty()) + { return CaptionsMap(); + } CaptionsMap captionsMap; MetaEngine::AltLangMap authorsMap; MetaEngine::AltLangMap datesMap; MetaEngine::AltLangMap titlesMap; QString commonAuthor; // Get author name from IPTC DescriptionWriter. Private namespace above gets precedence. + QVariant descriptionWriter = getMetadataField(MetadataInfo::DescriptionWriter); if (!descriptionWriter.isNull()) + { commonAuthor = descriptionWriter.toString(); + } // In first, we check XMP alternative language tags to create map of values. if (hasXmp()) { titlesMap = getXmpTagStringListLangAlt("Xmp.dc.title", false); if (!titlesMap.isEmpty()) { captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap); + return captionsMap; } - QString xmpTitle = getXmpTagString("Xmp.acdsee.caption" ,false); + QString xmpTitle = getXmpTagString("Xmp.acdsee.caption", false); if (!xmpTitle.isEmpty() && !xmpTitle.trimmed().isEmpty()) { titlesMap.insert(QLatin1String("x-default"), xmpTitle); captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap); + return captionsMap; } } // We trying to get IPTC title if (hasIptc()) { QString iptcTitle = getIptcTagString("Iptc.Application2.ObjectName", false); if (!iptcTitle.isEmpty() && !iptcTitle.trimmed().isEmpty()) { titlesMap.insert(QLatin1String("x-default"), iptcTitle); captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap); return captionsMap; } } return captionsMap; } bool DMetadata::setItemTitles(const CaptionsMap& titles) const { //qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Title: " << titles; QString defaultTitle = titles[QLatin1String("x-default")].caption; // In First we write comments into XMP. Language Alternative rule is not yet used. if (supportXmp()) { // NOTE : setXmpTagStringListLangAlt remove xmp tag before to add new values + if (!setXmpTagStringListLangAlt("Xmp.dc.title", titles.toAltLangMap())) { return false; } removeXmpTag("Xmp.acdsee.caption"); if (!defaultTitle.isEmpty()) { if (!setXmpTagString("Xmp.acdsee.caption", defaultTitle)) { return false; } } } // In Second we write comments into IPTC. // Note that Caption IPTC tag is limited to 64 char and ASCII charset. removeIptcTag("Iptc.Application2.ObjectName"); if (!defaultTitle.isNull()) { defaultTitle.truncate(64); // See if we have any non printable chars in there. If so, skip IPTC // to avoid confusing other apps and web services with invalid tags. + bool hasInvalidChar = false; - for (QString::const_iterator c = defaultTitle.constBegin(); c != defaultTitle.constEnd(); ++c) + for (QString::const_iterator c = defaultTitle.constBegin() ; c != defaultTitle.constEnd() ; ++c) { if (!(*c).isPrint()) { hasInvalidChar = true; break; } } if (!hasInvalidChar) { if (!setIptcTagString("Iptc.Application2.ObjectName", defaultTitle)) + { return false; + } } } return true; } MetaEngine::AltLangMap DMetadata::toAltLangMap(const QVariant& var) { MetaEngine::AltLangMap map; if (var.isNull()) { return map; } switch (var.type()) { case QVariant::String: + { map.insert(QLatin1String("x-default"), var.toString()); break; + } + case QVariant::Map: { QMap varMap = var.toMap(); - for (QMap::const_iterator it = varMap.constBegin(); it != varMap.constEnd(); ++it) + for (QMap::const_iterator it = varMap.constBegin() ; it != varMap.constEnd() ; ++it) { map.insert(it.key(), it.value().toString()); } break; } + default: break; } return map; } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_exif.cpp b/core/libs/metadataengine/dmetadata/dmetadata_exif.cpp index 1739df8179..ec6d89ef2f 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_exif.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_exif.cpp @@ -1,175 +1,181 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - Exif helpers. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Qt includes #include // Local includes #include "metaenginesettings.h" #include "iccprofile.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { int DMetadata::getMSecsInfo() const { int ms = 0; bool ok = mSecTimeStamp("Exif.Photo.SubSecTime", ms); if (ok) return ms; ok = mSecTimeStamp("Exif.Photo.SubSecTimeOriginal", ms); if (ok) return ms; ok = mSecTimeStamp("Exif.Photo.SubSecTimeDigitized", ms); if (ok) return ms; return 0; } bool DMetadata::mSecTimeStamp(const char* const exifTagName, int& ms) const { bool ok = false; QString val = getExifTagString(exifTagName); if (!val.isEmpty()) { int sub = val.toUInt(&ok); if (ok) { int _ms = (int)(QString::fromLatin1("0.%1").arg(sub).toFloat(&ok) * 1000.0); if (ok) { ms = _ms; qCDebug(DIGIKAM_METAENGINE_LOG) << "msec timestamp: " << ms; } } } return ok; } IccProfile DMetadata::getIccProfile() const { // Check if Exif data contains an ICC color profile. + QByteArray data = getExifTagData("Exif.Image.InterColorProfile"); if (!data.isNull()) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Found an ICC profile in Exif metadata"; return IccProfile(data); } // Else check the Exif color-space tag and use default profiles that we ship + switch (getItemColorWorkSpace()) { case DMetadata::WORKSPACE_SRGB: { qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif color-space tag is sRGB. Using default sRGB ICC profile."; return IccProfile::sRGB(); } case DMetadata::WORKSPACE_ADOBERGB: { qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif color-space tag is AdobeRGB. Using default AdobeRGB ICC profile."; return IccProfile::adobeRGB(); } default: break; } return IccProfile(); } bool DMetadata::setIccProfile(const IccProfile& profile) { if (profile.isNull()) { removeExifTag("Exif.Image.InterColorProfile"); } else { QByteArray data = IccProfile(profile).data(); if (!setExifTagData("Exif.Image.InterColorProfile", data)) { return false; } } removeExifColorSpace(); return true; } bool DMetadata::removeExifColorSpace() const { bool ret = true; ret &= removeExifTag("Exif.Photo.ColorSpace"); ret &= removeXmpTag("Xmp.exif.ColorSpace"); return ret; } QString DMetadata::getExifTagStringFromTagsList(const QStringList& tagsList) const { QString val; foreach (const QString& tag, tagsList) { val = getExifTagString(tag.toLatin1().constData()); if (!val.isEmpty()) + { return val; + } } return QString(); } bool DMetadata::removeExifTags(const QStringList& tagFilters) { MetaDataMap m = getExifTagsDataList(tagFilters); if (m.isEmpty()) + { return false; + } for (MetaDataMap::iterator it = m.begin() ; it != m.end() ; ++it) { removeExifTag(it.key().toLatin1().constData()); } return true; } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_faces.cpp b/core/libs/metadataengine/dmetadata/dmetadata_faces.cpp index 70545a7189..48b5fd6d3c 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_faces.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_faces.cpp @@ -1,274 +1,295 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - faces helpers * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2013 by Veaceslav Munteanu * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Qt includes #include // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { bool DMetadata::getItemFacesMap(QMultiMap& faces) const { faces.clear(); // The example code for Exiv2 says: // > There are no specialized values for structures, qualifiers and nested // > types. However, these can be added by using an XmpTextValue and a path as // > the key. // I think that means I have to iterate over the WLPG face tags in the clunky // way below (guess numbers and look them up as strings). (Leif) const QString personPathTemplate = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions[%1]/MPReg:PersonDisplayName"); const QString rectPathTemplate = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions[%1]/MPReg:Rectangle"); for (int i = 1 ; ; ++i) { QString rectString = getXmpTagString(rectPathTemplate.arg(i).toLatin1().constData(), false); if (rectString.isEmpty()) + { break; + } QString person = getXmpTagString(personPathTemplate.arg(i).toLatin1().constData(), false); // The WLPG tags have the format X.XX, Y.YY, W.WW, H.HH // That is, four decimal numbers ranging from 0-1. // The top left position is indicated by X.XX, Y.YY (as a // percentage of the width/height of the entire image). // Similarly the width and height of the face's box are // indicated by W.WW and H.HH. + QStringList list = rectString.split(QLatin1Char(',')); if (list.size() < 4) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Cannot parse WLPG rectangle string" << rectString; continue; } QRectF rect(list.at(0).toFloat(), list.at(1).toFloat(), list.at(2).toFloat(), list.at(3).toFloat()); faces.insertMulti(person, rect); } - /** Read face tags only if Exiv2 can write them, otherwise - * garbage tags will be generated on image transformation + /** + * Read face tags only if Exiv2 can write them, otherwise + * garbage tags will be generated on image transformation */ // Read face tags as saved by Picasa // https://www.exiv2.org/tags-xmp-mwg-rs.html + const QString mwg_personPathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Name"); const QString mwg_rect_x_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:x"); const QString mwg_rect_y_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:y"); const QString mwg_rect_w_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:w"); const QString mwg_rect_h_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:h"); for (int i = 1 ; ; ++i) { QString person = getXmpTagString(mwg_personPathTemplate.arg(i).toLatin1().constData(), false); if (person.isEmpty()) + { break; + } // x and y is the center point + float x = getXmpTagString(mwg_rect_x_PathTemplate.arg(i).toLatin1().constData(), false).toFloat(); float y = getXmpTagString(mwg_rect_y_PathTemplate.arg(i).toLatin1().constData(), false).toFloat(); float w = getXmpTagString(mwg_rect_w_PathTemplate.arg(i).toLatin1().constData(), false).toFloat(); float h = getXmpTagString(mwg_rect_h_PathTemplate.arg(i).toLatin1().constData(), false).toFloat(); QRectF rect(x - w/2, y - h/2, w, h); faces.insertMulti(person, rect); qCDebug(DIGIKAM_METAENGINE_LOG) << "Found new rect " << person << " "<< rect; } return !faces.isEmpty(); } bool DMetadata::setItemFacesMap(QMultiMap& facesPath, bool write) const { QString qxmpTagName = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList"); QString nameTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Name"); QString typeTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Type"); QString areaTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area"); QString areaxTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:x"); QString areayTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:y"); QString areawTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:w"); QString areahTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:h"); QString areanormTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:unit"); QString winQxmpTagName = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions"); QString winRectTagKey = winQxmpTagName + QLatin1String("[%1]/MPReg:Rectangle"); QString winNameTagKey = winQxmpTagName + QLatin1String("[%1]/MPReg:PersonDisplayName"); if (!write) { QString check = getXmpTagString(nameTagKey.arg(1).toLatin1().constData()); if (check.isEmpty()) + { return true; + } } setXmpTagString(qxmpTagName.toLatin1().constData(), QString(), MetaEngine::ArrayBagTag); setXmpTagString(winQxmpTagName.toLatin1().constData(), QString(), MetaEngine::ArrayBagTag); QMap::const_iterator it = facesPath.constBegin(); int i = 1; bool ok = true; while (it != facesPath.constEnd()) { qreal x, y, w, h; it.value().toRectF().getRect(&x, &y, &w, &h); qCDebug(DIGIKAM_METAENGINE_LOG) << "Set face region:" << x << y << w << h; // Write face tags in Windows Live Photo format QString rectString; rectString.append(QString::number(x) + QLatin1String(", ")); rectString.append(QString::number(y) + QLatin1String(", ")); rectString.append(QString::number(w) + QLatin1String(", ")); rectString.append(QString::number(h)); // Set tag rect + setXmpTagString(winRectTagKey.arg(i).toLatin1().constData(), rectString, MetaEngine::NormalTag); // Set tag name + setXmpTagString(winNameTagKey.arg(i).toLatin1().constData(), it.key(), MetaEngine::NormalTag); // Writing rectangle in Metadata Group format + x += w / 2; y += h / 2; // Set tag name + ok &= setXmpTagString(nameTagKey.arg(i).toLatin1().constData(), it.key(), MetaEngine::NormalTag); qCDebug(DIGIKAM_METAENGINE_LOG) << " => set tag name:" << ok; // Set tag type as Face + ok &= setXmpTagString(typeTagKey.arg(i).toLatin1().constData(), QLatin1String("Face"), MetaEngine::NormalTag); qCDebug(DIGIKAM_METAENGINE_LOG) << " => set tag type:" << ok; // Set tag Area, with xmp type struct + ok &= setXmpTagString(areaTagKey.arg(i).toLatin1().constData(), QString(), MetaEngine::StructureTag); qCDebug(DIGIKAM_METAENGINE_LOG) << " => set area struct:" << ok; // Set stArea:x inside Area structure + ok &= setXmpTagString(areaxTagKey.arg(i).toLatin1().constData(), QString::number(x), MetaEngine::NormalTag); qCDebug(DIGIKAM_METAENGINE_LOG) << " => set xpos:" << ok; // Set stArea:y inside Area structure + ok &= setXmpTagString(areayTagKey.arg(i).toLatin1().constData(), QString::number(y), MetaEngine::NormalTag); qCDebug(DIGIKAM_METAENGINE_LOG) << " => set ypos:" << ok; // Set stArea:w inside Area structure + ok &= setXmpTagString(areawTagKey.arg(i).toLatin1().constData(), QString::number(w), MetaEngine::NormalTag); qCDebug(DIGIKAM_METAENGINE_LOG) << " => set width:" << ok; // Set stArea:h inside Area structure + ok &= setXmpTagString(areahTagKey.arg(i).toLatin1().constData(), QString::number(h), MetaEngine::NormalTag); qCDebug(DIGIKAM_METAENGINE_LOG) << " => set heigh:" << ok; // Set stArea:unit inside Area structure as normalized + ok &= setXmpTagString(areanormTagKey.arg(i).toLatin1().constData(), QLatin1String("normalized"), MetaEngine::NormalTag); qCDebug(DIGIKAM_METAENGINE_LOG) << " => set unit:" << ok; ++it; ++i; } return ok; } void DMetadata::removeItemFacesMap() { QString qxmpStructName = QLatin1String("Xmp.mwg-rs.Regions"); QString qxmpTagName = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList"); QString winQxmpStructName = QLatin1String("Xmp.MP.RegionInfo"); QString winQxmpTagName = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions"); // Remove mwg-rs tags setXmpTagString(qxmpTagName.toLatin1().constData(), QString(), MetaEngine::ArrayBagTag); setXmpTagString(qxmpStructName.toLatin1().constData(), QString(), MetaEngine::StructureTag); // Remove MP tags setXmpTagString(winQxmpTagName.toLatin1().constData(), QString(), MetaEngine::ArrayBagTag); setXmpTagString(winQxmpStructName.toLatin1().constData(), QString(), MetaEngine::StructureTag); } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_fileio.cpp b/core/libs/metadataengine/dmetadata/dmetadata_fileio.cpp index cc61916837..a93cf40b15 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_fileio.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_fileio.cpp @@ -1,156 +1,161 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - file I/O helpers. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Qt includes #include #include #include #include #include #include // Local includes #include "filereadwritelock.h" #include "metaenginesettings.h" #include "drawinfo.h" #include "drawdecoder.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { bool DMetadata::load(const QString& filePath) { FileReadLocker lock(filePath); bool hasLoaded = false; QMimeDatabase mimeDB; if (!mimeDB.mimeTypeForFile(filePath).name().startsWith(QLatin1String("video/")) && !mimeDB.mimeTypeForFile(filePath).name().startsWith(QLatin1String("audio/")) ) { // Non video or audio file, process with Exiv2 backend or libraw if fail with RAW files // Never process video file Exiv2, the backend is very unstable. + if (!(hasLoaded = MetaEngine::load(filePath))) { hasLoaded = loadUsingRawEngine(filePath); } } else { // No image file, process with ffmpeg backend. + hasLoaded = loadUsingFFmpeg(filePath); hasLoaded |= loadFromSidecarAndMerge(filePath); } return hasLoaded; } bool DMetadata::save(const QString& filePath, bool setVersion) const { FileWriteLocker lock(filePath); + return MetaEngine::save(filePath, setVersion); } bool DMetadata::applyChanges(bool setVersion) const { FileWriteLocker lock(getFilePath()); + return MetaEngine::applyChanges(setVersion); } bool DMetadata::loadUsingRawEngine(const QString& filePath) { DRawInfo identify; if (DRawDecoder::rawFileIdentify(identify, filePath)) { long int num = 1; long int den = 1; if (!identify.model.isNull()) { setExifTagString("Exif.Image.Model", identify.model); } if (!identify.make.isNull()) { setExifTagString("Exif.Image.Make", identify.make); } if (!identify.owner.isNull()) { setExifTagString("Exif.Image.Artist", identify.owner); } if (identify.sensitivity != -1) { setExifTagLong("Exif.Photo.ISOSpeedRatings", lroundf(identify.sensitivity)); } if (identify.dateTime.isValid()) { setImageDateTime(identify.dateTime, false); } if (identify.exposureTime != -1.0) { convertToRationalSmallDenominator(identify.exposureTime, &num, &den); setExifTagRational("Exif.Photo.ExposureTime", num, den); } if (identify.aperture != -1.0) { convertToRational(identify.aperture, &num, &den, 8); setExifTagRational("Exif.Photo.ApertureValue", num, den); } if (identify.focalLength != -1.0) { convertToRational(identify.focalLength, &num, &den, 8); setExifTagRational("Exif.Photo.FocalLength", num, den); } if (identify.imageSize.isValid()) { setItemDimensions(identify.imageSize); } // A RAW image is always uncalibrated. */ + setItemColorWorkSpace(WORKSPACE_UNCALIBRATED); return true; } return false; } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_generic.cpp b/core/libs/metadataengine/dmetadata/dmetadata_generic.cpp index 3a88e90bc1..15fb5aef73 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_generic.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_generic.cpp @@ -1,917 +1,1064 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - generic helpers * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Qt includes #include // KDE includes #include // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { QVariant DMetadata::fromExifOrXmp(const char* const exifTagName, const char* const xmpTagName) const { QVariant var; if (exifTagName) { var = getExifTagVariant(exifTagName, false); if (!var.isNull()) { return var; } } if (xmpTagName) { var = getXmpTagVariant(xmpTagName); if (!var.isNull()) { return var; } } return var; } QVariant DMetadata::fromIptcOrXmp(const char* const iptcTagName, const char* const xmpTagName) const { if (iptcTagName) { QString iptcValue = getIptcTagString(iptcTagName); if (!iptcValue.isNull()) { return iptcValue; } } if (xmpTagName) { QVariant var = getXmpTagVariant(xmpTagName); if (!var.isNull()) { return var; } } return QVariant(QVariant::String); } QVariant DMetadata::getMetadataField(MetadataInfo::Field field) const { switch (field) { case MetadataInfo::Comment: { return getItemComments()[QLatin1String("x-default")].caption; } case MetadataInfo::CommentJfif: { return getCommentsDecoded(); } case MetadataInfo::CommentExif: { return getExifComment(); } case MetadataInfo::CommentIptc: { return fromIptcOrXmp("Iptc.Application2.Caption", nullptr); } case MetadataInfo::Description: { QVariant var = fromXmpLangAlt("Xmp.dc.description"); if (!var.isNull()) { return var; } var = fromXmpLangAlt("Xmp.tiff.ImageDescription"); if (!var.isNull()) { return var; } return fromIptcEmulateLangAlt("Iptc.Application2.Caption"); } case MetadataInfo::Headline: { return fromIptcOrXmp("Iptc.Application2.Headline", "Xmp.photoshop.Headline"); } case MetadataInfo::Title: { QString str = getItemTitles()[QLatin1String("x-default")].caption; if (str.isEmpty()) { return QVariant(QVariant::Map); } QMap map; map[QLatin1String("x-default")] = str; + return map; } case MetadataInfo::DescriptionWriter: { return fromIptcOrXmp("Iptc.Application2.Writer", "Xmp.photoshop.CaptionWriter"); } case MetadataInfo::Keywords: { QStringList list; getItemTagsPath(list); + return toStringListVariant(list); } case MetadataInfo::Faces: { QMultiMap faceMap; getItemFacesMap(faceMap); QVariant var(faceMap); + return var; } case MetadataInfo::Rating: { return getItemRating(); } case MetadataInfo::CreationDate: { return getItemDateTime(); } case MetadataInfo::DigitizationDate: { return getDigitizationDateTime(true); } case MetadataInfo::Orientation: { return (int)getItemOrientation(); } case MetadataInfo::Make: { QVariant var = fromExifOrXmp("Exif.Image.Make", "Xmp.tiff.Make"); + return QVariant(var.toString().trimmed()); } case MetadataInfo::Model: { QVariant var = fromExifOrXmp("Exif.Image.Model", "Xmp.tiff.Model"); + return QVariant(var.toString().trimmed()); } case MetadataInfo::Lens: { return getLensDescription(); } case MetadataInfo::Aperture: { QVariant var = fromExifOrXmp("Exif.Photo.FNumber", "Xmp.exif.FNumber"); if (var.isNull()) { var = fromExifOrXmp("Exif.Photo.ApertureValue", "Xmp.exif.ApertureValue"); if (!var.isNull()) { var = apexApertureToFNumber(var.toDouble()); } } return var; } case MetadataInfo::FocalLength: { return fromExifOrXmp("Exif.Photo.FocalLength", "Xmp.exif.FocalLength"); } case MetadataInfo::FocalLengthIn35mm: { return fromExifOrXmp("Exif.Photo.FocalLengthIn35mmFilm", "Xmp.exif.FocalLengthIn35mmFilm"); } case MetadataInfo::ExposureTime: { QVariant var = fromExifOrXmp("Exif.Photo.ExposureTime", "Xmp.exif.ExposureTime"); if (var.isNull()) { var = fromExifOrXmp("Exif.Photo.ShutterSpeedValue", "Xmp.exif.ShutterSpeedValue"); if (!var.isNull()) { var = apexShutterSpeedToExposureTime(var.toDouble()); } } return var; } case MetadataInfo::ExposureProgram: { return fromExifOrXmp("Exif.Photo.ExposureProgram", "Xmp.exif.ExposureProgram"); } case MetadataInfo::ExposureMode: { return fromExifOrXmp("Exif.Photo.ExposureMode", "Xmp.exif.ExposureMode"); } case MetadataInfo::Sensitivity: { QVariant var = fromExifOrXmp("Exif.Photo.ISOSpeedRatings", "Xmp.exif.ISOSpeedRatings"); - //if (var.isNull()) - // TODO: has this ISO format??? We must convert to the format of ISOSpeedRatings! - // var = fromExifOrXmp("Exif.Photo.ExposureIndex", "Xmp.exif.ExposureIndex"); +/* + if (var.isNull()) + { + TODO: has this ISO format??? We must convert to the format of ISOSpeedRatings! + var = fromExifOrXmp("Exif.Photo.ExposureIndex", "Xmp.exif.ExposureIndex"); + } +*/ return var; } case MetadataInfo::FlashMode: { return fromExifOrXmp("Exif.Photo.Flash", "Xmp.exif.Flash"); } case MetadataInfo::WhiteBalance: { return fromExifOrXmp("Exif.Photo.WhiteBalance", "Xmp.exif.WhiteBalance"); } case MetadataInfo::MeteringMode: { return fromExifOrXmp("Exif.Photo.MeteringMode", "Xmp.exif.MeteringMode"); } case MetadataInfo::SubjectDistance: { return fromExifOrXmp("Exif.Photo.SubjectDistance", "Xmp.exif.SubjectDistance"); } case MetadataInfo::SubjectDistanceCategory: { return fromExifOrXmp("Exif.Photo.SubjectDistanceRange", "Xmp.exif.SubjectDistanceRange"); } case MetadataInfo::WhiteBalanceColorTemperature: { //TODO: ?? return QVariant(QVariant::Int); } case MetadataInfo::Longitude: { return getGPSLongitudeString(); } case MetadataInfo::LongitudeNumber: { double longitude; if (getGPSLongitudeNumber(&longitude)) { return longitude; } else { return QVariant(QVariant::Double); } } case MetadataInfo::Latitude: { return getGPSLatitudeString(); } case MetadataInfo::LatitudeNumber: { double latitude; if (getGPSLatitudeNumber(&latitude)) { return latitude; } else { return QVariant(QVariant::Double); } } + case MetadataInfo::Altitude: { double altitude; if (getGPSAltitude(&altitude)) { return altitude; } else { return QVariant(QVariant::Double); } } case MetadataInfo::PositionOrientation: case MetadataInfo::PositionTilt: case MetadataInfo::PositionRoll: case MetadataInfo::PositionAccuracy: + { // TODO or unsupported? return QVariant(QVariant::Double); + } case MetadataInfo::PositionDescription: + { // TODO or unsupported? return QVariant(QVariant::String); + } case MetadataInfo::IptcCoreCopyrightNotice: { QVariant var = fromXmpLangAlt("Xmp.dc.rights"); if (!var.isNull()) { return var; } var = fromXmpLangAlt("Xmp.tiff.Copyright"); if (!var.isNull()) { return var; } return fromIptcEmulateLangAlt("Iptc.Application2.Copyright"); } case MetadataInfo::IptcCoreCreator: { QVariant var = fromXmpList("Xmp.dc.creator"); if (!var.isNull()) { return var; } QString artist = getXmpTagString("Xmp.tiff.Artist"); if (!artist.isNull()) { QStringList list; list << artist; + return list; } return fromIptcEmulateList("Iptc.Application2.Byline"); } case MetadataInfo::IptcCoreProvider: + { return fromIptcOrXmp("Iptc.Application2.Credit", "Xmp.photoshop.Credit"); + } case MetadataInfo::IptcCoreRightsUsageTerms: + { return fromXmpLangAlt("Xmp.xmpRights.UsageTerms"); + } case MetadataInfo::IptcCoreSource: + { return fromIptcOrXmp("Iptc.Application2.Source", "Xmp.photoshop.Source"); + } case MetadataInfo::IptcCoreCreatorJobTitle: + { return fromIptcOrXmp("Iptc.Application2.BylineTitle", "Xmp.photoshop.AuthorsPosition"); + } case MetadataInfo::IptcCoreInstructions: + { return fromIptcOrXmp("Iptc.Application2.SpecialInstructions", "Xmp.photoshop.Instructions"); + } case MetadataInfo::IptcCoreLocationInfo: { IptcCoreLocationInfo location = getIptcCoreLocation(); if (location.isNull()) { return QVariant(); } return QVariant::fromValue(location); } case MetadataInfo::IptcCoreCountryCode: + { return fromIptcOrXmp("Iptc.Application2.CountryCode", "Xmp.iptc.CountryCode"); + } case MetadataInfo::IptcCoreCountry: + { return fromIptcOrXmp("Iptc.Application2.CountryName", "Xmp.photoshop.Country"); + } case MetadataInfo::IptcCoreCity: + { return fromIptcOrXmp("Iptc.Application2.City", "Xmp.photoshop.City"); + } case MetadataInfo::IptcCoreLocation: + { return fromIptcOrXmp("Iptc.Application2.SubLocation", "Xmp.iptc.Location"); + } case MetadataInfo::IptcCoreProvinceState: + { return fromIptcOrXmp("Iptc.Application2.ProvinceState", "Xmp.photoshop.State"); + } case MetadataInfo::IptcCoreIntellectualGenre: + { return fromIptcOrXmp("Iptc.Application2.ObjectAttribute", "Xmp.iptc.IntellectualGenre"); + } case MetadataInfo::IptcCoreJobID: + { return fromIptcOrXmp("Iptc.Application2.TransmissionReference", "Xmp.photoshop.TransmissionReference"); + } case MetadataInfo::IptcCoreScene: + { return fromXmpList("Xmp.iptc.Scene"); + } case MetadataInfo::IptcCoreSubjectCode: + { return toStringListVariant(getIptcCoreSubjects()); + } case MetadataInfo::IptcCoreContactInfo: { IptcCoreContactInfo info = getCreatorContactInfo(); if (info.isNull()) { return QVariant(); } return QVariant::fromValue(info); } case MetadataInfo::IptcCoreContactInfoCity: + { return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity"); + } case MetadataInfo::IptcCoreContactInfoCountry: + { return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry"); + } case MetadataInfo::IptcCoreContactInfoAddress: + { return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr"); + } case MetadataInfo::IptcCoreContactInfoPostalCode: + { return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode"); + } case MetadataInfo::IptcCoreContactInfoProvinceState: + { return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion"); + } case MetadataInfo::IptcCoreContactInfoEmail: + { return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork"); + } case MetadataInfo::IptcCoreContactInfoPhone: + { return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork"); + } case MetadataInfo::IptcCoreContactInfoWebUrl: + { return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork"); + } case MetadataInfo::AspectRatio: { long num = 0; long den = 1; // NOTE: there is a bug in Exiv2 xmp::video tag definition as "Rational" value is defined as "Ratio"... - //QList list = getXmpTagVariant("Xmp.video.AspectRatio").toList(); - +/* + QList list = getXmpTagVariant("Xmp.video.AspectRatio").toList(); +*/ QString ar = getXmpTagString("Xmp.video.AspectRatio"); QStringList list = ar.split(QLatin1Char('/')); if (list.size() >= 1) + { num = list[0].toInt(); + } if (list.size() >= 2) + { den = list[1].toInt(); + } return QString::number((double)num / (double)den); } case MetadataInfo::AudioBitRate: + { return fromXmpLangAlt("Xmp.audio.SampleRate"); + } case MetadataInfo::AudioChannelType: + { return fromXmpLangAlt("Xmp.audio.ChannelType"); + } case MetadataInfo::AudioCodec: + { return fromXmpLangAlt("Xmp.audio.Codec"); + } case MetadataInfo::Duration: + { return fromXmpLangAlt("Xmp.video.duration"); // duration is in ms + } case MetadataInfo::FrameRate: + { return fromXmpLangAlt("Xmp.video.FrameRate"); + } case MetadataInfo::VideoCodec: + { return fromXmpLangAlt("Xmp.video.Codec"); + } case MetadataInfo::VideoBitDepth: + { return fromXmpLangAlt("Xmp.video.BitDepth"); + } case MetadataInfo::VideoHeight: + { return fromXmpLangAlt("Xmp.video.Height"); + } case MetadataInfo::VideoWidth: + { return fromXmpLangAlt("Xmp.video.Width"); + } case MetadataInfo::VideoColorSpace: { QString cs = getXmpTagString("Xmp.video.ColorSpace"); - if (cs == QLatin1String("sRGB")) + if (cs == QLatin1String("sRGB")) + { return QString::number(VIDEOCOLORMODEL_SRGB); + } else if (cs == QLatin1String("CCIR-601")) + { return QString::number(VIDEOCOLORMODEL_BT601); + } else if (cs == QLatin1String("CCIR-709")) + { return QString::number(VIDEOCOLORMODEL_BT709); + } else if (cs == QLatin1String("Other")) + { return QString::number(VIDEOCOLORMODEL_OTHER); + } else + { return QVariant(QVariant::Int); + } } default: + { return QVariant(); + } } } QVariantList DMetadata::getMetadataFields(const MetadataFields& fields) const { QVariantList list; foreach (MetadataInfo::Field field, fields) // krazy:exclude=foreach { list << getMetadataField(field); } return list; } QString DMetadata::valueToString(const QVariant& value, MetadataInfo::Field field) { MetaEngine exiv2Iface; switch (field) { case MetadataInfo::Rating: + { return value.toString(); + } + case MetadataInfo::CreationDate: case MetadataInfo::DigitizationDate: + { return value.toDateTime().toString(Qt::LocaleDate); + } + case MetadataInfo::Orientation: { switch (value.toInt()) { // Example why the English text differs from the enum names: ORIENTATION_ROT_90. // Rotation by 90 degrees is right (clockwise) rotation. // But: The enum names describe what needs to be done to get the image right again. // And an image that needs to be rotated 90 degrees is currently rotated 270 degrees = left. case ORIENTATION_UNSPECIFIED: return i18n("Unspecified"); + case ORIENTATION_NORMAL: return i18nc("Rotation of an unrotated image", "Normal"); + case ORIENTATION_HFLIP: return i18n("Flipped Horizontally"); + case ORIENTATION_ROT_180: return i18n("Rotated by 180 Degrees"); + case ORIENTATION_VFLIP: return i18n("Flipped Vertically"); + case ORIENTATION_ROT_90_HFLIP: return i18n("Flipped Horizontally and Rotated Left"); + case ORIENTATION_ROT_90: return i18n("Rotated Left"); + case ORIENTATION_ROT_90_VFLIP: return i18n("Flipped Vertically and Rotated Left"); + case ORIENTATION_ROT_270: return i18n("Rotated Right"); + default: return i18n("Unknown"); } + break; } + case MetadataInfo::Make: return exiv2Iface.createExifUserStringFromValue("Exif.Image.Make", value); + case MetadataInfo::Model: return exiv2Iface.createExifUserStringFromValue("Exif.Image.Model", value); + case MetadataInfo::Lens: // heterogeneous source, non-standardized string return value.toString(); + case MetadataInfo::Aperture: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FNumber", value); + case MetadataInfo::FocalLength: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FocalLength", value); + case MetadataInfo::FocalLengthIn35mm: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FocalLengthIn35mmFilm", value); + case MetadataInfo::ExposureTime: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureTime", value); + case MetadataInfo::ExposureProgram: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureProgram", value); + case MetadataInfo::ExposureMode: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureMode", value); + case MetadataInfo::Sensitivity: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ISOSpeedRatings", value); + case MetadataInfo::FlashMode: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.Flash", value); + case MetadataInfo::WhiteBalance: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.WhiteBalance", value); + case MetadataInfo::MeteringMode: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.MeteringMode", value); + case MetadataInfo::SubjectDistance: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.SubjectDistance", value); + case MetadataInfo::SubjectDistanceCategory: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.SubjectDistanceRange", value); + case MetadataInfo::WhiteBalanceColorTemperature: return i18nc("Temperature in Kelvin", "%1 K", value.toInt()); case MetadataInfo::AspectRatio: case MetadataInfo::AudioBitRate: case MetadataInfo::AudioChannelType: case MetadataInfo::AudioCodec: case MetadataInfo::Duration: case MetadataInfo::FrameRate: case MetadataInfo::VideoCodec: return value.toString(); case MetadataInfo::Longitude: { int degrees, minutes; double seconds; char directionRef; if (!convertToUserPresentableNumbers(value.toString(), °rees, &minutes, &seconds, &directionRef)) { return QString(); } QString direction = (QLatin1Char(directionRef) == QLatin1Char('W')) ? i18nc("For use in longitude coordinate", "West") : i18nc("For use in longitude coordinate", "East"); + return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) - .arg(minutes).arg(QChar(0x2032)) - .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); + .arg(minutes).arg(QChar(0x2032)) + .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } + case MetadataInfo::LongitudeNumber: { int degrees, minutes; double seconds; char directionRef; convertToUserPresentableNumbers(false, value.toDouble(), °rees, &minutes, &seconds, &directionRef); QString direction = (QLatin1Char(directionRef) == QLatin1Char('W')) ? i18nc("For use in longitude coordinate", "West") : i18nc("For use in longitude coordinate", "East"); + return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) - .arg(minutes).arg(QChar(0x2032)) - .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); + .arg(minutes).arg(QChar(0x2032)) + .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } + case MetadataInfo::Latitude: { int degrees, minutes; double seconds; char directionRef; if (!convertToUserPresentableNumbers(value.toString(), °rees, &minutes, &seconds, &directionRef)) { return QString(); } QString direction = (QLatin1Char(directionRef) == QLatin1Char('N')) ? i18nc("For use in latitude coordinate", "North") : i18nc("For use in latitude coordinate", "South"); + return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) - .arg(minutes).arg(QChar(0x2032)) - .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); + .arg(minutes).arg(QChar(0x2032)) + .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } + case MetadataInfo::LatitudeNumber: { int degrees, minutes; double seconds; char directionRef; convertToUserPresentableNumbers(false, value.toDouble(), °rees, &minutes, &seconds, &directionRef); QString direction = (QLatin1Char(directionRef) == QLatin1Char('N')) ? i18nc("For use in latitude coordinate", "North") : i18nc("For use in latitude coordinate", "South"); + return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) - .arg(minutes).arg(QChar(0x2032)) - .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); + .arg(minutes).arg(QChar(0x2032)) + .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } case MetadataInfo::Altitude: { QString meters = QString::fromLatin1("%L1").arg(value.toDouble(), 0, 'f', 2); + // xgettext: no-c-format + return i18nc("Height in meters", "%1m", meters); } case MetadataInfo::PositionOrientation: case MetadataInfo::PositionTilt: case MetadataInfo::PositionRoll: case MetadataInfo::PositionAccuracy: //TODO return value.toString(); case MetadataInfo::PositionDescription: return value.toString(); - // Lang Alt + // Lang Alt case MetadataInfo::IptcCoreCopyrightNotice: case MetadataInfo::IptcCoreRightsUsageTerms: case MetadataInfo::Description: case MetadataInfo::Title: { QMap map = value.toMap(); // the most common cases - if (map.isEmpty()) + + if (map.isEmpty()) { return QString(); } else if (map.size() == 1) { return map.begin().value().toString(); } // Try "en-us" + QString spec = QLocale().name().toLower().replace(QLatin1Char('_'), QLatin1Char('-')); if (map.contains(spec)) { return map[spec].toString(); } // Try "en-" + QStringList keys = map.keys(); QString spec2 = QLocale().name().toLower(); QRegExp exp(spec2.left(spec2.indexOf(QLatin1Char('_'))) + QLatin1Char('-')); QStringList matches = keys.filter(exp); if (!matches.isEmpty()) { return map[matches.first()].toString(); } // return default + if (map.contains(QLatin1String("x-default"))) { return map[QLatin1String("x-default")].toString(); } // return first entry + return map.begin().value().toString(); } // List case MetadataInfo::IptcCoreCreator: case MetadataInfo::IptcCoreScene: case MetadataInfo::IptcCoreSubjectCode: return value.toStringList().join(QLatin1Char(' ')); - // Text + // Text case MetadataInfo::Comment: case MetadataInfo::CommentJfif: case MetadataInfo::CommentExif: case MetadataInfo::CommentIptc: case MetadataInfo::Headline: case MetadataInfo::DescriptionWriter: case MetadataInfo::IptcCoreProvider: case MetadataInfo::IptcCoreSource: case MetadataInfo::IptcCoreCreatorJobTitle: case MetadataInfo::IptcCoreInstructions: case MetadataInfo::IptcCoreCountryCode: case MetadataInfo::IptcCoreCountry: case MetadataInfo::IptcCoreCity: case MetadataInfo::IptcCoreLocation: case MetadataInfo::IptcCoreProvinceState: case MetadataInfo::IptcCoreIntellectualGenre: case MetadataInfo::IptcCoreJobID: return value.toString(); default: break; } return QString(); } QStringList DMetadata::valuesToString(const QVariantList& values, const MetadataFields& fields) { int size = values.size(); Q_ASSERT(size == values.size()); QStringList list; for (int i = 0 ; i < size ; ++i) { list << valueToString(values.at(i), fields.at(i)); } return list; } QMap DMetadata::possibleValuesForEnumField(MetadataInfo::Field field) { QMap map; int min, max; switch (field) { case MetadataInfo::Orientation: /// Int, enum from libMetaEngine min = ORIENTATION_UNSPECIFIED; max = ORIENTATION_ROT_270; break; + case MetadataInfo::ExposureProgram: /// Int, enum from Exif min = 0; max = 8; break; + case MetadataInfo::ExposureMode: /// Int, enum from Exif min = 0; max = 2; break; + case MetadataInfo::WhiteBalance: /// Int, enum from Exif min = 0; max = 1; break; + case MetadataInfo::MeteringMode: /// Int, enum from Exif min = 0; max = 6; map[255] = valueToString(255, field); break; + case MetadataInfo::SubjectDistanceCategory: /// int, enum from Exif min = 0; max = 3; break; + case MetadataInfo::FlashMode: /// Int, bit mask from Exif // This one is a bit special. // We return a bit mask for binary AND searching. map[0x1] = i18n("Flash has been fired"); map[0x40] = i18n("Flash with red-eye reduction mode"); //more: TODO? return map; + default: qCWarning(DIGIKAM_METAENGINE_LOG) << "Unsupported field " << field << " in DMetadata::possibleValuesForEnumField"; return map; } for (int i = min ; i <= max ; ++i) { map[i] = valueToString(i, field); } return map; } bool DMetadata::hasValidField(const QVariantList& list) const { for (QVariantList::const_iterator it = list.constBegin() ; it != list.constEnd() ; ++it) { if (!(*it).isNull()) { return true; } } return false; } QVariant DMetadata::toStringListVariant(const QStringList& list) const { if (list.isEmpty()) { return QVariant(QVariant::StringList); } return list; } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_history.cpp b/core/libs/metadataengine/dmetadata/dmetadata_history.cpp index ca69a38cef..5495713132 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_history.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_history.cpp @@ -1,138 +1,140 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - history helpers. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { bool DMetadata::setItemHistory(QString& imageHistoryXml) const { if (supportXmp()) { if (!setXmpTagString("Xmp.digiKam.ImageHistory", imageHistoryXml)) { return false; } else { return true; } } return false; } QString DMetadata::getItemHistory() const { if (hasXmp()) { QString value = getXmpTagString("Xmp.digiKam.ImageHistory", false); //qCDebug(DIGIKAM_METAENGINE_LOG) << "Loading image history " << value; + return value; } return QString(); } bool DMetadata::hasItemHistoryTag() const { if (hasXmp()) { if (QString(getXmpTagString("Xmp.digiKam.ImageHistory", false)).length() > 0) { return true; } else { return false; } } return false; } QString DMetadata::getItemUniqueId() const { QString exifUid; if (hasXmp()) { QString uuid = getXmpTagString("Xmp.digiKam.ImageUniqueID"); if (!uuid.isEmpty()) { return uuid; } exifUid = getXmpTagString("Xmp.exif.ImageUniqueId"); } if (exifUid.isEmpty()) { exifUid = getExifTagString("Exif.Photo.ImageUniqueID"); } // same makers may choose to use a "click counter" to generate the id, // which is then weak and not a universally unique id // The Exif ImageUniqueID is 128bit, or 32 hex digits. // If the first 20 are zero, it's probably a counter, // the left 12 are sufficient for more then 10^14 clicks. + if (!exifUid.isEmpty() && !exifUid.startsWith(QLatin1String("00000000000000000000")) && !getExifTagString("Exif.Image.Make").contains(QLatin1String("SAMSUNG"), Qt::CaseInsensitive)) { return exifUid; } // Exif.Image.ImageID can also be a pathname, so it's not sufficiently unique QString dngUid = getExifTagString("Exif.Image.RawDataUniqueID"); if (!dngUid.isEmpty()) { return dngUid; } return QString(); } bool DMetadata::setItemUniqueId(const QString& uuid) const { if (supportXmp()) { return setXmpTagString("Xmp.digiKam.ImageUniqueID", uuid); } return false; } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_iptc.cpp b/core/libs/metadataengine/dmetadata/dmetadata_iptc.cpp index e1a4c7abf5..e6a781a92f 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_iptc.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_iptc.cpp @@ -1,418 +1,421 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - Iptc helpers. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Qt includes #include // KDE includes #include // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { IptcCoreContactInfo DMetadata::getCreatorContactInfo() const { MetadataFields fields; fields << MetadataInfo::IptcCoreContactInfoCity << MetadataInfo::IptcCoreContactInfoCountry << MetadataInfo::IptcCoreContactInfoAddress << MetadataInfo::IptcCoreContactInfoPostalCode << MetadataInfo::IptcCoreContactInfoProvinceState << MetadataInfo::IptcCoreContactInfoEmail << MetadataInfo::IptcCoreContactInfoPhone << MetadataInfo::IptcCoreContactInfoWebUrl; QVariantList metadataInfos = getMetadataFields(fields); IptcCoreContactInfo info; if (metadataInfos.size() == 8) { info.city = metadataInfos.at(0).toString(); info.country = metadataInfos.at(1).toString(); info.address = metadataInfos.at(2).toString(); info.postalCode = metadataInfos.at(3).toString(); info.provinceState = metadataInfos.at(4).toString(); info.email = metadataInfos.at(5).toString(); info.phone = metadataInfos.at(6).toString(); info.webUrl = metadataInfos.at(7).toString(); } return info; } bool DMetadata::setCreatorContactInfo(const IptcCoreContactInfo& info) const { if (!supportXmp()) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity", info.city)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry", info.country)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr", info.address)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode", info.postalCode)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion", info.provinceState)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork", info.email)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork", info.phone)) { return false; } if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork", info.webUrl)) { return false; } return true; } IptcCoreLocationInfo DMetadata::getIptcCoreLocation() const { MetadataFields fields; fields << MetadataInfo::IptcCoreCountry << MetadataInfo::IptcCoreCountryCode << MetadataInfo::IptcCoreCity << MetadataInfo::IptcCoreLocation << MetadataInfo::IptcCoreProvinceState; QVariantList metadataInfos = getMetadataFields(fields); IptcCoreLocationInfo location; if (fields.size() == 5) { location.country = metadataInfos.at(0).toString(); location.countryCode = metadataInfos.at(1).toString(); location.city = metadataInfos.at(2).toString(); location.location = metadataInfos.at(3).toString(); location.provinceState = metadataInfos.at(4).toString(); } return location; } bool DMetadata::setIptcCoreLocation(const IptcCoreLocationInfo& location) const { if (supportXmp()) { if (!setXmpTagString("Xmp.photoshop.Country", location.country)) { return false; } if (!setXmpTagString("Xmp.iptc.CountryCode", location.countryCode)) { return false; } if (!setXmpTagString("Xmp.photoshop.City", location.city)) { return false; } if (!setXmpTagString("Xmp.iptc.Location", location.location)) { return false; } if (!setXmpTagString("Xmp.photoshop.State", location.provinceState)) { return false; } } if (!setIptcTag(location.country, 64, "Country", "Iptc.Application2.CountryName")) { return false; } if (!setIptcTag(location.countryCode, 3, "Country Code", "Iptc.Application2.CountryCode")) { return false; } if (!setIptcTag(location.city, 32, "City", "Iptc.Application2.City")) { return false; } if (!setIptcTag(location.location, 32, "SubLocation", "Iptc.Application2.SubLocation")) { return false; } if (!setIptcTag(location.provinceState, 32, "Province/State", "Iptc.Application2.ProvinceState")) { return false; } return true; } QStringList DMetadata::getIptcCoreSubjects() const { QStringList list = getXmpSubjects(); if (!list.isEmpty()) { return list; } return getIptcSubjects(); } bool DMetadata::setIptcTag(const QString& text, int maxLength, const char* const debugLabel, const char* const tagKey) const { QString truncatedText = text; truncatedText.truncate(maxLength); qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> " << debugLabel << ": " << truncatedText; return setIptcTagString(tagKey, truncatedText); // returns false if failed } QVariant DMetadata::fromIptcEmulateList(const char* const iptcTagName) const { return toStringListVariant(getIptcTagsStringList(iptcTagName)); } QVariant DMetadata::fromIptcEmulateLangAlt(const char* const iptcTagName) const { QString str = getIptcTagString(iptcTagName); if (str.isNull()) { return QVariant(QVariant::Map); } QMap map; map[QLatin1String("x-default")] = str; return map; } bool DMetadata::removeIptcTags(const QStringList& tagFilters) { MetaDataMap m = getIptcTagsDataList(tagFilters); if (m.isEmpty()) + { return false; + } for (MetaDataMap::iterator it = m.begin() ; it != m.end() ; ++it) { removeIptcTag(it.key().toLatin1().constData()); } return true; } DMetadata::CountryCodeMap DMetadata::countryCodeMap() { CountryCodeMap countryCodeMap; + // All ISO 639 language code based on 2 characters // http://xml.coverpages.org/iso639a.html countryCodeMap.insert( QLatin1String("AA"), i18n("Afar")); countryCodeMap.insert( QLatin1String("AB"), i18n("Abkhazian")); countryCodeMap.insert( QLatin1String("AF"), i18n("Afrikaans")); countryCodeMap.insert( QLatin1String("AM"), i18n("Amharic")); countryCodeMap.insert( QLatin1String("AR"), i18n("Arabic")); countryCodeMap.insert( QLatin1String("AS"), i18n("Assamese")); countryCodeMap.insert( QLatin1String("AY"), i18n("Aymara")); countryCodeMap.insert( QLatin1String("AZ"), i18n("Azerbaijani")); countryCodeMap.insert( QLatin1String("BA"), i18n("Bashkir")); countryCodeMap.insert( QLatin1String("BE"), i18n("Byelorussian")); countryCodeMap.insert( QLatin1String("BG"), i18n("Bulgarian")); countryCodeMap.insert( QLatin1String("BH"), i18n("Bihari")); countryCodeMap.insert( QLatin1String("BI"), i18n("Bislama")); countryCodeMap.insert( QLatin1String("BN"), i18n("Bengali;Bangla") ); countryCodeMap.insert( QLatin1String("BO"), i18n("Tibetan")); countryCodeMap.insert( QLatin1String("BR"), i18n("Breton")); countryCodeMap.insert( QLatin1String("CA"), i18n("Catalan")); countryCodeMap.insert( QLatin1String("CO"), i18n("Corsican")); countryCodeMap.insert( QLatin1String("CS"), i18n("Czech")); countryCodeMap.insert( QLatin1String("CY"), i18n("Welsh")); countryCodeMap.insert( QLatin1String("DA"), i18n("Danish")); countryCodeMap.insert( QLatin1String("DE"), i18n("German")); countryCodeMap.insert( QLatin1String("DZ"), i18n("Bhutani")); countryCodeMap.insert( QLatin1String("EL"), i18n("Greek")); countryCodeMap.insert( QLatin1String("EN"), i18n("English")); countryCodeMap.insert( QLatin1String("EO"), i18n("Esperanto")); countryCodeMap.insert( QLatin1String("ES"), i18n("Spanish")); countryCodeMap.insert( QLatin1String("ET"), i18n("Estonian")); countryCodeMap.insert( QLatin1String("EU"), i18n("Basque")); countryCodeMap.insert( QLatin1String("FA"), i18n("Persian(farsi)") ); countryCodeMap.insert( QLatin1String("FI"), i18n("Finnish")); countryCodeMap.insert( QLatin1String("FJ"), i18n("Fiji")); countryCodeMap.insert( QLatin1String("FO"), i18n("Faroese") ); countryCodeMap.insert( QLatin1String("FR"), i18n("French") ); countryCodeMap.insert( QLatin1String("FY"), i18n("Frisian") ); countryCodeMap.insert( QLatin1String("GA"), i18n("Irish") ); countryCodeMap.insert( QLatin1String("GD"), i18n("Scotsgaelic") ); countryCodeMap.insert( QLatin1String("GL"), i18n("Galician") ); countryCodeMap.insert( QLatin1String("GN"), i18n("Guarani") ); countryCodeMap.insert( QLatin1String("GU"), i18n("Gujarati") ); countryCodeMap.insert( QLatin1String("HA"), i18n("Hausa") ); countryCodeMap.insert( QLatin1String("HE"), i18n("Hebrew") ); countryCodeMap.insert( QLatin1String("HI"), i18n("Hindi") ); countryCodeMap.insert( QLatin1String("HR"), i18n("Croatian") ); countryCodeMap.insert( QLatin1String("HU"), i18n("Hungarian") ); countryCodeMap.insert( QLatin1String("HY"), i18n("Armenian") ); countryCodeMap.insert( QLatin1String("IA"), i18n("Interlingua") ); countryCodeMap.insert( QLatin1String("IE"), i18n("Interlingue") ); countryCodeMap.insert( QLatin1String("IK"), i18n("Inupiak") ); countryCodeMap.insert( QLatin1String("ID"), i18n("Indonesian") ); countryCodeMap.insert( QLatin1String("IS"), i18n("Icelandic") ); countryCodeMap.insert( QLatin1String("IT"), i18n("Italian") ); countryCodeMap.insert( QLatin1String("IU"), i18n("Inuktitut") ); countryCodeMap.insert( QLatin1String("JA"), i18n("Japanese") ); countryCodeMap.insert( QLatin1String("JV"), i18n("Javanese") ); countryCodeMap.insert( QLatin1String("KA"), i18n("Georgian") ); countryCodeMap.insert( QLatin1String("KK"), i18n("Kazakh") ); countryCodeMap.insert( QLatin1String("KL"), i18n("Greenlandic") ); countryCodeMap.insert( QLatin1String("KM"), i18n("Cambodian") ); countryCodeMap.insert( QLatin1String("KN"), i18n("Kannada") ); countryCodeMap.insert( QLatin1String("KO"), i18n("Korean") ); countryCodeMap.insert( QLatin1String("KS"), i18n("Kashmiri") ); countryCodeMap.insert( QLatin1String("KU"), i18n("Kurdish") ); countryCodeMap.insert( QLatin1String("KY"), i18n("Kirghiz") ); countryCodeMap.insert( QLatin1String("LA"), i18n("Latin") ); countryCodeMap.insert( QLatin1String("LN"), i18n("Lingala") ); countryCodeMap.insert( QLatin1String("LO"), i18n("Laothian") ); countryCodeMap.insert( QLatin1String("LT"), i18n("Lithuanian") ); countryCodeMap.insert( QLatin1String("LV"), i18n("Latvian;Lettish") ); countryCodeMap.insert( QLatin1String("MG"), i18n("Malagasy") ); countryCodeMap.insert( QLatin1String("MI"), i18n("Maori") ); countryCodeMap.insert( QLatin1String("MK"), i18n("Macedonian") ); countryCodeMap.insert( QLatin1String("ML"), i18n("Malayalam") ); countryCodeMap.insert( QLatin1String("MN"), i18n("Mongolian") ); countryCodeMap.insert( QLatin1String("MO"), i18n("Moldavian") ); countryCodeMap.insert( QLatin1String("MR"), i18n("Marathi") ); countryCodeMap.insert( QLatin1String("MS"), i18n("Malay") ); countryCodeMap.insert( QLatin1String("MT"), i18n("Maltese") ); countryCodeMap.insert( QLatin1String("MY"), i18n("Burmese") ); countryCodeMap.insert( QLatin1String("NA"), i18n("Nauru") ); countryCodeMap.insert( QLatin1String("NE"), i18n("Nepali") ); countryCodeMap.insert( QLatin1String("NL"), i18n("Dutch") ); countryCodeMap.insert( QLatin1String("NO"), i18n("Norwegian") ); countryCodeMap.insert( QLatin1String("OC"), i18n("Occitan") ); countryCodeMap.insert( QLatin1String("OM"), i18n("Afan(oromo)") ); countryCodeMap.insert( QLatin1String("OR"), i18n("Oriya") ); countryCodeMap.insert( QLatin1String("PA"), i18n("Punjabi") ); countryCodeMap.insert( QLatin1String("PL"), i18n("Polish") ); countryCodeMap.insert( QLatin1String("PS"), i18n("Pashto;Pushto") ); countryCodeMap.insert( QLatin1String("PT"), i18n("Portuguese") ); countryCodeMap.insert( QLatin1String("QU"), i18n("Quechua") ); countryCodeMap.insert( QLatin1String("RM"), i18n("Rhaeto-romance") ); countryCodeMap.insert( QLatin1String("RN"), i18n("Kurundi") ); countryCodeMap.insert( QLatin1String("RO"), i18n("Romanian") ); countryCodeMap.insert( QLatin1String("RU"), i18n("Russian") ); countryCodeMap.insert( QLatin1String("RW"), i18n("Kinyarwanda") ); countryCodeMap.insert( QLatin1String("SA"), i18n("Sanskrit") ); countryCodeMap.insert( QLatin1String("SD"), i18n("Sindhi") ); countryCodeMap.insert( QLatin1String("SG"), i18n("Sangho") ); countryCodeMap.insert( QLatin1String("SH"), i18n("Serbo-croatian") ); countryCodeMap.insert( QLatin1String("SI"), i18n("Singhalese") ); countryCodeMap.insert( QLatin1String("SK"), i18n("Slovak") ); countryCodeMap.insert( QLatin1String("SL"), i18n("Slovenian") ); countryCodeMap.insert( QLatin1String("SM"), i18n("Samoan") ); countryCodeMap.insert( QLatin1String("SN"), i18n("Shona") ); countryCodeMap.insert( QLatin1String("SO"), i18n("Somali") ); countryCodeMap.insert( QLatin1String("SQ"), i18n("Albanian") ); countryCodeMap.insert( QLatin1String("SR"), i18n("Serbian") ); countryCodeMap.insert( QLatin1String("SS"), i18n("Siswati") ); countryCodeMap.insert( QLatin1String("ST"), i18n("Sesotho") ); countryCodeMap.insert( QLatin1String("SU"), i18n("Sundanese") ); countryCodeMap.insert( QLatin1String("SV"), i18n("Swedish") ); countryCodeMap.insert( QLatin1String("SW"), i18n("Swahili") ); countryCodeMap.insert( QLatin1String("TA"), i18n("Tamil") ); countryCodeMap.insert( QLatin1String("TE"), i18n("Telugu") ); countryCodeMap.insert( QLatin1String("TG"), i18n("Tajik") ); countryCodeMap.insert( QLatin1String("TH"), i18n("Thai") ); countryCodeMap.insert( QLatin1String("TI"), i18n("Tigrinya") ); countryCodeMap.insert( QLatin1String("TK"), i18n("Turkmen") ); countryCodeMap.insert( QLatin1String("TL"), i18n("Tagalog") ); countryCodeMap.insert( QLatin1String("TN"), i18n("Setswana") ); countryCodeMap.insert( QLatin1String("TO"), i18n("Tonga") ); countryCodeMap.insert( QLatin1String("TR"), i18n("Turkish") ); countryCodeMap.insert( QLatin1String("TS"), i18n("Tsonga") ); countryCodeMap.insert( QLatin1String("TT"), i18n("Tatar") ); countryCodeMap.insert( QLatin1String("TW"), i18n("Twi") ); countryCodeMap.insert( QLatin1String("UG"), i18n("Uigur") ); countryCodeMap.insert( QLatin1String("UK"), i18n("Ukrainian") ); countryCodeMap.insert( QLatin1String("UR"), i18n("Urdu") ); countryCodeMap.insert( QLatin1String("UZ"), i18n("Uzbek") ); countryCodeMap.insert( QLatin1String("VI"), i18n("Vietnamese") ); countryCodeMap.insert( QLatin1String("VO"), i18n("Volapuk") ); countryCodeMap.insert( QLatin1String("WO"), i18n("Wolof") ); countryCodeMap.insert( QLatin1String("XH"), i18n("Xhosa") ); countryCodeMap.insert( QLatin1String("YI"), i18n("Yiddish") ); countryCodeMap.insert( QLatin1String("YO"), i18n("Yoruba") ); countryCodeMap.insert( QLatin1String("ZA"), i18n("Zhuang") ); countryCodeMap.insert( QLatin1String("ZH"), i18n("Chinese") ); countryCodeMap.insert( QLatin1String("ZU"), i18n("Zulu") ); return countryCodeMap; } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_labels.cpp b/core/libs/metadataengine/dmetadata/dmetadata_labels.cpp index 2788cdf5cb..a7809498cb 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_labels.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_labels.cpp @@ -1,391 +1,412 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - labels helpers. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Qt includes #include // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { int DMetadata::getItemPickLabel() const { if (getFilePath().isEmpty()) { return -1; } if (hasXmp()) { QString value = getXmpTagString("Xmp.digiKam.PickLabel", false); if (!value.isEmpty()) { bool ok = false; long pickId = value.toLong(&ok); - if (ok && pickId >= NoPickLabel && pickId <= AcceptedLabel) + if (ok && (pickId >= NoPickLabel) && (pickId <= AcceptedLabel)) { return pickId; } } } return -1; } int DMetadata::getItemColorLabel() const { if (getFilePath().isEmpty()) { return -1; } if (hasXmp()) { QString value = getXmpTagString("Xmp.digiKam.ColorLabel", false); QString label = getXmpTagString("Xmp.xmp.Label", false); if (value.isEmpty() && label.isEmpty()) { // Nikon NX use this XMP tags to store Color Labels value = getXmpTagString("Xmp.photoshop.Urgency", false); } if (!value.isEmpty()) { bool ok = false; long colorId = value.toLong(&ok); - if (ok && colorId >= NoColorLabel && colorId <= WhiteLabel) + if (ok && (colorId >= NoColorLabel) && (colorId <= WhiteLabel)) { return colorId; } } // LightRoom use this tag to store color name as string. // Values are limited : see bug #358193. - if (label == QLatin1String("Blue")) + if (label == QLatin1String("Blue")) { return BlueLabel; } else if (label == QLatin1String("Green")) { return GreenLabel; } else if (label == QLatin1String("Red")) { return RedLabel; } else if (label == QLatin1String("Yellow")) { return YellowLabel; } else if (label == QLatin1String("Purple")) { return MagentaLabel; } } return -1; } int DMetadata::getItemRating(const DMetadataSettingsContainer& settings) const { if (getFilePath().isEmpty()) { return -1; } long rating = -1; bool xmpSupported = hasXmp(); bool iptcSupported = hasIptc(); bool exivSupported = hasExif(); for (NamespaceEntry entry : settings.getReadMapping(NamespaceEntry::DM_RATING_CONTAINER())) { if (entry.isDisabled) { continue; } const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); QString value; switch (entry.subspace) { case NamespaceEntry::XMP: if (xmpSupported) { value = getXmpTagString(nameSpace, false); } break; case NamespaceEntry::IPTC: if (iptcSupported) { value = QString::fromUtf8(getIptcTagData(nameSpace)); } break; case NamespaceEntry::EXIF: if (exivSupported) { value = getExifTagLong(nameSpace, rating); } break; default: break; } if (!value.isEmpty()) { bool ok = false; rating = value.toLong(&ok); if (!ok) { return -1; } } int index = entry.convertRatio.indexOf(rating); // Exact value was not found,but rating is in range, // so we try to approximate it + if ((index == -1) && (rating > entry.convertRatio.first()) && (rating < entry.convertRatio.last())) { for (int i = 0 ; i < entry.convertRatio.size() ; ++i) { if (rating > entry.convertRatio.at(i)) { index = i; } } } if (index != -1) { return index; } } return -1; } bool DMetadata::setItemPickLabel(int pickId) const { - if (pickId < NoPickLabel || pickId > AcceptedLabel) + if ((pickId < NoPickLabel) || (pickId > AcceptedLabel)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Pick Label value to write is out of range!"; return false; } //qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Pick Label: " << pickId; if (supportXmp()) { if (!setXmpTagString("Xmp.digiKam.PickLabel", QString::number(pickId))) { return false; } } return true; } bool DMetadata::setItemColorLabel(int colorId) const { - if (colorId < NoColorLabel || colorId > WhiteLabel) + if ((colorId < NoColorLabel) || (colorId > WhiteLabel)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Color Label value to write is out of range!"; + return false; } //qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Color Label: " << colorId; if (supportXmp()) { if (!setXmpTagString("Xmp.digiKam.ColorLabel", QString::number(colorId))) { return false; } // Nikon NX use this XMP tags to store Color Labels + if (!setXmpTagString("Xmp.photoshop.Urgency", QString::number(colorId))) { return false; } // LightRoom use this XMP tags to store Color Labels name // Values are limited : see bug #358193. QString LRLabel; switch(colorId) { case BlueLabel: LRLabel = QLatin1String("Blue"); break; + case GreenLabel: LRLabel = QLatin1String("Green"); break; + case RedLabel: LRLabel = QLatin1String("Red"); break; + case YellowLabel: LRLabel = QLatin1String("Yellow"); break; + case MagentaLabel: LRLabel = QLatin1String("Purple"); break; } if (!LRLabel.isEmpty()) { if (!setXmpTagString("Xmp.xmp.Label", LRLabel)) { return false; } } else { removeXmpTag("Xmp.xmp.Label"); } } return true; } bool DMetadata::setItemRating(int rating, const DMetadataSettingsContainer& settings) const { // NOTE : with digiKam 0.9.x, we have used IPTC Urgency to store Rating. // Now this way is obsolete, and we use standard XMP rating tag instead. - if (rating < RatingMin || rating > RatingMax) + if ((rating < RatingMin) || (rating > RatingMax)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Rating value to write is out of range!"; return false; } //qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Rating:" << rating; QList toWrite = settings.getReadMapping(NamespaceEntry::DM_RATING_CONTAINER()); if (!settings.unifyReadWrite()) { toWrite = settings.getWriteMapping(NamespaceEntry::DM_RATING_CONTAINER()); } for (NamespaceEntry entry : toWrite) { if (entry.isDisabled) + { continue; + } const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); switch(entry.subspace) { case NamespaceEntry::XMP: + if (!setXmpTagString(nameSpace, QString::number(entry.convertRatio.at(rating)))) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting rating failed" << nameSpace; return false; } + break; + case NamespaceEntry::EXIF: + if (QLatin1String(nameSpace) == QLatin1String("Exif.Image.0x4749")) { // Wrapper around rating percents managed by Windows Vista. + int ratePercents = 0; switch (rating) { case 0: ratePercents = 0; break; + case 1: ratePercents = 1; break; + case 2: ratePercents = 25; break; + case 3: ratePercents = 50; break; + case 4: ratePercents = 75; break; + case 5: ratePercents = 99; break; } if (!setExifTagLong(nameSpace, ratePercents)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting rating failed" << nameSpace; return false; } } else { if (!setExifTagLong(nameSpace, rating)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting rating failed" << nameSpace; return false; } } + break; + case NamespaceEntry::IPTC: // IPTC rating deprecated default: break; } } return true; } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_photo.cpp b/core/libs/metadataengine/dmetadata/dmetadata_photo.cpp index 1503d7ebb6..fbfcd03912 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_photo.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_photo.cpp @@ -1,437 +1,442 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - photo info helpers. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // C++ includes #include // Qt includes #include // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { PhotoInfoContainer DMetadata::getPhotographInformation() const { PhotoInfoContainer photoInfo; if (hasExif() || hasXmp()) { photoInfo.dateTime = getItemDateTime(); // ----------------------------------------------------------------------------------- photoInfo.make = getExifTagString("Exif.Image.Make"); if (photoInfo.make.isEmpty()) { photoInfo.make = getXmpTagString("Xmp.tiff.Make"); } // ----------------------------------------------------------------------------------- photoInfo.model = getExifTagString("Exif.Image.Model"); if (photoInfo.model.isEmpty()) { photoInfo.model = getXmpTagString("Xmp.tiff.Model"); } // ----------------------------------------------------------------------------------- photoInfo.lens = getLensDescription(); // ----------------------------------------------------------------------------------- photoInfo.aperture = getExifTagString("Exif.Photo.FNumber"); if (photoInfo.aperture.isEmpty()) { photoInfo.aperture = getExifTagString("Exif.Photo.ApertureValue"); } if (photoInfo.aperture.isEmpty()) { photoInfo.aperture = getXmpTagString("Xmp.exif.FNumber"); } if (photoInfo.aperture.isEmpty()) { photoInfo.aperture = getXmpTagString("Xmp.exif.ApertureValue"); } // ----------------------------------------------------------------------------------- photoInfo.exposureTime = getExifTagString("Exif.Photo.ExposureTime"); if (photoInfo.exposureTime.isEmpty()) { photoInfo.exposureTime = getExifTagString("Exif.Photo.ShutterSpeedValue"); } if (photoInfo.exposureTime.isEmpty()) { photoInfo.exposureTime = getXmpTagString("Xmp.exif.ExposureTime"); } if (photoInfo.exposureTime.isEmpty()) { photoInfo.exposureTime = getXmpTagString("Xmp.exif.ShutterSpeedValue"); } // ----------------------------------------------------------------------------------- photoInfo.exposureMode = getExifTagString("Exif.Photo.ExposureMode"); if (photoInfo.exposureMode.isEmpty()) { photoInfo.exposureMode = getXmpTagString("Xmp.exif.ExposureMode"); } if (photoInfo.exposureMode.isEmpty()) { photoInfo.exposureMode = getExifTagString("Exif.CanonCs.MeteringMode"); } // ----------------------------------------------------------------------------------- photoInfo.exposureProgram = getExifTagString("Exif.Photo.ExposureProgram"); if (photoInfo.exposureProgram.isEmpty()) { photoInfo.exposureProgram = getXmpTagString("Xmp.exif.ExposureProgram"); } if (photoInfo.exposureProgram.isEmpty()) { photoInfo.exposureProgram = getExifTagString("Exif.CanonCs.ExposureProgram"); } // ----------------------------------------------------------------------------------- photoInfo.focalLength = getExifTagString("Exif.Photo.FocalLength"); if (photoInfo.focalLength.isEmpty()) { photoInfo.focalLength = getXmpTagString("Xmp.exif.FocalLength"); } if (photoInfo.focalLength.isEmpty()) { photoInfo.focalLength = getExifTagString("Exif.Canon.FocalLength"); } // ----------------------------------------------------------------------------------- photoInfo.focalLength35mm = getExifTagString("Exif.Photo.FocalLengthIn35mmFilm"); if (photoInfo.focalLength35mm.isEmpty()) { photoInfo.focalLength35mm = getXmpTagString("Xmp.exif.FocalLengthIn35mmFilm"); } // ----------------------------------------------------------------------------------- QStringList ISOSpeedTags; ISOSpeedTags << QLatin1String("Exif.Photo.ISOSpeedRatings"); ISOSpeedTags << QLatin1String("Exif.Photo.ExposureIndex"); ISOSpeedTags << QLatin1String("Exif.Image.ISOSpeedRatings"); ISOSpeedTags << QLatin1String("Xmp.exif.ISOSpeedRatings"); ISOSpeedTags << QLatin1String("Xmp.exif.ExposureIndex"); ISOSpeedTags << QLatin1String("Exif.CanonSi.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.CanonCs.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Nikon1.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Nikon2.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Nikon3.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.NikonIi.ISO"); ISOSpeedTags << QLatin1String("Exif.NikonIi.ISO2"); ISOSpeedTags << QLatin1String("Exif.MinoltaCsNew.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.MinoltaCsOld.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.MinoltaCs5D.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.MinoltaCs7D.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Sony1Cs.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony2Cs.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony1Cs2.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony2Cs2.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Sony1MltCsA100.ISOSetting"); ISOSpeedTags << QLatin1String("Exif.Pentax.ISO"); ISOSpeedTags << QLatin1String("Exif.Olympus.ISOSpeed"); ISOSpeedTags << QLatin1String("Exif.Samsung2.ISO"); photoInfo.sensitivity = getExifTagStringFromTagsList(ISOSpeedTags); // ----------------------------------------------------------------------------------- photoInfo.flash = getExifTagString("Exif.Photo.Flash"); if (photoInfo.flash.isEmpty()) { photoInfo.flash = getXmpTagString("Xmp.exif.Flash"); } if (photoInfo.flash.isEmpty()) { photoInfo.flash = getExifTagString("Exif.CanonCs.FlashActivity"); } // ----------------------------------------------------------------------------------- photoInfo.whiteBalance = getExifTagString("Exif.Photo.WhiteBalance"); if (photoInfo.whiteBalance.isEmpty()) { photoInfo.whiteBalance = getXmpTagString("Xmp.exif.WhiteBalance"); } // ----------------------------------------------------------------------------------- double l, L, a; photoInfo.hasCoordinates = getGPSInfo(a, l, L); } return photoInfo; } QString DMetadata::getLensDescription() const { QString lens; QStringList lensExifTags; // In first, try to get Lens information from makernotes. lensExifTags.append(QLatin1String("Exif.CanonCs.LensType")); // Canon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.CanonCs.Lens")); // Canon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Canon.0x0095")); // Alternative Canon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.NikonLd1.LensIDNumber")); // Nikon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.NikonLd2.LensIDNumber")); // Nikon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.NikonLd3.LensIDNumber")); // Nikon Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Minolta.LensID")); // Minolta Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Photo.LensModel")); // Sony Cameras Makernote (and others?). lensExifTags.append(QLatin1String("Exif.Sony1.LensID")); // Sony Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Sony2.LensID")); // Sony Cameras Makernote. lensExifTags.append(QLatin1String("Exif.SonyMinolta.LensID")); // Sony Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Pentax.LensType")); // Pentax Cameras Makernote. lensExifTags.append(QLatin1String("Exif.PentaxDng.LensType")); // Pentax Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Panasonic.0x0051")); // Panasonic Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Panasonic.0x0310")); // Panasonic Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Sigma.LensRange")); // Sigma Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Samsung2.LensType")); // Samsung Cameras Makernote. lensExifTags.append(QLatin1String("Exif.Photo.0xFDEA")); // Non-standard Exif tag set by Camera Raw. lensExifTags.append(QLatin1String("Exif.OlympusEq.LensModel")); // Olympus Cameras Makernote. // Olympus Cameras Makernote. FIXME is this necessary? exiv2 returns complete name, // which doesn't match with lensfun information, see bug #311295 //lensExifTags.append("Exif.OlympusEq.LensType"); // TODO : add Fuji camera Makernotes. // ------------------------------------------------------------------- // Try to get Lens Data information from Exif. for (QStringList::const_iterator it = lensExifTags.constBegin() ; it != lensExifTags.constEnd() ; ++it) { lens = getExifTagString((*it).toLatin1().constData()); // To prevent undecoded tag values from Exiv2 as "(65535)" // or the value "----" from Exif.Photo.LensModel + if (!lens.isEmpty() && - lens != QLatin1String("----") && + (lens != QLatin1String("----")) && !(lens.startsWith(QLatin1Char('(')) && lens.endsWith(QLatin1Char(')')) ) ) { return lens; } } // ------------------------------------------------------------------- // Try to get Lens Data information from XMP. // XMP aux tags. + lens = getXmpTagString("Xmp.aux.Lens"); if (lens.isEmpty()) { // XMP M$ tags (Lens Maker + Lens Model). + lens = getXmpTagString("Xmp.MicrosoftPhoto.LensManufacturer"); if (!lens.isEmpty()) { lens.append(QLatin1Char(' ')); } lens.append(getXmpTagString("Xmp.MicrosoftPhoto.LensModel")); } return lens; } double DMetadata::apexApertureToFNumber(double aperture) { // convert from APEX. See Exif spec, Annex C. - if (aperture == 0.0) + + if (aperture == 0.0) { return 1; } else if (aperture == 1.0) { return 1.4; } else if (aperture == 2.0) { return 2; } else if (aperture == 3.0) { return 2.8; } else if (aperture == 4.0) { return 4; } else if (aperture == 5.0) { return 5.6; } else if (aperture == 6.0) { return 8; } else if (aperture == 7.0) { return 11; } else if (aperture == 8.0) { return 16; } else if (aperture == 9.0) { return 22; } else if (aperture == 10.0) { return 32; } return exp(log(2) * aperture / 2.0); } double DMetadata::apexShutterSpeedToExposureTime(double shutterSpeed) { // convert from APEX. See Exif spec, Annex C. - if (shutterSpeed == -5.0) + + if (shutterSpeed == -5.0) { return 30; } else if (shutterSpeed == -4.0) { return 15; } else if (shutterSpeed == -3.0) { return 8; } else if (shutterSpeed == -2.0) { return 4; } else if (shutterSpeed == -1.0) { return 2; } else if (shutterSpeed == 0.0) { return 1; } else if (shutterSpeed == 1.0) { return 0.5; } else if (shutterSpeed == 2.0) { return 0.25; } else if (shutterSpeed == 3.0) { return 0.125; } else if (shutterSpeed == 4.0) { return 1.0 / 15.0; } else if (shutterSpeed == 5.0) { return 1.0 / 30.0; } else if (shutterSpeed == 6.0) { return 1.0 / 60.0; } else if (shutterSpeed == 7.0) { return 0.008; // 1/125 } else if (shutterSpeed == 8.0) { return 0.004; // 1/250 } else if (shutterSpeed == 9.0) { return 0.002; // 1/500 } else if (shutterSpeed == 10.0) { return 0.001; // 1/1000 } else if (shutterSpeed == 11.0) { return 0.0005; // 1/2000 } // additions by me else if (shutterSpeed == 12.0) { return 0.00025; // 1/4000 } else if (shutterSpeed == 13.0) { return 0.000125; // 1/8000 } return exp( - log(2) * shutterSpeed); } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_tags.cpp b/core/libs/metadataengine/dmetadata/dmetadata_tags.cpp index ec9a445155..3c7af21985 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_tags.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_tags.cpp @@ -1,375 +1,393 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - tags helpers. * * Copyright (C) 2006-2020 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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 "dmetadata.h" // Qt includes #include // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { bool DMetadata::getItemTagsPath(QStringList& tagsPath, const DMetadataSettingsContainer& settings) const { for (NamespaceEntry entry : settings.getReadMapping(NamespaceEntry::DM_TAG_CONTAINER())) { if (entry.isDisabled) + { continue; + } int index = 0; QString currentNamespace = entry.namespaceName; NamespaceEntry::SpecialOptions currentOpts = entry.specialOpts; // Some namespaces have altenative paths, we must search them both switch (entry.subspace) { case NamespaceEntry::XMP: while (index < 2) { const std::string myStr = currentNamespace.toStdString(); const char* nameSpace = myStr.data(); switch(currentOpts) { case NamespaceEntry::TAG_XMPBAG: tagsPath = getXmpTagStringBag(nameSpace, false); break; + case NamespaceEntry::TAG_XMPSEQ: tagsPath = getXmpTagStringSeq(nameSpace, false); break; + case NamespaceEntry::TAG_ACDSEE: getACDSeeTagsPath(tagsPath); break; + // not used here, to suppress warnings case NamespaceEntry::COMMENT_XMP: case NamespaceEntry::COMMENT_ALTLANG: case NamespaceEntry::COMMENT_ATLLANGLIST: case NamespaceEntry::NO_OPTS: default: break; } - if (!tagsPath.isEmpty()) + if (!tagsPath.isEmpty()) { if (entry.separator != QLatin1String("/")) { tagsPath = tagsPath.replaceInStrings(entry.separator, QLatin1String("/")); } return true; } else if (!entry.alternativeName.isEmpty()) { currentNamespace = entry.alternativeName; currentOpts = entry.secondNameOpts; } else { break; // no alternative namespace, go to next one } index++; } break; case NamespaceEntry::IPTC: + // Try to get Tags Path list from IPTC keywords. // digiKam 0.9.x has used IPTC keywords to store Tags Path list. // This way is obsolete now since digiKam support XMP because IPTC // do not support UTF-8 and have strings size limitation. But we will // let the capability to import it for interworking issues. + tagsPath = getIptcKeywords(); if (!tagsPath.isEmpty()) { // Work around to Imach tags path list hosted in IPTC with '.' as separator. + QStringList ntp = tagsPath.replaceInStrings(entry.separator, QLatin1String("/")); if (ntp != tagsPath) { tagsPath = ntp; + //qCDebug(DIGIKAM_METAENGINE_LOG) << "Tags Path imported from Imach: " << tagsPath; } return true; } break; case NamespaceEntry::EXIF: { // Try to get Tags Path list from Exif Windows keywords. + QString keyWords = getExifTagString("Exif.Image.XPKeywords", false); if (!keyWords.isEmpty()) { tagsPath = keyWords.split(entry.separator); if (!tagsPath.isEmpty()) { return true; } } break; } default: break; } } return false; } bool DMetadata::setItemTagsPath(const QStringList& tagsPath, const DMetadataSettingsContainer& settings) const { // NOTE : with digiKam 0.9.x, we have used IPTC Keywords for that. // Now this way is obsolete, and we use XMP instead. // Set the new Tags path list. This is set, not add-to like setXmpKeywords. // Unlike the other keyword fields, we do not need to merge existing entries. + QList toWrite = settings.getReadMapping(NamespaceEntry::DM_TAG_CONTAINER()); if (!settings.unifyReadWrite()) + { toWrite = settings.getWriteMapping(NamespaceEntry::DM_TAG_CONTAINER()); + } for (NamespaceEntry entry : toWrite) { if (entry.isDisabled) + { continue; + } QStringList newList; // get keywords from tags path, for type tag + for (QString tagPath : tagsPath) { newList.append(tagPath.split(QLatin1Char('/')).last()); } switch(entry.subspace) { case NamespaceEntry::XMP: if (supportXmp()) { if (entry.tagPaths != NamespaceEntry::TAG) { newList = tagsPath; if (entry.separator.compare(QLatin1String("/")) != 0) { newList = newList.replaceInStrings(QLatin1String("/"), entry.separator); } } const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); switch(entry.specialOpts) { case NamespaceEntry::TAG_XMPSEQ: if (!setXmpTagStringSeq(nameSpace, newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace; return false; } break; case NamespaceEntry::TAG_XMPBAG: if (!setXmpTagStringBag(nameSpace, newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace; return false; } break; case NamespaceEntry::TAG_ACDSEE: if (!setACDSeeTagsPath(newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace; return false; } default: break; } } break; case NamespaceEntry::IPTC: if (entry.namespaceName == QLatin1String("Iptc.Application2.Keywords")) { if (!setIptcKeywords(getIptcKeywords(), newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << entry.namespaceName; return false; } } default: break; } } return true; } bool DMetadata::getACDSeeTagsPath(QStringList &tagsPath) const { // Try to get Tags Path list from ACDSee 8 Pro categories. + QString xmlACDSee = getXmpTagString("Xmp.acdsee.categories", false); if (!xmlACDSee.isEmpty()) { xmlACDSee.remove(QLatin1String("")); xmlACDSee.remove(QLatin1String("")); xmlACDSee.replace(QLatin1Char('/'), QLatin1Char('\\')); QStringList xmlTags = xmlACDSee.split(QLatin1String("")); int length = tags.length() - (11 * count) - 5; if (category == 0) { tagsPath << tags.mid(5, length); } else { tagsPath.last().append(QLatin1Char('/') + tags.mid(5, length)); } category = category - count + 1; - if (tags.left(5) == QLatin1String("=\"1\">") && category > 0) + if ((tags.left(5) == QLatin1String("=\"1\">")) && (category > 0)) { tagsPath << tagsPath.last().section(QLatin1Char('/'), 0, category - 1); } } } if (!tagsPath.isEmpty()) { //qCDebug(DIGIKAM_METAENGINE_LOG) << "Tags Path imported from ACDSee: " << tagsPath; return true; } } return false; } bool DMetadata::setACDSeeTagsPath(const QStringList &tagsPath) const { // Converting Tags path list to ACDSee 8 Pro categories. + const QString category(QLatin1String("")); QStringList splitTags; QStringList xmlTags; foreach (const QString& tags, tagsPath) { splitTags = tags.split(QLatin1Char('/')); int current = 0; - for (int index = 0; index < splitTags.size(); index++) + for (int index = 0 ; index < splitTags.size() ; index++) { int tagIndex = xmlTags.indexOf(category.arg(0) + splitTags[index]); if (tagIndex == -1) { tagIndex = xmlTags.indexOf(category.arg(1) + splitTags[index]); } splitTags[index].insert(0, category.arg(index == splitTags.size() - 1 ? 1 : 0)); if (tagIndex == -1) { if (index == 0) { xmlTags << splitTags[index]; xmlTags << QLatin1String(""); current = xmlTags.size() - 1; } else { xmlTags.insert(current, splitTags[index]); xmlTags.insert(current + 1, QLatin1String("")); current++; } } else { - if (index == splitTags.size() - 1) + if (index == (splitTags.size() - 1)) { xmlTags[tagIndex] = splitTags[index]; } current = tagIndex + 1; } } } QString xmlACDSee = QLatin1String("") + xmlTags.join(QLatin1String("")) + QLatin1String(""); //qCDebug(DIGIKAM_METAENGINE_LOG) << "xmlACDSee" << xmlACDSee; removeXmpTag("Xmp.acdsee.categories"); if (!xmlTags.isEmpty()) { if (!setXmpTagString("Xmp.acdsee.categories", xmlACDSee)) { return false; } } return true; } } // namespace Digikam