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 <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2023-09-04 07:22:31 +02:00 committed by Michael Keller
parent 02638d7c3e
commit 13d1188c41
6 changed files with 98 additions and 10 deletions

View file

@ -8,6 +8,7 @@ desktop: fix gas switches in UDDF exports
core: allow of up to 6 O2 sensors (and corresponding voting logic) core: allow of up to 6 O2 sensors (and corresponding voting logic)
desktop: add divemode as a possible dive list column desktop: add divemode as a possible dive list column
profile-widget: Now zomed in profiles can be panned with horizontal scroll. 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 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: 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 equipment: include unused cylinders in merged dive if the preference is enabled

View file

@ -166,6 +166,11 @@ if(NOT ANDROID)
endif() endif()
pkg_config_library(LIBUSB libusb-1.0 QUIET) pkg_config_library(LIBUSB libusb-1.0 QUIET)
pkg_config_library(LIBMTP libmtp QUIET) pkg_config_library(LIBMTP libmtp QUIET)
pkg_config_library(LIBRAW libraw QUIET)
endif()
if(LIBRAW_FOUND)
add_definitions(-DLIBRAW_SUPPORT)
endif() endif()
include_directories(. include_directories(.

View file

@ -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-qtbase-devel qt5-qtconnectivity-devel qt5-qtdeclarative-devel \
qt5-qtlocation-devel qt5-qtscript-devel qt5-qtsvg-devel \ qt5-qtlocation-devel qt5-qtscript-devel qt5-qtsvg-devel \
qt5-qttools-devel qt5-qtwebkit-devel redhat-rpm-config \ 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-qtbase-devel libQt5WebKit5-devel libqt5-qtsvg-devel \
libqt5-qtscript-devel libqt5-qtdeclarative-devel \ libqt5-qtscript-devel libqt5-qtdeclarative-devel \
libqt5-qtconnectivity-devel libqt5-qtlocation-devel libcurl-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 On Debian Bookworm this seems to work
@ -183,7 +182,7 @@ sudo apt install \
qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-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 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 \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-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 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 \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-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 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 \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick2 \
qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \ qt5-qmake qtchooser qtconnectivity5-dev qtdeclarative5-dev \
qtdeclarative5-private-dev qtlocation5-dev qtpositioning5-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 Note that you'll need to increase the swap space as the default of 100MB

View file

@ -14,6 +14,9 @@
#include <QSvgRenderer> #include <QSvgRenderer>
#include <QDataStream> #include <QDataStream>
#include <QPainter> #include <QPainter>
#ifdef LIBRAW_SUPPORT
#include <libraw/libraw.h>
#endif
#include <QtConcurrent> #include <QtConcurrent>
@ -79,12 +82,40 @@ static bool hasVideoFileExtension(const QString &filename)
return false; 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<unsigned char *>(raw.imgdata.thumbnail.thumb),
raw.imgdata.thumbnail.tlength);
return res;
}
case LIBRAW_THUMBNAIL_BITMAP:
return QImage(reinterpret_cast<unsigned char *>(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 // 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. // 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 // 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. // 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) Thumbnailer::Thumbnail Thumbnailer::fetchImage(const QString &urlfilename, const QString &originalFilename, bool tryDownload)
{ {
QUrl url = QUrl::fromUserInput(urlfilename); 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. // Try if Qt can parse this image. If it does, use this as a thumbnail.
QImage thumb(filename); QImage thumb(filename);
#ifdef LIBRAW_SUPPORT
// If note, perhaps a raw image?
if (thumb.isNull())
thumb = fetchRawThumbnail(filename);
#endif
if (!thumb.isNull()) { if (!thumb.isNull()) {
int size = maxThumbnailSize(); int size = maxThumbnailSize();
thumb = thumb.scaled(size, size, Qt::KeepAspectRatio); thumb = thumb.scaled(size, size, Qt::KeepAspectRatio);

View file

@ -7,6 +7,9 @@
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QDateTime> #include <QDateTime>
#ifdef LIBRAW_SUPPORT
#include <libraw/libraw.h>
#endif
// Weirdly, android builds fail owing to undefined UINT64_MAX // Weirdly, android builds fail owing to undefined UINT64_MAX
#ifndef UINT64_MAX #ifndef UINT64_MAX
@ -528,6 +531,33 @@ static bool parseASF(QFile &f, metadata *metadata)
return false; 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<int>(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) mediatype_t get_metadata(const char *filename_in, metadata *data)
{ {
data->timestamp = 0; data->timestamp = 0;
@ -535,6 +565,11 @@ mediatype_t get_metadata(const char *filename_in, metadata *data)
data->location.lat.udeg = 0; data->location.lat.udeg = 0;
data->location.lon.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)); QString filename = localFilePath(QString(filename_in));
QFile f(filename); QFile f(filename);
if (!f.open(QIODevice::ReadOnly)) if (!f.open(QIODevice::ReadOnly))

View file

@ -1091,6 +1091,17 @@ const QStringList videoExtensionsList = {
".avi", ".mp4", ".mov", ".mpeg", ".mpg", ".wmv" ".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() QStringList mediaExtensionFilters()
{ {
return imageExtensionFilters() + videoExtensionFilters(); return imageExtensionFilters() + videoExtensionFilters();
@ -1101,7 +1112,7 @@ QStringList imageExtensionFilters()
QStringList filters; QStringList filters;
for (QString format: QImageReader::supportedImageFormats()) for (QString format: QImageReader::supportedImageFormats())
filters.append("*." + format); filters.append("*." + format);
return filters; return filters + rawExtensionsList;
} }
QStringList videoExtensionFilters() QStringList videoExtensionFilters()