diff --git a/CMakeLists.txt b/CMakeLists.txt index d13ef27..5747665 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,134 +1,122 @@ # # Copyright (c) 2010-2015, Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. set(CMAKE_MIN_VERSION "3.0.0") set(ECM_MIN_VERSION "1.1.0") set(REQUIRED_QT_VERSION "5.2.0") -set(EXIV2_MIN_VERSION "0.24") +set(EXIV2_MIN_VERSION "0.25") cmake_minimum_required(VERSION ${CMAKE_MIN_VERSION}) -project(libkexiv2) +project(libkexiv2 VERSION "5.0.0") message(STATUS "----------------------------------------------------------------------------------") message(STATUS "Starting CMake configuration for: ${PROJECT_NAME}") # ======================================================= # Information to update before to release this library. # Library version history: # API ABI # 0.1.0 => 0.1.0 # 0.1.1 => 0.2.0 # 0.1.2 => 1.0.1 # 0.1.3 => 1.0.1 # 0.1.4 => 2.0.2 # 0.1.5 => 2.1.1 # 0.1.6 => 3.0.0 # 0.1.7 => 4.0.1 # 0.1.8 => 5.0.0 # 0.2.0 => 6.0.0 (released with KDE 4.1.0) # 0.3.0 => 7.0.0 (released with KDE 4.1.2) # 0.4.0 => 7.1.0 # 0.5.0 => 7.2.0 (Released with KDE 4.2.0) # 0.6.0 => 7.3.0 (Released with KDE 4.3.0) # 1.0.0 => 8.0.0 (Released with KDE 4.4.0) # 1.1.0 => 8.1.0 (Released with KDE 4.5.0) # 1.2.0 => 9.0.0 (Released with KDE 4.6.0) - Including XMP sidecar support # 2.0.0 => 10.0.0 (Released with KDE 4.7.0) # 2.1.0 => 10.0.1 (Released with KDE 4.7.1) - Add AltLangStrEdit visible lines API # 2.1.1 => 10.0.2 (Released with KDE 4.7.4) - Add AltLangStrEdit::setCurrentLanguageCode() # 2.2.0 => 11.0.0 (Released with KDE 4.8.1) - Remove deprecated methods # 2.3.0 => 11.1.0 (Released with KDE 4.8.2) - Add new static methods about XMP sidecar file management. # 2.3.1 => 11.2.0 - Add new method to set specific XMP tag string # 2.4.0 => 11.3.0 - Add new method to access on text edit widget from AltLangStrEdit # 5.0.0 => 15.0.0 (Released with KDE 5.x) -# Library API version -set(KEXIV2_LIB_MAJOR_VERSION "5") -set(KEXIV2_LIB_MINOR_VERSION "0") -set(KEXIV2_LIB_PATCH_VERSION "0") - # Library ABI version used by linker. # For details : http://www.gnu.org/software/libtool/manual/libtool.html#Updating-version-info set(KEXIV2_LIB_SO_CUR_VERSION "15") set(KEXIV2_LIB_SO_REV_VERSION "0") set(KEXIV2_LIB_SO_AGE_VERSION "0") -set(LIBKEXIV2_LIB_VERSION "${KEXIV2_LIB_MAJOR_VERSION}.${KEXIV2_LIB_MINOR_VERSION}.${KEXIV2_LIB_PATCH_VERSION}") set(LIBKEXIV2_SO_VERSION "${KEXIV2_LIB_SO_CUR_VERSION}.${KEXIV2_LIB_SO_REV_VERSION}.${KEXIV2_LIB_SO_AGE_VERSION}") ############## ECM setup ###################### find_package(ECM ${ECM_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${libkexiv2_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(ECMPackageConfigHelpers) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(GenerateExportHeader) include(FeatureSummary) +ecm_setup_version(${libkexiv2_VERSION} + VARIABLE_PREFIX KEXIV2 + VERSION_HEADER "src/libkexiv2_version.h" + PACKAGE_VERSION_FILE "KF5KExiv2ConfigVersion.cmake" + SOVERSION ${LIBKEXIV2_SO_VERSION} +) + ############## Find Packages ################### find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE COMPONENTS Core Gui ) -find_package(Exiv2 ${EXIV2_MIN_VERSION} REQUIRED) -set_package_properties("Exiv2" PROPERTIES - DESCRIPTION "Required to build libkexiv2" - URL "http://www.exiv2.org" - TYPE RECOMMENDED - PURPOSE "Library to manage image metadata" -) - -ecm_setup_version(${LIBKEXIV2_LIB_VERSION} - VARIABLE_PREFIX KEXIV2 - VERSION_HEADER "src/libkexiv2_version.h" - PACKAGE_VERSION_FILE "KF5KExiv2ConfigVersion.cmake" - SOVERSION ${LIBKEXIV2_SO_VERSION} -) +find_package(LibExiv2 ${EXIV2_MIN_VERSION} REQUIRED) ############## Targets ######################### add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(tests) endif() ############## CMake Config Files ############## message("${CMAKECONFIG_INSTALL_DIR}") set(CMAKECONFIG_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/KF5KExiv2") ecm_configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/KF5KExiv2Config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5KExiv2Config.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5KExiv2Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5KExiv2ConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5KExiv2Targets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5KExiv2Targets.cmake NAMESPACE KF5:: ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/cmake/modules/FindExiv2.cmake b/cmake/modules/FindExiv2.cmake deleted file mode 100644 index 82cc1c7..0000000 --- a/cmake/modules/FindExiv2.cmake +++ /dev/null @@ -1,80 +0,0 @@ -# - Try to find the Exiv2 library -# -# EXIV2_MIN_VERSION - You can set this variable to the minimum version you need -# before doing FIND_PACKAGE(Exiv2). The default is 0.12. -# -# Once done this will define -# -# EXIV2_FOUND - system has libexiv2 -# EXIV2_INCLUDE_DIR - the libexiv2 include directory -# EXIV2_LIBRARIES - Link these to use libexiv2 -# EXIV2_DEFINITIONS - Compiler switches required for using libexiv2 -# -# The minimum required version of Exiv2 can be specified using the -# standard syntax, e.g. find_package(Exiv2 0.17) -# -# For compatibility, also the variable EXIV2_MIN_VERSION can be set to the minimum version -# you need before doing FIND_PACKAGE(Exiv2). The default is 0.12. - -# Copyright (c) 2010, Alexander Neundorf, -# Copyright (c) 2008, Gilles Caulier, -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -# Support EXIV2_MIN_VERSION for compatibility: -if(NOT Exiv2_FIND_VERSION) - set(Exiv2_FIND_VERSION "${EXIV2_MIN_VERSION}") -endif(NOT Exiv2_FIND_VERSION) - -# the minimum version of exiv2 we require -if(NOT Exiv2_FIND_VERSION) - set(Exiv2_FIND_VERSION "0.12") -endif(NOT Exiv2_FIND_VERSION) - - -if (NOT WIN32) - # use pkg-config to get the directories and then use these values - # in the FIND_PATH() and FIND_LIBRARY() calls - find_package(PkgConfig) - pkg_check_modules(PC_EXIV2 QUIET exiv2) - set(EXIV2_DEFINITIONS ${PC_EXIV2_CFLAGS_OTHER}) -endif (NOT WIN32) - - -find_path(EXIV2_INCLUDE_DIR NAMES exiv2/exif.hpp - HINTS - ${PC_EXIV2_INCLUDEDIR} - ${PC_EXIV2_INCLUDE_DIRS} - ) - -find_library(EXIV2_LIBRARY NAMES exiv2 libexiv2 - HINTS - ${PC_EXIV2_LIBDIR} - ${PC_EXIV2_LIBRARY_DIRS} - ) - - -# Get the version number from exiv2/version.hpp and store it in the cache: -if(EXIV2_INCLUDE_DIR AND NOT EXIV2_VERSION) - file(READ ${EXIV2_INCLUDE_DIR}/exiv2/version.hpp EXIV2_VERSION_CONTENT) - string(REGEX MATCH "#define EXIV2_MAJOR_VERSION +\\( *([0-9]+) *\\)" _dummy "${EXIV2_VERSION_CONTENT}") - set(EXIV2_VERSION_MAJOR "${CMAKE_MATCH_1}") - - string(REGEX MATCH "#define EXIV2_MINOR_VERSION +\\( *([0-9]+) *\\)" _dummy "${EXIV2_VERSION_CONTENT}") - set(EXIV2_VERSION_MINOR "${CMAKE_MATCH_1}") - - string(REGEX MATCH "#define EXIV2_PATCH_VERSION +\\( *([0-9]+) *\\)" _dummy "${EXIV2_VERSION_CONTENT}") - set(EXIV2_VERSION_PATCH "${CMAKE_MATCH_1}") - - set(EXIV2_VERSION "${EXIV2_VERSION_MAJOR}.${EXIV2_VERSION_MINOR}.${EXIV2_VERSION_PATCH}" CACHE STRING "Version number of Exiv2" FORCE) -endif(EXIV2_INCLUDE_DIR AND NOT EXIV2_VERSION) - -set(EXIV2_LIBRARIES "${EXIV2_LIBRARY}") - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Exiv2 REQUIRED_VARS EXIV2_LIBRARY EXIV2_INCLUDE_DIR - VERSION_VAR EXIV2_VERSION) - -mark_as_advanced(EXIV2_INCLUDE_DIR EXIV2_LIBRARY) - diff --git a/cmake/modules/FindLibExiv2.cmake b/cmake/modules/FindLibExiv2.cmake new file mode 100644 index 0000000..935cee2 --- /dev/null +++ b/cmake/modules/FindLibExiv2.cmake @@ -0,0 +1,115 @@ +#.rst: +# FindLibExiv2 +# ------------ +# +# Try to find the Exiv2 library. +# +# This will define the following variables: +# +# ``LibExiv2_FOUND`` +# System has LibExiv2. +# +# ``LibExiv2_VERSION`` +# The version of LibExiv2. +# +# ``LibExiv2_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if +# the target is not used for linking. +# +# ``LibExiv2_LIBRARIES`` +# The LibExiv2 library. +# This can be passed to target_link_libraries() instead of +# the ``LibExiv2::LibExiv2`` target +# +# If ``LibExiv2_FOUND`` is TRUE, the following imported target +# will be available: +# +# ``LibExiv2::LibExiv2`` +# The Exiv2 library +# +# Since 5.53.0. +# +#============================================================================= +# Copyright (c) 2018, Christophe Giboudeaux, +# Copyright (c) 2010, Alexander Neundorf, +# Copyright (c) 2008, Gilles Caulier, +# +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_EXIV2 QUIET exiv2) + +find_path(LibExiv2_INCLUDE_DIRS NAMES exiv2/exif.hpp + HINTS ${PC_EXIV2_INCLUDEDIR} +) + +find_library(LibExiv2_LIBRARIES NAMES exiv2 libexiv2 + HINTS ${PC_EXIV2_LIBRARY_DIRS} +) + +set(LibExiv2_VERSION ${PC_EXIV2_VERSION}) + +if(NOT LibExiv2_VERSION AND DEFINED LibExiv2_INCLUDE_DIRS) + # With exiv >= 0.27, the version #defines are in exv_conf.h instead of version.hpp + foreach(_exiv2_version_file "version.hpp" "exv_conf.h") + if(EXISTS "${LibExiv2_INCLUDE_DIRS}/exiv2/${_exiv2_version_file}") + file(READ "${LibExiv2_INCLUDE_DIRS}/exiv2/${_exiv2_version_file}" _exiv_version_file_content) + string(REGEX MATCH "#define EXIV2_MAJOR_VERSION[ ]+\\([0-9]+\\)" EXIV2_MAJOR_VERSION_MATCH ${_exiv_version_file_content}) + string(REGEX MATCH "#define EXIV2_MINOR_VERSION[ ]+\\([0-9]+\\)" EXIV2_MINOR_VERSION_MATCH ${_exiv_version_file_content}) + string(REGEX MATCH "#define EXIV2_PATCH_VERSION[ ]+\\([0-9]+\\)" EXIV2_PATCH_VERSION_MATCH ${_exiv_version_file_content}) + if(EXIV2_MAJOR_VERSION_MATCH) + string(REGEX REPLACE ".*_MAJOR_VERSION[ ]+\\((.*)\\)" "\\1" EXIV2_MAJOR_VERSION ${EXIV2_MAJOR_VERSION_MATCH}) + string(REGEX REPLACE ".*_MINOR_VERSION[ ]+\\((.*)\\)" "\\1" EXIV2_MINOR_VERSION ${EXIV2_MINOR_VERSION_MATCH}) + string(REGEX REPLACE ".*_PATCH_VERSION[ ]+\\((.*)\\)" "\\1" EXIV2_PATCH_VERSION ${EXIV2_PATCH_VERSION_MATCH}) + endif() + endif() + endforeach() + + set(LibExiv2_VERSION "${EXIV2_MAJOR_VERSION}.${EXIV2_MINOR_VERSION}.${EXIV2_PATCH_VERSION}") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibExiv2 + FOUND_VAR LibExiv2_FOUND + REQUIRED_VARS LibExiv2_LIBRARIES LibExiv2_INCLUDE_DIRS + VERSION_VAR LibExiv2_VERSION +) + +mark_as_advanced(LibExiv2_INCLUDE_DIRS LibExiv2_LIBRARIES) + +if(LibExiv2_FOUND AND NOT TARGET LibExiv2::LibExiv2) + add_library(LibExiv2::LibExiv2 UNKNOWN IMPORTED) + set_target_properties(LibExiv2::LibExiv2 PROPERTIES + IMPORTED_LOCATION "${LibExiv2_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${LibExiv2_INCLUDE_DIRS}" + ) +endif() + +include(FeatureSummary) +set_package_properties(LibExiv2 PROPERTIES + URL "http://www.exiv2.org" + DESCRIPTION "Image metadata support" +) diff --git a/cmake/templates/libkexiv2.pc.cmake.in b/cmake/templates/libkexiv2.pc.cmake.in index 1df60df..e80e0ec 100644 --- a/cmake/templates/libkexiv2.pc.cmake.in +++ b/cmake/templates/libkexiv2.pc.cmake.in @@ -1,12 +1,12 @@ prefix=${CMAKE_INSTALL_PREFIX} exec_prefix=${BIN_INSTALL_DIR} libdir=${LIB_INSTALL_DIR} includedir=${INCLUDE_INSTALL_DIR} Name: ${PROJECT_NAME} Description: A C++ library to manipulate EXIF/IPTC/XMP metadata using Exiv2 library. URL: https://cgit.kde.org/libkexiv2.git/ Requires: -Version: ${KEXIV2_LIB_VERSION_STRING} +Version: ${libkexiv2_VERSION} Libs: -L${LIB_INSTALL_DIR} -lkexiv2 Cflags: -I${INCLUDE_INSTALL_DIR} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2b2df03..8ac18f5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,96 +1,95 @@ # # Copyright (c) 2010-2015, Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. kde_enable_exceptions() -include_directories(${EXIV2_INCLUDE_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) add_definitions(${EXIV2_DEFINITIONS}) if( WIN32 ) add_definitions( -DEXV_HAVE_DLL ) endif() set(kexiv2_LIB_SRCS kexiv2.cpp kexiv2_p.cpp kexiv2data.cpp kexiv2image.cpp kexiv2comments.cpp kexiv2exif.cpp kexiv2iptc.cpp kexiv2gps.cpp kexiv2xmp.cpp kexiv2previews.cpp rotationmatrix.cpp libkexiv2_debug.cpp ) ecm_generate_headers(kexiv2_CamelCase_HEADERS HEADER_NAMES KExiv2Data KExiv2 KExiv2Previews RotationMatrix PREFIX KExiv2 REQUIRED_HEADERS kexiv2_HEADERS ) add_library(KF5KExiv2 SHARED ${kexiv2_LIB_SRCS}) add_library(KF5::KExiv2 ALIAS KF5KExiv2) generate_export_header(KF5KExiv2 BASE_NAME libkexiv2 EXPORT_MACRO_NAME LIBKEXIV2_EXPORT) target_include_directories(KF5KExiv2 INTERFACE "$" "$" ) target_link_libraries(KF5KExiv2 PRIVATE - ${EXIV2_LIBRARIES} + LibExiv2::LibExiv2 PUBLIC Qt5::Core Qt5::Gui ) if( WIN32 ) target_link_libraries( KF5KExiv2 ${EXPAT_LIBRARIES} ) endif() install(TARGETS KF5KExiv2 EXPORT KF5KExiv2Targets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) set_target_properties(KF5KExiv2 PROPERTIES VERSION ${KEXIV2_VERSION_STRING} SOVERSION ${KEXIV2_SOVERSION} EXPORT_NAME KExiv2 ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libkexiv2_export.h DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KExiv2/kexiv2 COMPONENT Devel ) install(FILES ${kexiv2_CamelCase_HEADERS} DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KExiv2/KExiv2 COMPONENT Devel ) install(FILES ${kexiv2_HEADERS} DESTINATION ${KF5_INCLUDE_INSTALL_DIR}/KExiv2/kexiv2 COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libkexiv2_version.h DESTINATION ${KF5_INCLUDE_INSTALL_DIR} COMPONENT Devel ) diff --git a/src/kexiv2.cpp b/src/kexiv2.cpp index 04c4aa4..b7bbc1a 100644 --- a/src/kexiv2.cpp +++ b/src/kexiv2.cpp @@ -1,534 +1,534 @@ /** =========================================================== * @file * * This file is a part of KDE project * * * @date 2006-09-15 * @brief Exiv2 library interface for KDE * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2013 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "kexiv2.h" #include "kexiv2_p.h" // Local includes #include "libkexiv2_version.h" #include "libkexiv2_debug.h" namespace KExiv2Iface { KExiv2::KExiv2() : d(new Private) { } KExiv2::KExiv2(const KExiv2& metadata) : d(new Private) { d->copyPrivateData(metadata.d); } KExiv2::KExiv2(const KExiv2Data& data) : d(new Private) { setData(data); } KExiv2::KExiv2(const QString& filePath) : d(new Private) { load(filePath); } KExiv2::~KExiv2() { delete d; } KExiv2& KExiv2::operator=(const KExiv2& metadata) { d->copyPrivateData(metadata.d); return *this; } //-- Statics methods ---------------------------------------------- bool KExiv2::initializeExiv2() { #ifdef _XMP_SUPPORT_ if (!Exiv2::XmpParser::initialize()) return false; registerXmpNameSpace(QString::fromLatin1("http://ns.adobe.com/lightroom/1.0/"), QString::fromLatin1("lr")); registerXmpNameSpace(QString::fromLatin1("http://www.digikam.org/ns/kipi/1.0/"), QString::fromLatin1("kipi")); registerXmpNameSpace(QString::fromLatin1("http://ns.microsoft.com/photo/1.2/"), QString::fromLatin1("MP")); registerXmpNameSpace(QString::fromLatin1("http://ns.acdsee.com/iptc/1.0/"), QString::fromLatin1("acdsee")); registerXmpNameSpace(QString::fromLatin1("http://www.video"), QString::fromLatin1("video")); #endif // _XMP_SUPPORT_ return true; } bool KExiv2::cleanupExiv2() { // Fix memory leak if Exiv2 support XMP. #ifdef _XMP_SUPPORT_ unregisterXmpNameSpace(QString::fromLatin1("http://ns.adobe.com/lightroom/1.0/")); unregisterXmpNameSpace(QString::fromLatin1("http://www.digikam.org/ns/kipi/1.0/")); unregisterXmpNameSpace(QString::fromLatin1("http://ns.microsoft.com/photo/1.2/")); unregisterXmpNameSpace(QString::fromLatin1("http://ns.acdsee.com/iptc/1.0/")); unregisterXmpNameSpace(QString::fromLatin1("http://www.video")); Exiv2::XmpParser::terminate(); #endif // _XMP_SUPPORT_ return true; } bool KExiv2::supportXmp() { #ifdef _XMP_SUPPORT_ return true; #else return false; #endif // _XMP_SUPPORT_ } bool KExiv2::supportMetadataWritting(const QString& typeMime) { if (typeMime == QString::fromLatin1("image/jpeg")) { return true; } else if (typeMime == QString::fromLatin1("image/tiff")) { return true; } else if (typeMime == QString::fromLatin1("image/png")) { return true; } else if (typeMime == QString::fromLatin1("image/jp2")) { return true; } else if (typeMime == QString::fromLatin1("image/x-raw")) { return true; } else if (typeMime == QString::fromLatin1("image/pgf")) { return true; } return false; } QString KExiv2::Exiv2Version() { // Since 0.14.0 release, we can extract run-time version of Exiv2. // else we return make version. - return QString::fromLatin1(Exiv2::version()); + return QString::fromStdString(Exiv2::versionString()); } QString KExiv2::version() { return QString::fromLatin1(KEXIV2_VERSION_STRING); } QString KExiv2::sidecarFilePathForFile(const QString& path) { QString ret; if (!path.isEmpty()) { ret = path + QString::fromLatin1(".xmp"); } return ret; } QUrl KExiv2::sidecarUrl(const QUrl& url) { QString sidecarPath = sidecarFilePathForFile(url.path()); QUrl sidecarUrl(url); sidecarUrl.setPath(sidecarPath); return sidecarUrl; } QUrl KExiv2::sidecarUrl(const QString& path) { return QUrl::fromLocalFile(sidecarFilePathForFile(path)); } QString KExiv2::sidecarPath(const QString& path) { return sidecarFilePathForFile(path); } bool KExiv2::hasSidecar(const QString& path) { return QFileInfo(sidecarFilePathForFile(path)).exists(); } //-- General methods ---------------------------------------------- KExiv2Data KExiv2::data() const { KExiv2Data data; data.d = d->data; return data; } void KExiv2::setData(const KExiv2Data& data) { if (data.d) { d->data = data.d; } else { // KExiv2Data can have a null pointer, // but we never want a null pointer in Private. d->data->clear(); } } bool KExiv2::loadFromData(const QByteArray& imgData) const { if (imgData.isEmpty()) return false; try { Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((Exiv2::byte*)imgData.data(), imgData.size()); d->filePath.clear(); image->readMetadata(); // Size and mimetype --------------------------------- d->pixelSize = QSize(image->pixelWidth(), image->pixelHeight()); d->mimeType = QString::fromLatin1(image->mimeType().c_str()); // Image comments --------------------------------- d->imageComments() = image->comment(); // Exif metadata ---------------------------------- d->exifMetadata() = image->exifData(); // Iptc metadata ---------------------------------- d->iptcMetadata() = image->iptcData(); #ifdef _XMP_SUPPORT_ // Xmp metadata ----------------------------------- d->xmpMetadata() = image->xmpData(); #endif // _XMP_SUPPORT_ return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot load metadata using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } bool KExiv2::load(const QString& filePath) const { if (filePath.isEmpty()) { return false; } d->filePath = filePath; bool hasLoaded = false; try { Exiv2::Image::AutoPtr image; image = Exiv2::ImageFactory::open((const char*)(QFile::encodeName(filePath)).constData()); image->readMetadata(); // Size and mimetype --------------------------------- d->pixelSize = QSize(image->pixelWidth(), image->pixelHeight()); d->mimeType = QString::fromLatin1(image->mimeType().c_str()); // Image comments --------------------------------- d->imageComments() = image->comment(); // Exif metadata ---------------------------------- d->exifMetadata() = image->exifData(); // Iptc metadata ---------------------------------- d->iptcMetadata() = image->iptcData(); #ifdef _XMP_SUPPORT_ // Xmp metadata ----------------------------------- d->xmpMetadata() = image->xmpData(); #endif // _XMP_SUPPORT_ hasLoaded = true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot load metadata from file "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } #ifdef _XMP_SUPPORT_ try { if (d->useXMPSidecar4Reading) { QString xmpSidecarPath = sidecarFilePathForFile(filePath); QFileInfo xmpSidecarFileInfo(xmpSidecarPath); Exiv2::Image::AutoPtr xmpsidecar; if (xmpSidecarFileInfo.exists() && xmpSidecarFileInfo.isReadable()) { // Read sidecar data xmpsidecar = Exiv2::ImageFactory::open(QFile::encodeName(xmpSidecarPath).constData()); xmpsidecar->readMetadata(); // Merge d->loadSidecarData(xmpsidecar); hasLoaded = true; } } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot load XMP sidecar"), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } #endif // _XMP_SUPPORT_ return hasLoaded; } bool KExiv2::save(const QString& imageFilePath) const { // If our image is really a symlink, we should follow the symlink so that // when we delete the file and rewrite it, we are honoring the symlink // (rather than just deleting it and putting a file there). // However, this may be surprising to the user when they are writing sidecar // files. They might expect them to show up where the symlink is. So, we // shouldn't follow the link when figuring out what the filename for the // sidecar should be. // Note, we are not yet handling the case where the sidecar itself is a // symlink. QString regularFilePath = imageFilePath; // imageFilePath might be a // symlink. Below we will change // regularFile to the pointed to // file if so. QFileInfo givenFileInfo(imageFilePath); if (givenFileInfo.isSymLink()) { qCDebug(LIBKEXIV2_LOG) << "filePath" << imageFilePath << "is a symlink." << "Using target" << givenFileInfo.canonicalPath(); regularFilePath = givenFileInfo.canonicalPath();// Walk all the symlinks } // NOTE: see B.K.O #137770 & #138540 : never touch the file if is read only. QFileInfo finfo(regularFilePath); QFileInfo dinfo(finfo.path()); if (!dinfo.isWritable()) { qCDebug(LIBKEXIV2_LOG) << "Dir '" << dinfo.filePath() << "' is read-only. Metadata not saved."; return false; } bool writeToFile = false; bool writeToSidecar = false; bool writeToSidecarIfFileNotPossible = false; bool writtenToFile = false; bool writtenToSidecar = false; qCDebug(LIBKEXIV2_LOG) << "KExiv2::metadataWritingMode" << d->metadataWritingMode; switch(d->metadataWritingMode) { case WRITETOSIDECARONLY: writeToSidecar = true; break; case WRITETOIMAGEONLY: writeToFile = true; break; case WRITETOSIDECARANDIMAGE: writeToFile = true; writeToSidecar = true; break; case WRITETOSIDECARONLY4READONLYFILES: writeToFile = true; writeToSidecarIfFileNotPossible = true; break; } if (writeToFile) { qCDebug(LIBKEXIV2_LOG) << "Will write Metadata to file" << finfo.absoluteFilePath(); writtenToFile = d->saveToFile(finfo); if (writeToFile) { qCDebug(LIBKEXIV2_LOG) << "Metadata for file" << finfo.fileName() << "written to file."; } } if (writeToSidecar || (writeToSidecarIfFileNotPossible && !writtenToFile)) { qCDebug(LIBKEXIV2_LOG) << "Will write XMP sidecar for file" << givenFileInfo.fileName(); writtenToSidecar = d->saveToXMPSidecar(imageFilePath); if (writtenToSidecar) { qCDebug(LIBKEXIV2_LOG) << "Metadata for file '" << givenFileInfo.fileName() << "' written to XMP sidecar."; } } return writtenToFile || writtenToSidecar; } bool KExiv2::applyChanges() const { if (d->filePath.isEmpty()) { qCDebug(LIBKEXIV2_LOG) << "Failed to apply changes: file path is empty!"; return false; } return save(d->filePath); } bool KExiv2::isEmpty() const { if (!hasComments() && !hasExif() && !hasIptc() && !hasXmp()) return true; return false; } void KExiv2::setFilePath(const QString& path) { d->filePath = path; } QString KExiv2::getFilePath() const { return d->filePath; } QSize KExiv2::getPixelSize() const { return d->pixelSize; } QString KExiv2::getMimeType() const { return d->mimeType; } void KExiv2::setWriteRawFiles(const bool on) { d->writeRawFiles = on; } bool KExiv2::writeRawFiles() const { return d->writeRawFiles; } void KExiv2::setUseXMPSidecar4Reading(const bool on) { d->useXMPSidecar4Reading = on; } bool KExiv2::useXMPSidecar4Reading() const { return d->useXMPSidecar4Reading; } void KExiv2::setMetadataWritingMode(const int mode) { d->metadataWritingMode = mode; } int KExiv2::metadataWritingMode() const { return d->metadataWritingMode; } void KExiv2::setUpdateFileTimeStamp(bool on) { d->updateFileTimeStamp = on; } bool KExiv2::updateFileTimeStamp() const { return d->updateFileTimeStamp; } bool KExiv2::setProgramId(bool /*on*/) const { return true; } } // NameSpace KExiv2Iface diff --git a/src/kexiv2_p.cpp b/src/kexiv2_p.cpp index cb6b134..2a287ab 100644 --- a/src/kexiv2_p.cpp +++ b/src/kexiv2_p.cpp @@ -1,732 +1,723 @@ /** =========================================================== * @file * * This file is a part of KDE project * * * @date 2007-09-03 * @brief Exiv2 library interface for KDE * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2012 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "kexiv2_p.h" // C ANSI includes extern "C" { #include #ifndef _MSC_VER #include #else #include #endif } // Qt includes #include // Local includes #include "libkexiv2_debug.h" // Pragma directives to reduce warnings from Exiv2. #if !defined(__APPLE__) && defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #if defined(__APPLE__) && defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif namespace KExiv2Iface { KExiv2::Private::Private() : data(new KExiv2Data::Private) { writeRawFiles = false; updateFileTimeStamp = false; useXMPSidecar4Reading = false; metadataWritingMode = WRITETOIMAGEONLY; loadedFromSidecar = false; Exiv2::LogMsg::setHandler(KExiv2::Private::printExiv2MessageHandler); } KExiv2::Private::~Private() { } void KExiv2::Private::copyPrivateData(const Private* const other) { data = other->data; filePath = other->filePath; writeRawFiles = other->writeRawFiles; updateFileTimeStamp = other->updateFileTimeStamp; useXMPSidecar4Reading = other->useXMPSidecar4Reading; metadataWritingMode = other->metadataWritingMode; } bool KExiv2::Private::saveToXMPSidecar(const QFileInfo& finfo) const { QString filePath = KExiv2::sidecarFilePathForFile(finfo.filePath()); if (filePath.isEmpty()) { return false; } try { Exiv2::Image::AutoPtr image; image = Exiv2::ImageFactory::create(Exiv2::ImageType::xmp, (const char*)(QFile::encodeName(filePath).constData())); return saveOperations(finfo, image); } catch( Exiv2::Error& e ) { printExiv2ExceptionError(QString::fromLatin1("Cannot save metadata to XMP sidecar using Exiv2 "), e); return false; } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; return false; } } bool KExiv2::Private::saveToFile(const QFileInfo& finfo) const { if (!finfo.isWritable()) { qCDebug(LIBKEXIV2_LOG) << "File '" << finfo.fileName().toLatin1().constData() << "' is read only. Metadata not written."; return false; } QStringList rawTiffBasedSupported, rawTiffBasedNotSupported; - // Raw files supported by Exiv2 0.21 + // Raw files supported by Exiv2 0.23 rawTiffBasedSupported << QString::fromLatin1("dng") << QString::fromLatin1("nef") << QString::fromLatin1("pef") << QString::fromLatin1("orf") - << QString::fromLatin1("srw"); + << QString::fromLatin1("srw") + << QString::fromLatin1("cr2"); - if (Exiv2::testVersion(0,23,0)) - { - rawTiffBasedSupported << QString::fromLatin1("cr2"); - } - - // Raw files not supported by Exiv2 0.21 + // Raw files not supported by Exiv2 0.23 rawTiffBasedNotSupported << QString::fromLatin1("3fr") << QString::fromLatin1("arw") << QString::fromLatin1("dcr") << QString::fromLatin1("erf") << QString::fromLatin1("k25") << QString::fromLatin1("kdc") << QString::fromLatin1("mos") << QString::fromLatin1("raw") << QString::fromLatin1("sr2") << QString::fromLatin1("srf") << QString::fromLatin1("rw2"); - if (!Exiv2::testVersion(0,23,0)) - { - rawTiffBasedNotSupported << QString::fromLatin1("cr2"); - } - QString ext = finfo.suffix().toLower(); if (!writeRawFiles && (rawTiffBasedSupported.contains(ext) || rawTiffBasedNotSupported.contains(ext)) ) { qCDebug(LIBKEXIV2_LOG) << finfo.fileName() << "is a TIFF based RAW file, writing to such a file is disabled by current settings."; return false; } /* if (rawTiffBasedNotSupported.contains(ext)) { qCDebug(LIBKEXIV2_LOG) << finfo.fileName() << "is TIFF based RAW file not yet supported. Metadata not saved."; return false; } if (rawTiffBasedSupported.contains(ext) && !writeRawFiles) { qCDebug(LIBKEXIV2_LOG) << finfo.fileName() << "is TIFF based RAW file supported but writing mode is disabled. " << "Metadata not saved."; return false; } qCDebug(LIBKEXIV2_LOG) << "File Extension: " << ext << " is supported for writing mode"; bool ret = false; */ try { Exiv2::Image::AutoPtr image; image = Exiv2::ImageFactory::open((const char*)(QFile::encodeName(finfo.filePath()).constData())); return saveOperations(finfo, image); } catch( Exiv2::Error& e ) { printExiv2ExceptionError(QString::fromLatin1("Cannot save metadata to image using Exiv2 "), e); return false; } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; return false; } } bool KExiv2::Private::saveOperations(const QFileInfo& finfo, Exiv2::Image::AutoPtr image) const { try { Exiv2::AccessMode mode; bool wroteComment = false, wroteEXIF = false, wroteIPTC = false, wroteXMP = false; // We need to load target file metadata to merge with new one. It's mandatory with TIFF format: // like all tiff file structure is based on Exif. image->readMetadata(); // Image Comments --------------------------------- mode = image->checkMode(Exiv2::mdComment); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { image->setComment(imageComments()); wroteComment = true; } qCDebug(LIBKEXIV2_LOG) << "wroteComment: " << wroteComment; // Exif metadata ---------------------------------- mode = image->checkMode(Exiv2::mdExif); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { if (image->mimeType() == "image/tiff") { Exiv2::ExifData orgExif = image->exifData(); Exiv2::ExifData newExif; QStringList untouchedTags; // With tiff image we cannot overwrite whole Exif data as well, because // image data are stored in Exif container. We need to take a care about // to not lost image data. untouchedTags << QString::fromLatin1("Exif.Image.ImageWidth"); untouchedTags << QString::fromLatin1("Exif.Image.ImageLength"); untouchedTags << QString::fromLatin1("Exif.Image.BitsPerSample"); untouchedTags << QString::fromLatin1("Exif.Image.Compression"); untouchedTags << QString::fromLatin1("Exif.Image.PhotometricInterpretation"); untouchedTags << QString::fromLatin1("Exif.Image.FillOrder"); untouchedTags << QString::fromLatin1("Exif.Image.SamplesPerPixel"); untouchedTags << QString::fromLatin1("Exif.Image.StripOffsets"); untouchedTags << QString::fromLatin1("Exif.Image.RowsPerStrip"); untouchedTags << QString::fromLatin1("Exif.Image.StripByteCounts"); untouchedTags << QString::fromLatin1("Exif.Image.XResolution"); untouchedTags << QString::fromLatin1("Exif.Image.YResolution"); untouchedTags << QString::fromLatin1("Exif.Image.PlanarConfiguration"); untouchedTags << QString::fromLatin1("Exif.Image.ResolutionUnit"); for (Exiv2::ExifData::iterator it = orgExif.begin(); it != orgExif.end(); ++it) { if (untouchedTags.contains(QString::fromLatin1(it->key().c_str()))) { newExif[it->key().c_str()] = orgExif[it->key().c_str()]; } } Exiv2::ExifData readedExif = exifMetadata(); for (Exiv2::ExifData::iterator it = readedExif.begin(); it != readedExif.end(); ++it) { if (!untouchedTags.contains(QString::fromLatin1(it->key().c_str()))) { newExif[it->key().c_str()] = readedExif[it->key().c_str()]; } } image->setExifData(newExif); } else { image->setExifData(exifMetadata()); } wroteEXIF = true; } qCDebug(LIBKEXIV2_LOG) << "wroteEXIF: " << wroteEXIF; // Iptc metadata ---------------------------------- mode = image->checkMode(Exiv2::mdIptc); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { image->setIptcData(iptcMetadata()); wroteIPTC = true; } qCDebug(LIBKEXIV2_LOG) << "wroteIPTC: " << wroteIPTC; // Xmp metadata ----------------------------------- mode = image->checkMode(Exiv2::mdXmp); if ((mode == Exiv2::amWrite) || (mode == Exiv2::amReadWrite)) { #ifdef _XMP_SUPPORT_ image->setXmpData(xmpMetadata()); wroteXMP = true; #endif } qCDebug(LIBKEXIV2_LOG) << "wroteXMP: " << wroteXMP; if (!wroteComment && !wroteEXIF && !wroteIPTC && !wroteXMP) { qCDebug(LIBKEXIV2_LOG) << "Writing metadata is not supported for file" << finfo.fileName(); return false; } else if (!wroteEXIF || !wroteIPTC || !wroteXMP) { qCDebug(LIBKEXIV2_LOG) << "Support for writing metadata is limited for file" << finfo.fileName(); } if (!updateFileTimeStamp) { // Don't touch access and modification timestamp of file. struct stat st; struct utimbuf ut; int ret = ::stat(QFile::encodeName(filePath).constData(), &st); if (ret == 0) { ut.modtime = st.st_mtime; ut.actime = st.st_atime; } image->writeMetadata(); if (ret == 0) { ::utime(QFile::encodeName(filePath).constData(), &ut); } qCDebug(LIBKEXIV2_LOG) << "File time stamp restored"; } else { image->writeMetadata(); } return true; } catch( Exiv2::Error& e ) { printExiv2ExceptionError(QString::fromLatin1("Cannot save metadata using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } void KExiv2Data::Private::clear() { imageComments.clear(); exifMetadata.clear(); iptcMetadata.clear(); #ifdef _XMP_SUPPORT_ xmpMetadata.clear(); #endif } void KExiv2::Private::printExiv2ExceptionError(const QString& msg, Exiv2::Error& e) { std::string s(e.what()); qCCritical(LIBKEXIV2_LOG) << msg.toLatin1().constData() << " (Error #" << e.code() << ": " << s.c_str(); } void KExiv2::Private::printExiv2MessageHandler(int lvl, const char* msg) { qCDebug(LIBKEXIV2_LOG) << "Exiv2 (" << lvl << ") : " << msg; } QString KExiv2::Private::convertCommentValue(const Exiv2::Exifdatum& exifDatum) const { try { std::string comment; std::string charset; comment = exifDatum.toString(); // libexiv2 will prepend "charset=\"SomeCharset\" " if charset is specified // Before conversion to QString, we must know the charset, so we stay with std::string for a while if (comment.length() > 8 && comment.substr(0, 8) == "charset=") { // the prepended charset specification is followed by a blank std::string::size_type pos = comment.find_first_of(' '); if (pos != std::string::npos) { // extract string between the = and the blank charset = comment.substr(8, pos-8); // get the rest of the string after the charset specification comment = comment.substr(pos+1); } } if (charset == "\"Unicode\"") { return QString::fromUtf8(comment.data()); } else if (charset == "\"Jis\"") { QTextCodec* const codec = QTextCodec::codecForName("JIS7"); return codec->toUnicode(comment.c_str()); } else if (charset == "\"Ascii\"") { return QString::fromLatin1(comment.c_str()); } else { return detectEncodingAndDecode(comment); } } catch( Exiv2::Error& e ) { printExiv2ExceptionError(QString::fromLatin1("Cannot convert Comment using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return QString(); } QString KExiv2::Private::detectEncodingAndDecode(const std::string& value) const { // For charset autodetection, we could use sophisticated code // (Mozilla chardet, KHTML's autodetection, QTextCodec::codecForContent), // but that is probably too much. // We check for UTF8, Local encoding and ASCII. // Look like KEncodingDetector class can provide a full implementation for encoding detection. if (value.empty()) { return QString(); } if (isUtf8(value.c_str())) { return QString::fromUtf8(value.c_str()); } // Utf8 has a pretty unique byte pattern. // Thats not true for ASCII, it is not possible // to reliably autodetect different ISO-8859 charsets. // So we can use either local encoding, or latin1. return QString::fromLocal8Bit(value.c_str()); } bool KExiv2::Private::isUtf8(const char* const buffer) const { int i, n; register unsigned char c; bool gotone = false; if (!buffer) return true; // character never appears in text #define F 0 // character appears in plain ASCII text #define T 1 // character appears in ISO-8859 text #define I 2 // character appears in non-ISO extended ASCII (Mac, IBM PC) #define X 3 static const unsigned char text_chars[256] = { // BEL BS HT LF FF CR F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F, // 0x0X // ESC F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F, // 0x1X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x2X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x3X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x4X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x5X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, // 0x6X T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F, // 0x7X // NEL X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, // 0x8X X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, // 0x9X I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xaX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xbX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xcX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xdX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, // 0xeX I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I // 0xfX }; for (i = 0; (c = buffer[i]); ++i) { if ((c & 0x80) == 0) { // 0xxxxxxx is plain ASCII // Even if the whole file is valid UTF-8 sequences, // still reject it if it uses weird control characters. if (text_chars[c] != T) return false; } else if ((c & 0x40) == 0) { // 10xxxxxx never 1st byte return false; } else { // 11xxxxxx begins UTF-8 int following = 0; if ((c & 0x20) == 0) { // 110xxxxx following = 1; } else if ((c & 0x10) == 0) { // 1110xxxx following = 2; } else if ((c & 0x08) == 0) { // 11110xxx following = 3; } else if ((c & 0x04) == 0) { // 111110xx following = 4; } else if ((c & 0x02) == 0) { // 1111110x following = 5; } else { return false; } for (n = 0; n < following; ++n) { i++; if (!(c = buffer[i])) goto done; if ((c & 0x80) == 0 || (c & 0x40)) return false; } gotone = true; } } done: return gotone; // don't claim it's UTF-8 if it's all 7-bit. } #undef F #undef T #undef I #undef X int KExiv2::Private::getXMPTagsListFromPrefix(const QString& pf, KExiv2::TagsMap& tagsMap) const { int i = 0; #ifdef _XMP_SUPPORT_ try { QList tags; tags << Exiv2::XmpProperties::propertyList(pf.toLatin1().data()); for (QList::iterator it = tags.begin(); it != tags.end(); ++it) { while ( (*it) && !QString::fromLatin1((*it)->name_).isNull() ) { QString key = QLatin1String( Exiv2::XmpKey( pf.toLatin1().data(), (*it)->name_ ).key().c_str() ); QStringList values; values << QString::fromLatin1((*it)->name_) << QString::fromLatin1((*it)->title_) << QString::fromLatin1((*it)->desc_); tagsMap.insert(key, values); ++(*it); i++; } } } catch( Exiv2::Error& e ) { printExiv2ExceptionError(QString::fromLatin1("Cannot get Xmp tags list using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } #else Q_UNUSED(pf); Q_UNUSED(tagsMap); #endif // _XMP_SUPPORT_ return i; } #ifdef _XMP_SUPPORT_ void KExiv2::Private::loadSidecarData(Exiv2::Image::AutoPtr xmpsidecar) { // Having a sidecar is a special situation. // The sidecar data often "dominates", see in particular bug 309058 for important aspects: // If a field is removed from the sidecar, we must ignore (older) data for this field in the file. // First: Ignore file XMP, only use sidecar XMP xmpMetadata() = xmpsidecar->xmpData(); loadedFromSidecar = true; // EXIF // Four groups of properties are mapped between EXIF and XMP: // Date/Time, Description, Copyright, Creator // A few more tags are defined "writeback" tags in the XMP specification, the sidecar value therefore overrides the Exif value. // The rest is kept side-by-side. // (to understand, remember that the xmpsidecar's Exif data is actually XMP data mapped back to Exif) // Description, Copyright and Creator is dominated by the sidecar: Remove file Exif fields, if field not in XMP. ExifMergeHelper exifDominatedHelper; exifDominatedHelper << QLatin1String("Exif.Image.ImageDescription") << QLatin1String("Exif.Photo.UserComment") << QLatin1String("Exif.Image.Copyright") << QLatin1String("Exif.Image.Artist"); exifDominatedHelper.exclusiveMerge(xmpsidecar->exifData(), exifMetadata()); // Date/Time and "the few more" from the XMP spec are handled as writeback // Note that Date/Time mapping is slightly contradictory in latest specs. ExifMergeHelper exifWritebackHelper; exifWritebackHelper << QLatin1String("Exif.Image.DateTime") << QLatin1String("Exif.Image.DateTime") << QLatin1String("Exif.Photo.DateTimeOriginal") << QLatin1String("Exif.Photo.DateTimeDigitized") << QLatin1String("Exif.Image.Orientation") << QLatin1String("Exif.Image.XResolution") << QLatin1String("Exif.Image.YResolution") << QLatin1String("Exif.Image.ResolutionUnit") << QLatin1String("Exif.Image.Software") << QLatin1String("Exif.Photo.RelatedSoundFile"); exifWritebackHelper.mergeFields(xmpsidecar->exifData(), exifMetadata()); // IPTC // These fields cover almost all relevant IPTC data and are defined in the XMP specification for reconciliation. IptcMergeHelper iptcDominatedHelper; iptcDominatedHelper << QLatin1String("Iptc.Application2.ObjectName") << QLatin1String("Iptc.Application2.Urgency") << QLatin1String("Iptc.Application2.Category") << QLatin1String("Iptc.Application2.SuppCategory") << QLatin1String("Iptc.Application2.Keywords") << QLatin1String("Iptc.Application2.SubLocation") << QLatin1String("Iptc.Application2.SpecialInstructions") << QLatin1String("Iptc.Application2.Byline") << QLatin1String("Iptc.Application2.BylineTitle") << QLatin1String("Iptc.Application2.City") << QLatin1String("Iptc.Application2.ProvinceState") << QLatin1String("Iptc.Application2.CountryCode") << QLatin1String("Iptc.Application2.CountryName") << QLatin1String("Iptc.Application2.TransmissionReference") << QLatin1String("Iptc.Application2.Headline") << QLatin1String("Iptc.Application2.Credit") << QLatin1String("Iptc.Application2.Source") << QLatin1String("Iptc.Application2.Copyright") << QLatin1String("Iptc.Application2.Caption") << QLatin1String("Iptc.Application2.Writer"); iptcDominatedHelper.exclusiveMerge(xmpsidecar->iptcData(), iptcMetadata()); IptcMergeHelper iptcWritebackHelper; iptcWritebackHelper << QLatin1String("Iptc.Application2.DateCreated") << QLatin1String("Iptc.Application2.TimeCreated") << QLatin1String("Iptc.Application2.DigitizationDate") << QLatin1String("Iptc.Application2.DigitizationTime"); iptcWritebackHelper.mergeFields(xmpsidecar->iptcData(), iptcMetadata()); /* * TODO: Exiv2 (referring to 0.23) does not correctly synchronize all times values as given below. * Time values and their synchronization: * Original Date/Time – Creation date of the intellectual content (e.g. the photograph), rather than the creatio*n date of the content being shown Exif DateTimeOriginal (36867, 0x9003) and SubSecTimeOriginal (37521, 0x9291) IPTC DateCreated (IIM 2:55, 0x0237) and TimeCreated (IIM 2:60, 0x023C) XMP (photoshop:DateCreated) * Digitized Date/Time – Creation date of the digital representation Exif DateTimeDigitized (36868, 0x9004) and SubSecTimeDigitized (37522, 0x9292) IPTC DigitalCreationDate (IIM 2:62, 0x023E) and DigitalCreationTime (IIM 2:63, 0x023F) XMP (xmp:CreateDate) * Modification Date/Time – Modification date of the digital image file Exif DateTime (306, 0x132) and SubSecTime (37520, 0x9290) XMP (xmp:ModifyDate) */ } #endif // _XMP_SUPPORT_ } // NameSpace KExiv2Iface // Restore warnings #if !defined(__APPLE__) && defined(__GNUC__) #pragma GCC diagnostic pop #endif #if defined(__APPLE__) && defined(__clang__) #pragma clang diagnostic pop #endif diff --git a/src/kexiv2_p.h b/src/kexiv2_p.h index ddf8f6d..be90bf5 100644 --- a/src/kexiv2_p.h +++ b/src/kexiv2_p.h @@ -1,324 +1,303 @@ /** =========================================================== * @file * * This file is a part of KDE project * * * @date 2007-09-03 * @brief Exiv2 library interface for KDE * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2012 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef KEXIV2PRIVATE_H #define KEXIV2PRIVATE_H #include "kexiv2.h" // C++ includes #include #include #include #include #include #include #include #include // Qt includes #include #include #include #include #include // Exiv2 includes ------------------------------------------------------- // NOTE: All Exiv2 header must be stay there to not expose external source code to Exiv2 API // and reduce Exiv2 dependency to client code. // The pragmas are required to be able to catch exceptions thrown by libexiv2: // See http://gcc.gnu.org/wiki/Visibility, the section about c++ exceptions. // They are needed for all libexiv2 versions that do not care about visibility. #ifdef __GNUC__ #pragma GCC visibility push(default) #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include // Check if Exiv2 support XMP #ifdef EXV_HAVE_XMP_TOOLKIT # define _XMP_SUPPORT_ 1 #endif -// Make sure an EXIV2_TEST_VERSION macro exists: - -#ifdef EXIV2_VERSION -# ifndef EXIV2_TEST_VERSION -# define EXIV2_TEST_VERSION(major,minor,patch) \ - ( EXIV2_VERSION >= EXIV2_MAKE_VERSION(major,minor,patch) ) -# endif -#else -# define EXIV2_TEST_VERSION(major,minor,patch) (false) -#endif - // With exiv2 > 0.20.0, all makernote header files have been removed to increase binary compatibility. // See Exiv2 bugzilla entry http://dev.exiv2.org/issues/719 // and wiki topic http://dev.exiv2.org/boards/3/topics/583 #ifdef __GNUC__ #pragma GCC visibility pop #endif // End of Exiv2 headers ------------------------------------------------------ namespace KExiv2Iface { class Q_DECL_HIDDEN KExiv2Data::Private : public QSharedData { public: void clear(); public: std::string imageComments; Exiv2::ExifData exifMetadata; Exiv2::IptcData iptcMetadata; #ifdef _XMP_SUPPORT_ Exiv2::XmpData xmpMetadata; #endif }; // -------------------------------------------------------------------------- class Q_DECL_HIDDEN KExiv2::Private { public: Private(); ~Private(); void copyPrivateData(const Private* const other); bool saveToXMPSidecar(const QFileInfo& finfo) const; bool saveToFile(const QFileInfo& finfo) const; bool saveOperations(const QFileInfo& finfo, Exiv2::Image::AutoPtr image) const; /** Wrapper method to convert a Comments content to a QString. */ QString convertCommentValue(const Exiv2::Exifdatum& exifDatum) const; /** Charset autodetection to convert a string to a QString. */ QString detectEncodingAndDecode(const std::string& value) const; /** UTF8 autodetection from a string. */ bool isUtf8(const char* const buffer) const; int getXMPTagsListFromPrefix(const QString& pf, KExiv2::TagsMap& tagsMap) const; const Exiv2::ExifData& exifMetadata() const { return data.constData()->exifMetadata; } const Exiv2::IptcData& iptcMetadata() const { return data.constData()->iptcMetadata; } const std::string& imageComments() const { return data.constData()->imageComments; } #ifdef _XMP_SUPPORT_ const Exiv2::XmpData& xmpMetadata() const { return data.constData()->xmpMetadata; } #endif Exiv2::ExifData& exifMetadata() { return data.data()->exifMetadata; } Exiv2::IptcData& iptcMetadata() { return data.data()->iptcMetadata; } std::string& imageComments() { return data.data()->imageComments; } #ifdef _XMP_SUPPORT_ Exiv2::XmpData& xmpMetadata() { return data.data()->xmpMetadata; } void loadSidecarData(Exiv2::Image::AutoPtr xmpsidecar); #endif public: /** Generic method to print the Exiv2 C++ Exception error message from 'e'. * 'msg' string is printed using kDebug rules.. */ static void printExiv2ExceptionError(const QString& msg, Exiv2::Error& e); /** Generic method to print debug message from Exiv2. * 'msg' string is printed using kDebug rules. 'lvl' is the debug level of Exiv2 message. */ static void printExiv2MessageHandler(int lvl, const char* msg); public: bool writeRawFiles; bool updateFileTimeStamp; bool useXMPSidecar4Reading; /// A mode from #MetadataWritingMode enum. int metadataWritingMode; /// XMP, and parts of EXIF/IPTC, were loaded from an XMP sidecar file bool loadedFromSidecar; QString filePath; QSize pixelSize; QString mimeType; QSharedDataPointer data; }; // -------------------------------------------------------------------------------------------- template > class MergeHelper { public: KeyStringList keys; MergeHelper& operator<<(const KeyString& key) { keys << key; return *this; } /** * Merge two (Exif,IPTC,Xmp)Data packages, where the result is stored in dest * and fields from src take precedence over existing data from dest. */ void mergeAll(const Data& src, Data& dest) { for (typename Data::const_iterator it = src.begin(); it != src.end(); ++it) { typename Data::iterator destIt = dest.findKey(Key(it->key())); if (destIt == dest.end()) { dest.add(*it); } else { *destIt = *it; } } } /** * Merge two (Exif,IPTC,Xmp)Data packages, the result is stored in dest. * Only keys in keys are considered for merging. * Fields from src take precedence over existing data from dest. */ void mergeFields(const Data& src, Data& dest) { foreach (const KeyString& keyString, keys) { Key key(keyString.latin1()); typename Data::const_iterator it = src.findKey(key); if (it == src.end()) { continue; } typename Data::iterator destIt = dest.findKey(key); if (destIt == dest.end()) { dest.add(*it); } else { *destIt = *it; } } } /** * Merge two (Exif,IPTC,Xmp)Data packages, the result is stored in dest. * The following steps apply only to keys in "keys": * The result is determined by src. * Keys must exist in src to kept in dest. * Fields from src take precedence over existing data from dest. */ void exclusiveMerge(const Data& src, Data& dest) { foreach (const KeyString& keyString, keys) { Key key(keyString.latin1()); typename Data::const_iterator it = src.findKey(key); typename Data::iterator destIt = dest.findKey(key); if (destIt == dest.end()) { if (it != src.end()) { dest.add(*it); } } else { if (it == src.end()) { dest.erase(destIt); } else { *destIt = *it; } } } } }; class ExifMergeHelper : public MergeHelper { }; class IptcMergeHelper : public MergeHelper { }; #ifdef _XMP_SUPPORT_ class XmpMergeHelper : public MergeHelper { }; #endif } // NameSpace KExiv2Iface #endif // KEXIV2PRIVATE_H diff --git a/src/kexiv2exif.cpp b/src/kexiv2exif.cpp index 11c8948..52cd1a4 100644 --- a/src/kexiv2exif.cpp +++ b/src/kexiv2exif.cpp @@ -1,1161 +1,1165 @@ /** =========================================================== * @file * * This file is a part of KDE project * * * @date 2006-09-15 * @brief Exif manipulation methods * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2012 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "kexiv2.h" #include "kexiv2_p.h" // C++ includes #include // Qt includes #include #include // Local includes #include "rotationmatrix.h" #include "libkexiv2_debug.h" namespace KExiv2Iface { bool KExiv2::canWriteExif(const QString& filePath) { try { Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*) (QFile::encodeName(filePath).constData())); Exiv2::AccessMode mode = image->checkMode(Exiv2::mdExif); return (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite); } catch( Exiv2::Error& e ) { std::string s(e.what()); qCCritical(LIBKEXIV2_LOG) << "Cannot check Exif access mode using Exiv2 (Error #" << e.code() << ": " << s.c_str() << ")"; } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } bool KExiv2::hasExif() const { return !d->exifMetadata().empty(); } bool KExiv2::clearExif() const { try { d->exifMetadata().clear(); return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot clear Exif data using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } QByteArray KExiv2::getExifEncoded(bool addExifHeader) const { try { if (!d->exifMetadata().empty()) { QByteArray data; Exiv2::ExifData& exif = d->exifMetadata(); Exiv2::Blob blob; Exiv2::ExifParser::encode(blob, Exiv2::bigEndian, exif); QByteArray ba((const char*)&blob[0], blob.size()); if (addExifHeader) { const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; data.resize(ba.size() + sizeof(ExifHeader)); memcpy(data.data(), ExifHeader, sizeof(ExifHeader)); memcpy(data.data() + sizeof(ExifHeader), ba.data(), ba.size()); } else { data = ba; } return data; } } catch( Exiv2::Error& e ) { if (!d->filePath.isEmpty()) qCDebug(LIBKEXIV2_LOG) << "From file " << d->filePath.toLatin1().constData(); d->printExiv2ExceptionError(QString::fromLatin1("Cannot get Exif data using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return QByteArray(); } bool KExiv2::setExif(const QByteArray& data) const { try { if (!data.isEmpty()) { Exiv2::ExifParser::decode(d->exifMetadata(), (const Exiv2::byte*)data.data(), data.size()); return (!d->exifMetadata().empty()); } } catch( Exiv2::Error& e ) { if (!d->filePath.isEmpty()) qCCritical(LIBKEXIV2_LOG) << "From file " << d->filePath.toLatin1().constData(); d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Exif data using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } KExiv2::MetaDataMap KExiv2::getExifTagsDataList(const QStringList& exifKeysFilter, bool invertSelection) const { if (d->exifMetadata().empty()) return MetaDataMap(); try { Exiv2::ExifData exifData = d->exifMetadata(); exifData.sortByKey(); QString ifDItemName; MetaDataMap metaDataMap; for (Exiv2::ExifData::iterator md = exifData.begin(); md != exifData.end(); ++md) { QString key = QString::fromLatin1(md->key().c_str()); // Decode the tag value with a user friendly output. QString tagValue; if (key == QString::fromLatin1("Exif.Photo.UserComment")) { tagValue = d->convertCommentValue(*md); } else if (key == QString::fromLatin1("Exif.Image.0x935c")) { tagValue = QString::number(md->value().size()); } else { std::ostringstream os; os << *md; // Exif tag contents can be an translated strings, no only simple ascii. tagValue = QString::fromLocal8Bit(os.str().c_str()); } tagValue.replace(QString::fromLatin1("\n"), QString::fromLatin1(" ")); // We apply a filter to get only the Exif tags that we need. if (!exifKeysFilter.isEmpty()) { if (!invertSelection) { if (exifKeysFilter.contains(key.section(QString::fromLatin1("."), 1, 1))) metaDataMap.insert(key, tagValue); } else { if (!exifKeysFilter.contains(key.section(QString::fromLatin1("."), 1, 1))) metaDataMap.insert(key, tagValue); } } else // else no filter at all. { metaDataMap.insert(key, tagValue); } } return metaDataMap; } catch (Exiv2::Error& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot parse EXIF metadata using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return MetaDataMap(); } QString KExiv2::getExifComment() const { try { if (!d->exifMetadata().empty()) { Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifKey key("Exif.Photo.UserComment"); Exiv2::ExifData::iterator it = exifData.findKey(key); if (it != exifData.end()) { QString exifComment = d->convertCommentValue(*it); // some cameras fill the UserComment with whitespace if (!exifComment.isEmpty() && !exifComment.trimmed().isEmpty()) return exifComment; } Exiv2::ExifKey key2("Exif.Image.ImageDescription"); Exiv2::ExifData::iterator it2 = exifData.findKey(key2); if (it2 != exifData.end()) { QString exifComment = d->convertCommentValue(*it2); // Some cameras fill in nonsense default values QStringList blackList; blackList << QString::fromLatin1("SONY DSC"); // + whitespace blackList << QString::fromLatin1("OLYMPUS DIGITAL CAMERA"); blackList << QString::fromLatin1("MINOLTA DIGITAL CAMERA"); QString trimmedComment = exifComment.trimmed(); // some cameras fill the UserComment with whitespace if (!exifComment.isEmpty() && !trimmedComment.isEmpty() && !blackList.contains(trimmedComment)) return exifComment; } } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif User Comment using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return QString(); } static bool is7BitAscii(const QByteArray& s) { const int size = s.size(); for (int i=0; icanEncode(comment)) { // We know it's in the ISO-8859-1 8bit range. // Check if it's in the ASCII 7bit range if (is7BitAscii(comment.toLatin1())) { // write as ASCII std::string exifComment("charset=\"Ascii\" "); exifComment += comment.toLatin1().constData(); d->exifMetadata()["Exif.Photo.UserComment"] = exifComment; return true; } } // write as Unicode (UCS-2) std::string exifComment("charset=\"Unicode\" "); exifComment += comment.toUtf8().constData(); d->exifMetadata()["Exif.Photo.UserComment"] = exifComment; } return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Exif Comment using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } QString KExiv2::getExifTagTitle(const char* exifTagName) { try { std::string exifkey(exifTagName); Exiv2::ExifKey ek(exifkey); return QString::fromLocal8Bit( ek.tagLabel().c_str() ); } catch (Exiv2::Error& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot get metadata tag title using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return QString(); } QString KExiv2::getExifTagDescription(const char* exifTagName) { try { std::string exifkey(exifTagName); Exiv2::ExifKey ek(exifkey); return QString::fromLocal8Bit( ek.tagDesc().c_str() ); } catch (Exiv2::Error& e) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot get metadata tag description using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return QString(); } bool KExiv2::removeExifTag(const char* exifTagName, bool setProgramName) const { if (!setProgramId(setProgramName)) return false; try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData::iterator it = d->exifMetadata().findKey(exifKey); if (it != d->exifMetadata().end()) { d->exifMetadata().erase(it); return true; } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot remove Exif tag using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } bool KExiv2::getExifTagRational(const char* exifTagName, long int& num, long int& den, int component) const { try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::iterator it = exifData.findKey(exifKey); if (it != exifData.end()) { num = (*it).toRational(component).first; den = (*it).toRational(component).second; return true; } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif Rational value from key '%1' into image using Exiv2 ").arg(QString::fromLatin1(exifTagName)), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } bool KExiv2::setExifTagLong(const char* exifTagName, long val, bool setProgramName) const { if (!setProgramId(setProgramName)) return false; try { d->exifMetadata()[exifTagName] = static_cast(val); return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Exif tag long value into image using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } bool KExiv2::setExifTagRational(const char* exifTagName, long int num, long int den, bool setProgramName) const { if (!setProgramId(setProgramName)) return false; try { d->exifMetadata()[exifTagName] = Exiv2::Rational(num, den); return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Exif tag rational value into image using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } bool KExiv2::setExifTagData(const char* exifTagName, const QByteArray& data, bool setProgramName) const { if (data.isEmpty()) return false; if (!setProgramId(setProgramName)) return false; try { Exiv2::DataValue val((Exiv2::byte*)data.data(), data.size()); d->exifMetadata()[exifTagName] = val; return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Exif tag data into image using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } bool KExiv2::setExifTagVariant(const char* exifTagName, const QVariant& val, bool rationalWantSmallDenominator, bool setProgramName) const { switch (val.type()) { case QVariant::Int: case QVariant::UInt: case QVariant::Bool: case QVariant::LongLong: case QVariant::ULongLong: return setExifTagLong(exifTagName, val.toInt(), setProgramName); case QVariant::Double: { long num, den; if (rationalWantSmallDenominator) convertToRationalSmallDenominator(val.toDouble(), &num, &den); else convertToRational(val.toDouble(), &num, &den, 4); return setExifTagRational(exifTagName, num, den, setProgramName); } case QVariant::List: { long num = 0, den = 1; QList list = val.toList(); if (list.size() >= 1) num = list[0].toInt(); if (list.size() >= 2) den = list[1].toInt(); return setExifTagRational(exifTagName, num, den, setProgramName); } case QVariant::Date: case QVariant::DateTime: { QDateTime dateTime = val.toDateTime(); if(!dateTime.isValid()) return false; if (!setProgramId(setProgramName)) return false; try { const std::string &exifdatetime(dateTime.toString(QString::fromLatin1("yyyy:MM:dd hh:mm:ss")).toLatin1().constData()); d->exifMetadata()[exifTagName] = exifdatetime; } catch( Exiv2::Error &e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Date & Time in image using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } case QVariant::String: case QVariant::Char: return setExifTagString(exifTagName, val.toString(), setProgramName); case QVariant::ByteArray: return setExifTagData(exifTagName, val.toByteArray(), setProgramName); default: break; } return false; } QString KExiv2::createExifUserStringFromValue(const char* exifTagName, const QVariant& val, bool escapeCR) { try { Exiv2::ExifKey key(exifTagName); Exiv2::Exifdatum datum(key); switch (val.type()) { case QVariant::Int: case QVariant::Bool: case QVariant::LongLong: case QVariant::ULongLong: datum = (int32_t)val.toInt(); break; case QVariant::UInt: datum = (uint32_t)val.toUInt(); break; case QVariant::Double: { long num, den; convertToRationalSmallDenominator(val.toDouble(), &num, &den); Exiv2::Rational rational; rational.first = num; rational.second = den; datum = rational; break; } case QVariant::List: { long num = 0, den = 1; QList list = val.toList(); if (list.size() >= 1) num = list[0].toInt(); if (list.size() >= 2) den = list[1].toInt(); Exiv2::Rational rational; rational.first = num; rational.second = den; datum = rational; break; } case QVariant::Date: case QVariant::DateTime: { QDateTime dateTime = val.toDateTime(); if(!dateTime.isValid()) break; const std::string &exifdatetime(dateTime.toString(QString::fromLatin1("yyyy:MM:dd hh:mm:ss")).toLatin1().constData()); datum = exifdatetime; break; } case QVariant::String: case QVariant::Char: datum = (std::string)val.toString().toLatin1().constData(); break; default: break; } std::ostringstream os; os << datum; QString tagValue = QString::fromLocal8Bit(os.str().c_str()); if (escapeCR) tagValue.replace(QString::fromLatin1("\n"), QString::fromLatin1(" ")); return tagValue; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Iptc tag string into image using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return QString(); } bool KExiv2::getExifTagLong(const char* exifTagName, long& val) const { return getExifTagLong(exifTagName, val, 0); } bool KExiv2::getExifTagLong(const char* exifTagName, long& val, int component) const { try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::iterator it = exifData.findKey(exifKey); if (it != exifData.end() && it->count() > 0) { val = it->toLong(component); return true; } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image using Exiv2 ").arg(QString::fromLatin1(exifTagName)), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } QByteArray KExiv2::getExifTagData(const char* exifTagName) const { try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::iterator it = exifData.findKey(exifKey); if (it != exifData.end()) { char* const s = new char[(*it).size()]; (*it).copy((Exiv2::byte*)s, Exiv2::bigEndian); QByteArray data(s, (*it).size()); delete[] s; return data; } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image using Exiv2 ").arg(QString::fromLatin1(exifTagName)), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return QByteArray(); } QVariant KExiv2::getExifTagVariant(const char* exifTagName, bool rationalAsListOfInts, bool stringEscapeCR, int component) const { try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::iterator it = exifData.findKey(exifKey); if (it != exifData.end()) { switch (it->typeId()) { case Exiv2::unsignedByte: case Exiv2::unsignedShort: case Exiv2::unsignedLong: case Exiv2::signedShort: case Exiv2::signedLong: if (it->count() > component) return QVariant((int)it->toLong(component)); else return QVariant(QVariant::Int); case Exiv2::unsignedRational: case Exiv2::signedRational: if (rationalAsListOfInts) { if (it->count() <= component) return QVariant(QVariant::List); QList list; list << (*it).toRational(component).first; list << (*it).toRational(component).second; return QVariant(list); } else { if (it->count() <= component) return QVariant(QVariant::Double); // prefer double precision double num = (*it).toRational(component).first; double den = (*it).toRational(component).second; if (den == 0.0) return QVariant(QVariant::Double); return QVariant(num / den); } case Exiv2::date: case Exiv2::time: { QDateTime dateTime = QDateTime::fromString(QString::fromLatin1(it->toString().c_str()), Qt::ISODate); return QVariant(dateTime); } case Exiv2::asciiString: case Exiv2::comment: case Exiv2::string: { std::ostringstream os; os << *it; QString tagValue = QString::fromLocal8Bit(os.str().c_str()); if (stringEscapeCR) tagValue.replace(QString::fromLatin1("\n"), QString::fromLatin1(" ")); return QVariant(tagValue); } default: break; } } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' in the image using Exiv2 ").arg(QString::fromLatin1(exifTagName)), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return QVariant(); } QString KExiv2::getExifTagString(const char* exifTagName, bool escapeCR) const { try { Exiv2::ExifKey exifKey(exifTagName); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::iterator it = exifData.findKey(exifKey); if (it != exifData.end()) { // See B.K.O #184156 comment #13 std::string val = it->print(&exifData); QString tagValue = QString::fromLocal8Bit(val.c_str()); if (escapeCR) tagValue.replace(QString::fromLatin1("\n"), QString::fromLatin1(" ")); return tagValue; } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image using Exiv2 ").arg(QString::fromLatin1(exifTagName)), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return QString(); } bool KExiv2::setExifTagString(const char* exifTagName, const QString& value, bool setProgramName) const { if (!setProgramId(setProgramName)) return false; try { d->exifMetadata()[exifTagName] = std::string(value.toLatin1().constData()); return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Exif tag string into image using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } QImage KExiv2::getExifThumbnail(bool fixOrientation) const { QImage thumbnail; if (d->exifMetadata().empty()) return thumbnail; try { Exiv2::ExifThumbC thumb(d->exifMetadata()); Exiv2::DataBuf const c1 = thumb.copy(); thumbnail.loadFromData(c1.pData_, c1.size_); if (!thumbnail.isNull()) { if (fixOrientation) { Exiv2::ExifKey key1("Exif.Thumbnail.Orientation"); Exiv2::ExifKey key2("Exif.Image.Orientation"); Exiv2::ExifData exifData(d->exifMetadata()); Exiv2::ExifData::iterator it = exifData.findKey(key1); if (it == exifData.end()) it = exifData.findKey(key2); if (it != exifData.end() && it->count()) { long orientation = it->toLong(); qCDebug(LIBKEXIV2_LOG) << "Exif Thumbnail Orientation: " << (int)orientation; rotateExifQImage(thumbnail, (ImageOrientation)orientation); } return thumbnail; } } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot get Exif Thumbnail using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return thumbnail; } bool KExiv2::rotateExifQImage(QImage& image, ImageOrientation orientation) const { QMatrix matrix = RotationMatrix::toMatrix(orientation); if ((orientation != ORIENTATION_NORMAL) && (orientation != ORIENTATION_UNSPECIFIED)) { image = image.transformed(matrix); return true; } return false; } bool KExiv2::setExifThumbnail(const QImage& thumbImage, bool setProgramName) const { if (!setProgramId(setProgramName)) return false; if (thumbImage.isNull()) { return removeExifThumbnail(); } try { QByteArray data; QBuffer buffer(&data); buffer.open(QIODevice::WriteOnly); thumbImage.save(&buffer, "JPEG"); Exiv2::ExifThumb thumb(d->exifMetadata()); thumb.setJpegThumbnail((Exiv2::byte *)data.data(), data.size()); return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Exif Thumbnail using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } bool KExiv2::setTiffThumbnail(const QImage& thumbImage, bool setProgramName) const { if (!setProgramId(setProgramName)) return false; removeExifThumbnail(); try { // Make sure IFD0 is explicitly marked as a main image Exiv2::ExifData::const_iterator pos = d->exifMetadata().findKey(Exiv2::ExifKey("Exif.Image.NewSubfileType")); if (pos == d->exifMetadata().end() || pos->count() != 1 || pos->toLong() != 0) { +#if EXIV2_TEST_VERSION(0,27,0) + throw Exiv2::Error(Exiv2::kerErrorMessage, "Exif.Image.NewSubfileType missing or not set as main image"); +#else throw Exiv2::Error(1, "Exif.Image.NewSubfileType missing or not set as main image"); +#endif } // Remove sub-IFD tags std::string subImage1("SubImage1"); for (Exiv2::ExifData::iterator md = d->exifMetadata().begin(); md != d->exifMetadata().end();) { if (md->groupName() == subImage1) md = d->exifMetadata().erase(md); else ++md; } if (!thumbImage.isNull()) { // Set thumbnail tags QByteArray data; QBuffer buffer(&data); buffer.open(QIODevice::WriteOnly); thumbImage.save(&buffer, "JPEG"); Exiv2::DataBuf buf((Exiv2::byte *)data.data(), data.size()); Exiv2::ULongValue val; val.read("0"); val.setDataArea(buf.pData_, buf.size_); d->exifMetadata()["Exif.SubImage1.JPEGInterchangeFormat"] = val; d->exifMetadata()["Exif.SubImage1.JPEGInterchangeFormatLength"] = uint32_t(buf.size_); d->exifMetadata()["Exif.SubImage1.Compression"] = uint16_t(6); // JPEG (old-style) d->exifMetadata()["Exif.SubImage1.NewSubfileType"] = uint32_t(1); // Thumbnail image return true; } } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot set TIFF Thumbnail using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } bool KExiv2::removeExifThumbnail() const { try { // Remove all IFD0 subimages. Exiv2::ExifThumb thumb(d->exifMetadata()); thumb.erase(); return true; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot remove Exif Thumbnail using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return false; } KExiv2::TagsMap KExiv2::getStdExifTagsList() const { try { QList tags; TagsMap tagsMap; const Exiv2::GroupInfo* gi = Exiv2::ExifTags::groupList(); while (gi->tagList_ != 0) { if (QString::fromLatin1(gi->ifdName_) != QString::fromLatin1("Makernote")) { Exiv2::TagListFct tl = gi->tagList_; const Exiv2::TagInfo* ti = tl(); while (ti->tag_ != 0xFFFF) { tags << ti; ++ti; } } ++gi; } for (QList::iterator it = tags.begin(); it != tags.end(); ++it) { do { const Exiv2::TagInfo* const ti = *it; QString key = QLatin1String(Exiv2::ExifKey(*ti).key().c_str()); QStringList values; values << QString::fromLatin1(ti->name_) << QString::fromLatin1(ti->title_) << QString::fromLatin1(ti->desc_); tagsMap.insert(key, values); ++(*it); } while((*it)->tag_ != 0xffff); } return tagsMap; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot get Exif Tags list using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return TagsMap(); } KExiv2::TagsMap KExiv2::getMakernoteTagsList() const { try { QList tags; TagsMap tagsMap; const Exiv2::GroupInfo* gi = Exiv2::ExifTags::groupList(); while (gi->tagList_ != 0) { if (QString::fromLatin1(gi->ifdName_) == QString::fromLatin1("Makernote")) { Exiv2::TagListFct tl = gi->tagList_; const Exiv2::TagInfo* ti = tl(); while (ti->tag_ != 0xFFFF) { tags << ti; ++ti; } } ++gi; } for (QList::iterator it = tags.begin(); it != tags.end(); ++it) { do { const Exiv2::TagInfo* const ti = *it; QString key = QLatin1String(Exiv2::ExifKey(*ti).key().c_str()); QStringList values; values << QString::fromLatin1(ti->name_) << QString::fromLatin1(ti->title_) << QString::fromLatin1(ti->desc_); tagsMap.insert(key, values); ++(*it); } while((*it)->tag_ != 0xffff); } return tagsMap; } catch( Exiv2::Error& e ) { d->printExiv2ExceptionError(QString::fromLatin1("Cannot get Makernote Tags list using Exiv2 "), e); } catch(...) { qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2"; } return TagsMap(); } } // NameSpace KExiv2Iface