diff --git a/modules/gui/qt/components/mediacenter/mlgenre.cpp b/modules/gui/qt/components/mediacenter/mlgenre.cpp index 8bed1c1fc7779cc29edd58b31b317a123373d50b..a20686c16b676bbd9e502f07d86bb5e4847f2dd8 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 61e44a9df3bffd4948dfc1c6b4e82d9000a6265d..66b5773be487f4ea072f9bd89723c04f151d75bf 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 c4a23ecbae430673b9bd8fcdd117a49614756d38..cf248411c656960e1ed04ad6b94187d92e66d58a 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; }