From 5705f02636bd13ad473488d391ab754801564582 Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Sun, 15 Nov 2020 19:53:47 +0800 Subject: [PATCH] feat: use libexiv2 to obtain more image metadata --- .github/workflows/ubuntu.yml | 2 +- CMakeLists.txt | 33 +++++++++++- app/aboutdialog.cpp | 3 ++ app/exiv2wrapper.cpp | 100 +++++++++++++++++++++++++++++++++++ app/exiv2wrapper.h | 36 +++++++++++++ app/metadatamodel.cpp | 42 +++++++++++++-- app/metadatamodel.h | 3 ++ pineapple-pictures.pro | 6 ++- 8 files changed, 218 insertions(+), 7 deletions(-) create mode 100644 app/exiv2wrapper.cpp create mode 100644 app/exiv2wrapper.h diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index e5e05e2..8de297a 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Get build dept. - run: sudo apt install cmake qtbase5-dev libqt5svg5-dev qttools5-dev + run: sudo apt install cmake extra-cmake-modules qtbase5-dev libqt5svg5-dev qttools5-dev libexiv2-dev - name: Build it run: | mkdir build diff --git a/CMakeLists.txt b/CMakeLists.txt index 288b02e..66a81dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,21 @@ set (CMAKE_AUTOMOC ON) set (CMAKE_AUTORCC ON) set (QT_MINIMUM_VERSION "5.10") +option (EXIV2_METADATA_SUPPORT "Better image metadata support via libexiv2" ON) + find_package(Qt5 ${QT_MINIMUM_VERSION} CONFIG REQUIRED Widgets Svg LinguistTools) +if (EXIV2_METADATA_SUPPORT) + find_package(Exiv2) + set_package_properties(Exiv2 PROPERTIES + URL "https://www.exiv2.org" + DESCRIPTION "image metadata support" + TYPE OPTIONAL + PURPOSE "Bring better image metadata support" + ) +endif () + +#LibExiv2_FOUND set (PPIC_CPP_FILES app/main.cpp app/mainwindow.cpp @@ -25,6 +38,7 @@ set (PPIC_CPP_FILES app/aboutdialog.cpp app/metadatamodel.cpp app/metadatadialog.cpp + app/exiv2wrapper.cpp ) set (PPIC_HEADER_FILES @@ -40,6 +54,7 @@ set (PPIC_HEADER_FILES app/aboutdialog.h app/metadatamodel.h app/metadatadialog.h + app/exiv2wrapper.h ) set (PPIC_QRC_FILES @@ -72,6 +87,20 @@ add_executable (${EXE_NAME} target_link_libraries (${EXE_NAME} Qt5::Widgets Qt5::Svg) +if (Exiv2_FOUND) + message(INFO ${LibExiv2_INCLUDE_DIRS}) + target_include_directories(${EXE_NAME} + PRIVATE + ${LibExiv2_INCLUDE_DIRS} + ) + target_link_libraries (${EXE_NAME} + Exiv2 + ) + target_compile_definitions(${EXE_NAME} PRIVATE + HAVE_EXIV2_VERSION="${LibExiv2_VERSION}" + ) +endif () + # Extra build settings if (WIN32) set_property ( @@ -194,6 +223,8 @@ install ( DESTINATION ${QM_FILE_INSTALL_DIR} ) +feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) + # CPACK: General Settings set (CPACK_GENERATOR "TBZ2") set (CPACK_PACKAGE_NAME "pineapple-pictures") @@ -206,7 +237,7 @@ elseif (APPLE) # ... elseif (UNIX) set (CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") - set (CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5svg5") + set (CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5svg5 libexiv2-14") set (CPACK_DEBIAN_PACKAGE_RECOMMENDS "kimageformat-plugins") endif() diff --git a/app/aboutdialog.cpp b/app/aboutdialog.cpp index df4e0da..54eba01 100644 --- a/app/aboutdialog.cpp +++ b/app/aboutdialog.cpp @@ -109,6 +109,9 @@ SOFTWARE. QStringLiteral("

%1

").arg(tr("Third-party Libraries used by %1")), tr("%1 is built on the following free software libraries:"), QStringLiteral("") }; diff --git a/app/exiv2wrapper.cpp b/app/exiv2wrapper.cpp new file mode 100644 index 0000000..1ec5d82 --- /dev/null +++ b/app/exiv2wrapper.cpp @@ -0,0 +1,100 @@ +#include "exiv2wrapper.h" + +#ifdef HAVE_EXIV2_VERSION +#include +#else // HAVE_EXIV2_VERSION +namespace Exiv2 { +class Image {}; +} +#endif // HAVE_EXIV2_VERSION + +#include + +#include +#include + +Exiv2Wrapper::Exiv2Wrapper() +{ + +} + +Exiv2Wrapper::~Exiv2Wrapper() +{ + +} + +template +void Exiv2Wrapper::cacheSection(Collection collection) +{ + const Collection& exifData = collection; + Iterator it = exifData.begin(), end = exifData.end(); + for (; it != end; ++it) { + QString key = QString::fromUtf8(it->key().c_str()); + QString label = QString::fromLocal8Bit(it->tagLabel().c_str()); + std::ostringstream stream; + stream << *it; + QString value = QString::fromLocal8Bit(stream.str().c_str()); + m_metadataValue.insert(key, value); + m_metadataLabel.insert(key, label); + + qDebug() << key << label << value; + } +} + +bool Exiv2Wrapper::load(const QString &filePath) +{ +#ifdef HAVE_EXIV2_VERSION + QByteArray filePathByteArray = QFile::encodeName(filePath); + try { + m_exivImage.reset(Exiv2::ImageFactory::open(filePathByteArray.constData()).release()); + m_exivImage->readMetadata(); + } catch (const Exiv2::Error& error) { + m_errMsg = QString::fromUtf8(error.what()); + return false; + } + return true; +#else // HAVE_EXIV2_VERSION + Q_UNUSED(filePath); + return false; +#endif // HAVE_EXIV2_VERSION +} + +void Exiv2Wrapper::cacheSections() +{ +#ifdef HAVE_EXIV2_VERSION + if (m_exivImage->checkMode(Exiv2::mdExif) & Exiv2::amRead) { + cacheSection(m_exivImage->exifData()); + } + + if (m_exivImage->checkMode(Exiv2::mdIptc) & Exiv2::amRead) { + cacheSection(m_exivImage->iptcData()); + } + + if (m_exivImage->checkMode(Exiv2::mdXmp) & Exiv2::amRead) { + cacheSection(m_exivImage->xmpData()); + } + +// qDebug() << m_metadataValue; +// qDebug() << m_metadataLabel; +#endif // HAVE_EXIV2_VERSION +} + +QString Exiv2Wrapper::comment() const +{ +#ifdef HAVE_EXIV2_VERSION + return m_exivImage->comment().c_str(); +#else // HAVE_EXIV2_VERSION + return QString(); +#endif // HAVE_EXIV2_VERSION +} + +QString Exiv2Wrapper::label(const QString &key) const +{ + return m_metadataLabel.value(key); +} + +QString Exiv2Wrapper::value(const QString &key) const +{ + return m_metadataValue.value(key); +} + diff --git a/app/exiv2wrapper.h b/app/exiv2wrapper.h new file mode 100644 index 0000000..e2d050b --- /dev/null +++ b/app/exiv2wrapper.h @@ -0,0 +1,36 @@ +#ifndef EXIV2WRAPPER_H +#define EXIV2WRAPPER_H + +#include + +#include +#include + +namespace Exiv2 { +class Image; +} + +class Exiv2Wrapper +{ +public: + Exiv2Wrapper(); + ~Exiv2Wrapper(); + + bool load(const QString& filePath); + void cacheSections(); + + QString comment() const; + QString label(const QString & key) const; + QString value(const QString & key) const; + +private: + std::unique_ptr m_exivImage; + QMap m_metadataValue; + QMap m_metadataLabel; + QString m_errMsg; + + template + void cacheSection(Collection collection); +}; + +#endif // EXIV2WRAPPER_H diff --git a/app/metadatamodel.cpp b/app/metadatamodel.cpp index ecacc62..640ee0a 100644 --- a/app/metadatamodel.cpp +++ b/app/metadatamodel.cpp @@ -1,4 +1,5 @@ #include "metadatamodel.h" +#include "exiv2wrapper.h" #include #include @@ -31,14 +32,12 @@ void MetadataModel::setFile(const QString &imageFilePath) const QString & imageDimensionsString = imageSize(imgReader.size()); const QString & imageRatioString = imageSizeRatio(imgReader.size()); -#if 0 appendSection(QStringLiteral("Description"), tr("Description", "Section name.")); appendSection(QStringLiteral("Origin"), tr("Origin", "Section name.")); -#endif // 0 appendSection(QStringLiteral("Image"), tr("Image", "Section name.")); -#if 0 appendSection(QStringLiteral("Camera"), tr("Camera", "Section name.")); appendSection(QStringLiteral("AdvancedPhoto"), tr("Advanced photo", "Section name.")); +#if 0 appendSection(QStringLiteral("GPS"), tr("GPS", "Section name.")); #endif // 0 appendSection(QStringLiteral("File"), tr("File", "Section name.")); @@ -60,6 +59,31 @@ void MetadataModel::setFile(const QString &imageFilePath) tr("Date Created"), birthTimeString); appendProperty(QStringLiteral("File"), QStringLiteral("File.LastModified"), tr("Date Modified"), lastModifiedTimeString); + + Exiv2Wrapper wrapper; + if (wrapper.load(imageFilePath)) { + wrapper.cacheSections(); + + appendProperty(QStringLiteral("Description"), QStringLiteral("Description.Comments"), + tr("Comments"), wrapper.comment()); + + appendExivPropertyIfExist(wrapper, QStringLiteral("Origin"), + QStringLiteral("Exif.Image.Software"), tr("Program name")); + appendExivPropertyIfExist(wrapper, QStringLiteral("Image"), + QStringLiteral("Exif.Photo.ColorSpace"), tr("Colour representation")); + appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"), + QStringLiteral("Exif.Image.Make"), tr("Camera maker")); + appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"), + QStringLiteral("Exif.Image.Model"), tr("Camera model")); + appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"), + QStringLiteral("Exif.Photo.ISOSpeedRatings"), tr("ISO speed")); + appendExivPropertyIfExist(wrapper, QStringLiteral("Camera"), + QStringLiteral("Exif.Photo.FocalLength"), tr("Focal length")); + appendExivPropertyIfExist(wrapper, QStringLiteral("AdvancedPhoto"), + QStringLiteral("Exif.Photo.DigitalZoomRatio"), tr("Digital zoom")); + appendExivPropertyIfExist(wrapper, QStringLiteral("AdvancedPhoto"), + QStringLiteral("Exif.Photo.ExifVersion"), tr("EXIF version")); + } } QString MetadataModel::imageSize(const QSize &size) @@ -126,6 +150,18 @@ bool MetadataModel::updateProperty(const QString &propertyKey, const QString &pr return false; } +bool MetadataModel::appendExivPropertyIfExist(const Exiv2Wrapper &wrapper, const QString §ionKey, const QString &exiv2propertyKey, const QString &propertyDisplayName) +{ + const QString & value = wrapper.value(exiv2propertyKey); + if (!value.isEmpty()) { + appendProperty(sectionKey, exiv2propertyKey, + propertyDisplayName.isEmpty() ? wrapper.label(exiv2propertyKey) : propertyDisplayName, + value); + return true; + } + return false; +} + QModelIndex MetadataModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { diff --git a/app/metadatamodel.h b/app/metadatamodel.h index babfe70..9112d39 100644 --- a/app/metadatamodel.h +++ b/app/metadatamodel.h @@ -3,6 +3,7 @@ #include +class Exiv2Wrapper; class MetadataModel : public QAbstractItemModel { Q_OBJECT @@ -18,6 +19,8 @@ public: bool appendProperty(const QString & sectionKey, const QString & propertyKey, const QString & propertyDisplayName, const QString & propertyValue = QString()); bool updateProperty(const QString & propertyKey, const QString & propertyValue); + bool appendExivPropertyIfExist(const Exiv2Wrapper & wrapper, const QString & sectionKey, + const QString & exiv2propertyKey, const QString & propertyDisplayName = QString()); private: enum RowType : quintptr { diff --git a/pineapple-pictures.pro b/pineapple-pictures.pro index 35e9f21..724fd00 100644 --- a/pineapple-pictures.pro +++ b/pineapple-pictures.pro @@ -29,7 +29,8 @@ SOURCES += \ app/settings.cpp \ app/settingsdialog.cpp \ app/metadatamodel.cpp \ - app/metadatadialog.cpp + app/metadatadialog.cpp \ + app/exiv2wrapper.cpp HEADERS += \ app/aboutdialog.h \ @@ -43,7 +44,8 @@ HEADERS += \ app/settings.h \ app/settingsdialog.h \ app/metadatamodel.h \ - app/metadatadialog.h + app/metadatadialog.h \ + app/exiv2wrapper.h TRANSLATIONS = \ translations/PineapplePictures.ts \