qt: vlcaccess image provider

artwork may reference a network resource, so far we were using QNetwork to read
these resources, but it has some drawbacks

* The protocol for the artwork may not be handled by QNetwork

* The access to the artwork may require credentials that will likely be the same
  as the one required to access the media

* QNetwork may not be present as this is not a requirement for building VLC

Image may require to be loaded using VLC access using special URI image://vlcaccess/?uri=......
This commit is contained in:
Pierre Lamot 2024-03-28 15:14:20 +01:00 committed by Steve Lhomme
parent f7498f6b19
commit d8d598e860
4 changed files with 381 additions and 0 deletions

View File

@ -298,6 +298,7 @@ libqt_plugin_la_SOURCES = \
util/base_model.hpp util/base_model_p.hpp util/base_model.cpp \
util/color_scheme_model.cpp util/color_scheme_model.hpp \
util/color_svg_image_provider.cpp util/color_svg_image_provider.hpp \
util/vlcaccess_image_provider.cpp util/vlcaccess_image_provider.hpp \
util/covergenerator.cpp \
util/covergenerator.hpp \
util/imageluminanceextractor.cpp util/imageluminanceextractor.hpp \
@ -464,6 +465,7 @@ nodist_libqt_plugin_la_SOURCES = \
util/base_model.moc.cpp \
util/color_scheme_model.moc.cpp \
util/color_svg_image_provider.moc.cpp \
util/vlcaccess_image_provider.moc.cpp \
util/imageluminanceextractor.moc.cpp \
util/csdbuttonmodel.moc.cpp \
util/keyhelper.moc.cpp \

View File

@ -41,6 +41,7 @@
#include "util/flickable_scroll_handler.hpp"
#include "util/color_svg_image_provider.hpp"
#include "util/effects_image_provider.hpp"
#include "util/vlcaccess_image_provider.hpp"
#include "util/csdbuttonmodel.hpp"
#include "util/vlctick.hpp"
#include "util/list_selection_model.hpp"
@ -144,6 +145,7 @@ MainUI::MainUI(qt_intf_t *p_intf, MainCtx *mainCtx, QWindow* interfaceWindow, Q
SingletonRegisterHelper<SystemPalette>::setInstance( new SystemPalette(this) );
SingletonRegisterHelper<QmlKeyHelper>::setInstance( new QmlKeyHelper(this) );
SingletonRegisterHelper<SVGColorImage>::setInstance( new SVGColorImage(this) );
SingletonRegisterHelper<VLCAccessImage>::setInstance( new VLCAccessImage(this) );
if (m_mainCtx->hasMediaLibrary())
{
@ -174,6 +176,7 @@ bool MainUI::setup(QQmlEngine* engine)
SingletonRegisterHelper<EffectsImageProvider>::setInstance(new EffectsImageProvider(engine));
engine->addImageProvider(QStringLiteral("svgcolor"), new SVGColorImageImageProvider());
engine->addImageProvider(QStringLiteral("vlcaccess"), new VLCAccessImageProvider());
m_component = new QQmlComponent(engine, QStringLiteral("qrc:/main/MainInterface.qml"), QQmlComponent::PreferSynchronous, engine);
if (m_component->isLoading())
@ -239,6 +242,7 @@ void MainUI::registerQMLTypes()
qmlRegisterSingletonType<QmlKeyHelper>(uri, versionMajor, versionMinor, "KeyHelper", SingletonRegisterHelper<QmlKeyHelper>::callback);
qmlRegisterSingletonType<EffectsImageProvider>(uri, versionMajor, versionMinor, "Effects", SingletonRegisterHelper<EffectsImageProvider>::callback);
qmlRegisterSingletonType<SVGColorImage>(uri, versionMajor, versionMinor, "SVGColorImage", SingletonRegisterHelper<SVGColorImage>::callback);
qmlRegisterSingletonType<VLCAccessImage>(uri, versionMajor, versionMinor, "VLCAccessImage", SingletonRegisterHelper<VLCAccessImage>::callback);
qmlRegisterSingletonType<PlaylistController>(uri, versionMajor, versionMinor, "MainPlaylistController", SingletonRegisterHelper<PlaylistController>::callback);
qmlRegisterType<DelayEstimator>( uri, versionMajor, versionMinor, "DelayEstimator" );

View File

@ -0,0 +1,273 @@
/*****************************************************************************
* Copyright (C) 2024 VLC authors and VideoLAN
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_access.h>
#include <vlc_stream.h>
#include "vlcaccess_image_provider.hpp"
#include "util/asynctask.hpp"
#include "qt.hpp"
#include <QImageReader>
#include <QFile>
#include <QQmlFile>
#include <QUrlQuery>
#define PATH_KEY "uri"
namespace {
class ImageReader : public AsyncTask<QImage>
{
public:
/**
* @brief ImageReader
* @param device i/o source to read from
*
* @param requestedSize only taken as hint, the Image is resized with PreserveAspectCrop
*
* @param radius
*/
ImageReader(std::unique_ptr<QIODevice> device, QSize requestedSize, VLCAccessImageProvider::ImagePostProcessCb postProcessCb)
: device(std::move(device))
, requestedSize {requestedSize}
, postProcessCb {postProcessCb}
{
}
QString errorString() const {
return errorStr;
}
QImage execute() override
{
QImageReader reader;
reader.setDevice(device.get());
const QSize sourceSize = reader.size();
if (requestedSize.isValid())
reader.setScaledSize(sourceSize.scaled(requestedSize, Qt::KeepAspectRatioByExpanding));
auto img = reader.read();
if (img.isNull()) {
errorStr = reader.errorString();
}
if (!img.isNull() && postProcessCb)
img = postProcessCb(img, requestedSize);
return img;
}
private:
std::unique_ptr<QIODevice> device;
QSize requestedSize;
QString errorStr;
VLCAccessImageProvider::ImagePostProcessCb postProcessCb;
};
class VLCAccessImageResponse : public QQuickImageResponse
{
public:
VLCAccessImageResponse(const QUrl& url, const QSize &requestedSize, VLCAccessImageProvider::ImagePostProcessCb postProcessCb = nullptr)
: imagePostProcessCb(postProcessCb)
{
std::unique_ptr<QIODevice> device;
if (url.scheme().compare(QStringLiteral("qrc"), Qt::CaseInsensitive) == 0)
{
QString qrcPath = QQmlFile::urlToLocalFileOrQrc(url);
device = std::make_unique<QFile>(qrcPath);
}
else
{
QUrl fileUrl = url;
if (fileUrl.scheme().isEmpty())
fileUrl.setScheme("file");
device = std::make_unique<VLCIODevice>(fileUrl.toString(QUrl::FullyEncoded));
}
reader.reset(new ImageReader(std::move(device), requestedSize, postProcessCb));
connect(reader.get(), &ImageReader::result, this, &VLCAccessImageResponse::handleImageRead);
reader->start(*QThreadPool::globalInstance());
}
QQuickTextureFactory *textureFactory() const override
{
return result.isNull() ? nullptr : QQuickTextureFactory::textureFactoryForImage(result);
}
QString errorString() const override
{
return errorStr;
}
private:
void handleImageRead()
{
result = reader->takeResult();
errorStr = reader->errorString();
reader.reset();
emit finished();
}
VLCAccessImageProvider::ImagePostProcessCb imagePostProcessCb;
QImage result;
TaskHandle<ImageReader> reader;
QString errorStr;
};
} // anonymous namespace
//// VLCAccessImageProvider
VLCAccessImageProvider::VLCAccessImageProvider(VLCAccessImageProvider::ImagePostProcessCb cb)
: QQuickAsyncImageProvider()
, postProcessCb(cb)
{
}
QQuickImageResponse* VLCAccessImageProvider::requestImageResponse(const QString& id, const QSize& requestedSize)
{
QUrl url {id};
QUrlQuery query {url};
if (!query.hasQueryItem(PATH_KEY))
return nullptr;
QString vlcurl = query.queryItemValue(PATH_KEY, QUrl::FullyEncoded);
return new VLCAccessImageResponse(QUrl::fromEncoded(vlcurl.toUtf8()), requestedSize, postProcessCb);
}
QString VLCAccessImageProvider::wrapUri(QString path)
{
QUrlQuery query;
query.addQueryItem(PATH_KEY, path);
return QStringLiteral("image://vlcaccess/?") + query.toString(QUrl::FullyEncoded);
}
//// VLCImageAccess
VLCAccessImage::VLCAccessImage(QObject* parent)
: QObject(parent)
{}
QString VLCAccessImage::uri(QString path)
{
return VLCAccessImageProvider::wrapUri(path);
}
//// VLCIODevice
VLCIODevice::VLCIODevice(const QString& filename, QObject* parent)
: QIODevice(parent)
, m_filename(filename)
{
}
VLCIODevice::~VLCIODevice()
{
close();
}
bool VLCIODevice::open(OpenMode mode)
{
//we only support reading
if (mode & QIODevice::OpenModeFlag::WriteOnly)
return false;
m_stream = vlc_access_NewMRL(nullptr, qtu(m_filename));
if (m_stream == nullptr)
return false;
return QIODevice::open(mode);
}
bool VLCIODevice::isSequential() const
{
assert(m_stream);
//some access (like http) will perform really poorly with the way
//Qt uses the QIODevice (lots of seeks)
//return !vlc_stream_CanSeek(m_stream);
return true;
}
void VLCIODevice::close()
{
if (!m_stream)
return;
vlc_stream_Delete(m_stream);
m_stream = nullptr;
}
qint64 VLCIODevice::pos() const
{
assert(m_stream);
return vlc_stream_Tell(m_stream);
}
qint64 VLCIODevice::size() const
{
assert(m_stream);
uint64_t streamSize;
bool ret = vlc_stream_GetSize(m_stream, &streamSize);
if (!ret)
return -1;
return static_cast<qint64>(streamSize);
}
bool VLCIODevice::seek(qint64 pos)
{
assert(m_stream);
QIODevice::seek(pos);
if (pos < 0)
return false;
return vlc_stream_Seek(m_stream, pos) == VLC_SUCCESS;
}
bool VLCIODevice::atEnd() const
{
assert(m_stream);
return vlc_stream_Eof(m_stream);
}
bool VLCIODevice::reset()
{
assert(m_stream);
return vlc_stream_Seek(m_stream, 0) == VLC_SUCCESS;
}
qint64 VLCIODevice::readData(char* data, qint64 maxlen)
{
assert(m_stream);
return vlc_stream_Read(m_stream, data, maxlen);
}
qint64 VLCIODevice::writeData(const char*, qint64)
{
assert(m_stream);
return -1;
}

View File

@ -0,0 +1,102 @@
/*****************************************************************************
* Copyright (C) 2024 VLC authors and VideoLAN
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef VLCACCESSIMAGEPROVIDER_HPP
#define VLCACCESSIMAGEPROVIDER_HPP
#include <QObject>
#include <QQuickAsyncImageProvider>
#include <QString>
#include <QIODevice>
typedef struct stream_t stream_t;
/**
* VLCIODevice is a QIODevice based on vlc_access
*/
class VLCIODevice : public QIODevice
{
public:
VLCIODevice(const QString &filename, QObject *parent = nullptr);
virtual ~VLCIODevice();
public:
bool open(QIODevice::OpenMode mode) override;
bool isSequential() const override;
void close() override;
qint64 pos() const override;
qint64 size() const override;
bool seek(qint64 pos) override;
bool atEnd() const override;
bool reset() override;
protected:
qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char*, qint64) override;
private:
QString m_filename;
stream_t* m_stream = nullptr;
};
class VLCAccessImageProvider: public QQuickAsyncImageProvider
{
public:
typedef std::function<QImage (QImage&, const QSize &)> ImagePostProcessCb;
VLCAccessImageProvider(ImagePostProcessCb cb = nullptr);
QQuickImageResponse* requestImageResponse(const QString &id, const QSize &requestedSize) override;
static QString wrapUri(QString path);
private:
ImagePostProcessCb postProcessCb;
};
class VLCAccessImage : public QObject {
Q_OBJECT
public:
VLCAccessImage(QObject* parent = nullptr);
/**
* @brief adapt @path to open it using
* @param path to the artwork
*
* sample usage:
*
* @code{qml}
* Image {
* src: VLCImageAccess.uri("file:///path/to/assert.svg")
* }
* @code
*
*/
Q_INVOKABLE QString uri(QString path);
};
#endif // VLCACCESSIMAGEPROVIDER_HPP