[vlc-devel] [PATCH 1/3] qt: generate medialib genre covers on C++ side

Pierre Lamot pierre at videolabs.io
Tue Oct 29 14:47:16 CET 2019


---
 .../gui/qt/components/mediacenter/mlgenre.cpp | 230 +++++++++++++++++-
 .../gui/qt/components/mediacenter/mlgenre.hpp |  20 +-
 .../components/mediacenter/mlgenremodel.cpp   |   8 +-
 3 files changed, 254 insertions(+), 4 deletions(-)

diff --git a/modules/gui/qt/components/mediacenter/mlgenre.cpp b/modules/gui/qt/components/mediacenter/mlgenre.cpp
index 8bed1c1fc7..a20686c16b 100644
--- a/modules/gui/qt/components/mediacenter/mlgenre.cpp
+++ b/modules/gui/qt/components/mediacenter/mlgenre.cpp
@@ -17,25 +17,208 @@
  *****************************************************************************/
 
 #include <cassert>
+#include <QPainter>
+#include <QImage>
+#include <QThreadPool>
+#include <QMutex>
+#include <QWaitCondition>
+#include <QDir>
 #include "mlgenre.hpp"
+#include "qt.hpp"
 
-MLGenre::MLGenre(const vlc_ml_genre_t *_data, QObject *_parent )
+namespace  {
+
+#define THUMBNAIL_WIDTH 512
+#define THUMBNAIL_HEIGHT 512
+
+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 thumnails;
+        int count = 0;
+        for( const vlc_ml_album_t& media: ml_range_iterate<vlc_ml_album_t>( album_list ) ) {
+            if (media.thumbnails[VLC_ML_THUMBNAIL_SMALL].b_generated) {
+                QUrl mediaURL( media.thumbnails[VLC_ML_THUMBNAIL_SMALL].psz_mrl );
+                //QImage only accept local file
+                if (mediaURL.isValid() && mediaURL.isLocalFile()) {
+                    thumnails.append(mediaURL.path());
+                    count++;
+                    if (count >= 4)
+                        break;
+                }
+            }
+        }
+
+        {
+            QMutexLocker lock(&m_taskLock);
+            if (m_canceled) {
+                m_running = false;
+                m_taskCond.wakeAll();
+                return;
+            }
+        }
+
+        QImage image(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, QImage::Format_RGB32);
+
+        QPainter painter;
+        painter.begin(&image);
+        switch (count) {
+            case 0:
+                break;
+
+            case 1:
+                drawRegion(painter, thumnails[0], QRect(0,                 0,                  THUMBNAIL_WIDTH,   THUMBNAIL_HEIGHT   ));
+                break;
+
+            case 2:
+                drawRegion(painter, thumnails[0], QRect(0,                 0,                  THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT   ));
+                drawRegion(painter, thumnails[1], QRect(THUMBNAIL_WIDTH/2, 0,                  THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT   ));
+                break;
+
+            case 3:
+                drawRegion(painter, thumnails[0], QRect(0,                 0,                  THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT   ));
+                drawRegion(painter, thumnails[1], QRect(THUMBNAIL_WIDTH/2, 0,                  THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT/2));
+                drawRegion(painter, thumnails[2], QRect(THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT/2, THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT/2));
+                break;
+
+            case 4:
+                drawRegion(painter, thumnails[0], QRect(0,                 0,                  THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT/2));
+                drawRegion(painter, thumnails[1], QRect(THUMBNAIL_WIDTH/2, 0,                  THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT/2));
+                drawRegion(painter, thumnails[2], QRect(THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT/2, THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT/2));
+                drawRegion(painter, thumnails[3], QRect(0,                 THUMBNAIL_HEIGHT/2, THUMBNAIL_WIDTH/2, THUMBNAIL_HEIGHT/2));
+                break;
+        }
+        painter.end();
+
+        if (count > 0) {
+            if (image.save(m_filepath, "jpg"))
+                m_genre->setCover(QUrl::fromLocalFile(m_filepath).toString());
+        }
+
+        {
+            QMutexLocker lock(&m_taskLock);
+            m_running = false;
+            m_taskCond.wakeAll();
+        }
+    }
+
+    void cancel()
+    {
+        QMutexLocker lock(&m_taskLock);
+        if (!m_running)
+            return;
+        m_canceled = true;
+        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;
+};
+
+}
+
+
+MLGenre::MLGenre(vlc_medialibrary_t* ml, const vlc_ml_genre_t *_data, QObject *_parent )
     : QObject(_parent)
+    , m_ml      ( ml )
     , m_id      ( _data->i_id, VLC_ML_PARENT_GENRE )
     , m_name    ( QString::fromUtf8( _data->psz_name ) )
 
 {
     assert(_data);
+    connect(this, &MLGenre::askGenerateCover, this, &MLGenre::generateThumbnail);
 }
 
 MLGenre::MLGenre(const MLGenre &genre, QObject *_parent)
     : QObject(_parent)
+    , m_ml      ( genre.m_ml )
     , m_id      ( genre.m_id )
     , m_name    ( genre.m_name )
 {
 
 }
 
+MLGenre::~MLGenre()
+{
+    if (m_coverTask) {
+        if (!QThreadPool::globalInstance()->tryTake(m_coverTask)) {
+            //task is done or running
+            static_cast<GenerateCoverTask*>(m_coverTask)->cancel();
+        }
+        delete m_coverTask;
+    }
+}
+
 MLParentId MLGenre::getId() const
 {
     return m_id;
@@ -51,8 +234,53 @@ unsigned int MLGenre::getNbTracks() const
     return m_nbTracks;
 }
 
+QString MLGenre::getCover() const
+{
+    if (!m_cover.isEmpty())
+        return  m_cover;
+    if (!m_coverTask) {
+        emit askGenerateCover( QPrivateSignal() );
+    }
+    return m_cover;
+}
+
+void MLGenre::setCover(const QString cover)
+{
+    m_cover = cover;
+    //TODO store in media library
+    emit coverChanged(m_cover);
+}
+
 MLGenre *MLGenre::clone(QObject *parent) const
 {
     return new MLGenre(*this, parent);
 }
 
+
+
+void MLGenre::generateThumbnail()
+{
+    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(m_id.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);
+        }
+    }
+}
+
diff --git a/modules/gui/qt/components/mediacenter/mlgenre.hpp b/modules/gui/qt/components/mediacenter/mlgenre.hpp
index 61e44a9df3..66b5773be4 100644
--- a/modules/gui/qt/components/mediacenter/mlgenre.hpp
+++ b/modules/gui/qt/components/mediacenter/mlgenre.hpp
@@ -28,6 +28,7 @@
 #include <QObject>
 #include <QString>
 #include <QList>
+#include <QRunnable>
 #include <vlc_media_library.h>
 #include "mlhelper.hpp"
 #include "mlqmltypes.hpp"
@@ -39,21 +40,38 @@ class MLGenre : public QObject
     Q_PROPERTY(MLParentId id READ getId CONSTANT)
     Q_PROPERTY(QString name READ getName CONSTANT)
     Q_PROPERTY(unsigned int nbtracks READ getNbTracks CONSTANT)
+    Q_PROPERTY(QString cover READ getCover WRITE setCover NOTIFY coverChanged)
 
 public:
-    MLGenre( const vlc_ml_genre_t *_data, QObject *_parent = nullptr);
+    MLGenre( vlc_medialibrary_t* _ml, const vlc_ml_genre_t *_data, QObject *_parent = nullptr);
+    ~MLGenre();
 
     MLParentId getId() const;
     QString getName() const;
     unsigned int getNbTracks() const;
+    QString getCover() const;
 
     MLGenre* clone(QObject *parent = nullptr) const;
 
+signals:
+    void coverChanged( const QString );
+    void askGenerateCover( QPrivateSignal ) const;
+
+public slots:
+    void setCover(const QString cover);
+
+private slots:
+    void generateThumbnail();
+
 private:
     MLGenre( const MLGenre& genre, QObject *_parent = nullptr);
 
+    vlc_medialibrary_t* m_ml;
+
     MLParentId m_id;
     QString m_name;
+    QString m_cover;
+    QRunnable* m_coverTask = nullptr;
     unsigned int m_nbTracks;
 };
 
diff --git a/modules/gui/qt/components/mediacenter/mlgenremodel.cpp b/modules/gui/qt/components/mediacenter/mlgenremodel.cpp
index c4a23ecbae..cf248411c6 100644
--- a/modules/gui/qt/components/mediacenter/mlgenremodel.cpp
+++ b/modules/gui/qt/components/mediacenter/mlgenremodel.cpp
@@ -29,6 +29,7 @@ namespace {
         GENRE_ARTISTS,
         GENRE_TRACKS,
         GENRE_ALBUMS,
+        GENRE_COVER
     };
 }
 
@@ -59,6 +60,8 @@ QVariant MLGenreModel::data(const QModelIndex &index, int role) const
         return QVariant::fromValue( ml_genre->getName() );
     case GENRE_NB_TRACKS:
         return QVariant::fromValue( ml_genre->getNbTracks() );
+    case GENRE_COVER:
+        return QVariant::fromValue( ml_genre->getCover() );
     default :
         return QVariant();
     }
@@ -72,7 +75,8 @@ QHash<int, QByteArray> MLGenreModel::roleNames() const
         { GENRE_NB_TRACKS, "nb_tracks" },
         { GENRE_ARTISTS, "artists" },
         { GENRE_TRACKS, "tracks" },
-        { GENRE_ALBUMS, "albums" }
+        { GENRE_ALBUMS, "albums" },
+        { GENRE_COVER, "cover" }
     };
 }
 
@@ -85,7 +89,7 @@ std::vector<std::unique_ptr<MLGenre>> MLGenreModel::fetch()
         return {};
     std::vector<std::unique_ptr<MLGenre>> res;
     for( const vlc_ml_genre_t& genre: ml_range_iterate<vlc_ml_genre_t>( genre_list ) )
-        res.emplace_back( std::unique_ptr<MLGenre>{ new MLGenre( &genre ) } );
+        res.emplace_back( std::unique_ptr<MLGenre>{ new MLGenre( m_ml, &genre ) } );
     return res;
 }
 
-- 
2.17.1



More information about the vlc-devel mailing list