diff --git a/modules/gui/qt/medialibrary/mlgrouplistmodel.cpp b/modules/gui/qt/medialibrary/mlgrouplistmodel.cpp
index 85defa5d3738815639c66b20dd49f7c0bb3ed14b..666cdec03c5141ff512119fdca63bfda62ede8bd 100644
--- a/modules/gui/qt/medialibrary/mlgrouplistmodel.cpp
+++ b/modules/gui/qt/medialibrary/mlgrouplistmodel.cpp
@@ -53,40 +53,26 @@ static const QHash<QByteArray, vlc_ml_sorting_criteria_t> criterias =
 // MLGroupListModel
 //=================================================================================================
 
-/* explicit */ MLGroupListModel::MLGroupListModel(QObject * parent)
-    : MLBaseModel(parent) {}
+/* explicit */ MLGroupListModel::MLGroupListModel(QObject * parent) : MLVideoModel(parent) {}
 
 //-------------------------------------------------------------------------------------------------
-// QAbstractItemModel implementation
+// MLVideoModel reimplementation
 //-------------------------------------------------------------------------------------------------
 
 QHash<int, QByteArray> MLGroupListModel::roleNames() const /* override */
 {
-    return
-    {
-        { GROUP_IS_VIDEO,           "isVideo"            },
-        { GROUP_ID,                 "id"                 },
-        { GROUP_TITLE,              "title"              },
-        { GROUP_THUMBNAIL,          "thumbnail"          },
-        { GROUP_DURATION,           "duration"           },
-        { GROUP_DATE,               "date"               },
-        { GROUP_COUNT,              "count"              },
-        // NOTE: Media specific.
-        { GROUP_IS_NEW,             "isNew"              },
-        { GROUP_FILENAME,           "fileName"           },
-        { GROUP_PROGRESS,           "progress"           },
-        { GROUP_PLAYCOUNT,          "playcount"          },
-        { GROUP_RESOLUTION,         "resolution_name"    },
-        { GROUP_CHANNEL,            "channel"            },
-        { GROUP_MRL,                "mrl"                },
-        { GROUP_MRL_DISPLAY,        "display_mrl"        },
-        { GROUP_VIDEO_TRACK,        "videoDesc"          },
-        { GROUP_AUDIO_TRACK,        "audioDesc"          },
-        { GROUP_TITLE_FIRST_SYMBOL, "title_first_symbol" }
-    };
+    QHash<int, QByteArray> hash = MLVideoModel::roleNames();
+
+    hash.insert(GROUP_IS_VIDEO, "isVideo");
+    hash.insert(GROUP_DATE,     "date");
+    hash.insert(GROUP_COUNT,    "count");
+
+    return hash;
 }
 
-QVariant MLGroupListModel::itemRoleData(MLItem *item, const int role) const /* override */
+// Protected MLVideoModel implementation
+
+QVariant MLGroupListModel::itemRoleData(MLItem * item, const int role) const /* override */
 {
     if (item == nullptr)
         return QVariant();
@@ -101,16 +87,17 @@ QVariant MLGroupListModel::itemRoleData(MLItem *item, const int role) const /* o
             case Qt::DisplayRole:
                 return QVariant::fromValue(group->getTitle());
             // NOTE: These are the conditions for QML view(s).
-            case GROUP_IS_VIDEO:
-                return false;
-            case GROUP_ID:
+            case VIDEO_ID:
                 return QVariant::fromValue(group->getId());
-            case GROUP_TITLE:
+            case VIDEO_TITLE:
                 return QVariant::fromValue(group->getTitle());
-            case GROUP_THUMBNAIL:
-                return getCover(group);
-            case GROUP_DURATION:
+            case VIDEO_THUMBNAIL:
+                return getVideoListCover(this, group, MLGROUPLISTMODEL_COVER_WIDTH,
+                                         MLGROUPLISTMODEL_COVER_HEIGHT, VIDEO_THUMBNAIL);
+            case VIDEO_DURATION:
                 return QVariant::fromValue(group->getDuration());
+            case GROUP_IS_VIDEO:
+                return false;
             case GROUP_DATE:
                 return QVariant::fromValue(group->getDate());
             case GROUP_COUNT:
@@ -129,66 +116,24 @@ QVariant MLGroupListModel::itemRoleData(MLItem *item, const int role) const /* o
                 return QVariant::fromValue(video->getTitle());
             case GROUP_IS_VIDEO:
                 return true;
-            case GROUP_ID:
-                return QVariant::fromValue(video->getId());
-            case GROUP_TITLE:
-                return QVariant::fromValue(video->getTitle());
-            case GROUP_THUMBNAIL:
-            {
-                vlc_ml_thumbnail_status_t status;
-                const QString thumbnail = video->getThumbnail(&status);
-                if (status == VLC_ML_THUMBNAIL_STATUS_MISSING || status == VLC_ML_THUMBNAIL_STATUS_FAILURE)
-                {
-                    generateVideoThumbnail(item->getId().id);
-                }
-                return QVariant::fromValue( thumbnail );
-            }
-            case GROUP_DURATION:
-                return QVariant::fromValue(video->getDuration());
             case GROUP_DATE:
                 return QVariant();
             case GROUP_COUNT:
                 return 1;
             // NOTE: Media specific.
-            case GROUP_IS_NEW:
-                return QVariant::fromValue(video->isNew());
-            case GROUP_FILENAME:
-                return QVariant::fromValue(video->getFileName());
-            case GROUP_PROGRESS:
-                return QVariant::fromValue(video->getProgress());
-            case GROUP_PLAYCOUNT:
-                return QVariant::fromValue(video->getPlayCount());
-            case GROUP_RESOLUTION:
-                return QVariant::fromValue(video->getResolutionName());
-            case GROUP_CHANNEL:
-                return QVariant::fromValue(video->getChannel());
-            case GROUP_MRL:
-                return QVariant::fromValue(video->getMRL());
-            case GROUP_MRL_DISPLAY:
-                return QVariant::fromValue(video->getDisplayMRL());
-            case GROUP_VIDEO_TRACK:
-                return QVariant::fromValue(video->getVideoDesc());
-            case GROUP_AUDIO_TRACK:
-                return QVariant::fromValue(video->getAudioDesc());
-            case GROUP_TITLE_FIRST_SYMBOL:
-                return QVariant::fromValue(getFirstSymbol(video->getTitle()));
             default:
-                return QVariant();
+                return MLVideoModel::itemRoleData(item, role);
         }
     }
 }
 
-//-------------------------------------------------------------------------------------------------
-// Protected MLBaseModel implementation
-//-------------------------------------------------------------------------------------------------
-
 vlc_ml_sorting_criteria_t MLGroupListModel::roleToCriteria(int role) const /* override */
 {
     switch (role)
     {
-        case GROUP_TITLE:
+        case VIDEO_TITLE:
             return VLC_ML_SORTING_ALPHA;
-        case GROUP_DURATION:
+        case VIDEO_DURATION:
             return VLC_ML_SORTING_DURATION;
         case GROUP_DATE:
             return VLC_ML_SORTING_INSERTIONDATE;
@@ -208,81 +153,11 @@ QByteArray MLGroupListModel::criteriaToName(vlc_ml_sorting_criteria_t criteria)
     return criterias.key(criteria, "");
 }
 
-//-------------------------------------------------------------------------------------------------
-
 ListCacheLoader<std::unique_ptr<MLItem>> * MLGroupListModel::createLoader() const /* override */
 {
     return new Loader(*this);
 }
 
-//-------------------------------------------------------------------------------------------------
-// Private functions
-//-------------------------------------------------------------------------------------------------
-
-QString MLGroupListModel::getCover(MLGroup * group) const
-{
-    QString cover = group->getCover();
-
-    // NOTE: Making sure we're not already generating a cover.
-    if (cover.isNull() == false || group->hasGenerator())
-        return cover;
-
-    MLItemId groupId = group->getId();
-    struct Context{
-        QString cover;
-    };
-    group->setGenerator(true);
-    m_mediaLib->runOnMLThread<Context>(this,
-    //ML thread
-    [groupId]
-    (vlc_medialibrary_t* ml, Context& ctx){
-        CoverGenerator generator{ml, groupId};
-
-        generator.setSize(QSize(MLGROUPLISTMODEL_COVER_WIDTH,
-                                 MLGROUPLISTMODEL_COVER_HEIGHT));
-
-        generator.setDefaultThumbnail(":/noart_videoCover.svg");
-
-        if (generator.cachedFileAvailable())
-            ctx.cover = generator.cachedFileURL();
-        else
-            ctx.cover = generator.execute();
-    },
-    //UI Thread
-    [this, groupId]
-    (quint64, Context& ctx)
-    {
-        int row;
-        // NOTE: We want to avoid calling 'MLBaseModel::item' for performance issues.
-        auto group = static_cast<MLGroup*>(findInCache(groupId, &row));
-        if (!group)
-            return;
-
-        group->setCover(ctx.cover);
-        group->setGenerator(false);
-
-        QModelIndex modelIndex =this->index(row);
-        //we're running in a callback
-        emit const_cast<MLGroupListModel*>(this)->dataChanged(modelIndex, modelIndex, { GROUP_THUMBNAIL });
-    });
-
-    return cover;
-}
-
-
-void MLGroupListModel::generateVideoThumbnail(uint64_t id) const
-{
-    m_mediaLib->runOnMLThread(this,
-    //ML thread
-    [id](vlc_medialibrary_t* ml){
-        vlc_ml_media_generate_thumbnail(ml, id, VLC_ML_THUMBNAIL_SMALL, 512, 320, .15 );
-    });
-}
-
-//-------------------------------------------------------------------------------------------------
-// Private MLBaseModel reimplementation
-//-------------------------------------------------------------------------------------------------
-
 void MLGroupListModel::onVlcMlEvent(const MLEvent & event) /* override */
 {
     int type = event.i_type;
@@ -306,14 +181,6 @@ void MLGroupListModel::onVlcMlEvent(const MLEvent & event) /* override */
     MLBaseModel::onVlcMlEvent(event);
 }
 
-void MLGroupListModel::thumbnailUpdated(const QModelIndex& idx, MLItem* mlitem, const QString& mrl, vlc_ml_thumbnail_status_t status) /* override */
-{
-    auto videoItem = static_cast<MLVideo*>(mlitem);
-    videoItem->setThumbnail(status, mrl);
-    emit dataChanged(idx, idx, { GROUP_THUMBNAIL });
-}
-
-
 //=================================================================================================
 // Loader
 //=================================================================================================
diff --git a/modules/gui/qt/medialibrary/mlgrouplistmodel.hpp b/modules/gui/qt/medialibrary/mlgrouplistmodel.hpp
index 37b8b7153d4daddc1ff03e6d784aa7de224f53b6..f40691ee2f7c1f05347d488f4adc98123d869437 100644
--- a/modules/gui/qt/medialibrary/mlgrouplistmodel.hpp
+++ b/modules/gui/qt/medialibrary/mlgrouplistmodel.hpp
@@ -22,49 +22,31 @@
 #define MLGROUPLISTMODEL_HPP
 
 // MediaLibrary includes
-#include "mlbasemodel.hpp"
+#include "mlvideomodel.hpp"
 
 // Forward declarations
-struct vlc_medialibrary_t;
 class MLGroup;
 
-class MLGroupListModel : public MLBaseModel
+class MLGroupListModel : public MLVideoModel
 {
     Q_OBJECT
 
 public:
     enum Roles
     {
-        GROUP_IS_VIDEO = Qt::UserRole + 1,
-        GROUP_ID,
-        GROUP_TITLE,
-        GROUP_THUMBNAIL,
-        GROUP_DURATION,
+        // NOTE: Group specific.
+        GROUP_IS_VIDEO = VIDEO_TITLE_FIRST_SYMBOL + 1,
         GROUP_DATE,
-        GROUP_COUNT,
-        // NOTE: Media specific.
-        GROUP_IS_NEW,
-        GROUP_FILENAME,
-        GROUP_PROGRESS,
-        GROUP_PLAYCOUNT,
-        GROUP_RESOLUTION,
-        GROUP_CHANNEL,
-        GROUP_MRL,
-        GROUP_MRL_DISPLAY,
-        GROUP_VIDEO_TRACK,
-        GROUP_AUDIO_TRACK,
-
-        GROUP_TITLE_FIRST_SYMBOL
+        GROUP_COUNT
     };
 
 public:
     explicit MLGroupListModel(QObject * parent = nullptr);
 
-
-public: // QAbstractItemModel implementation
+public: // MLVideoModel reimplementation
     QHash<int, QByteArray> roleNames() const override;
 
-protected: // MLBaseModel implementation
+protected: // MLVideoModel reimplementation
     QVariant itemRoleData(MLItem *item, int role = Qt::DisplayRole) const override;
 
     vlc_ml_sorting_criteria_t roleToCriteria(int role) const override;
@@ -75,18 +57,10 @@ protected: // MLBaseModel implementation
 
     ListCacheLoader<std::unique_ptr<MLItem>> * createLoader() const override;
 
-    void thumbnailUpdated(const QModelIndex& idx, MLItem* mlitem, const QString& mrl, vlc_ml_thumbnail_status_t status) override;
-
-private: // Functions
-    QString getCover(MLGroup * group) const;
-
-private: // MLBaseModel implementation
     void onVlcMlEvent(const MLEvent & event) override;
 
-    void generateVideoThumbnail(uint64_t id) const;
-
 private:
-    struct Loader : public MLBaseModel::BaseLoader
+    struct Loader : public BaseLoader
     {
         Loader(const MLGroupListModel & model);
 
diff --git a/modules/gui/qt/menus/qml_menu_wrapper.cpp b/modules/gui/qt/menus/qml_menu_wrapper.cpp
index 134a7c3e79aabb84fa52cf918cae549b74b76c2c..830bc9b9428ce67edcc9a0b5ad31c909caa3c25c 100644
--- a/modules/gui/qt/menus/qml_menu_wrapper.cpp
+++ b/modules/gui/qt/menus/qml_menu_wrapper.cpp
@@ -564,7 +564,7 @@ void GroupListContextMenu::popup(const QModelIndexList & selected, QPoint pos, Q
     QVariantList ids;
 
     for (const QModelIndex & index : selected)
-        ids.push_back(m_model->data(index, MLGroupListModel::GROUP_ID));
+        ids.push_back(m_model->data(index, MLVideoModel::VIDEO_ID));
 
     m_menu = new QMenu();