1
mirror of https://code.videolan.org/videolan/vlc synced 2024-08-27 04:21:53 +02: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.
This commit is contained in:
Benjamin Arnaud 2021-04-12 10:19:17 +02:00 committed by Hugo Beauzée-Luyssen
parent 80b1d99153
commit 22bc7af601
4 changed files with 498 additions and 0 deletions

View File

@ -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 \
@ -374,6 +376,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 \

View File

@ -0,0 +1,379 @@
/*****************************************************************************
* Copyright (C) 2021 VLC authors and VideoLAN
*
* Authors: Benjamin Arnaud <bunjee@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;
}

View File

@ -0,0 +1,114 @@
/*****************************************************************************
* Copyright (C) 2021 VLC authors and VideoLAN
*
* Authors: Benjamin Arnaud <bunjee@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

View File

@ -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