From 13d1188c41d037c91791b589333e9372529846db Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Mon, 4 Sep 2023 07:22:31 +0200 Subject: [PATCH] media: load metadata and thumbnails of raw pictures using libraw The distinguished photographer shoots raw images. There is a comprehensive library that can extract metadata and thumbnails from these images. Let's use it if available. Signed-off-by: Berthold Stoeger --- CHANGELOG.md | 1 + CMakeLists.txt | 5 +++++ INSTALL.md | 13 ++++++------- core/imagedownloader.cpp | 41 ++++++++++++++++++++++++++++++++++++++-- core/metadata.cpp | 35 ++++++++++++++++++++++++++++++++++ core/qthelper.cpp | 13 ++++++++++++- 6 files changed, 98 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28cf83916..031f1f2f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ desktop: fix gas switches in UDDF exports core: allow of up to 6 O2 sensors (and corresponding voting logic) desktop: add divemode as a possible dive list column profile-widget: Now zomed in profiles can be panned with horizontal scroll. +media: support raw files if libraw is installed desktop: hide only events with the same severity when 'Hide similar events' is used equipment: mark gas mixes reported by the dive computer as 'inactive' as 'not used' equipment: include unused cylinders in merged dive if the preference is enabled diff --git a/CMakeLists.txt b/CMakeLists.txt index dfbf4e233..b592c30e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,11 @@ if(NOT ANDROID) endif() pkg_config_library(LIBUSB libusb-1.0 QUIET) pkg_config_library(LIBMTP libmtp QUIET) + pkg_config_library(LIBRAW libraw QUIET) +endif() + +if(LIBRAW_FOUND) + add_definitions(-DLIBRAW_SUPPORT) endif() include_directories(. diff --git a/INSTALL.md b/INSTALL.md index 89f314f4f..30ba828fb 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -156,7 +156,7 @@ sudo dnf install autoconf automake bluez-libs-devel cmake gcc-c++ git \ qt5-qtbase-devel qt5-qtconnectivity-devel qt5-qtdeclarative-devel \ qt5-qtlocation-devel qt5-qtscript-devel qt5-qtsvg-devel \ qt5-qttools-devel qt5-qtwebkit-devel redhat-rpm-config \ - bluez-libs-devel libgit2-devel libzip-devel libmtp-devel + bluez-libs-devel libgit2-devel libzip-devel libmtp-devel libraw-devel ``` @@ -169,8 +169,7 @@ sudo zypper install git gcc-c++ make autoconf automake libtool cmake libzip-deve libqt5-qtbase-devel libQt5WebKit5-devel libqt5-qtsvg-devel \ libqt5-qtscript-devel libqt5-qtdeclarative-devel \ libqt5-qtconnectivity-devel libqt5-qtlocation-devel libcurl-devel \ - bluez-devel libgit2-devel libmtp-devel -``` + bluez-devel libgit2-devel libmtp-devel libraw-devel On Debian Bookworm this seems to work @@ -183,7 +182,7 @@ sudo apt install \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \ - qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev + qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev libraw-dev ``` In order to build and run mobile-on-desktop, you also need @@ -207,7 +206,7 @@ sudo apt install \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \ - qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev + qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev libraw-dev ``` In order to build and run mobile-on-desktop, you also need @@ -231,7 +230,7 @@ sudo apt install \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \ - qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev + qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev libraw-dev ``` In order to build and run mobile-on-desktop, you also need @@ -260,7 +259,7 @@ sudo apt install \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-dev \ - qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev + qtscript5-dev qttools5-dev qttools5-dev-tools libmtp-dev libraw-dev ``` Note that you'll need to increase the swap space as the default of 100MB diff --git a/core/imagedownloader.cpp b/core/imagedownloader.cpp index 47b190fa6..9b5d3e558 100644 --- a/core/imagedownloader.cpp +++ b/core/imagedownloader.cpp @@ -14,6 +14,9 @@ #include #include #include +#ifdef LIBRAW_SUPPORT +#include +#endif #include @@ -79,12 +82,40 @@ static bool hasVideoFileExtension(const QString &filename) return false; } -// Fetch a picture from the given filename and determine its type (picture of video). +#ifdef LIBRAW_SUPPORT +QImage fetchRawThumbnail(const QString &filename) +{ + LibRaw raw; // Might think about reusing that, one instance per thread + + // TODO: Convert filename to UTF-16 for windows + if (raw.open_file(qPrintable(filename)) != LIBRAW_SUCCESS || + raw.unpack_thumb() != LIBRAW_SUCCESS) { + return QImage(); + } + + switch (raw.imgdata.thumbnail.tformat) { + case LIBRAW_THUMBNAIL_JPEG: { + QImage res; + res.loadFromData(reinterpret_cast(raw.imgdata.thumbnail.thumb), + raw.imgdata.thumbnail.tlength); + return res; + } + case LIBRAW_THUMBNAIL_BITMAP: + return QImage(reinterpret_cast(raw.imgdata.thumbnail.thumb), + raw.imgdata.thumbnail.twidth, raw.imgdata.thumbnail.theight, + QImage::Format_RGB888); + default: // Unsupported + return QImage(); + } +} + +#endif + +// Fetch a picture from the given filename and determine its type (picture or video). // If this is a non-remote file, fetch it from disk. Remote files are fetched from the // net in a background thread. In such a case, the output-type is set to MEDIATYPE_STILL_LOADING. // If the input-flag "tryDownload" is set to false, no download attempt is made. This is to // prevent infinite loops, where failed image downloads would be repeated ad infinitum. -// Returns: fetched image, type Thumbnailer::Thumbnail Thumbnailer::fetchImage(const QString &urlfilename, const QString &originalFilename, bool tryDownload) { QUrl url = QUrl::fromUserInput(urlfilename); @@ -102,6 +133,12 @@ Thumbnailer::Thumbnail Thumbnailer::fetchImage(const QString &urlfilename, const // Try if Qt can parse this image. If it does, use this as a thumbnail. QImage thumb(filename); + +#ifdef LIBRAW_SUPPORT + // If note, perhaps a raw image? + if (thumb.isNull()) + thumb = fetchRawThumbnail(filename); +#endif if (!thumb.isNull()) { int size = maxThumbnailSize(); thumb = thumb.scaled(size, size, Qt::KeepAspectRatio); diff --git a/core/metadata.cpp b/core/metadata.cpp index 52c2e2024..46c3f9621 100644 --- a/core/metadata.cpp +++ b/core/metadata.cpp @@ -7,6 +7,9 @@ #include #include #include +#ifdef LIBRAW_SUPPORT +#include +#endif // Weirdly, android builds fail owing to undefined UINT64_MAX #ifndef UINT64_MAX @@ -528,6 +531,33 @@ static bool parseASF(QFile &f, metadata *metadata) return false; } +// Transform a (deg, min, sec) float triple into microdegrees +degrees_t degminsec_to_udeg(float a[3]) +{ + if (a[0] == 0.0 && a[1] == 0.0 && a[2] == 0.0) + return { 0 }; + return { static_cast(round(a[0] * 1'000'000.0 + + a[1] * (1'000'000.0/60.0) + + a[2] * (1'000'000.0/3600.0))) }; +} + +#ifdef LIBRAW_SUPPORT +static bool parseRaw(const char *fn, metadata *metadata) +{ + LibRaw raw; // Might think about reusing that + + // TODO: Convert filename to UTF-16 for windows + if (raw.open_file(fn) != LIBRAW_SUCCESS) + return false; + + metadata->timestamp = raw.imgdata.other.timestamp; + metadata->location.lat = degminsec_to_udeg(raw.imgdata.other.parsed_gps.latitude); + metadata->location.lon = degminsec_to_udeg(raw.imgdata.other.parsed_gps.longitude); + + return true; +} +#endif + mediatype_t get_metadata(const char *filename_in, metadata *data) { data->timestamp = 0; @@ -535,6 +565,11 @@ mediatype_t get_metadata(const char *filename_in, metadata *data) data->location.lat.udeg = 0; data->location.lon.udeg = 0; +#ifdef LIBRAW_SUPPORT + if (parseRaw(filename_in, data)) + return MEDIATYPE_PICTURE; +#endif + QString filename = localFilePath(QString(filename_in)); QFile f(filename); if (!f.open(QIODevice::ReadOnly)) diff --git a/core/qthelper.cpp b/core/qthelper.cpp index 18937e316..9d45ec446 100644 --- a/core/qthelper.cpp +++ b/core/qthelper.cpp @@ -1091,6 +1091,17 @@ const QStringList videoExtensionsList = { ".avi", ".mp4", ".mov", ".mpeg", ".mpg", ".wmv" }; +// Raw extensions according to https://en.wikipedia.org/wiki/Raw_image_format +static const QStringList rawExtensionsList = { +#ifdef LIBRAW_SUPPORT + "*.3fr", "*.ari", "*.arw", "*.bay", "*.braw", "*.crw", "*.cr2", "*.cr3", "*.cap", + "*.data", "*.dcs", "*.dcr", "*.dng", "*.drf", "*.eip", "*.erf", "*.fff", "*.gpr", + "*.iiq", "*.k25", "*.kdc", "*.mdc", "*.mef", "*.mos", "*.mrw", "*.nef", "*.nrw", + "*.obm", "*.orf", "*.pef", "*.ptx", "*.pxn", "*.r3d", "*.raf", "*.raw", "*.rwl", + "*.rw2", "*.rwz", "*.sr2", "*.srf", "*.srw", "*.x3f" +#endif +}; + QStringList mediaExtensionFilters() { return imageExtensionFilters() + videoExtensionFilters(); @@ -1101,7 +1112,7 @@ QStringList imageExtensionFilters() QStringList filters; for (QString format: QImageReader::supportedImageFormats()) filters.append("*." + format); - return filters; + return filters + rawExtensionsList; } QStringList videoExtensionFilters()