[vlc-commits] [Git][videolan/vlc][master] 7 commits: qt/mlbasemodel: Add the 'itemCache' function
Hugo Beauzée-Luyssen
gitlab at videolan.org
Sat May 8 11:47:42 UTC 2021
Hugo Beauzée-Luyssen pushed to branch master at VideoLAN / VLC
Commits:
80b1d991 by Benjamin Arnaud at 2021-05-08T10:59:52+00:00
qt/mlbasemodel: Add the 'itemCache' function
This is useful when we only want to return a cached item.
- - - - -
22bc7af6 by Benjamin Arnaud at 2021-05-08T10:59:52+00:00
qt: Create CoverGenerator
This class can be useful to retrieve composed thumbnails for groups, playlists and genres.
It supports a few options like custom size, count and division type.
- - - - -
74ffcb26 by Benjamin Arnaud at 2021-05-08T10:59:52+00:00
qt/mlgroup: Add CoverGenerator implementation
- - - - -
e6ebc5f8 by Benjamin Arnaud at 2021-05-08T10:59:52+00:00
qt/mlgrouplistmodel: Add CoverGenerator implementation
- - - - -
73072c14 by Benjamin Arnaud at 2021-05-08T10:59:52+00:00
qt/mlgenre: Add CoverGenerator implementation
- - - - -
43e3ea1e by Benjamin Arnaud at 2021-05-08T10:59:52+00:00
qt/mlgenremodel: Add CoverGenerator implementation
- - - - -
4362af46 by Benjamin Arnaud at 2021-05-08T10:59:52+00:00
qml/MusicGenres: Add gradient overlay
fix #25595
- - - - -
16 changed files:
- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/medialibrary/mlbasemodel.cpp
- modules/gui/qt/medialibrary/mlbasemodel.hpp
- modules/gui/qt/medialibrary/mlgenre.cpp
- modules/gui/qt/medialibrary/mlgenre.hpp
- modules/gui/qt/medialibrary/mlgenremodel.cpp
- modules/gui/qt/medialibrary/mlgenremodel.hpp
- modules/gui/qt/medialibrary/mlgroup.cpp
- modules/gui/qt/medialibrary/mlgroup.hpp
- modules/gui/qt/medialibrary/mlgrouplistmodel.cpp
- modules/gui/qt/medialibrary/mlgrouplistmodel.hpp
- modules/gui/qt/medialibrary/qml/MusicGenres.qml
- + modules/gui/qt/util/covergenerator.cpp
- + modules/gui/qt/util/covergenerator.hpp
- po/POTFILES.in
Changes:
=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -218,6 +218,8 @@ libqt_plugin_la_SOURCES = \
gui/qt/util/audio_device_model.cpp \
gui/qt/util/audio_device_model.hpp \
gui/qt/util/color_scheme_model.cpp gui/qt/util/color_scheme_model.hpp \
+ gui/qt/util/covergenerator.cpp \
+ gui/qt/util/covergenerator.hpp \
gui/qt/util/imageluminanceextractor.cpp gui/qt/util/imageluminanceextractor.hpp \
gui/qt/util/imagehelper.cpp gui/qt/util/imagehelper.hpp \
gui/qt/util/i18n.cpp gui/qt/util/i18n.hpp \
@@ -343,9 +345,7 @@ nodist_libqt_plugin_la_SOURCES = \
gui/qt/medialibrary/mlartistmodel.moc.cpp \
gui/qt/medialibrary/mlbasemodel.moc.cpp \
gui/qt/medialibrary/mlfoldersmodel.moc.cpp \
- gui/qt/medialibrary/mlgenre.moc.cpp \
gui/qt/medialibrary/mlgenremodel.moc.cpp \
- gui/qt/medialibrary/mlgroup.moc.cpp \
gui/qt/medialibrary/mlgrouplistmodel.moc.cpp \
gui/qt/medialibrary/mlqmltypes.moc.cpp \
gui/qt/medialibrary/mlrecentsmodel.moc.cpp \
@@ -374,6 +374,7 @@ nodist_libqt_plugin_la_SOURCES = \
gui/qt/util/asynctask.moc.cpp \
gui/qt/util/audio_device_model.moc.cpp \
gui/qt/util/color_scheme_model.moc.cpp \
+ gui/qt/util/covergenerator.moc.cpp \
gui/qt/util/imageluminanceextractor.moc.cpp \
gui/qt/util/i18n.moc.cpp \
gui/qt/util/listcache.moc.cpp \
=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -199,7 +199,6 @@ void MainUI::registerQMLTypes()
registerAnonymousType<MLAlbum>("org.videolan.medialib", 1);
registerAnonymousType<MLArtist>("org.videolan.medialib", 1);
registerAnonymousType<MLAlbumTrack>("org.videolan.medialib", 1);
- registerAnonymousType<MLGenre>("org.videolan.medialib", 1);
registerAnonymousType<MLPlaylist>("org.videolan.medialib", 1);
qmlRegisterType<AlbumContextMenu>( "org.videolan.medialib", 0, 1, "AlbumContextMenu" );
=====================================
modules/gui/qt/medialibrary/mlbasemodel.cpp
=====================================
@@ -408,19 +408,37 @@ void MLBaseModel::invalidateCache()
m_cache.reset();
}
+//-------------------------------------------------------------------------------------------------
+
MLItem *MLBaseModel::item(int signedidx) const
{
validateCache();
ssize_t count = m_cache->count();
- if (count == COUNT_UNINITIALIZED || signedidx < 0
- || signedidx >= count)
+
+ if (count == COUNT_UNINITIALIZED || signedidx < 0 || signedidx >= count)
return nullptr;
unsigned int idx = static_cast<unsigned int>(signedidx);
+
m_cache->refer(idx);
const std::unique_ptr<MLItem> *item = m_cache->get(idx);
+
+ if (!item)
+ /* Not in cache */
+ return nullptr;
+
+ /* Return raw pointer */
+ return item->get();
+}
+
+MLItem *MLBaseModel::itemCache(int signedidx) const
+{
+ unsigned int idx = static_cast<unsigned int>(signedidx);
+
+ const std::unique_ptr<MLItem> *item = m_cache->get(idx);
+
if (!item)
/* Not in cache */
return nullptr;
@@ -429,6 +447,8 @@ MLItem *MLBaseModel::item(int signedidx) const
return item->get();
}
+//-------------------------------------------------------------------------------------------------
+
MLBaseModel::BaseLoader::BaseLoader(vlc_medialibrary_t *ml, MLItemId parent, QString searchPattern,
vlc_ml_sorting_criteria_t sort, bool sort_desc)
: m_ml(ml)
=====================================
modules/gui/qt/medialibrary/mlbasemodel.hpp
=====================================
@@ -107,7 +107,12 @@ protected:
void validateCache() const;
void invalidateCache();
- MLItem* item(int signedidx) const;
+
+ MLItem *item(int signedidx) const;
+
+ // NOTE: This is faster because it only returns items available in cache.
+ MLItem *itemCache(int signedidx) const;
+
virtual void onVlcMlEvent( const MLEvent &event );
virtual ListCacheLoader<std::unique_ptr<MLItem>> *createLoader() const = 0;
=====================================
modules/gui/qt/medialibrary/mlgenre.cpp
=====================================
@@ -16,220 +16,27 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
-#include <cassert>
-#include <QPainter>
-#include <QImage>
-#include <QThreadPool>
-#include <QMutex>
-#include <QWaitCondition>
-#include <QDir>
-#include <QGradient>
-#include <QGraphicsScene>
-#include <QGraphicsPixmapItem>
-#include <QGraphicsBlurEffect>
-#include <algorithm>
#include "mlgenre.hpp"
-#include "qt.hpp"
-namespace {
-
-#define THUMBNAIL_WIDTH 260
-#define THUMBNAIL_HEIGHT 130
-
-QImage blurImage(const QImage& src)
-{
- QGraphicsScene scene;
- QGraphicsPixmapItem item;
- item.setPixmap(QPixmap::fromImage(src));
- QGraphicsBlurEffect blurEffect;
- blurEffect.setBlurRadius(4);
- blurEffect.setBlurHints(QGraphicsBlurEffect::QualityHint);
- item.setGraphicsEffect(&blurEffect);
- scene.addItem(&item);
- QImage res(src.size(), QImage::Format_ARGB32);
- QPainter ptr(&res);
- scene.render(&ptr);
- return res;
-}
+MLGenre::MLGenre(vlc_medialibrary_t* ml, const vlc_ml_genre_t *_data )
+ : MLItem ( MLItemId( _data->i_id, VLC_ML_PARENT_GENRE ) )
+ , m_ml ( ml )
+ , m_generator( nullptr )
+ , m_name ( QString::fromUtf8( _data->psz_name ) )
+ , m_nbTracks ( (unsigned int)_data->i_nb_tracks )
-class GenerateCoverTask : public QRunnable
{
-public:
- GenerateCoverTask(vlc_medialibrary_t* ml, MLGenre* genre, QString filepath)
- : QRunnable()
- , m_ml(ml)
- , m_genre(genre)
- , m_filepath(filepath)
- {
- }
-
- void drawRegion(QPainter& target, QString source, const QRect& rect)
- {
- QImage tmpImage;
- if (tmpImage.load(source))
- {
- QRect sourceRect;
- int size = std::min(tmpImage.width(), tmpImage.height());
- if (rect.width() == rect.height())
- {
- sourceRect = QRect( (tmpImage.width() - size) / 2,
- (tmpImage.height() - size) / 2,
- size,
- size);
- }
- else if (rect.width() > rect.height())
- {
- sourceRect = QRect( (tmpImage.width() - size) / 2,
- (tmpImage.height() - size/2) / 2,
- size,
- size/2);
- }
- else
- {
- sourceRect = QRect( (tmpImage.width() - size / 2) / 2,
- (tmpImage.height() - size) / 2,
- size/2,
- size);
- }
- target.drawImage(rect, tmpImage, sourceRect);
- }
- else
- {
- target.setPen(Qt::black);
- target.drawRect(rect);
- }
- }
-
- void run() override
- {
- {
- QMutexLocker lock(&m_taskLock);
- if (m_canceled) {
- m_taskCond.wakeAll();
- return;
- }
- m_running = true;
- }
-
- int64_t genreId = m_genre->getId().id;
- ml_unique_ptr<vlc_ml_album_list_t> album_list;
- //TODO only retreive albums with a cover.
- vlc_ml_query_params_t queryParams;
- memset(&queryParams, 0, sizeof(vlc_ml_query_params_t));
- album_list.reset( vlc_ml_list_genre_albums(m_ml, &queryParams, genreId) );
-
- QStringList thumbnails;
- thumbnails.reserve(8);
- for( const vlc_ml_album_t& media: ml_range_iterate<vlc_ml_album_t>( album_list ) ) {
- if (media.thumbnails[VLC_ML_THUMBNAIL_SMALL].i_status ==
- VLC_ML_THUMBNAIL_STATUS_AVAILABLE) {
- QUrl mediaURL( media.thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl );
- //QImage only accept local file
- if (mediaURL.isValid() && mediaURL.isLocalFile()) {
- thumbnails.append(mediaURL.path());
- if (thumbnails.size() == 8)
- break;
- }
- }
- }
-
- if (thumbnails.empty()) {
- thumbnails.append(":/noart_album.svg");
- }
-
- assert(thumbnails.size() <= 8);
- std::copy(thumbnails.begin(), ( thumbnails.begin() + ( 8 - thumbnails.size() ) ), std::back_inserter(thumbnails));
- assert(thumbnails.size() == 8);
-
- {
- QMutexLocker lock(&m_taskLock);
- if (m_canceled) {
- m_running = false;
- m_taskCond.wakeAll();
- return;
- }
- }
-
- QImage image(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, QImage::Format_RGB32);
- image.fill(Qt::white);
-
- QPainter painter;
- painter.begin(&image);
- for (int i = 0; i < 2; i++) {
- for (int j = 0; j < 4; j++) {
- drawRegion(painter, thumbnails[2*j+1], QRect( ( THUMBNAIL_WIDTH / 4 ) * j, ( THUMBNAIL_HEIGHT / 2 ) * i, THUMBNAIL_WIDTH / 4, THUMBNAIL_HEIGHT / 2 ));
- }
- }
- painter.end();
-
- image = blurImage(image);
-
- QLinearGradient gradient;
- gradient.setColorAt(0, QColor(0, 0, 0, 255*.3));
- gradient.setColorAt(1, QColor(0, 0, 0, 255*.7));
- painter.begin(&image);
- painter.setOpacity(.7);
- painter.fillRect(image.rect(), gradient);
- painter.end();
-
- if (image.save(m_filepath, "jpg"))
- /* Set the cover from the main thread */
- QMetaObject::invokeMethod(m_genre, [genre = m_genre, cover = QUrl::fromLocalFile(m_filepath).toString()]
- {
- genre->setCover(std::move(cover));
- });
-
- {
- QMutexLocker lock(&m_taskLock);
- m_running = false;
- m_taskCond.wakeAll();
- }
- }
-
- void cancel()
- {
- QMutexLocker lock(&m_taskLock);
- m_canceled = true;
- if (!m_running)
- return;
- m_taskCond.wait(&m_taskLock);
- }
-
-private:
- bool m_canceled = false;
- bool m_running = false;
- QMutex m_taskLock;
- QWaitCondition m_taskCond;
-
- vlc_medialibrary_t* m_ml = nullptr;
- MLGenre* m_genre = nullptr;
- QString m_filepath;
-};
-
+ assert(_data);
}
-
-MLGenre::MLGenre(vlc_medialibrary_t* ml, const vlc_ml_genre_t *_data, QObject *_parent )
- : QObject(_parent)
- , MLItem ( MLItemId( _data->i_id, VLC_ML_PARENT_GENRE ) )
- , m_ml ( ml )
- , m_name ( QString::fromUtf8( _data->psz_name ) )
- , m_nbTracks ( (unsigned int)_data->i_nb_tracks )
-
+bool MLGenre::hasGenerator() const
{
- assert(_data);
- connect(this, &MLGenre::askGenerateCover, this, &MLGenre::generateThumbnail);
+ return m_generator.get();
}
-MLGenre::~MLGenre()
+void MLGenre::setGenerator(CoverGenerator * generator)
{
- if (m_coverTask) {
- if (!QThreadPool::globalInstance()->tryTake(m_coverTask)) {
- //task is done or running
- static_cast<GenerateCoverTask*>(m_coverTask)->cancel();
- }
- delete m_coverTask;
- }
+ m_generator.reset(generator);
}
QString MLGenre::getName() const
@@ -244,43 +51,11 @@ unsigned int MLGenre::getNbTracks() const
QString MLGenre::getCover() const
{
- if (!m_cover.isEmpty())
- return m_cover;
- if (!m_coverTask) {
- emit askGenerateCover( QPrivateSignal() );
- }
return m_cover;
}
-void MLGenre::setCover(QString cover)
-{
- m_cover = cover;
- //TODO store in media library
-}
-
-void MLGenre::generateThumbnail()
+void MLGenre::setCover(const QString & fileName)
{
- if (!m_coverTask && m_cover.isNull()) {
-
- QDir dir(config_GetUserDir(VLC_CACHE_DIR));
- dir.mkdir("art");
- dir.cd("art");
- dir.mkdir("qt-genre-covers");
- dir.cd("qt-genre-covers");
-
- QString filename = QString("genre_thumbnail_%1.jpg").arg(getId().id);
- QString absoluteFilePath = dir.absoluteFilePath(filename);
- if (dir.exists(filename))
- {
- setCover(QUrl::fromLocalFile(absoluteFilePath).toString());
- }
- else
- {
- GenerateCoverTask* coverTask = new GenerateCoverTask(m_ml, this, absoluteFilePath);
- coverTask->setAutoDelete(false);
- m_coverTask = coverTask;
- QThreadPool::globalInstance()->start(coverTask);
- }
- }
+ m_cover = fileName;
}
=====================================
modules/gui/qt/medialibrary/mlgenre.hpp
=====================================
@@ -22,33 +22,26 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-#include "vlc_common.h"
-#include <memory>
-#include <QObject>
-#include <QString>
-#include <QList>
-#include <QRunnable>
-#include <vlc_media_library.h>
-#include "mlhelper.hpp"
+// Util includes
+#include "util/covergenerator.hpp"
+
+// MediaLibrary includes
#include "mlqmltypes.hpp"
-class MLGenre : public QObject, public MLItem
+class MLGenre : public MLItem
{
- Q_OBJECT
-
public:
- MLGenre( vlc_medialibrary_t* _ml, const vlc_ml_genre_t *_data, QObject *_parent = nullptr);
- ~MLGenre();
+ MLGenre( vlc_medialibrary_t* _ml, const vlc_ml_genre_t *_data );
+
+ bool hasGenerator() const;
+ void setGenerator(CoverGenerator * generator);
QString getName() const;
unsigned int getNbTracks() const;
- QString getCover() const;
- void setCover(QString cover);
-
-signals:
- void askGenerateCover( QPrivateSignal ) const;
+ QString getCover() const;
+ void setCover(const QString & fileName);
private slots:
void generateThumbnail();
@@ -56,9 +49,11 @@ private slots:
private:
vlc_medialibrary_t* m_ml;
+ TaskHandle<CoverGenerator> m_generator;
+
QString m_name;
QString m_cover;
- QRunnable* m_coverTask = nullptr;
+
unsigned int m_nbTracks;
};
=====================================
modules/gui/qt/medialibrary/mlgenremodel.cpp
=====================================
@@ -18,8 +18,25 @@
#include "mlgenremodel.hpp"
+// Util includes
+#include "util/covergenerator.hpp"
+
+// MediaLibrary includes
#include "mlartistmodel.hpp"
+//-------------------------------------------------------------------------------------------------
+// Static variables
+
+// NOTE: We multiply by 2 to cover most dpi settings.
+static const int MLGENREMODEL_COVER_WIDTH = 260 * 2;
+static const int MLGENREMODEL_COVER_HEIGHT = 130 * 2;
+
+static const int MLGENREMODEL_COVER_COUNTX = 4;
+static const int MLGENREMODEL_COVER_COUNTY = 2;
+
+static const int MLGENREMODEL_COVER_BLUR = 4;
+
+//-------------------------------------------------------------------------------------------------
QHash<QByteArray, vlc_ml_sorting_criteria_t> MLGenreModel::M_names_to_criteria = {
{"title", VLC_ML_SORTING_ALPHA}
@@ -32,10 +49,12 @@ MLGenreModel::MLGenreModel(QObject *parent)
QVariant MLGenreModel::data(const QModelIndex &index, int role) const
{
- if (!index.isValid() || index.row() < 0)
+ int row = index.row();
+
+ if (!index.isValid() || row < 0)
return QVariant();
- const MLGenre* ml_genre = static_cast<MLGenre *>(item(index.row()));
+ MLGenre* ml_genre = static_cast<MLGenre *>(item(row));
if (!ml_genre)
return QVariant();
@@ -49,7 +68,7 @@ QVariant MLGenreModel::data(const QModelIndex &index, int role) const
case GENRE_NB_TRACKS:
return QVariant::fromValue( ml_genre->getNbTracks() );
case GENRE_COVER:
- return QVariant::fromValue( ml_genre->getCover() );
+ return getCover(ml_genre, row);
default :
return QVariant();
}
@@ -102,6 +121,72 @@ vlc_ml_sorting_criteria_t MLGenreModel::nameToCriteria(QByteArray name) const
return M_names_to_criteria.value(name, VLC_ML_SORTING_DEFAULT);
}
+QString MLGenreModel::getCover(MLGenre * genre, int index) const
+{
+ QString cover = genre->getCover();
+
+ // NOTE: Making sure we're not already generating a cover.
+ if (cover.isNull() == false || genre->hasGenerator())
+ return cover;
+
+ CoverGenerator * generator = new CoverGenerator(m_ml, genre->getId(), index);
+
+ generator->setSize(QSize(MLGENREMODEL_COVER_WIDTH,
+ MLGENREMODEL_COVER_HEIGHT));
+
+ generator->setCountX(MLGENREMODEL_COVER_COUNTX);
+ generator->setCountY(MLGENREMODEL_COVER_COUNTY);
+
+ generator->setSplit(CoverGenerator::Duplicate);
+
+ generator->setBlur(MLGENREMODEL_COVER_BLUR);
+
+ generator->setDefaultThumbnail(":/noart_album.svg");
+
+ // NOTE: We'll apply the new cover once it's loaded.
+ connect(generator, &CoverGenerator::result, this, &MLGenreModel::onCover);
+
+ generator->start(*QThreadPool::globalInstance());
+
+ genre->setGenerator(generator);
+
+ return cover;
+}
+
+//-------------------------------------------------------------------------------------------------
+// Private slots
+//-------------------------------------------------------------------------------------------------
+
+void MLGenreModel::onCover()
+{
+ CoverGenerator * generator = static_cast<CoverGenerator *> (sender());
+
+ int index = generator->getIndex();
+
+ // NOTE: We want to avoid calling 'MLBaseModel::item' for performance issues.
+ MLItem * item = this->itemCache(index);
+
+ // NOTE: When the item is no longer cached or has been moved we return right away.
+ if (item == nullptr || item->getId() != generator->getId())
+ {
+ generator->deleteLater();
+
+ return;
+ }
+
+ MLGenre * genre = static_cast<MLGenre *> (item);
+
+ QString fileName = QUrl::fromLocalFile(generator->takeResult()).toString();
+
+ genre->setCover(fileName);
+
+ genre->setGenerator(nullptr);
+
+ thumbnailUpdated(index);
+}
+
+//-------------------------------------------------------------------------------------------------
+
ListCacheLoader<std::unique_ptr<MLItem>> *
MLGenreModel::createLoader() const
{
=====================================
modules/gui/qt/medialibrary/mlgenremodel.hpp
=====================================
@@ -60,15 +60,21 @@ private:
vlc_ml_sorting_criteria_t roleToCriteria(int role) const override;
vlc_ml_sorting_criteria_t nameToCriteria(QByteArray name) const override;
+ QString getCover(MLGenre * genre, int index) const;
- static QHash<QByteArray, vlc_ml_sorting_criteria_t> M_names_to_criteria;
+private slots:
+ void onCover();
+private:
struct Loader : public BaseLoader
{
Loader(const MLGenreModel &model) : BaseLoader(model) {}
size_t count() const override;
std::vector<std::unique_ptr<MLItem>> load(size_t index, size_t count) const override;
};
+
+private: // Variables
+ static QHash<QByteArray, vlc_ml_sorting_criteria_t> M_names_to_criteria;
};
=====================================
modules/gui/qt/medialibrary/mlgroup.cpp
=====================================
@@ -21,17 +21,16 @@
#include "mlgroup.hpp"
// VLC includes
-#include <vlc_media_library.h>
#include "qt.hpp"
//-------------------------------------------------------------------------------------------------
// Ctor / dtor
//-------------------------------------------------------------------------------------------------
-MLGroup::MLGroup(vlc_medialibrary_t * ml, const vlc_ml_group_t * data, QObject * parent)
- : QObject(parent)
- , MLItem(MLItemId(data->i_id, VLC_ML_PARENT_GROUP))
+MLGroup::MLGroup(vlc_medialibrary_t * ml, const vlc_ml_group_t * data)
+ : MLItem(MLItemId(data->i_id, VLC_ML_PARENT_GROUP))
, m_ml(ml)
+ , m_generator(nullptr)
, m_name(qfu(data->psz_name))
, m_duration(data->i_duration)
, m_date(data->i_creation_date)
@@ -44,16 +43,37 @@ MLGroup::MLGroup(vlc_medialibrary_t * ml, const vlc_ml_group_t * data, QObject *
// Interface
//-------------------------------------------------------------------------------------------------
+bool MLGroup::hasGenerator() const
+{
+ return m_generator.get();
+}
+
+void MLGroup::setGenerator(CoverGenerator * generator)
+{
+ m_generator.reset(generator);
+}
+
+//-------------------------------------------------------------------------------------------------
+
QString MLGroup::getName() const
{
return m_name;
}
-QString MLGroup::getThumbnail()
+//-------------------------------------------------------------------------------------------------
+
+QString MLGroup::getCover() const
{
- return QString();
+ return m_cover;
}
+void MLGroup::setCover(const QString & fileName)
+{
+ m_cover = fileName;
+}
+
+//-------------------------------------------------------------------------------------------------
+
int64_t MLGroup::getDuration() const
{
return m_duration;
=====================================
modules/gui/qt/medialibrary/mlgroup.hpp
=====================================
@@ -25,23 +25,25 @@
#include "config.h"
#endif
+// Util includes
+#include "util/covergenerator.hpp"
+
// MediaLibrary includes
#include "mlqmltypes.hpp"
-// Qt includes
-#include <QObject>
-
-class MLGroup : public QObject, public MLItem
+class MLGroup : public MLItem
{
- Q_OBJECT
-
public:
- MLGroup(vlc_medialibrary_t * ml, const vlc_ml_group_t * data, QObject * parent = nullptr);
+ MLGroup(vlc_medialibrary_t * ml, const vlc_ml_group_t * data);
public: // Interface
+ bool hasGenerator() const;
+ void setGenerator(CoverGenerator * generator);
+
QString getName() const;
- QString getThumbnail();
+ QString getCover() const;
+ void setCover(const QString & fileName);
int64_t getDuration() const;
@@ -52,8 +54,12 @@ public: // Interface
private:
vlc_medialibrary_t * m_ml;
+ TaskHandle<CoverGenerator> m_generator;
+
QString m_name;
+ QString m_cover;
+
int64_t m_duration;
unsigned int m_date;
=====================================
modules/gui/qt/medialibrary/mlgrouplistmodel.cpp
=====================================
@@ -27,6 +27,9 @@
// VLC includes
#include <vlc_media_library.h>
+// Util includes
+#include "util/covergenerator.hpp"
+
// MediaLibrary includes
#include "mlhelper.hpp"
#include "mlgroup.hpp"
@@ -35,6 +38,10 @@
//-------------------------------------------------------------------------------------------------
// Static variables
+// NOTE: We multiply by 2 to cover most dpi settings.
+static const int MLGROUPLISTMODEL_COVER_WIDTH = 512 * 2; // 16 / 10 ratio
+static const int MLGROUPLISTMODEL_COVER_HEIGHT = 320 * 2;
+
static const QHash<QByteArray, vlc_ml_sorting_criteria_t> criterias =
{
{"id", VLC_ML_SORTING_DEFAULT},
@@ -85,8 +92,9 @@ QHash<int, QByteArray> MLGroupListModel::roleNames() const /* override */
QVariant MLGroupListModel::data(const QModelIndex & index, int role) const /* override */
{
+ int row = index.row();
- MLItem * item = this->item(index.row());
+ MLItem * item = this->item(row);
if (item == nullptr)
return QVariant();
@@ -109,7 +117,7 @@ QVariant MLGroupListModel::data(const QModelIndex & index, int role) const /* ov
case GROUP_NAME:
return QVariant::fromValue(group->getName());
case GROUP_THUMBNAIL:
- return QVariant::fromValue(group->getThumbnail());
+ return getCover(group, row);
case GROUP_DURATION:
return QVariant::fromValue(group->getDuration());
case GROUP_DATE:
@@ -207,6 +215,35 @@ ListCacheLoader<std::unique_ptr<MLItem>> * MLGroupListModel::createLoader() cons
return new Loader(*this);
}
+//-------------------------------------------------------------------------------------------------
+// Private functions
+//-------------------------------------------------------------------------------------------------
+
+QString MLGroupListModel::getCover(MLGroup * group, int index) const
+{
+ QString cover = group->getCover();
+
+ // NOTE: Making sure we're not already generating a cover.
+ if (cover.isNull() == false || group->hasGenerator())
+ return cover;
+
+ CoverGenerator * generator = new CoverGenerator(m_ml, group->getId(), index);
+
+ generator->setSize(QSize(MLGROUPLISTMODEL_COVER_WIDTH,
+ MLGROUPLISTMODEL_COVER_HEIGHT));
+
+ generator->setDefaultThumbnail(":/noart_videoCover.svg");
+
+ // NOTE: We'll apply the new thumbnail once it's loaded.
+ connect(generator, &CoverGenerator::result, this, &MLGroupListModel::onCover);
+
+ generator->start(*QThreadPool::globalInstance());
+
+ group->setGenerator(generator);
+
+ return cover;
+}
+
//-------------------------------------------------------------------------------------------------
// Private MLBaseModel reimplementation
//-------------------------------------------------------------------------------------------------
@@ -230,7 +267,41 @@ void MLGroupListModel::onVlcMlEvent(const MLEvent & event) /* override */
void MLGroupListModel::thumbnailUpdated(int idx) /* override */
{
- emit dataChanged(index(idx), index(idx), { GROUP_THUMBNAIL });
+ QModelIndex index = this->index(idx);
+
+ emit dataChanged(index, index, { GROUP_THUMBNAIL });
+}
+
+//-------------------------------------------------------------------------------------------------
+// Private slots
+//-------------------------------------------------------------------------------------------------
+
+void MLGroupListModel::onCover()
+{
+ CoverGenerator * generator = static_cast<CoverGenerator *> (sender());
+
+ int index = generator->getIndex();
+
+ // NOTE: We want to avoid calling 'MLBaseModel::item' for performance issues.
+ MLItem * item = this->itemCache(index);
+
+ // NOTE: When the item is no longer cached or has been moved we return right away.
+ if (item == nullptr || item->getId() != generator->getId())
+ {
+ generator->deleteLater();
+
+ return;
+ }
+
+ MLGroup * group = static_cast<MLGroup *> (item);
+
+ QString fileName = QUrl::fromLocalFile(generator->takeResult()).toString();
+
+ group->setCover(fileName);
+
+ group->setGenerator(nullptr);
+
+ thumbnailUpdated(index);
}
//=================================================================================================
=====================================
modules/gui/qt/medialibrary/mlgrouplistmodel.hpp
=====================================
@@ -26,6 +26,7 @@
// Forward declarations
class vlc_medialibrary_t;
+class MLGroup;
class MLGroupListModel : public MLBaseModel
{
@@ -72,11 +73,17 @@ protected: // MLBaseModel implementation
ListCacheLoader<std::unique_ptr<MLItem>> * createLoader() const override;
+private: // Functions
+ QString getCover(MLGroup * group, int index) const;
+
private: // MLBaseModel implementation
void onVlcMlEvent(const MLEvent & event) override;
void thumbnailUpdated(int idx) override;
+private slots:
+ void onCover();
+
private:
struct Loader : public MLBaseModel::BaseLoader
{
=====================================
modules/gui/qt/medialibrary/qml/MusicGenres.qml
=====================================
@@ -187,6 +187,18 @@ Widgets.NavigableFocusScope {
}
pictureOverlay: Item {
+ Rectangle
+ {
+ anchors.fill: parent
+
+ radius: VLCStyle.gridCover_radius
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, 0.3) }
+ GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.7) }
+ }
+ }
+
Column {
anchors.centerIn: parent
=====================================
modules/gui/qt/util/covergenerator.cpp
=====================================
@@ -0,0 +1,379 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * Authors: Benjamin Arnaud <bunjee at omega.gg>
+ *
+ * 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.
+ *****************************************************************************/
+
+#include "covergenerator.hpp"
+
+// VLC includes
+#include "qt.hpp"
+
+// MediaLibrary includes
+#include "medialibrary/mlhelper.hpp"
+
+// Qt includes
+#include <QDir>
+#include <QGraphicsScene>
+#include <QGraphicsPixmapItem>
+#include <QGraphicsBlurEffect>
+
+//-------------------------------------------------------------------------------------------------
+// Static variables
+
+static const QString COVERGENERATOR_STORAGE = "/art/qt-covers";
+
+static const int COVERGENERATOR_COUNT = 2;
+
+static const QString COVERGENERATOR_DEFAULT = ":/noart.png";
+
+//-------------------------------------------------------------------------------------------------
+// Ctor / dtor
+//-------------------------------------------------------------------------------------------------
+
+CoverGenerator::CoverGenerator(vlc_medialibrary_t * ml, const MLItemId & itemId, int index)
+ : m_ml(ml)
+ , m_id(itemId)
+ , m_index(index)
+ , m_countX(COVERGENERATOR_COUNT)
+ , m_countY(COVERGENERATOR_COUNT)
+ , m_split(Divide)
+ , m_smooth(false)
+ , m_blur(0)
+ , m_default(COVERGENERATOR_DEFAULT) {}
+
+//-------------------------------------------------------------------------------------------------
+// Interface
+//-------------------------------------------------------------------------------------------------
+
+/* Q_INVOKABLE */ MLItemId CoverGenerator::getId()
+{
+ return m_id;
+}
+
+/* Q_INVOKABLE */ int CoverGenerator::getIndex()
+{
+ return m_index;
+}
+
+//-------------------------------------------------------------------------------------------------
+
+/* Q_INVOKABLE */ void CoverGenerator::setSize(const QSize & size)
+{
+ m_size = size;
+}
+
+/* Q_INVOKABLE */ void CoverGenerator::setCountX(int x)
+{
+ m_countX = x;
+}
+
+/* Q_INVOKABLE */ void CoverGenerator::setCountY(int y)
+{
+ m_countY = y;
+}
+
+/* Q_INVOKABLE */ void CoverGenerator::setSplit(Split split)
+{
+ m_split = split;
+}
+
+/* Q_INVOKABLE */ void CoverGenerator::setSmooth(bool enabled)
+{
+ m_smooth = enabled;
+}
+
+/* Q_INVOKABLE */ void CoverGenerator::setBlur(int radius)
+{
+ m_blur = radius;
+}
+
+/* Q_INVOKABLE */ void CoverGenerator::setDefaultThumbnail(const QString & fileName)
+{
+ m_default = fileName;
+}
+
+//-------------------------------------------------------------------------------------------------
+// QRunnable implementation
+//-------------------------------------------------------------------------------------------------
+
+QString CoverGenerator::execute() /* override */
+{
+ QDir dir(config_GetUserDir(VLC_CACHE_DIR) + COVERGENERATOR_STORAGE);
+
+ dir.mkpath(dir.absolutePath());
+
+ vlc_ml_parent_type type = m_id.type;
+
+ int64_t id = m_id.id;
+
+ QString string = getStringType(type);
+
+ QString fileName = QString("%1_thumbnail_%2.jpg").arg(string).arg(id);
+
+ fileName = dir.absoluteFilePath(fileName);
+
+ if (dir.exists(fileName))
+ {
+ return fileName;
+ }
+
+ QStringList thumbnails;
+
+ int count = m_countX * m_countY;
+
+ if (type == VLC_ML_PARENT_GENRE)
+ thumbnails = getGenre(count, id);
+ else
+ thumbnails = getMedias(count, id, type);
+
+ if (thumbnails.isEmpty())
+ {
+ if (m_split == CoverGenerator::Duplicate)
+ {
+ while (thumbnails.count() != count)
+ {
+ thumbnails.append(m_default);
+ }
+ }
+ else
+ {
+ thumbnails.append(m_default);
+
+ m_countX = 1;
+ m_countY = 1;
+ }
+ }
+ else if (m_split == CoverGenerator::Duplicate)
+ {
+ int index = 0;
+
+ while (thumbnails.count() != count)
+ {
+ thumbnails.append(thumbnails.at(index));
+
+ index++;
+ }
+ }
+ else // if (m_split == CoverGenerator::Divide)
+ {
+ // NOTE: This handles the 2x2 case.
+ if (thumbnails.count() == 2)
+ {
+ m_countX = 2;
+ m_countY = 1;
+ }
+ }
+
+ QImage image(m_size, QImage::Format_RGB32);
+
+ image.fill(Qt::white);
+
+ QPainter painter;
+
+ painter.begin(&image);
+
+ draw(painter, thumbnails);
+
+ painter.end();
+
+ if (m_blur > 0)
+ blur(&image);
+
+ image.save(fileName, "jpg");
+
+ return fileName;
+}
+
+//-------------------------------------------------------------------------------------------------
+// Private functions
+//-------------------------------------------------------------------------------------------------
+
+void CoverGenerator::draw(QPainter & painter, const QStringList & fileNames)
+{
+ int count = fileNames.count();
+
+ int width = m_size.width() / m_countX;
+ int height = m_size.height() / m_countY;
+
+ for (int y = 0; y < m_countY; y++)
+ {
+ for (int x = 0; x < m_countX; x++)
+ {
+ int index = m_countX * y + x;
+
+ if (index == count) return;
+
+ QRect rect;
+
+ // NOTE: This handles the wider thumbnail case (e.g. for a 2x1 grid).
+ if (index == count - 1 && x != m_countX - 1)
+ {
+ rect = QRect(width * x, height * y, width * m_countX - x, height);
+ }
+ else
+ rect = QRect(width * x, height * y, width, height);
+
+ drawImage(painter, fileNames.at(index), rect);
+ }
+ }
+}
+
+void CoverGenerator::drawImage(QPainter & painter, const QString & fileName, const QRect & target)
+{
+ QImage image;
+
+ if (fileName.isEmpty())
+ image.load(m_default);
+ else
+ image.load(fileName);
+
+ // NOTE: This image does not seem valid so we paint the placeholder instead.
+ if (image.isNull())
+ {
+ image.load(m_default);
+ }
+
+ // NOTE: Should we use Qt::SmoothTransformation or favor efficiency ?
+ if (m_smooth)
+ image = image.scaled(target.size(), Qt::KeepAspectRatioByExpanding,
+ Qt::SmoothTransformation);
+ else
+ image = image.scaled(target.size(), Qt::KeepAspectRatioByExpanding);
+
+ int x = (image.width () - target.width ()) / 2;
+ int y = (image.height() - target.height()) / 2;
+
+ QRect source(x, y, target.width(), target.height());
+
+ painter.drawImage(target, image, source);
+}
+
+//-------------------------------------------------------------------------------------------------
+
+// FIXME: This implementation is not ideal and uses a dedicated QGraphicsScene.
+void CoverGenerator::blur(QImage * image)
+{
+ assert(image);
+
+ QGraphicsScene scene;
+
+ QGraphicsPixmapItem item(QPixmap::fromImage(*image));
+
+ QGraphicsBlurEffect effect;
+
+ effect.setBlurRadius(m_blur);
+
+ effect.setBlurHints(QGraphicsBlurEffect::QualityHint);
+
+ item.setGraphicsEffect(&effect);
+
+ scene.addItem(&item);
+
+ QImage result(image->size(), QImage::Format_ARGB32);
+
+ QPainter painter(&result);
+
+ scene.render(&painter);
+
+ *image = result;
+}
+
+//-------------------------------------------------------------------------------------------------
+
+QString CoverGenerator::getStringType(vlc_ml_parent_type type) const
+{
+ switch (type)
+ {
+ case VLC_ML_PARENT_GENRE:
+ return "genre";
+ case VLC_ML_PARENT_GROUP:
+ return "group";
+ case VLC_ML_PARENT_PLAYLIST:
+ return "playlist";
+ default:
+ return "unknown";
+ }
+}
+
+//-------------------------------------------------------------------------------------------------
+
+QStringList CoverGenerator::getGenre(int count, int64_t id) const
+{
+ QStringList thumbnails;
+
+ vlc_ml_query_params_t params;
+
+ memset(¶ms, 0, sizeof(vlc_ml_query_params_t));
+
+ // NOTE: We retrieve twice the count to maximize our chances to get a valid thumbnail.
+ params.i_nbResults = count * 2;
+
+ ml_unique_ptr<vlc_ml_album_list_t> list(vlc_ml_list_genre_albums(m_ml, ¶ms, id));
+
+ for (const vlc_ml_album_t & album : ml_range_iterate<vlc_ml_album_t>(list))
+ {
+ if (album.thumbnails[VLC_ML_THUMBNAIL_SMALL].i_status != VLC_ML_THUMBNAIL_STATUS_AVAILABLE)
+ continue;
+
+ QUrl url(album.thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl);
+
+ // NOTE: We only want local files to compose the cover.
+ if (url.isLocalFile() == false)
+ continue;
+
+ thumbnails.append(url.path());
+
+ if (thumbnails.count() == count)
+ return thumbnails;
+ }
+
+ return thumbnails;
+}
+
+QStringList CoverGenerator::getMedias(int count, int64_t id, vlc_ml_parent_type type) const
+{
+ QStringList thumbnails;
+
+ vlc_ml_query_params_t params;
+
+ memset(¶ms, 0, sizeof(vlc_ml_query_params_t));
+
+ // NOTE: We retrieve twice the count to maximize our chances to get a valid thumbnail.
+ params.i_nbResults = count * 2;
+
+ ml_unique_ptr<vlc_ml_media_list_t> list(vlc_ml_list_media_of(m_ml, ¶ms, type, id));
+
+ for (const vlc_ml_media_t & media : ml_range_iterate<vlc_ml_media_t>(list))
+ {
+ if (media.thumbnails[VLC_ML_THUMBNAIL_SMALL].i_status != VLC_ML_THUMBNAIL_STATUS_AVAILABLE)
+ continue;
+
+ QUrl url(media.thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl);
+
+ // NOTE: We only want local files to compose the cover.
+ if (url.isLocalFile() == false)
+ continue;
+
+ thumbnails.append(url.path());
+
+ if (thumbnails.count() == count)
+ return thumbnails;
+ }
+
+ return thumbnails;
+}
=====================================
modules/gui/qt/util/covergenerator.hpp
=====================================
@@ -0,0 +1,114 @@
+/*****************************************************************************
+ * Copyright (C) 2021 VLC authors and VideoLAN
+ *
+ * Authors: Benjamin Arnaud <bunjee at omega.gg>
+ *
+ * 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 COVERGENERATOR_HPP
+#define COVERGENERATOR_HPP
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+// MediaLibrary includes
+#include "medialibrary/mlqmltypes.hpp"
+
+// Util includes
+#include "util/asynctask.hpp"
+
+// Qt includes
+#include <QPainter>
+
+// Forward declarations
+class vlc_medialibrary_t;
+class MLItemId;
+
+class CoverGenerator : public AsyncTask<QString>
+{
+ Q_OBJECT
+
+ Q_ENUMS(Split)
+
+public: // Enums
+ enum Split
+ {
+ Divide,
+ Duplicate
+ };
+
+public:
+ CoverGenerator(vlc_medialibrary_t * ml, const MLItemId & itemId, int index = -1);
+
+public: // Interface
+ Q_INVOKABLE MLItemId getId();
+
+ Q_INVOKABLE int getIndex();
+
+ Q_INVOKABLE void setSize(const QSize & size);
+
+ Q_INVOKABLE void setCountX(int x);
+ Q_INVOKABLE void setCountY(int y);
+
+ // NOTE: Do we want to divide or duplicate thumbnails to reach the proper count ?
+ Q_INVOKABLE void setSplit(Split split);
+
+ // NOTE: Applies SmoothTransformation to thumbnails. Disabled by default.
+ Q_INVOKABLE void setSmooth(bool enabled);
+
+ // NOTE: You need to specify a radius to enable blur, 8 looks good.
+ Q_INVOKABLE void setBlur(int radius);
+
+ Q_INVOKABLE void setDefaultThumbnail(const QString & fileName);
+
+public: // AsyncTask implementation
+ QString execute() override;
+
+private: // Functions
+ void draw(QPainter & painter, const QStringList & fileNames);
+
+ void drawImage(QPainter & painter, const QString & fileName, const QRect & rect);
+
+ void blur(QImage * image);
+
+ QString getStringType(vlc_ml_parent_type type) const;
+
+ QStringList getMedias(int count, int64_t id, vlc_ml_parent_type type) const;
+ QStringList getGenre (int count, int64_t id) const;
+
+private:
+ vlc_medialibrary_t * m_ml;
+
+ MLItemId m_id;
+
+ int m_index;
+
+ QSize m_size;
+
+ int m_countX;
+ int m_countY;
+
+ Split m_split;
+
+ bool m_smooth;
+
+ int m_blur;
+
+ QString m_default;
+};
+
+#endif // COVERGENERATOR_HPP
=====================================
po/POTFILES.in
=====================================
@@ -791,6 +791,8 @@ modules/gui/qt/widgets/native/animators.cpp
modules/gui/qt/widgets/native/animators.hpp
modules/gui/qt/widgets/native/customwidgets.cpp
modules/gui/qt/widgets/native/customwidgets.hpp
+modules/gui/qt/util/covergenerator.cpp
+modules/gui/qt/util/covergenerator.hpp
modules/gui/qt/util/imagehelper.cpp
modules/gui/qt/util/imagehelper.hpp
modules/gui/qt/util/qt_dirs.cpp
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/12d1b4f87cdd89cc9a4d325c1303445ac94abf03...4362af465dc8f395cfdb3cd2e67a8992c1b67111
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/12d1b4f87cdd89cc9a4d325c1303445ac94abf03...4362af465dc8f395cfdb3cd2e67a8992c1b67111
You're receiving this email because of your account on code.videolan.org.
More information about the vlc-commits
mailing list