diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 482a7147621..cf53d6a13de 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -56,7 +56,7 @@ jobs: - name: Install Dependencies run: | brew update - brew install cmake ninja ccache geographiclib SDL2 exiv2 shapelib + brew install cmake ninja ccache geographiclib SDL2 exiv2 expat zlib shapelib - name: Install Gstreamer run: | diff --git a/cmake/find-modules/FindLibExiv2.cmake b/cmake/find-modules/FindLibExiv2.cmake new file mode 100644 index 00000000000..ef568db0f8b --- /dev/null +++ b/cmake/find-modules/FindLibExiv2.cmake @@ -0,0 +1,103 @@ +# SPDX-FileCopyrightText: 2018 Christophe Giboudeaux +# SPDX-FileCopyrightText: 2010 Alexander Neundorf +# SPDX-FileCopyrightText: 2008 Gilles Caulier +# +# SPDX-License-Identifier: BSD-3-Clause + +#[=======================================================================[.rst: +FindLibExiv2 +------------ + +Try to find the Exiv2 library. + +This will define the following variables: + +``LibExiv2_FOUND`` + True if (the requested version of) Exiv2 is available + +``LibExiv2_VERSION`` + The version of Exiv2 + +``LibExiv2_INCLUDE_DIRS`` + The include dirs of Exiv2 for use with target_include_directories() + +``LibExiv2_LIBRARIES`` + The Exiv2 library for use with target_link_libraries(). + This can be passed to target_link_libraries() instead of + the ``LibExiv2::LibExiv2`` target + +If ``LibExiv2_FOUND`` is TRUE, it will also define the following imported +target: + +``LibExiv2::LibExiv2`` + The Exiv2 library + +In general we recommend using the imported target, as it is easier to use. +Bear in mind, however, that if the target is in the link interface of an +exported library, it must be made available by the package config file. + +Since 5.53.0. +#]=======================================================================] + +# find_package(exiv2 REQUIRED CONFIG NAMES exiv2) +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]+U?\\)" EXIV2_MAJOR_VERSION_MATCH ${_exiv_version_file_content}) + string(REGEX MATCH "#define EXIV2_MINOR_VERSION[ ]+\\([0-9]+U?\\)" EXIV2_MINOR_VERSION_MATCH ${_exiv_version_file_content}) + string(REGEX MATCH "#define EXIV2_PATCH_VERSION[ ]+\\([0-9]+U?\\)" EXIV2_PATCH_VERSION_MATCH ${_exiv_version_file_content}) + if(EXIV2_MAJOR_VERSION_MATCH) + string(REGEX REPLACE ".*_MAJOR_VERSION[ ]+\\(([0-9]*)U?\\)" "\\1" EXIV2_MAJOR_VERSION ${EXIV2_MAJOR_VERSION_MATCH}) + string(REGEX REPLACE ".*_MINOR_VERSION[ ]+\\(([0-9]*)U?\\)" "\\1" EXIV2_MINOR_VERSION ${EXIV2_MINOR_VERSION_MATCH}) + string(REGEX REPLACE ".*_PATCH_VERSION[ ]+\\(([0-9]*)U?\\)" "\\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}" + ) + if (LibExiv2_VERSION VERSION_LESS 0.28.0) + # exiv2 0.27 or older still uses std::auto_ptr, which is no longer available + # by default when using newer C++ versions + set_target_properties(LibExiv2::LibExiv2 PROPERTIES + INTERFACE_COMPILE_DEFINITIONS "_LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR=1;_HAS_AUTO_PTR_ETC=1" + ) + endif() +endif() + +include(FeatureSummary) +set_package_properties(LibExiv2 PROPERTIES + URL "https://www.exiv2.org" + DESCRIPTION "Image metadata support" +) diff --git a/src/AnalyzeView/CMakeLists.txt b/src/AnalyzeView/CMakeLists.txt index 4531ad9295b..9cd572d2113 100644 --- a/src/AnalyzeView/CMakeLists.txt +++ b/src/AnalyzeView/CMakeLists.txt @@ -1,8 +1,6 @@ find_package(Qt6 REQUIRED COMPONENTS Core Charts Gui Qml QmlIntegration) qt_add_library(AnalyzeView STATIC - ExifParser.cc - ExifParser.h GeoTagController.cc GeoTagController.h GeoTagWorker.cc @@ -25,24 +23,13 @@ qt_add_library(AnalyzeView STATIC MAVLinkSystem.h PX4LogParser.cc PX4LogParser.h - ULogParser.cc - ULogParser.h ) -include(FetchContent) -FetchContent_Declare(ulogparser - GIT_REPOSITORY https://github.com/PX4/ulog_cpp.git - GIT_TAG main - GIT_SHALLOW TRUE -) -FetchContent_MakeAvailable(ulogparser) - target_link_libraries(AnalyzeView PRIVATE Qt6::Charts Qt6::Gui Qt6::Qml - ulog_cpp::ulog_cpp FactSystem QGC Settings @@ -55,28 +42,61 @@ target_link_libraries(AnalyzeView QmlControls ) -set(MINIMUM_EXIV2_VERSION 0.28.3) +target_include_directories(AnalyzeView PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +#===========================================================================# + +include(FetchContent) +FetchContent_Declare(ulogparser + GIT_REPOSITORY https://github.com/PX4/ulog_cpp.git + GIT_TAG main + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(ulogparser) +if(TARGET ulog_cpp::ulog_cpp) + target_link_libraries(AnalyzeView PRIVATE ulog_cpp::ulog_cpp) + target_sources(AnalyzeView PRIVATE ULogParser.cc ULogParser.h) +endif() + +#===========================================================================# + +set(MINIMUM_EXIV2_VERSION 0.28.2) if(NOT QGC_BUILD_DEPENDENCIES) - find_package(Exiv2 ${MINIMUM_EXIV2_VERSION} CONFIG) - if(Exiv2_FOUND) - message(STATUS "Found Exiv2 ${Exiv2_VERSION_STRING}") - target_link_libraries(AnalyzeView PRIVATE Exiv2::exiv2lib) + find_package(LibExiv2 ${MINIMUM_EXIV2_VERSION}) + if(LibExiv2_FOUND AND TARGET LibExiv2::LibExiv2) + target_link_libraries(AnalyzeView PRIVATE LibExiv2::LibExiv2) else() - find_package(PkgConfig) - if(PkgConfig_FOUND) - pkg_check_modules(Exiv2 IMPORTED_TARGET exiv2>=${MINIMUM_EXIV2_VERSION}) - if(Exiv2_FOUND) - message(STATUS "Found Exiv2 ${Exiv2_VERSION}") - target_link_libraries(AnalyzeView PRIVATE PkgConfig::Exiv2) - endif() + find_package(exiv2 ${MINIMUM_EXIV2_VERSION} CONFIG NAMES exiv2) + if(exiv2_FOUND) + target_link_libraries(AnalyzeView PRIVATE Exiv2::exiv2lib) endif() endif() endif() -if(NOT Exiv2_FOUND) +if(NOT exiv2_FOUND AND NOT LibExiv2_FOUND) message(STATUS "Building Exiv2") + include(FetchContent) + + # TODO: XMP Compatibility + # FetchContent_Declare(EXPAT + # GIT_REPOSITORY https://github.com/libexpat/libexpat.git + # GIT_TAG R_2_6_3 + # GIT_SHALLOW TRUE + # GIT_PROGRESS TRUE + # SOURCE_SUBDIR expat + # ) + # set(EXPAT_BUILD_EXAMPLES OFF CACHE INTERNAL "" FORCE) + # set(EXPAT_BUILD_TESTS OFF CACHE INTERNAL "" FORCE) + # set(EXPAT_BUILD_TOOLS OFF CACHE INTERNAL "" FORCE) + # FetchContent_MakeAvailable(EXPAT) + + # find_package(Iconv) + # if(ICONV_FOUND) + # target_link_libraries(AnalyzeView PRIVATE Iconv::Iconv) + # endif() + FetchContent_Declare(EXIV2 GIT_REPOSITORY https://github.com/Exiv2/exiv2.git GIT_TAG v0.28.3 @@ -88,7 +108,7 @@ if(NOT Exiv2_FOUND) set(EXIV2_ENABLE_PNG OFF CACHE INTERNAL "" FORCE) set(EXIV2_ENABLE_NLS OFF CACHE INTERNAL "" FORCE) set(EXIV2_ENABLE_LENSDATA OFF CACHE INTERNAL "" FORCE) - set(EXIV2_ENABLE_DYNAMIC_RUNTIME ON CACHE INTERNAL "" FORCE) + set(EXIV2_ENABLE_DYNAMIC_RUNTIME OFF CACHE INTERNAL "" FORCE) set(EXIV2_ENABLE_WEBREADY OFF CACHE INTERNAL "" FORCE) set(EXIV2_ENABLE_CURL OFF CACHE INTERNAL "" FORCE) set(EXIV2_ENABLE_BMFF OFF CACHE INTERNAL "" FORCE) @@ -104,7 +124,7 @@ if(NOT Exiv2_FOUND) set(BUILD_WITH_CCACHE ON CACHE INTERNAL "" FORCE) FetchContent_MakeAvailable(EXIV2) - target_link_libraries(AnalyzeView PRIVATE exiv2lib) + target_link_libraries(AnalyzeView PRIVATE Exiv2::exiv2lib) target_include_directories(AnalyzeView PRIVATE ${CMAKE_BINARY_DIR} @@ -113,61 +133,45 @@ if(NOT Exiv2_FOUND) ) endif() -include(FetchContent) -FetchContent_Declare(easyexif - GIT_REPOSITORY https://github.com/mayanklahiri/easyexif.git - GIT_TAG master - GIT_SHALLOW TRUE -) -FetchContent_MakeAvailable(easyexif) - -target_sources(AnalyzeView - PRIVATE - ${easyexif_SOURCE_DIR}/exif.cpp - ${easyexif_SOURCE_DIR}/exif.h -) +target_sources(AnalyzeView PRIVATE ExifParser.cc ExifParser.h) -target_include_directories(AnalyzeView - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ${easyexif_SOURCE_DIR} -) +#===========================================================================# # qt_add_qml_module(AnalyzeView # URI QGroundControl.AnalyzeView # VERSION 1.0 # QML_FILES -# AnalyzePage.qml -# AnalyzeView.qml -# GeoTagPage.qml -# LogDownloadPage.qml -# MAVLinkConsolePage.qml -# MAVLinkInspectorPage.qml -# VibrationPage.qml +# AnalyzePage.qml +# AnalyzeView.qml +# GeoTagPage.qml +# LogDownloadPage.qml +# MAVLinkConsolePage.qml +# MAVLinkInspectorPage.qml +# VibrationPage.qml # RESOURCES -# FloatingWindow.svg -# GeoTagIcon.svg -# LogDownloadIcon.svg -# MAVLinkConsoleIcon.svg -# MAVLinkInspector.svg -# VibrationPageIcon.png +# FloatingWindow.svg +# GeoTagIcon.svg +# LogDownloadIcon.svg +# MAVLinkConsoleIcon.svg +# MAVLinkInspector.svg +# VibrationPageIcon.png # OUTPUT_TARGETS AnalyzeView_targets # IMPORT_PATH ${QT_QML_OUTPUT_DIRECTORY} # IMPORTS -# QtQuick -# QtQuick.Controls -# QtQuick.Dialogs -# QtQuick.Layouts -# QtQuick.Window -# QtCharts -# Qt.labs.qmlmodels -# QGroundControl -# QGroundControl.Palette -# QGroundControl.Controls -# QGroundControl.Controllers -# QGroundControl.FactSystem -# QGroundControl.FactControls -# QGroundControl.ScreenTools +# QGroundControl +# QGroundControl.Controllers +# QGroundControl.Controls +# QGroundControl.FactControls +# QGroundControl.FactSystem +# QGroundControl.Palette +# QGroundControl.ScreenTools +# Qt.labs.qmlmodels +# QtCharts +# QtQuick +# QtQuick.Controls +# QtQuick.Dialogs +# QtQuick.Layouts +# QtQuick.Window # DEPENDENCIES # QtCore # ) diff --git a/src/AnalyzeView/ExifParser.cc b/src/AnalyzeView/ExifParser.cc index 7b3a5fa4ddf..5aa15cf440e 100644 --- a/src/AnalyzeView/ExifParser.cc +++ b/src/AnalyzeView/ExifParser.cc @@ -13,66 +13,36 @@ #include #include -#include #include QGC_LOGGING_CATEGORY(ExifParserLog, "qgc.analyzeview.exifparser") -namespace ExifParser { - -double readTime(const QByteArray &buf) +namespace ExifParser { - easyexif::EXIFInfo result; - if (result.parseFrom(reinterpret_cast(buf.constData()), buf.size()) != PARSE_EXIF_SUCCESS) { - qCWarning(ExifParserLog) << "Could not parse buffer"; - return -1.0; - } - - const QString createDate = QString(result.DateTimeOriginal.c_str()); - - const QStringList createDateList = createDate.split(' '); - if (createDateList.count() < 2) { - qCWarning(ExifParserLog) << "Could not decode creation time and date: " << createDateList; - return -1.0; - } - - const QStringList dateList = createDateList.at(0).split(':'); - if (dateList.count() < 3) { - qCWarning(ExifParserLog) << "Could not decode creation date: " << dateList; - return -1.0; - } - const QStringList timeList = createDateList.at(1).split(':'); - if (timeList.count() < 3) { - qCWarning(ExifParserLog) << "Could not decode creation time: " << timeList; - return -1.0; - } - - const QDate date(dateList.at(0).toInt(), dateList.at(1).toInt(), dateList.at(2).toInt()); - const QTime time(timeList.at(0).toInt(), timeList.at(1).toInt(), timeList.at(2).toInt()); - - const QDateTime tagTime(date, time); - - return (tagTime.toMSecsSinceEpoch() / 1000.0); +void init() +{ + Exiv2::XmpParser::initialize(); + ::atexit(Exiv2::XmpParser::terminate); } -double readTime2(const QByteArray &buf) +double readTime(const QByteArray &buf) { try { // Convert QByteArray to std::string for Exiv2 const Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open(reinterpret_cast(buf.constData()), buf.size()); image->readMetadata(); - Exiv2::ExifData &exifData = image->exifData(); + const Exiv2::ExifData &exifData = image->exifData(); if (exifData.empty()) { qCWarning(ExifParserLog) << "No EXIF data found in the image."; return -1.0; } // Read DateTimeOriginal - // Exiv2::ExifData::const_iterator it = dateTimeOriginal(exifData); const Exiv2::ExifKey key("Exif.Photo.DateTimeOriginal"); - const Exiv2::ExifData::iterator pos = exifData.findKey(key); + // Exiv2::ExifData::const_iterator it = dateTimeOriginal(exifData); + const Exiv2::ExifData::const_iterator pos = exifData.findKey(key); if (pos == exifData.end()) { qCWarning(ExifParserLog) << "No DateTimeOriginal found."; return -1.0; @@ -90,7 +60,7 @@ double readTime2(const QByteArray &buf) const QStringList dateList = createDateList[0].split(':'); const QStringList timeList = createDateList[1].split(':'); - if (dateList.size() < 3 || timeList.size() < 3) { + if ((dateList.size() < 3) || (timeList.size() < 3)) { qCWarning(ExifParserLog) << "Could not parse creation date/time: " << dateList << " " << timeList; return -1.0; } @@ -101,7 +71,7 @@ double readTime2(const QByteArray &buf) const QDateTime tagTime(date, time); return (tagTime.toMSecsSinceEpoch() / 1000.0); - } catch (Exiv2::Error& e) { + } catch (const Exiv2::Error &e) { qCWarning(ExifParserLog) << "Error reading EXIF data:" << e.what(); return -1.0; } diff --git a/src/AnalyzeView/ExifParser.h b/src/AnalyzeView/ExifParser.h index 71c0f96e96d..dd804758d00 100644 --- a/src/AnalyzeView/ExifParser.h +++ b/src/AnalyzeView/ExifParser.h @@ -18,7 +18,7 @@ class QByteArray; Q_DECLARE_LOGGING_CATEGORY(ExifParserLog) namespace ExifParser { + void init(); double readTime(const QByteArray &buf); - double readTime2(const QByteArray &buf); bool write(QByteArray &buf, const GeoTagWorker::cameraFeedbackPacket &geotag); -} // namespace ExifParser +} diff --git a/test/AnalyzeView/CMakeLists.txt b/test/AnalyzeView/CMakeLists.txt index a25b4012452..449f8b887be 100644 --- a/test/AnalyzeView/CMakeLists.txt +++ b/test/AnalyzeView/CMakeLists.txt @@ -29,6 +29,7 @@ target_link_libraries(AnalyzeViewTest target_include_directories(AnalyzeViewTest PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +# https://github.com/ianare/exif-samples qt_add_resources(AnalyzeViewTest "AnalyzeViewTest_res" PREFIX "/" FILES diff --git a/tools/setup/install-dependencies-debian.sh b/tools/setup/install-dependencies-debian.sh index 2d6ddb00f6a..eae9bbd0afa 100755 --- a/tools/setup/install-dependencies-debian.sh +++ b/tools/setup/install-dependencies-debian.sh @@ -82,6 +82,18 @@ if apt-cache show gstreamer1.0-qt6 >/dev/null 2>&1 && apt-cache show gstreamer1. DEBIAN_FRONTEND=noninteractive apt-get install -y --quiet gstreamer1.0-qt6 fi +# Exiv2 +DEBIAN_FRONTEND=noninteractive apt -y --quiet install \ + libbrotli-dev \ + libcurl4-openssl-dev \ + libexiv2-dev \ + libexpat1-dev \ + libinih-dev \ + libssh-dev \ + libxml2-utils \ + libz-dev \ + zlib1g-dev + # Additional DEBIAN_FRONTEND=noninteractive apt -y --quiet install \ flite1-dev \ @@ -90,7 +102,6 @@ DEBIAN_FRONTEND=noninteractive apt -y --quiet install \ libass-dev \ libdrm-dev \ libegl1-mesa-dev \ - libexiv2-dev \ libgbm-dev \ libgl1-mesa-dev \ libgl-dev \