[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(&params, 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, &params, 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(&params, 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, &params, 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