[vlc-commits] [Git][videolan/vlc][master] 9 commits: medialibrary: Add nb_media and nb_present_media for playlists

Jean-Baptiste Kempf gitlab at videolan.org
Thu May 20 11:35:28 UTC 2021



Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC


Commits:
9e43f884 by Benjamin Arnaud at 2021-05-20T11:15:31+00:00
medialibrary: Add nb_media and nb_present_media for playlists

- - - - -
56cc0c9c by Benjamin Arnaud at 2021-05-20T11:15:31+00:00
qt/covergenerator: Add the prefix property

This lets us enforce a specific prefix for the cover fileName.

- - - - -
6d98cbe4 by Benjamin Arnaud at 2021-05-20T11:15:31+00:00
qt/covergenerator: Add better svg support and QImageReader implementation

- - - - -
27eb7756 by Benjamin Arnaud at 2021-05-20T11:15:31+00:00
qt/covergenerator: Improve Divide and count(s) implementation

- - - - -
b23998a5 by Benjamin Arnaud at 2021-05-20T11:15:31+00:00
qt/mlplaylist: Add CoverGenerator support and proper duration

- - - - -
6b7a873f by Benjamin Arnaud at 2021-05-20T11:15:31+00:00
qt/mlplaylistlistmodel: Add cover generation and duration support

- - - - -
54c0ecc8 by Benjamin Arnaud at 2021-05-20T11:15:31+00:00
qt: Create MLItemCover

This class allows us to avoid duplicates for the CoverGenerator and the cover property.

- - - - -
42f964bd by Benjamin Arnaud at 2021-05-20T11:15:31+00:00
qml/PlaylistMediaList: Revamp implementation based on design guidelines

- - - - -
f235d8ff by Benjamin Arnaud at 2021-05-20T11:15:31+00:00
qml/MusicPlaylistsDisplay: Revamp implementation based on design guidelines

- - - - -


19 changed files:

- include/vlc_media_library.h
- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/medialibrary/mlgenre.cpp
- modules/gui/qt/medialibrary/mlgenre.hpp
- modules/gui/qt/medialibrary/mlgroup.cpp
- modules/gui/qt/medialibrary/mlgroup.hpp
- + modules/gui/qt/medialibrary/mlitemcover.cpp
- + modules/gui/qt/medialibrary/mlitemcover.hpp
- modules/gui/qt/medialibrary/mlplaylist.cpp
- modules/gui/qt/medialibrary/mlplaylist.hpp
- modules/gui/qt/medialibrary/mlplaylistlistmodel.cpp
- modules/gui/qt/medialibrary/mlplaylistlistmodel.hpp
- modules/gui/qt/medialibrary/qml/MusicPlaylistsDisplay.qml
- modules/gui/qt/medialibrary/qml/PlaylistMediaList.qml
- modules/gui/qt/util/covergenerator.cpp
- modules/gui/qt/util/covergenerator.hpp
- modules/misc/medialibrary/entities.cpp
- po/POTFILES.in


Changes:

=====================================
include/vlc_media_library.h
=====================================
@@ -251,6 +251,9 @@ typedef struct vlc_ml_playlist_t
 
     char* psz_artwork_mrl;
 
+    unsigned int i_nb_media;
+    unsigned int i_nb_present_media;
+
     uint32_t i_creation_date;
 
     bool b_is_read_only;


=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -163,6 +163,8 @@ libqt_plugin_la_SOURCES = \
 	gui/qt/medialibrary/mlgrouplistmodel.hpp \
 	gui/qt/medialibrary/mlhelper.cpp \
 	gui/qt/medialibrary/mlhelper.hpp \
+	gui/qt/medialibrary/mlitemcover.cpp \
+	gui/qt/medialibrary/mlitemcover.hpp \
 	gui/qt/medialibrary/mlqmltypes.hpp \
 	gui/qt/medialibrary/mlqueryparams.cpp \
 	gui/qt/medialibrary/mlqueryparams.hpp \
@@ -353,7 +355,6 @@ nodist_libqt_plugin_la_SOURCES = \
 	gui/qt/medialibrary/mlurlmodel.moc.cpp \
 	gui/qt/medialibrary/mlvideo.moc.cpp \
 	gui/qt/medialibrary/mlvideomodel.moc.cpp \
-	gui/qt/medialibrary/mlplaylist.moc.cpp \
 	gui/qt/medialibrary/mlplaylistlistmodel.moc.cpp \
 	gui/qt/medialibrary/mlplaylistmodel.moc.cpp \
 	gui/qt/menus/custom_menus.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<MLPlaylist>("org.videolan.medialib", 1);
 
         qmlRegisterType<AlbumContextMenu>( "org.videolan.medialib", 0, 1, "AlbumContextMenu" );
         qmlRegisterType<ArtistContextMenu>( "org.videolan.medialib", 0, 1, "ArtistContextMenu" );


=====================================
modules/gui/qt/medialibrary/mlgenre.cpp
=====================================
@@ -19,9 +19,8 @@
 #include "mlgenre.hpp"
 
 MLGenre::MLGenre(vlc_medialibrary_t* ml, const vlc_ml_genre_t *_data )
-    : MLItem     ( MLItemId( _data->i_id, VLC_ML_PARENT_GENRE ) )
+    : MLItemCover( 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 )
 
@@ -29,16 +28,6 @@ MLGenre::MLGenre(vlc_medialibrary_t* ml, const vlc_ml_genre_t *_data )
     assert(_data);
 }
 
-bool MLGenre::hasGenerator() const
-{
-    return m_generator.get();
-}
-
-void MLGenre::setGenerator(CoverGenerator * generator)
-{
-    m_generator.reset(generator);
-}
-
 QString MLGenre::getName() const
 {
     return m_name;
@@ -48,14 +37,3 @@ unsigned int MLGenre::getNbTracks() const
 {
     return m_nbTracks;
 }
-
-QString MLGenre::getCover() const
-{
-    return m_cover;
-}
-
-void MLGenre::setCover(const QString & fileName)
-{
-    m_cover = fileName;
-}
-


=====================================
modules/gui/qt/medialibrary/mlgenre.hpp
=====================================
@@ -23,36 +23,22 @@
 #include "config.h"
 #endif
 
-// Util includes
-#include "util/covergenerator.hpp"
-
 // MediaLibrary includes
-#include "mlqmltypes.hpp"
+#include "mlitemcover.hpp"
 
-class MLGenre : public MLItem
+class MLGenre : public MLItemCover
 {
 public:
-    MLGenre( vlc_medialibrary_t* _ml, const vlc_ml_genre_t *_data );
-
-    bool hasGenerator() const;
-    void setGenerator(CoverGenerator * generator);
+    MLGenre(vlc_medialibrary_t * _ml, const vlc_ml_genre_t * _data);
 
     QString getName() const;
-    unsigned int getNbTracks() const;
 
-    QString getCover() const;
-    void    setCover(const QString & fileName);
-
-private slots:
-    void generateThumbnail();
+    unsigned int getNbTracks() const;
 
 private:
-    vlc_medialibrary_t* m_ml;
-
-    TaskHandle<CoverGenerator> m_generator;
+    vlc_medialibrary_t * m_ml;
 
     QString m_name;
-    QString m_cover;
 
     unsigned int m_nbTracks;
 };


=====================================
modules/gui/qt/medialibrary/mlgroup.cpp
=====================================
@@ -28,9 +28,8 @@
 //-------------------------------------------------------------------------------------------------
 
 MLGroup::MLGroup(vlc_medialibrary_t * ml, const vlc_ml_group_t * data)
-    : MLItem(MLItemId(data->i_id, VLC_ML_PARENT_GROUP))
+    : MLItemCover(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)
@@ -43,18 +42,6 @@ MLGroup::MLGroup(vlc_medialibrary_t * ml, const vlc_ml_group_t * data)
 // 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;
@@ -62,18 +49,6 @@ QString MLGroup::getName() const
 
 //-------------------------------------------------------------------------------------------------
 
-QString MLGroup::getCover() const
-{
-    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,26 +25,17 @@
 #include "config.h"
 #endif
 
-// Util includes
-#include "util/covergenerator.hpp"
-
 // MediaLibrary includes
-#include "mlqmltypes.hpp"
+#include "mlitemcover.hpp"
 
-class MLGroup : public MLItem
+class MLGroup : public MLItemCover
 {
 public:
     MLGroup(vlc_medialibrary_t * ml, const vlc_ml_group_t * data);
 
 public: // Interface
-    bool hasGenerator() const;
-    void setGenerator(CoverGenerator * generator);
-
     QString getName() const;
 
-    QString getCover() const;
-    void    setCover(const QString & fileName);
-
     int64_t getDuration() const;
 
     unsigned int getDate() const;
@@ -54,12 +45,8 @@ 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/mlitemcover.cpp
=====================================
@@ -0,0 +1,55 @@
+/*****************************************************************************
+ * 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 "mlitemcover.hpp"
+
+//-------------------------------------------------------------------------------------------------
+// Ctor / dtor
+//-------------------------------------------------------------------------------------------------
+
+MLItemCover::MLItemCover(const MLItemId & id)
+    : MLItem(id)
+    , m_generator(nullptr) {}
+
+//-------------------------------------------------------------------------------------------------
+// Interface
+//-------------------------------------------------------------------------------------------------
+
+bool MLItemCover::hasGenerator() const
+{
+    return m_generator.get();
+}
+
+void MLItemCover::setGenerator(CoverGenerator * generator)
+{
+    m_generator.reset(generator);
+}
+
+//-------------------------------------------------------------------------------------------------
+
+QString MLItemCover::getCover() const
+{
+    return m_cover;
+}
+
+void MLItemCover::setCover(const QString & fileName)
+{
+    m_cover = fileName;
+}


=====================================
modules/gui/qt/medialibrary/mlitemcover.hpp
=====================================
@@ -0,0 +1,52 @@
+/*****************************************************************************
+ * 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 MLITEMCOVER_HPP
+#define MLITEMCOVER_HPP
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+// Util includes
+#include "util/covergenerator.hpp"
+
+// MediaLibrary includes
+#include "mlqmltypes.hpp"
+
+class MLItemCover : public MLItem
+{
+public:
+    /* explicit */ MLItemCover(const MLItemId & id);
+
+public: // Interface
+    bool hasGenerator() const;
+    void setGenerator(CoverGenerator * generator);
+
+    QString getCover() const;
+    void    setCover(const QString & fileName);
+
+private:
+    TaskHandle<CoverGenerator> m_generator;
+
+    QString m_cover;
+};
+
+#endif


=====================================
modules/gui/qt/medialibrary/mlplaylist.cpp
=====================================
@@ -25,19 +25,14 @@
 // Ctor / dtor
 //-------------------------------------------------------------------------------------------------
 
-MLPlaylist::MLPlaylist(vlc_medialibrary_t * ml, const vlc_ml_playlist_t * data, QObject * parent)
-    : QObject(parent)
-    , MLItem(MLItemId(data->i_id, VLC_ML_PARENT_PLAYLIST))
+MLPlaylist::MLPlaylist(vlc_medialibrary_t * ml, const vlc_ml_playlist_t * data)
+    : MLItemCover(MLItemId(data->i_id, VLC_ML_PARENT_PLAYLIST))
     , m_ml(ml)
+    , m_name(qfu(data->psz_name))
+    , m_duration(0) // TODO m_duration
+    , m_count(data->i_nb_media)
 {
     assert(data);
-
-    m_name = qfu(data->psz_name);
-
-    // TODO m_cover
-
-    // TODO m_count
-    m_count = 0;//data->i_nb_tracks;
 }
 
 //-------------------------------------------------------------------------------------------------
@@ -49,9 +44,11 @@ QString MLPlaylist::getName() const
     return m_name;
 }
 
-QString MLPlaylist::getCover() const
+//-------------------------------------------------------------------------------------------------
+
+int64_t MLPlaylist::getDuration() const
 {
-    return m_cover;
+    return m_duration;
 }
 
 unsigned int MLPlaylist::getCount() const


=====================================
modules/gui/qt/medialibrary/mlplaylist.hpp
=====================================
@@ -24,22 +24,17 @@
 #endif
 
 // MediaLibrary includes
-#include "mlqmltypes.hpp"
+#include "mlitemcover.hpp"
 
-// Qt includes
-#include <QObject>
-
-class MLPlaylist : public QObject, public MLItem
+class MLPlaylist : public MLItemCover
 {
-    Q_OBJECT
-
 public:
-    MLPlaylist(vlc_medialibrary_t * ml,
-               const vlc_ml_playlist_t * data, QObject * parent = nullptr);
+    MLPlaylist(vlc_medialibrary_t * ml, const vlc_ml_playlist_t * data);
 
 public: // Interface
-    QString getName () const;
-    QString getCover() const;
+    QString getName() const;
+
+    int64_t getDuration() const;
 
     unsigned int getCount() const;
 
@@ -47,7 +42,8 @@ private:
     vlc_medialibrary_t * m_ml;
 
     QString m_name;
-    QString m_cover;
+
+    int64_t m_duration;
 
     unsigned int m_count;
 };


=====================================
modules/gui/qt/medialibrary/mlplaylistlistmodel.cpp
=====================================
@@ -30,18 +30,27 @@
 #include "mlhelper.hpp"
 #include "mlplaylist.hpp"
 
+//-------------------------------------------------------------------------------------------------
+// Static variables
+
+// NOTE: We multiply by 2 to cover most dpi settings.
+static const int MLPLAYLISTMODEL_COVER_WIDTH  = 512 * 2; // 16 / 10 ratio
+static const int MLPLAYLISTMODEL_COVER_HEIGHT = 320 * 2;
+
 //=================================================================================================
 // MLPlaylistListModel
 //=================================================================================================
 
 MLPlaylistListModel::MLPlaylistListModel(vlc_medialibrary_t * ml, QObject * parent)
     : MLBaseModel(parent)
+    , m_coverDefault(":/noart_videoCover.svg")
 {
     m_ml = ml;
 }
 
 /* explicit */ MLPlaylistListModel::MLPlaylistListModel(QObject * parent)
-    : MLBaseModel(parent) {}
+    : MLBaseModel(parent)
+    , m_coverSize(MLPLAYLISTMODEL_COVER_WIDTH, MLPLAYLISTMODEL_COVER_HEIGHT) {}
 
 //-------------------------------------------------------------------------------------------------
 
@@ -156,16 +165,19 @@ QHash<int, QByteArray> MLPlaylistListModel::roleNames() const /* override */
 {
     return
     {
-        { PLAYLIST_ID,    "id"    },
-        { PLAYLIST_NAME,  "name"  },
-        { PLAYLIST_COVER, "cover" },
-        { PLAYLIST_COUNT, "count" }
+        { PLAYLIST_ID,        "id"        },
+        { PLAYLIST_NAME,      "name"      },
+        { PLAYLIST_THUMBNAIL, "thumbnail" },
+        { PLAYLIST_DURATION,  "duration"  },
+        { PLAYLIST_COUNT,     "count"     }
     };
 }
 
 QVariant MLPlaylistListModel::data(const QModelIndex & index, int role) const /* override */
 {
-    const MLPlaylist * playlist = static_cast<MLPlaylist *>(item(index.row()));
+    int row = index.row();
+
+    MLPlaylist * playlist = static_cast<MLPlaylist *>(item(row));
 
     if (playlist == nullptr)
         return QVariant();
@@ -175,16 +187,18 @@ QVariant MLPlaylistListModel::data(const QModelIndex & index, int role) const /*
         // NOTE: This is the condition for QWidget view(s).
         case Qt::DisplayRole:
             if (index.column() == 0)
-                return QVariant::fromValue(playlist->getName());
+                return playlist->getName();
             else
                 return QVariant();
         // NOTE: These are the conditions for QML view(s).
         case PLAYLIST_ID:
             return QVariant::fromValue(playlist->getId());
         case PLAYLIST_NAME:
-            return QVariant::fromValue(playlist->getName());
-        case PLAYLIST_COVER:
-            return QVariant::fromValue(playlist->getCover());
+            return playlist->getName();
+        case PLAYLIST_THUMBNAIL:
+            return getCover(playlist, row);
+        case PLAYLIST_DURATION:
+            return QVariant::fromValue(playlist->getDuration());
         case PLAYLIST_COUNT:
             return QVariant::fromValue(playlist->getCount());
         default:
@@ -225,6 +239,36 @@ ListCacheLoader<std::unique_ptr<MLItem>> * MLPlaylistListModel::createLoader() c
     return new Loader(*this);
 }
 
+//-------------------------------------------------------------------------------------------------
+// Private functions
+//-------------------------------------------------------------------------------------------------
+
+QString MLPlaylistListModel::getCover(MLPlaylist * playlist, int index) const
+{
+    QString cover = playlist->getCover();
+
+    // NOTE: Making sure we're not already generating a cover.
+    if (cover.isNull() == false || playlist->hasGenerator())
+        return cover;
+
+    CoverGenerator * generator = new CoverGenerator(m_ml, playlist->getId(), index);
+
+    generator->setSize(m_coverSize);
+
+    generator->setDefaultThumbnail(m_coverDefault);
+
+    generator->setPrefix(m_coverPrefix);
+
+    // NOTE: We'll apply the new thumbnail once it's loaded.
+    connect(generator, &CoverGenerator::result, this, &MLPlaylistListModel::onCover);
+
+    generator->start(*QThreadPool::globalInstance());
+
+    playlist->setGenerator(generator);
+
+    return cover;
+}
+
 //-------------------------------------------------------------------------------------------------
 // Private MLBaseModel reimplementation
 //-------------------------------------------------------------------------------------------------
@@ -246,6 +290,94 @@ void MLPlaylistListModel::onVlcMlEvent(const MLEvent & event) /* override */
     MLBaseModel::onVlcMlEvent(event);
 }
 
+void MLPlaylistListModel::thumbnailUpdated(int idx) /* override */
+{
+    QModelIndex index = this->index(idx);
+
+    emit dataChanged(index, index, { PLAYLIST_THUMBNAIL });
+}
+
+//-------------------------------------------------------------------------------------------------
+// Private slots
+//-------------------------------------------------------------------------------------------------
+
+void MLPlaylistListModel::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;
+    }
+
+    MLPlaylist * playlist = static_cast<MLPlaylist *> (item);
+
+    QString fileName = QUrl::fromLocalFile(generator->takeResult()).toString();
+
+    playlist->setCover(fileName);
+
+    playlist->setGenerator(nullptr);
+
+    thumbnailUpdated(index);
+}
+
+//-------------------------------------------------------------------------------------------------
+// Properties
+//-------------------------------------------------------------------------------------------------
+
+QSize MLPlaylistListModel::coverSize() const
+{
+    return m_coverSize;
+}
+
+void MLPlaylistListModel::setCoverSize(const QSize & size)
+{
+    if (m_coverSize == size)
+        return;
+
+    m_coverSize = size;
+
+    emit coverSizeChanged();
+}
+
+QString MLPlaylistListModel::coverDefault() const
+{
+    return m_coverDefault;
+}
+
+void MLPlaylistListModel::setCoverDefault(const QString & fileName)
+{
+    if (m_coverDefault == fileName)
+        return;
+
+    m_coverDefault = fileName;
+
+    emit coverDefaultChanged();
+}
+
+QString MLPlaylistListModel::coverPrefix() const
+{
+    return m_coverPrefix;
+}
+
+void MLPlaylistListModel::setCoverPrefix(const QString & prefix)
+{
+    if (m_coverPrefix == prefix)
+        return;
+
+    m_coverPrefix = prefix;
+
+    emit coverPrefixChanged();
+}
+
 //=================================================================================================
 // Loader
 //=================================================================================================


=====================================
modules/gui/qt/medialibrary/mlplaylistlistmodel.hpp
=====================================
@@ -24,18 +24,26 @@
 
 // Forward declarations
 class vlc_medialibrary_t;
-class vlc_ml_playlist_t;
+class MLPlaylist;
 
 class MLPlaylistListModel : public MLBaseModel
 {
     Q_OBJECT
 
+    Q_PROPERTY(QSize coverSize READ coverSize WRITE setCoverSize NOTIFY coverSizeChanged)
+
+    Q_PROPERTY(QString coverDefault READ coverDefault WRITE setCoverDefault
+               NOTIFY coverDefaultChanged)
+
+    Q_PROPERTY(QString coverPrefix READ coverPrefix WRITE setCoverPrefix NOTIFY coverPrefixChanged)
+
 public:
     enum Roles
     {
         PLAYLIST_ID = Qt::UserRole + 1,
         PLAYLIST_NAME,
-        PLAYLIST_COVER,
+        PLAYLIST_THUMBNAIL,
+        PLAYLIST_DURATION,
         PLAYLIST_COUNT
     };
 
@@ -67,9 +75,37 @@ protected: // MLBaseModel implementation
 
     ListCacheLoader<std::unique_ptr<MLItem>> * createLoader() const override;
 
+private: // Functions
+    QString getCover(MLPlaylist * playlist, int index) const;
+
 private: // MLBaseModel implementation
     void onVlcMlEvent(const MLEvent & event) override;
 
+    void thumbnailUpdated(int idx) override;
+
+private slots:
+    void onCover();
+
+signals:
+    void coverSizeChanged   ();
+    void coverDefaultChanged();
+    void coverPrefixChanged ();
+
+public: // Properties
+    QSize coverSize() const;
+    void  setCoverSize(const QSize & size);
+
+    QString coverDefault() const;
+    void    setCoverDefault(const QString & fileName);
+
+    QString coverPrefix() const;
+    void    setCoverPrefix(const QString & prefix);
+
+private: // Variables
+    QSize   m_coverSize;
+    QString m_coverDefault;
+    QString m_coverPrefix;
+
 private:
     struct Loader : public MLBaseModel::BaseLoader
     {


=====================================
modules/gui/qt/medialibrary/qml/MusicPlaylistsDisplay.qml
=====================================
@@ -92,6 +92,8 @@ Widgets.PageLoader {
         PlaylistMediaList {
             anchors.fill: parent
 
+            isMusic: true
+
             onCurrentIndexChanged: _updateHistoryList(currentIndex)
 
             onShowList: history.push(["mc", "music", "playlists", "list",


=====================================
modules/gui/qt/medialibrary/qml/PlaylistMediaList.qml
=====================================
@@ -37,6 +37,8 @@ Widgets.NavigableFocusScope {
 
     readonly property int currentIndex: currentItem.currentIndex
 
+    property bool isMusic: false
+
     property int initialIndex: 0
 
     property var sortModel: [{ text: i18n.qtr("Alphabetic"), criteria: "title" }]
@@ -44,15 +46,23 @@ Widgets.NavigableFocusScope {
     //---------------------------------------------------------------------------------------------
     // Private
 
-    property int _width: VLCStyle.colWidth(2)
+    property int _width: (isMusic) ? VLCStyle.gridItem_music_width
+                                   : VLCStyle.gridItem_video_width
 
-    property int _height: Math.round(_width / 2)
+    property int _height: (isMusic) ? VLCStyle.gridItem_music_height
+                                    : VLCStyle.gridItem_video_height
 
     property int _widthColumn:
         Math.max(VLCStyle.gridColumnsForWidth(tableView.availableRowWidth
                                               - VLCStyle.listAlbumCover_width
                                               - VLCStyle.column_margin_width) - 1, 1)
 
+    property int _widthCover: (isMusic) ? VLCStyle.gridCover_music_width
+                                        : VLCStyle.gridCover_video_width
+
+    property int _heightCover: (isMusic) ? VLCStyle.gridCover_music_height
+                                         : VLCStyle.gridCover_video_height
+
     //---------------------------------------------------------------------------------------------
     // Alias
     //---------------------------------------------------------------------------------------------
@@ -145,6 +155,16 @@ Widgets.NavigableFocusScope {
         }
     }
 
+    function _getCount(model)
+    {
+        var count = model.count;
+
+        if (count < 100)
+            return count;
+        else
+            return i18n.qtr("99+");
+    }
+
     //---------------------------------------------------------------------------------------------
     // Childs
     //---------------------------------------------------------------------------------------------
@@ -154,6 +174,13 @@ Widgets.NavigableFocusScope {
 
         ml: medialib
 
+        coverSize: (isMusic) ? Qt.size(512, 512)
+                             : Qt.size(1024, 640)
+
+        coverDefault: (isMusic) ? ":/noart_album.svg" : ":/noart_videoCover.svg"
+
+        coverPrefix: (isMusic) ? "playlist-music" : "playlist-video"
+
         onCountChanged: {
             if (count === 0 || modelSelect.hasSelection) return;
 
@@ -183,7 +210,7 @@ Widgets.NavigableFocusScope {
             })
 
             var covers = items.map(function (item) {
-                return { artwork: item.cover || VLCStyle.noArtCover };
+                return { artwork: item.thumbnail || VLCStyle.noArtCover };
             })
 
             var title = items.map(function (item) {
@@ -237,21 +264,7 @@ Widgets.NavigableFocusScope {
 
             focus: true
 
-            //-------------------------------------------------------------------------------------
-            // Events
-
-            onSelectAll: modelSelect.selectAll()
-
-            onSelectionUpdated: modelSelect.updateSelection(keyModifiers, oldIndex, newIndex)
-
-            onActionAtIndex: _actionAtIndex()
-
-            //-------------------------------------------------------------------------------------
-            // Childs
-
-            delegate: Widgets.GridItem {
-                id: item
-
+            delegate: VideoGridItem {
                 //---------------------------------------------------------------------------------
                 // Properties
 
@@ -262,76 +275,70 @@ Widgets.NavigableFocusScope {
                 //---------------------------------------------------------------------------------
                 // Settings
 
-                width : _width
-                height: _height
+                pictureWidth : _widthCover
+                pictureHeight: _heightCover
+
+                title: (model.name) ? model.name
+                                    : i18n.qtr("Unknown title")
 
-                pictureWidth : width
-                pictureHeight: height
+                labels: (model.count > 1) ? [ i18n.qtr("%1 Tracks").arg(_getCount(model)) ]
+                                          : [ i18n.qtr("%1 Track") .arg(_getCount(model)) ]
 
-                image: VLCStyle.noArtAlbum
+                // NOTE: We don't want to show the new indicator for a playlist.
+                showNewIndicator: false
 
                 dragItem: dragItemPlaylist
 
+                selectedUnderlay  : shadows.selected
                 unselectedUnderlay: shadows.unselected
-                selectedUnderlay: shadows.selected
-
-                pictureOverlay: Item {
-                    Column {
-                        anchors.centerIn: parent
-
-                        Label {
-                             width: item.width
 
-                             horizontalAlignment: Text.AlignHCenter
-
-                             text: model.name
-
-                             elide: Text.ElideRight
+                //---------------------------------------------------------------------------------
+                // Events
 
-                             color: "white"
+                onItemClicked: gridView.leftClickOnItem(modifier, index)
 
-                             font.pixelSize: VLCStyle.fontSize_large
-                             font.weight   : Font.DemiBold
-                        }
+                onItemDoubleClicked: showList(model)
 
-                        Widgets.CaptionLabel {
-                            width: item.width
+                onPlayClicked: if (model.id) medialib.addAndPlay(model.id)
 
-                            horizontalAlignment: Text.AlignHCenter
+                onContextMenuButtonClicked: {
+                    gridView.rightClickOnItem(index);
 
-                            opacity: 0.7
+                    contextMenu.popup(modelSelect.selectedIndexes, globalMousePos);
+                }
 
-                            text: (model.count > 1) ? i18n.qtr("%1 Tracks").arg(model.count)
-                                                    : i18n.qtr("%1 Track") .arg(model.count)
+                //---------------------------------------------------------------------------------
+                // Animations
 
-                            color: "white"
-                        }
-                    }
-                }
+                Behavior on opacity { NumberAnimation { duration: 100 } }
+            }
 
-                playCoverBorderWidth: VLCStyle.dp(3, VLCStyle.scale)
+            //-------------------------------------------------------------------------------------
+            // Events
 
-                //---------------------------------------------------------------------------------
-                // Events
+            // NOTE: Define the initial position and selection. This is done on activeFocus rather
+            //       than Component.onCompleted because modelSelect.selectedGroup update itself
+            //       after this event.
+            onActiveFocusChanged: {
+                if (activeFocus == false || model.count === 0 || modelSelect.hasSelection) return;
 
-                onItemDoubleClicked: showList(model)
+                modelSelect.select(model.index(0,0), ItemSelectionModel.ClearAndSelect)
+            }
 
-                onItemClicked: gridView.leftClickOnItem(modifier, index)
+            onSelectAll: modelSelect.selectAll()
 
-                onPlayClicked: if (model.id) medialib.addAndPlay(model.id)
+            onSelectionUpdated: modelSelect.updateSelection(keyModifiers, oldIndex, newIndex)
 
-                onContextMenuButtonClicked: {
-                    gridView.rightClickOnItem(index);
+            onActionAtIndex: _actionAtIndex()
 
-                    contextMenu.popup(modelSelect.selectedIndexes, globalMousePos);
-                }
-            }
+            //-------------------------------------------------------------------------------------
+            // Childs
 
             Widgets.GridShadows {
                 id: shadows
 
-                coverWidth: root._width
-                coverHeight: root._height
+                coverWidth : _widthCover
+                coverHeight: _heightCover
             }
         }
     }
@@ -371,7 +378,7 @@ Widgets.NavigableFocusScope {
 
             sortModel: [{
                 isPrimary: true,
-                criteria: "cover",
+                criteria: "thumbnail",
 
                 width: VLCStyle.listAlbumCover_width,
 
@@ -409,9 +416,24 @@ Widgets.NavigableFocusScope {
 
                 showTitleText: false
 
-                titleCover_width : VLCStyle.listAlbumCover_width
-                titleCover_height: VLCStyle.listAlbumCover_height
-                titleCover_radius: VLCStyle.listAlbumCover_radius
+                //---------------------------------------------------------------------------------
+                // NOTE: When it's music we want the cover to be square
+
+                titleCover_width: (isMusic) ? VLCStyle.trackListAlbumCover_width
+                                            : VLCStyle.listAlbumCover_width
+
+                titleCover_height: (isMusic) ? VLCStyle.trackListAlbumCover_heigth
+                                             : VLCStyle.listAlbumCover_height
+
+                titleCover_radius: (isMusic) ? VLCStyle.trackListAlbumCover_radius
+                                             : VLCStyle.listAlbumCover_radius
+
+                //---------------------------------------------------------------------------------
+
+                // NOTE: This makes sure we display the playlist count on the item.
+                function titlecoverLabels(model) {
+                    return [ _getCount(model) ];
+                }
             }
         }
     }


=====================================
modules/gui/qt/util/covergenerator.cpp
=====================================
@@ -28,6 +28,7 @@
 
 // Qt includes
 #include <QDir>
+#include <QImageReader>
 #include <QGraphicsScene>
 #include <QGraphicsPixmapItem>
 #include <QGraphicsBlurEffect>
@@ -107,6 +108,11 @@ CoverGenerator::CoverGenerator(vlc_medialibrary_t * ml, const MLItemId & itemId,
     m_default = fileName;
 }
 
+/* Q_INVOKABLE */ void CoverGenerator::setPrefix(const QString & prefix)
+{
+    m_prefix = prefix;
+}
+
 //-------------------------------------------------------------------------------------------------
 // QRunnable implementation
 //-------------------------------------------------------------------------------------------------
@@ -121,9 +127,15 @@ QString CoverGenerator::execute() /* override */
 
     int64_t id = m_id.id;
 
-    QString string = getStringType(type);
+    QString fileName;
 
-    QString fileName = QString("%1_thumbnail_%2.jpg").arg(string).arg(id);
+    // NOTE: If we don't have a valid prefix we generate one based on the item type.
+    if (m_prefix.isEmpty())
+    {
+        m_prefix = getPrefix(type);
+    }
+
+    fileName = QString("%1_thumbnail_%2.jpg").arg(m_prefix).arg(id);
 
     fileName = dir.absoluteFilePath(fileName);
 
@@ -141,6 +153,9 @@ QString CoverGenerator::execute() /* override */
     else
         thumbnails = getMedias(count, id, type);
 
+    int countX;
+    int countY;
+
     if (thumbnails.isEmpty())
     {
         if (m_split == CoverGenerator::Duplicate)
@@ -149,13 +164,16 @@ QString CoverGenerator::execute() /* override */
             {
                 thumbnails.append(m_default);
             }
+
+            countX = m_countX;
+            countY = m_countY;
         }
         else
         {
             thumbnails.append(m_default);
 
-            m_countX = 1;
-            m_countY = 1;
+            countX = 1;
+            countY = 1;
         }
     }
     else if (m_split == CoverGenerator::Duplicate)
@@ -168,15 +186,16 @@ QString CoverGenerator::execute() /* override */
 
             index++;
         }
+
+        countX = m_countX;
+        countY = m_countY;
     }
     else // if (m_split == CoverGenerator::Divide)
     {
-        // NOTE: This handles the 2x2 case.
-        if (thumbnails.count() == 2)
-        {
-            m_countX = 2;
-            m_countY = 1;
-        }
+        countX = m_countX;
+
+        // NOTE: We try to divide thumbnails as far as we can based on their total count.
+        countY = std::ceil((qreal) thumbnails.count() / m_countX);
     }
 
     QImage image(m_size, QImage::Format_RGB32);
@@ -187,7 +206,7 @@ QString CoverGenerator::execute() /* override */
 
     painter.begin(&image);
 
-    draw(painter, thumbnails);
+    draw(painter, thumbnails, countX, countY);
 
     painter.end();
 
@@ -203,57 +222,89 @@ QString CoverGenerator::execute() /* override */
 // Private functions
 //-------------------------------------------------------------------------------------------------
 
-void CoverGenerator::draw(QPainter & painter, const QStringList & fileNames)
+void CoverGenerator::draw(QPainter & painter,
+                          const QStringList & fileNames, int countX, int countY)
 {
     int count = fileNames.count();
 
-    int width  = m_size.width()  / m_countX;
-    int height = m_size.height() / m_countY;
+    int width  = m_size.width()  / countX;
+    int height = m_size.height() / countY;
 
-    for (int y = 0; y < m_countY; y++)
+    for (int y = 0; y < countY; y++)
     {
-        for (int x = 0; x < m_countX; x++)
+        for (int x = 0; x < countX; x++)
         {
-            int index = m_countX * y + x;
+            int index = 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)
+            if (index == count - 1 && x != countX - 1)
             {
-                rect = QRect(width * x, height * y, width * m_countX - x, height);
+                rect = QRect(width * x, height * y, width * countX - x, height);
             }
             else
                 rect = QRect(width * x, height * y, width, height);
 
-            drawImage(painter, fileNames.at(index), rect);
+            QString fileName = fileNames.at(index);
+
+            if (fileName.isEmpty())
+                drawImage(painter, m_default, rect);
+            else
+                drawImage(painter, fileName, 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);
+    QFile file(fileName);
 
-    // NOTE: This image does not seem valid so we paint the placeholder instead.
-    if (image.isNull())
+    if (file.open(QIODevice::ReadOnly) == false)
     {
-        image.load(m_default);
+        // NOTE: This image does not seem valid so we paint the placeholder instead.
+        if (fileName != m_default)
+            drawImage(painter, m_default, target);
+
+        return;
     }
 
-    // NOTE: Should we use Qt::SmoothTransformation or favor efficiency ?
-    if (m_smooth)
-        image = image.scaled(target.size(), Qt::KeepAspectRatioByExpanding,
-                             Qt::SmoothTransformation);
+    QImageReader reader(&file);
+
+    if (reader.canRead() == false)
+        return;
+
+    QSize size = reader.size().scaled(target.width(),
+                                      target.height(), Qt::KeepAspectRatioByExpanding);
+
+    QImage image;
+
+    if (fileName.endsWith(".svg", Qt::CaseInsensitive))
+    {
+        if (size.isEmpty() == false)
+        {
+            reader.setScaledSize(size);
+        }
+
+        if (reader.read(&image) == false)
+            return;
+    }
     else
-        image = image.scaled(target.size(), Qt::KeepAspectRatioByExpanding);
+    {
+        if (reader.read(&image) == false)
+            return;
+
+        if (size.isEmpty() == false)
+        {
+            // NOTE: Should we use Qt::SmoothTransformation or favor efficiency ?
+            if (m_smooth)
+                image = image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+            else
+                image = image.scaled(size, Qt::IgnoreAspectRatio);
+        }
+    }
 
     int x = (image.width () - target.width ()) / 2;
     int y = (image.height() - target.height()) / 2;
@@ -295,7 +346,7 @@ void CoverGenerator::blur(QImage * image)
 
 //-------------------------------------------------------------------------------------------------
 
-QString CoverGenerator::getStringType(vlc_ml_parent_type type) const
+QString CoverGenerator::getPrefix(vlc_ml_parent_type type) const
 {
     switch (type)
     {


=====================================
modules/gui/qt/util/covergenerator.hpp
=====================================
@@ -75,17 +75,20 @@ public: // Interface
 
     Q_INVOKABLE void setDefaultThumbnail(const QString & fileName);
 
+    // NOTE: This lets us enforce a specific prefix for the cover fileName.
+    Q_INVOKABLE void setPrefix(const QString & prefix);
+
 public: // AsyncTask implementation
     QString execute() override;
 
 private: // Functions
-    void draw(QPainter & painter, const QStringList & fileNames);
+    void draw(QPainter & painter, const QStringList & fileNames, int countX, int countY);
 
     void drawImage(QPainter & painter, const QString & fileName, const QRect & rect);
 
     void blur(QImage * image);
 
-    QString getStringType(vlc_ml_parent_type type) const;
+    QString getPrefix(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;
@@ -109,6 +112,8 @@ private:
     int m_blur;
 
     QString m_default;
+
+    QString m_prefix;
 };
 
 #endif // COVERGENERATOR_HPP


=====================================
modules/misc/medialibrary/entities.cpp
=====================================
@@ -429,6 +429,9 @@ bool Convert( const medialibrary::IPlaylist* input, vlc_ml_playlist_t& output )
 {
     output.i_id = input->id();
 
+    output.i_nb_media         = input->nbMedia();
+    output.i_nb_present_media = input->nbPresentMedia();
+
     output.i_creation_date = input->creationDate();
 
     output.b_is_read_only = input->isReadOnly();


=====================================
po/POTFILES.in
=====================================
@@ -777,6 +777,8 @@ 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/mlitemcover.cpp
+modules/gui/qt/medialibrary/mlitemcover.hpp
 modules/gui/qt/medialibrary/mlplaylistlistmodel.cpp
 modules/gui/qt/medialibrary/mlplaylistlistmodel.hpp
 modules/gui/qt/medialibrary/mlplaylistmedia.cpp



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/a7f24ef49f462442749f14e19388ffb3d6d03bd5...f235d8ff4282705936729298d4cf25aee595a82a

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/a7f24ef49f462442749f14e19388ffb3d6d03bd5...f235d8ff4282705936729298d4cf25aee595a82a
You're receiving this email because of your account on code.videolan.org.




More information about the vlc-commits mailing list