mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
Dive pictures: Recognize video files
When generating thumbnails, test for video files. If it is, use a dummy-thumbnail. Write only the type (video), but no image to the thumbnail cache, for forward-compatibility. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
9a844a075c
commit
b28dba6087
5 changed files with 83 additions and 47 deletions
|
@ -69,27 +69,40 @@ void ImageDownloader::saveImage(QNetworkReply *reply)
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch a picture from the given filename. If this is a non-remote filename, fetch it from disk.
|
static bool isVideoFile(const QString &filename)
|
||||||
// Remote files are fetched from the net in a background thread. In such a case, the output-flag
|
{
|
||||||
// "stillLoading" is set to true.
|
// Currently, we're very crude. Simply check if the file exists and if it
|
||||||
|
// is an MP4-style format.
|
||||||
|
QFileInfo fi(filename);
|
||||||
|
if (!fi.exists() && !fi.isFile())
|
||||||
|
return false;
|
||||||
|
metadata md;
|
||||||
|
return get_metadata(qPrintable(filename), &md) == MEDIATYPE_VIDEO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch a picture from the given filename and determine its type (picture of 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
|
// 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, stillLoading flag
|
// Returns: fetched image, type
|
||||||
static std::pair<QImage, bool> fetchImage(const QString &filename, const QString &originalFilename, bool tryDownload)
|
Thumbnailer::Thumbnail Thumbnailer::fetchImage(const QString &filename, const QString &originalFilename, bool tryDownload)
|
||||||
{
|
{
|
||||||
QImage thumb;
|
|
||||||
bool stillLoading = false;
|
|
||||||
QUrl url = QUrl::fromUserInput(filename);
|
QUrl url = QUrl::fromUserInput(filename);
|
||||||
if (url.isLocalFile()) {
|
if (url.isLocalFile()) {
|
||||||
thumb.load(url.toLocalFile());
|
QString filename = url.toLocalFile();
|
||||||
|
if (isVideoFile(filename))
|
||||||
|
return { videoImage, MEDIATYPE_VIDEO };
|
||||||
|
QImage thumb(filename);
|
||||||
|
return { thumb, thumb.isNull() ? MEDIATYPE_IO_ERROR : MEDIATYPE_PICTURE };
|
||||||
} else if (tryDownload) {
|
} else if (tryDownload) {
|
||||||
// This has to be done in UI main thread, because QNetworkManager refuses
|
// This has to be done in UI main thread, because QNetworkManager refuses
|
||||||
// to treat requests from other threads. invokeMethod() is Qt's way of calling a
|
// to treat requests from other threads. invokeMethod() is Qt's way of calling a
|
||||||
// function in a different thread, namely the thread the called object is associated to.
|
// function in a different thread, namely the thread the called object is associated to.
|
||||||
QMetaObject::invokeMethod(ImageDownloader::instance(), "load", Qt::AutoConnection, Q_ARG(QUrl, url), Q_ARG(QString, originalFilename));
|
QMetaObject::invokeMethod(ImageDownloader::instance(), "load", Qt::AutoConnection, Q_ARG(QUrl, url), Q_ARG(QString, originalFilename));
|
||||||
stillLoading = true;
|
return { QImage(), MEDIATYPE_STILL_LOADING };
|
||||||
}
|
}
|
||||||
return { thumb, stillLoading };
|
return { QImage(), MEDIATYPE_IO_ERROR };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch a picture based on its original filename. If there is a translated filename (obtained either
|
// Fetch a picture based on its original filename. If there is a translated filename (obtained either
|
||||||
|
@ -98,25 +111,24 @@ static std::pair<QImage, bool> fetchImage(const QString &filename, const QString
|
||||||
// was downloaded previously, but for some reason the cached picture was lost. Therefore, in such a
|
// was downloaded previously, but for some reason the cached picture was lost. Therefore, in such a
|
||||||
// case, try the canonical filename. If that likewise fails, give up. For input and output parameters
|
// case, try the canonical filename. If that likewise fails, give up. For input and output parameters
|
||||||
// see fetchImage() above.
|
// see fetchImage() above.
|
||||||
static std::pair<QImage, bool> getHashedImage(const QString &filename, bool tryDownload)
|
Thumbnailer::Thumbnail Thumbnailer::getHashedImage(const QString &filename, bool tryDownload)
|
||||||
{
|
{
|
||||||
QImage thumb;
|
|
||||||
bool stillLoading = false;
|
|
||||||
QString localFilename = localFilePath(filename);
|
QString localFilename = localFilePath(filename);
|
||||||
|
|
||||||
// If there is a translated filename, try that first
|
// If there is a translated filename, try that first
|
||||||
|
Thumbnail thumbnail { QImage(), MEDIATYPE_UNKNOWN };
|
||||||
if (localFilename != filename)
|
if (localFilename != filename)
|
||||||
std::tie(thumb, stillLoading) = fetchImage(localFilename, filename, tryDownload);
|
thumbnail = fetchImage(localFilename, filename, tryDownload);
|
||||||
|
|
||||||
// Note that the translated filename should never be a remote file and therefore checking for
|
// Note that the translated filename should never be a remote file and therefore checking for
|
||||||
// stillLoading is currently not necessary. But in the future, we might support such a use case
|
// still-loading is currently not necessary. But in the future, we might support such a use case
|
||||||
// (e.g. images stored in the cloud).
|
// (e.g. images stored in the cloud).
|
||||||
if (thumb.isNull() && !stillLoading)
|
if (thumbnail.img.isNull() && thumbnail.type != MEDIATYPE_STILL_LOADING)
|
||||||
std::tie(thumb, stillLoading) = fetchImage(filename, filename, tryDownload);
|
thumbnail = fetchImage(filename, filename, tryDownload);
|
||||||
|
|
||||||
if (thumb.isNull() && !stillLoading)
|
if (thumbnail.img.isNull() && thumbnail.type != MEDIATYPE_STILL_LOADING)
|
||||||
qInfo() << "Error loading image" << filename << "[local:" << localFilename << "]";
|
qInfo() << "Error loading image" << filename << "[local:" << localFilename << "]";
|
||||||
return { thumb, stillLoading };
|
return thumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QImage renderIcon(const char *id, int size)
|
static QImage renderIcon(const char *id, int size)
|
||||||
|
@ -130,7 +142,8 @@ static QImage renderIcon(const char *id, int size)
|
||||||
}
|
}
|
||||||
|
|
||||||
Thumbnailer::Thumbnailer() : failImage(renderIcon(":filter-close", maxThumbnailSize())), // TODO: Don't misuse filter close icon
|
Thumbnailer::Thumbnailer() : failImage(renderIcon(":filter-close", maxThumbnailSize())), // TODO: Don't misuse filter close icon
|
||||||
dummyImage(renderIcon(":camera-icon", maxThumbnailSize()))
|
dummyImage(renderIcon(":camera-icon", maxThumbnailSize())),
|
||||||
|
videoImage(renderIcon(":video-icon", maxThumbnailSize()))
|
||||||
{
|
{
|
||||||
// Currently, we only process one image at a time. Stefan Fuchs reported problems when
|
// Currently, we only process one image at a time. Stefan Fuchs reported problems when
|
||||||
// calculating multiple thumbnails at once and this hopefully helps.
|
// calculating multiple thumbnails at once and this hopefully helps.
|
||||||
|
@ -145,11 +158,11 @@ Thumbnailer *Thumbnailer::instance()
|
||||||
return &self;
|
return &self;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QImage getThumbnailFromCache(const QString &picture_filename)
|
Thumbnailer::Thumbnail Thumbnailer::getThumbnailFromCache(const QString &picture_filename)
|
||||||
{
|
{
|
||||||
QString filename = thumbnailFileName(picture_filename);
|
QString filename = thumbnailFileName(picture_filename);
|
||||||
if (filename.isEmpty())
|
if (filename.isEmpty())
|
||||||
return QImage();
|
return { QImage(), MEDIATYPE_UNKNOWN };
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
|
|
||||||
if (prefs.auto_recalculate_thumbnails) {
|
if (prefs.auto_recalculate_thumbnails) {
|
||||||
|
@ -163,27 +176,32 @@ static QImage getThumbnailFromCache(const QString &picture_filename)
|
||||||
if (pictureTime.isValid() && thumbnailTime.isValid() && thumbnailTime < pictureTime) {
|
if (pictureTime.isValid() && thumbnailTime.isValid() && thumbnailTime < pictureTime) {
|
||||||
// Both files exist, have valid timestamps and thumbnail was calculated before picture.
|
// Both files exist, have valid timestamps and thumbnail was calculated before picture.
|
||||||
// Return an empty thumbnail to signal recalculation of the thumbnail
|
// Return an empty thumbnail to signal recalculation of the thumbnail
|
||||||
return QImage();
|
return { QImage(), MEDIATYPE_UNKNOWN };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
return QImage();
|
return { QImage(), MEDIATYPE_UNKNOWN };
|
||||||
QDataStream stream(&file);
|
QDataStream stream(&file);
|
||||||
|
|
||||||
// Each thumbnail file is composed of a media-type and an image file.
|
// Each thumbnail file is composed of a media-type and an image file.
|
||||||
// Currently, the type is ignored. This will be used to mark videos.
|
|
||||||
quint32 type;
|
quint32 type;
|
||||||
QImage res;
|
QImage res;
|
||||||
stream >> type;
|
stream >> type;
|
||||||
stream >> res;
|
stream >> res;
|
||||||
return res;
|
|
||||||
|
// Thumbnails of videos currently not supported - replace by dummy
|
||||||
|
// TODO: Perhaps extract thumbnails
|
||||||
|
if (type == MEDIATYPE_VIDEO)
|
||||||
|
res = videoImage;
|
||||||
|
|
||||||
|
return { res, (mediatype_t)type };
|
||||||
}
|
}
|
||||||
|
|
||||||
static void addThumbnailToCache(const QImage &thumbnail, const QString &picture_filename)
|
void Thumbnailer::addThumbnailToCache(const Thumbnail &thumbnail, const QString &picture_filename)
|
||||||
{
|
{
|
||||||
if (thumbnail.isNull())
|
if (thumbnail.img.isNull())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QString filename = thumbnailFileName(picture_filename);
|
QString filename = thumbnailFileName(picture_filename);
|
||||||
|
@ -192,51 +210,48 @@ static void addThumbnailToCache(const QImage &thumbnail, const QString &picture_
|
||||||
return;
|
return;
|
||||||
QDataStream stream(&file);
|
QDataStream stream(&file);
|
||||||
|
|
||||||
// For format of the file, see comments in getThumnailForCache
|
stream << (quint32)thumbnail.type;
|
||||||
quint32 type = MEDIATYPE_PICTURE;
|
if (thumbnail.type == MEDIATYPE_PICTURE) // TODO: Perhaps also support caching of video thumbnails
|
||||||
stream << type;
|
stream << thumbnail.img;
|
||||||
stream << thumbnail;
|
|
||||||
file.commit();
|
file.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Thumbnailer::recalculate(QString filename)
|
void Thumbnailer::recalculate(QString filename)
|
||||||
{
|
{
|
||||||
auto res = getHashedImage(filename, true);
|
Thumbnail thumbnail = getHashedImage(filename, true);
|
||||||
|
|
||||||
// If we couldn't load the image from disk -> leave old thumbnail.
|
// If we couldn't load the image from disk -> leave old thumbnail.
|
||||||
// The case "load from web" is a bit inconsistent: it will call into processItem() later
|
// The case "load from web" is a bit inconsistent: it will call into processItem() later
|
||||||
// and therefore a "broken" image symbol may be shown.
|
// and therefore a "broken" image symbol may be shown.
|
||||||
if (res.second || res.first.isNull())
|
if (thumbnail.type == MEDIATYPE_STILL_LOADING || thumbnail.img.isNull())
|
||||||
return;
|
return;
|
||||||
QImage thumbnail = res.first;
|
|
||||||
addThumbnailToCache(thumbnail, filename);
|
addThumbnailToCache(thumbnail, filename);
|
||||||
|
|
||||||
QMutexLocker l(&lock);
|
QMutexLocker l(&lock);
|
||||||
emit thumbnailChanged(filename, thumbnail);
|
emit thumbnailChanged(filename, thumbnail.img);
|
||||||
workingOn.remove(filename);
|
workingOn.remove(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Thumbnailer::processItem(QString filename, bool tryDownload)
|
void Thumbnailer::processItem(QString filename, bool tryDownload)
|
||||||
{
|
{
|
||||||
QImage thumbnail = getThumbnailFromCache(filename);
|
Thumbnail thumbnail = getThumbnailFromCache(filename);
|
||||||
|
|
||||||
if (thumbnail.isNull()) {
|
if (thumbnail.img.isNull()) {
|
||||||
auto res = getHashedImage(filename, tryDownload);
|
thumbnail = getHashedImage(filename, tryDownload);
|
||||||
if (res.second)
|
if (thumbnail.type == MEDIATYPE_STILL_LOADING)
|
||||||
return;
|
return;
|
||||||
thumbnail = res.first;
|
|
||||||
|
|
||||||
if (thumbnail.isNull()) {
|
if (thumbnail.img.isNull()) {
|
||||||
thumbnail = failImage;
|
thumbnail.img = failImage;
|
||||||
} else {
|
} else {
|
||||||
int size = maxThumbnailSize();
|
int size = maxThumbnailSize();
|
||||||
thumbnail = thumbnail.scaled(size, size, Qt::KeepAspectRatio);
|
thumbnail.img = thumbnail.img.scaled(size, size, Qt::KeepAspectRatio);
|
||||||
addThumbnailToCache(thumbnail, filename);
|
addThumbnailToCache(thumbnail, filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QMutexLocker l(&lock);
|
QMutexLocker l(&lock);
|
||||||
emit thumbnailChanged(filename, thumbnail);
|
emit thumbnailChanged(filename, thumbnail.img);
|
||||||
workingOn.remove(filename);
|
workingOn.remove(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#ifndef IMAGEDOWNLOADER_H
|
#ifndef IMAGEDOWNLOADER_H
|
||||||
#define IMAGEDOWNLOADER_H
|
#define IMAGEDOWNLOADER_H
|
||||||
|
|
||||||
|
#include "metadata.h"
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
@ -48,14 +49,24 @@ public slots:
|
||||||
signals:
|
signals:
|
||||||
void thumbnailChanged(QString filename, QImage thumbnail);
|
void thumbnailChanged(QString filename, QImage thumbnail);
|
||||||
private:
|
private:
|
||||||
|
struct Thumbnail {
|
||||||
|
QImage img;
|
||||||
|
mediatype_t type;
|
||||||
|
};
|
||||||
|
|
||||||
Thumbnailer();
|
Thumbnailer();
|
||||||
|
static void addThumbnailToCache(const Thumbnail &thumbnail, const QString &picture_filename);
|
||||||
void recalculate(QString filename);
|
void recalculate(QString filename);
|
||||||
void processItem(QString filename, bool tryDownload);
|
void processItem(QString filename, bool tryDownload);
|
||||||
|
Thumbnail getThumbnailFromCache(const QString &picture_filename);
|
||||||
|
Thumbnail fetchImage(const QString &filename, const QString &originalFilename, bool tryDownload);
|
||||||
|
Thumbnail getHashedImage(const QString &filename, bool tryDownload);
|
||||||
|
|
||||||
mutable QMutex lock;
|
mutable QMutex lock;
|
||||||
QThreadPool pool;
|
QThreadPool pool;
|
||||||
QImage failImage; // Shown when image-fetching fails
|
QImage failImage; // Shown when image-fetching fails
|
||||||
QImage dummyImage; // Shown before thumbnail is fetched
|
QImage dummyImage; // Shown before thumbnail is fetched
|
||||||
|
QImage videoImage; // Place holder for videos
|
||||||
|
|
||||||
QMap<QString,QFuture<void>> workingOn;
|
QMap<QString,QFuture<void>> workingOn;
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,10 +10,11 @@ struct metadata {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum mediatype_t {
|
enum mediatype_t {
|
||||||
MEDIATYPE_IO_ERROR, // Couldn't read file
|
MEDIATYPE_UNKNOWN, // Couldn't (yet) identify file
|
||||||
MEDIATYPE_UNKNOWN, // Couldn't identify file
|
MEDIATYPE_IO_ERROR, // Couldn't read file
|
||||||
MEDIATYPE_PICTURE,
|
MEDIATYPE_PICTURE,
|
||||||
MEDIATYPE_VIDEO,
|
MEDIATYPE_VIDEO,
|
||||||
|
MEDIATYPE_STILL_LOADING, // Still processing in the background
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
@ -1209,11 +1209,19 @@ QString localFilePath(const QString &originalFilename)
|
||||||
return localFilenameOf.value(originalFilename, originalFilename);
|
return localFilenameOf.value(originalFilename, originalFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Apparently Qt has no simple way of listing the supported video
|
||||||
|
// codecs? Do we have to query them by hand using QMediaPlayer::hasSupport()?
|
||||||
|
const QStringList videoExtensionsList = {
|
||||||
|
".avi", ".mp4", ".mpeg", ".mpg", ".wmv"
|
||||||
|
};
|
||||||
|
|
||||||
QStringList imageExtensionFilters() {
|
QStringList imageExtensionFilters() {
|
||||||
QStringList filters;
|
QStringList filters;
|
||||||
foreach (QString format, QImageReader::supportedImageFormats()) {
|
foreach (QString format, QImageReader::supportedImageFormats()) {
|
||||||
filters.append(QString("*.").append(format));
|
filters.append(QString("*.").append(format));
|
||||||
}
|
}
|
||||||
|
foreach (const QString &format, videoExtensionsList)
|
||||||
|
filters.append("*" + format);
|
||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ QByteArray getCurrentAppState();
|
||||||
void setCurrentAppState(const QByteArray &state);
|
void setCurrentAppState(const QByteArray &state);
|
||||||
void init_proxy();
|
void init_proxy();
|
||||||
QString getUUID();
|
QString getUUID();
|
||||||
|
extern const QStringList videoExtensionsList;
|
||||||
QStringList imageExtensionFilters();
|
QStringList imageExtensionFilters();
|
||||||
char *intdup(int index);
|
char *intdup(int index);
|
||||||
char *copy_qstring(const QString &);
|
char *copy_qstring(const QString &);
|
||||||
|
|
Loading…
Add table
Reference in a new issue