parent
e4c89c1aed
commit
133f094d72
|
@ -34,6 +34,7 @@ set(SOURCES
|
||||||
core/thread.cpp
|
core/thread.cpp
|
||||||
core/urlhandler.cpp
|
core/urlhandler.cpp
|
||||||
core/utilities.cpp
|
core/utilities.cpp
|
||||||
|
core/imageutils.cpp
|
||||||
core/iconloader.cpp
|
core/iconloader.cpp
|
||||||
core/qtsystemtrayicon.cpp
|
core/qtsystemtrayicon.cpp
|
||||||
core/standarditemiconloader.cpp
|
core/standarditemiconloader.cpp
|
||||||
|
|
|
@ -52,8 +52,6 @@
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "sqlrow.h"
|
#include "sqlrow.h"
|
||||||
|
|
||||||
const char *CollectionBackend::kSettingsGroup = "Collection";
|
|
||||||
|
|
||||||
CollectionBackend::CollectionBackend(QObject *parent) :
|
CollectionBackend::CollectionBackend(QObject *parent) :
|
||||||
CollectionBackendInterface(parent),
|
CollectionBackendInterface(parent),
|
||||||
db_(nullptr),
|
db_(nullptr),
|
||||||
|
@ -671,19 +669,32 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbumsByArtist(const QString
|
||||||
return GetAlbums(artist, false, opt);
|
return GetAlbums(artist, false, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetSongsByAlbum(const QString &album, const QueryOptions &opt) {
|
SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt) {
|
||||||
|
|
||||||
CollectionQuery query(opt);
|
CollectionQuery query(opt);
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
query.AddWhere("album", album);
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
return ExecCollectionQuery(&query);
|
return ExecCollectionQuery(&query);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetSongs(const QString &artist, const QString &album, const QueryOptions &opt) {
|
SongList CollectionBackend::GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt) {
|
||||||
|
|
||||||
CollectionQuery query(opt);
|
CollectionQuery query(opt);
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
query.AddWhere("artist", artist);
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
query.AddWhere("album", album);
|
query.AddWhere("album", album);
|
||||||
return ExecCollectionQuery(&query);
|
return ExecCollectionQuery(&query);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList CollectionBackend::GetSongsByAlbum(const QString &album, const QueryOptions &opt) {
|
||||||
|
|
||||||
|
CollectionQuery query(opt);
|
||||||
|
query.AddCompilationRequirement(false);
|
||||||
|
query.AddWhere("album", album);
|
||||||
|
return ExecCollectionQuery(&query);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::ExecCollectionQuery(CollectionQuery *query) {
|
SongList CollectionBackend::ExecCollectionQuery(CollectionQuery *query) {
|
||||||
|
@ -1006,15 +1017,15 @@ void CollectionBackend::UpdateCompilations(QSqlQuery &find_song, QSqlQuery &upda
|
||||||
CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist, const bool compilation_required, const QueryOptions &opt) {
|
CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist, const bool compilation_required, const QueryOptions &opt) {
|
||||||
|
|
||||||
CollectionQuery query(opt);
|
CollectionQuery query(opt);
|
||||||
query.SetColumnSpec("url, artist, albumartist, album, compilation_effective, art_automatic, art_manual");
|
query.SetColumnSpec("url, effective_albumartist, album, compilation_effective, art_automatic, art_manual, filetype, cue_path");
|
||||||
query.SetOrderBy("album");
|
query.SetOrderBy("effective_albumartist, album, url");
|
||||||
|
|
||||||
if (compilation_required) {
|
if (compilation_required) {
|
||||||
query.AddCompilationRequirement(true);
|
query.AddCompilationRequirement(true);
|
||||||
}
|
}
|
||||||
else if (!artist.isEmpty()) {
|
else if (!artist.isEmpty()) {
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
query.AddWhereArtist(artist);
|
query.AddWhere("effective_albumartist", artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1024,17 +1035,16 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||||
|
|
||||||
QMap<QString, Album> albums;
|
QMap<QString, Album> albums;
|
||||||
while (query.Next()) {
|
while (query.Next()) {
|
||||||
bool is_compilation = query.Value(4).toBool();
|
bool is_compilation = query.Value(3).toBool();
|
||||||
|
|
||||||
Album info;
|
Album info;
|
||||||
info.first_url = QUrl::fromEncoded(query.Value(0).toByteArray());
|
QUrl url = QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||||
if (!is_compilation) {
|
if (!is_compilation) {
|
||||||
info.artist = query.Value(1).toString();
|
info.album_artist = query.Value(1).toString();
|
||||||
info.album_artist = query.Value(2).toString();
|
|
||||||
}
|
}
|
||||||
info.album_name = query.Value(3).toString();
|
info.album = query.Value(2).toString();
|
||||||
|
|
||||||
QString art_automatic = query.Value(5).toString();
|
QString art_automatic = query.Value(4).toString();
|
||||||
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
|
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
|
||||||
info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
|
info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
|
||||||
}
|
}
|
||||||
|
@ -1042,7 +1052,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||||
info.art_automatic = QUrl::fromLocalFile(art_automatic);
|
info.art_automatic = QUrl::fromLocalFile(art_automatic);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString art_manual = query.Value(6).toString();
|
QString art_manual = query.Value(5).toString();
|
||||||
if (art_manual.contains(QRegularExpression("..+:.*"))) {
|
if (art_manual.contains(QRegularExpression("..+:.*"))) {
|
||||||
info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
|
info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
|
||||||
}
|
}
|
||||||
|
@ -1050,19 +1060,31 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||||
info.art_manual = QUrl::fromLocalFile(art_manual);
|
info.art_manual = QUrl::fromLocalFile(art_manual);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString effective_albumartist = info.album_artist.isEmpty() ? info.artist : info.album_artist;
|
info.filetype = Song::FileType(query.Value(6).toInt());
|
||||||
|
QString filetype = Song::TextForFiletype(info.filetype);
|
||||||
|
info.cue_path = query.Value(7).toString();
|
||||||
|
|
||||||
QString key;
|
QString key;
|
||||||
if (!effective_albumartist.isEmpty()) {
|
if (!info.album_artist.isEmpty()) {
|
||||||
key.append(effective_albumartist);
|
key.append(info.album_artist);
|
||||||
}
|
}
|
||||||
if (!info.album_name.isEmpty()) {
|
if (!info.album.isEmpty()) {
|
||||||
if (!key.isEmpty()) key.append("-");
|
if (!key.isEmpty()) key.append("-");
|
||||||
key.append(info.album_name);
|
key.append(info.album);
|
||||||
|
}
|
||||||
|
if (!filetype.isEmpty()) {
|
||||||
|
key.append(filetype);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.isEmpty()) continue;
|
if (key.isEmpty()) continue;
|
||||||
|
|
||||||
if (!albums.contains(key)) albums.insert(key, info);
|
if (albums.contains(key)) {
|
||||||
|
albums[key].urls.append(url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
info.urls << url;
|
||||||
|
albums.insert(key, info);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1070,20 +1092,16 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album) {
|
CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective_albumartist, const QString &album) {
|
||||||
|
|
||||||
Album ret;
|
Album ret;
|
||||||
ret.album_name = album;
|
ret.album = album;
|
||||||
ret.artist = artist;
|
ret.album_artist = effective_albumartist;
|
||||||
ret.album_artist = albumartist;
|
|
||||||
|
|
||||||
CollectionQuery query = CollectionQuery(QueryOptions());
|
CollectionQuery query = CollectionQuery(QueryOptions());
|
||||||
query.SetColumnSpec("art_automatic, art_manual, url");
|
query.SetColumnSpec("art_automatic, art_manual, url");
|
||||||
if (!albumartist.isEmpty()) {
|
if (!effective_albumartist.isEmpty()) {
|
||||||
query.AddWhere("albumartist", albumartist);
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
}
|
|
||||||
else if (!artist.isEmpty()) {
|
|
||||||
query.AddWhere("artist", artist);
|
|
||||||
}
|
}
|
||||||
query.AddWhere("album", album);
|
query.AddWhere("album", album);
|
||||||
|
|
||||||
|
@ -1093,20 +1111,20 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &artist, c
|
||||||
if (query.Next()) {
|
if (query.Next()) {
|
||||||
ret.art_automatic = QUrl::fromEncoded(query.Value(0).toByteArray());
|
ret.art_automatic = QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||||
ret.art_manual = QUrl::fromEncoded(query.Value(1).toByteArray());
|
ret.art_manual = QUrl::fromEncoded(query.Value(1).toByteArray());
|
||||||
ret.first_url = QUrl::fromEncoded(query.Value(2).toByteArray());
|
ret.urls << QUrl::fromEncoded(query.Value(2).toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url) {
|
void CollectionBackend::UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic) {
|
||||||
|
|
||||||
metaObject()->invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, artist), Q_ARG(QString, albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url));
|
metaObject()->invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url), Q_ARG(bool, clear_art_automatic));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url) {
|
void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
@ -1114,15 +1132,9 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QStrin
|
||||||
// Get the songs before they're updated
|
// Get the songs before they're updated
|
||||||
CollectionQuery query;
|
CollectionQuery query;
|
||||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||||
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
query.AddWhere("album", album);
|
query.AddWhere("album", album);
|
||||||
|
|
||||||
if (!albumartist.isEmpty()) {
|
|
||||||
query.AddWhere("albumartist", albumartist);
|
|
||||||
}
|
|
||||||
else if (!artist.isEmpty()) {
|
|
||||||
query.AddWhere("artist", artist);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ExecQuery(&query)) return;
|
if (!ExecQuery(&query)) return;
|
||||||
|
|
||||||
SongList deleted_songs;
|
SongList deleted_songs;
|
||||||
|
@ -1133,26 +1145,73 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QStrin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the songs
|
// Update the songs
|
||||||
QString sql(QString("UPDATE %1 SET art_manual = :cover WHERE album = :album AND unavailable = 0").arg(songs_table_));
|
QString sql(QString("UPDATE %1 SET art_manual = :cover ").arg(songs_table_));
|
||||||
|
if (clear_art_automatic) {
|
||||||
if (!albumartist.isEmpty()) {
|
sql += "AND art_automatic = '' ";
|
||||||
sql += " AND albumartist = :albumartist";
|
|
||||||
}
|
|
||||||
else if (!artist.isNull()) {
|
|
||||||
sql += " AND artist = :artist";
|
|
||||||
}
|
}
|
||||||
|
sql += "WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
|
||||||
|
|
||||||
QSqlQuery q(db);
|
QSqlQuery q(db);
|
||||||
q.prepare(sql);
|
q.prepare(sql);
|
||||||
q.bindValue(":cover", cover_url.toString(QUrl::FullyEncoded));
|
q.bindValue(":cover", cover_url.isValid() ? cover_url.toString(QUrl::FullyEncoded) : "");
|
||||||
|
q.bindValue(":effective_albumartist", effective_albumartist);
|
||||||
q.bindValue(":album", album);
|
q.bindValue(":album", album);
|
||||||
if (!albumartist.isEmpty()) {
|
|
||||||
q.bindValue(":albumartist", albumartist);
|
q.exec();
|
||||||
|
db_->CheckErrors(q);
|
||||||
|
|
||||||
|
// Now get the updated songs
|
||||||
|
if (!ExecQuery(&query)) return;
|
||||||
|
|
||||||
|
SongList added_songs;
|
||||||
|
while (query.Next()) {
|
||||||
|
Song song(source_);
|
||||||
|
song.InitFromQuery(query, true);
|
||||||
|
added_songs << song;
|
||||||
}
|
}
|
||||||
else if (!artist.isEmpty()) {
|
|
||||||
q.bindValue(":artist", artist);
|
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||||
|
emit SongsDeleted(deleted_songs);
|
||||||
|
emit SongsDiscovered(added_songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url) {
|
||||||
|
|
||||||
|
metaObject()->invokeMethod(this, "UpdateAutomaticAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url) {
|
||||||
|
|
||||||
|
QMutexLocker l(db_->Mutex());
|
||||||
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
// Get the songs before they're updated
|
||||||
|
CollectionQuery query;
|
||||||
|
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||||
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
|
query.AddWhere("album", album);
|
||||||
|
|
||||||
|
if (!ExecQuery(&query)) return;
|
||||||
|
|
||||||
|
SongList deleted_songs;
|
||||||
|
while (query.Next()) {
|
||||||
|
Song song(source_);
|
||||||
|
song.InitFromQuery(query, true);
|
||||||
|
deleted_songs << song;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the songs
|
||||||
|
QString sql(QString("UPDATE %1 SET art_automatic = :cover WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||||
|
|
||||||
|
QSqlQuery q(db);
|
||||||
|
q.prepare(sql);
|
||||||
|
q.bindValue(":cover", cover_url.isValid() ? cover_url.toString(QUrl::FullyEncoded) : "");
|
||||||
|
q.bindValue(":effective_albumartist", effective_albumartist);
|
||||||
|
q.bindValue(":album", album);
|
||||||
|
|
||||||
q.exec();
|
q.exec();
|
||||||
db_->CheckErrors(q);
|
db_->CheckErrors(q);
|
||||||
|
|
||||||
|
|
|
@ -50,25 +50,23 @@ class CollectionBackendInterface : public QObject {
|
||||||
|
|
||||||
struct Album {
|
struct Album {
|
||||||
Album() {}
|
Album() {}
|
||||||
Album(const QString &_artist, const QString &_album_artist, const QString &_album_name, const QUrl &_art_automatic, const QUrl &_art_manual, const QUrl &_first_url) :
|
Album(const QString &_album_artist, const QString &_album, const QUrl &_art_automatic, const QUrl &_art_manual, const QList<QUrl> &_urls, const Song::FileType _filetype, const QString &_cue_path) :
|
||||||
artist(_artist),
|
|
||||||
album_artist(_album_artist),
|
album_artist(_album_artist),
|
||||||
album_name(_album_name),
|
album(_album),
|
||||||
art_automatic(_art_automatic),
|
art_automatic(_art_automatic),
|
||||||
art_manual(_art_manual),
|
art_manual(_art_manual),
|
||||||
first_url(_first_url) {}
|
urls(_urls),
|
||||||
|
filetype(_filetype),
|
||||||
|
cue_path(_cue_path) {}
|
||||||
|
|
||||||
const QString &effective_albumartist() const {
|
|
||||||
return album_artist.isEmpty() ? artist : album_artist;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString artist;
|
|
||||||
QString album_artist;
|
QString album_artist;
|
||||||
QString album_name;
|
QString album;
|
||||||
|
|
||||||
QUrl art_automatic;
|
QUrl art_automatic;
|
||||||
QUrl art_manual;
|
QUrl art_manual;
|
||||||
QUrl first_url;
|
QList<QUrl> urls;
|
||||||
|
Song::FileType filetype;
|
||||||
|
QString cue_path;
|
||||||
};
|
};
|
||||||
typedef QList<Album> AlbumList;
|
typedef QList<Album> AlbumList;
|
||||||
|
|
||||||
|
@ -88,8 +86,9 @@ class CollectionBackendInterface : public QObject {
|
||||||
|
|
||||||
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
|
virtual SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
|
virtual SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
virtual SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
virtual SongList GetSongs(const QString &artist, const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
|
||||||
|
|
||||||
virtual SongList GetCompilationSongs(const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetCompilationSongs(const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
|
|
||||||
|
@ -97,8 +96,10 @@ class CollectionBackendInterface : public QObject {
|
||||||
virtual AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
virtual AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
|
|
||||||
virtual void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url) = 0;
|
virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) = 0;
|
||||||
virtual Album GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album) = 0;
|
virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url) = 0;
|
||||||
|
|
||||||
|
virtual Album GetAlbumArt(const QString &effective_albumartist, const QString &album) = 0;
|
||||||
|
|
||||||
virtual Song GetSongById(const int id) = 0;
|
virtual Song GetSongById(const int id) = 0;
|
||||||
|
|
||||||
|
@ -118,7 +119,6 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static const char *kSettingsGroup;
|
|
||||||
|
|
||||||
Q_INVOKABLE explicit CollectionBackend(QObject *parent = nullptr);
|
Q_INVOKABLE explicit CollectionBackend(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
@ -148,8 +148,9 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||||
QStringList GetAll(const QString &column, const QueryOptions &opt = QueryOptions());
|
QStringList GetAll(const QString &column, const QueryOptions &opt = QueryOptions());
|
||||||
QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) override;
|
QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) override;
|
||||||
QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) override;
|
QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) override;
|
||||||
|
SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) override;
|
||||||
|
SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
||||||
SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
||||||
SongList GetSongs(const QString &artist, const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
|
||||||
|
|
||||||
SongList GetCompilationSongs(const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
SongList GetCompilationSongs(const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
||||||
|
|
||||||
|
@ -157,8 +158,10 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||||
AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) override;
|
AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) override;
|
||||||
AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) override;
|
AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) override;
|
||||||
|
|
||||||
void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url) override;
|
void UpdateManualAlbumArtAsync(const QString &album_artist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) override;
|
||||||
Album GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album) override;
|
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url) override;
|
||||||
|
|
||||||
|
Album GetAlbumArt(const QString &album_artist, const QString &album) override;
|
||||||
|
|
||||||
Song GetSongById(const int id) override;
|
Song GetSongById(const int id) override;
|
||||||
SongList GetSongsById(const QList<int> &ids);
|
SongList GetSongsById(const QList<int> &ids);
|
||||||
|
@ -205,7 +208,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||||
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||||
void AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
|
void AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
|
||||||
void CompilationsNeedUpdating();
|
void CompilationsNeedUpdating();
|
||||||
void UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url);
|
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false);
|
||||||
|
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url);
|
||||||
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
|
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
|
||||||
void IncrementPlayCount(const int id);
|
void IncrementPlayCount(const int id);
|
||||||
void IncrementSkipCount(const int id, const float progress);
|
void IncrementSkipCount(const int id, const float progress);
|
||||||
|
|
|
@ -102,9 +102,10 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
|
||||||
group_by_[1] = GroupBy_AlbumDisc;
|
group_by_[1] = GroupBy_AlbumDisc;
|
||||||
group_by_[2] = GroupBy_None;
|
group_by_[2] = GroupBy_None;
|
||||||
|
|
||||||
cover_loader_options_.desired_height_ = kPrettyCoverSize;
|
cover_loader_options_.get_image_data_ = false;
|
||||||
cover_loader_options_.pad_output_image_ = true;
|
|
||||||
cover_loader_options_.scale_output_image_ = true;
|
cover_loader_options_.scale_output_image_ = true;
|
||||||
|
cover_loader_options_.pad_output_image_ = true;
|
||||||
|
cover_loader_options_.desired_height_ = kPrettyCoverSize;
|
||||||
|
|
||||||
if (app_) {
|
if (app_) {
|
||||||
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CollectionModel::AlbumCoverLoaded);
|
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CollectionModel::AlbumCoverLoaded);
|
||||||
|
@ -677,7 +678,7 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderR
|
||||||
pending_cache_keys_.remove(cache_key);
|
pending_cache_keys_.remove(cache_key);
|
||||||
|
|
||||||
// Insert this image in the cache.
|
// Insert this image in the cache.
|
||||||
if (result.image_scaled.isNull()) {
|
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type_ManuallyUnset) {
|
||||||
// Set the no_cover image so we don't continually try to load art.
|
// Set the no_cover image so we don't continually try to load art.
|
||||||
QPixmapCache::insert(cache_key, no_cover_icon_);
|
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||||
}
|
}
|
||||||
|
@ -688,9 +689,9 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderR
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a valid cover not already in the disk cache
|
// If we have a valid cover not already in the disk cache
|
||||||
if (use_disk_cache_ && sIconCache) {
|
if (use_disk_cache_ && sIconCache && result.success && !result.image_scaled.isNull()) {
|
||||||
std::unique_ptr<QIODevice> cached_img(sIconCache->data(QUrl(cache_key)));
|
std::unique_ptr<QIODevice> cached_img(sIconCache->data(QUrl(cache_key)));
|
||||||
if (!cached_img && !result.image_scaled.isNull()) {
|
if (!cached_img) {
|
||||||
QNetworkCacheMetaData item_metadata;
|
QNetworkCacheMetaData item_metadata;
|
||||||
item_metadata.setSaveToDisk(true);
|
item_metadata.setSaveToDisk(true);
|
||||||
item_metadata.setUrl(QUrl(cache_key));
|
item_metadata.setUrl(QUrl(cache_key));
|
||||||
|
|
|
@ -471,11 +471,7 @@ void CollectionView::SetShowInVarious(const bool on) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (other_artists.count() > 0) {
|
if (other_artists.count() > 0) {
|
||||||
if (QMessageBox::question(this,
|
if (QMessageBox::question(this, tr("There are other songs in this album"), tr("Would you like to move the other songs on this album to Various Artists as well?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
|
||||||
tr("There are other songs in this album"),
|
|
||||||
tr("Would you like to move the other songs on this album to Various Artists as well?"),
|
|
||||||
QMessageBox::Yes | QMessageBox::No,
|
|
||||||
QMessageBox::Yes) == QMessageBox::Yes) {
|
|
||||||
for (const QString &s : other_artists) {
|
for (const QString &s : other_artists) {
|
||||||
albums.insert(album, s);
|
albums.insert(album, s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -534,7 +534,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file, const So
|
||||||
// If a cue got deleted, we turn it's first section into the new 'raw' (cueless) song and we just remove the rest of the sections from the collection
|
// If a cue got deleted, we turn it's first section into the new 'raw' (cueless) song and we just remove the rest of the sections from the collection
|
||||||
if (cue_deleted) {
|
if (cue_deleted) {
|
||||||
for (const Song &song : backend_->GetSongsByUrl(QUrl::fromLocalFile(file))) {
|
for (const Song &song : backend_->GetSongsByUrl(QUrl::fromLocalFile(file))) {
|
||||||
if (!song.IsMetadataEqual(matching_song)) {
|
if (!song.IsMetadataAndArtEqual(matching_song)) {
|
||||||
t->deleted_songs << song;
|
t->deleted_songs << song;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -611,7 +611,7 @@ void CollectionWatcher::PreserveUserSetData(const QString &file, const QUrl &ima
|
||||||
|
|
||||||
t->new_songs << *out;
|
t->new_songs << *out;
|
||||||
}
|
}
|
||||||
else if (!matching_song.IsMetadataEqual(*out)) {
|
else if (!matching_song.IsMetadataAndArtEqual(*out)) {
|
||||||
qLog(Debug) << file << "metadata changed";
|
qLog(Debug) << file << "metadata changed";
|
||||||
|
|
||||||
// Update the song in the DB
|
// Update the song in the DB
|
||||||
|
|
|
@ -37,8 +37,8 @@
|
||||||
#include <QContextMenuEvent>
|
#include <QContextMenuEvent>
|
||||||
#include <QPaintEvent>
|
#include <QPaintEvent>
|
||||||
|
|
||||||
|
#include "core/imageutils.h"
|
||||||
#include "covermanager/albumcoverchoicecontroller.h"
|
#include "covermanager/albumcoverchoicecontroller.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
|
||||||
|
|
||||||
#include "contextview.h"
|
#include "contextview.h"
|
||||||
#include "contextalbum.h"
|
#include "contextalbum.h"
|
||||||
|
@ -62,8 +62,8 @@ ContextAlbum::ContextAlbum(QWidget *parent) :
|
||||||
cover_loader_options_.desired_height_ = 600;
|
cover_loader_options_.desired_height_ = 600;
|
||||||
cover_loader_options_.pad_output_image_ = true;
|
cover_loader_options_.pad_output_image_ = true;
|
||||||
cover_loader_options_.scale_output_image_ = true;
|
cover_loader_options_.scale_output_image_ = true;
|
||||||
QPair<QImage, QImage> images = AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_strawberry_);
|
QImage image = ImageUtils::ScaleAndPad(image_strawberry_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
||||||
pixmap_current_ = QPixmap::fromImage(images.first);
|
if (!image.isNull()) pixmap_current_ = QPixmap::fromImage(image);
|
||||||
|
|
||||||
QObject::connect(timeline_fade_, &QTimeLine::valueChanged, this, &ContextAlbum::FadePreviousTrack);
|
QObject::connect(timeline_fade_, &QTimeLine::valueChanged, this, &ContextAlbum::FadePreviousTrack);
|
||||||
timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
||||||
|
@ -78,9 +78,10 @@ void ContextAlbum::Init(ContextView *context_view, AlbumCoverChoiceController *a
|
||||||
QObject::connect(album_cover_choice_controller_, &AlbumCoverChoiceController::AutomaticCoverSearchDone, this, &ContextAlbum::AutomaticCoverSearchDone);
|
QObject::connect(album_cover_choice_controller_, &AlbumCoverChoiceController::AutomaticCoverSearchDone, this, &ContextAlbum::AutomaticCoverSearchDone);
|
||||||
|
|
||||||
QList<QAction*> cover_actions = album_cover_choice_controller_->GetAllActions();
|
QList<QAction*> cover_actions = album_cover_choice_controller_->GetAllActions();
|
||||||
cover_actions.append(album_cover_choice_controller_->search_cover_auto_action());
|
|
||||||
menu_->addActions(cover_actions);
|
menu_->addActions(cover_actions);
|
||||||
menu_->addSeparator();
|
menu_->addSeparator();
|
||||||
|
menu_->addAction(album_cover_choice_controller_->search_cover_auto_action());
|
||||||
|
menu_->addSeparator();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +116,9 @@ void ContextAlbum::DrawImage(QPainter *p) {
|
||||||
|
|
||||||
if (width() != prev_width_) {
|
if (width() != prev_width_) {
|
||||||
cover_loader_options_.desired_height_ = width() - kWidgetSpacing;
|
cover_loader_options_.desired_height_ = width() - kWidgetSpacing;
|
||||||
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_).first);
|
QImage image = ImageUtils::ScaleAndPad(image_original_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
||||||
|
if (image.isNull()) pixmap_current_ = QPixmap();
|
||||||
|
else pixmap_current_ = QPixmap::fromImage(image);
|
||||||
prev_width_ = width();
|
prev_width_ = width();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +147,9 @@ void ContextAlbum::FadePreviousTrack(const qreal value) {
|
||||||
void ContextAlbum::ScaleCover() {
|
void ContextAlbum::ScaleCover() {
|
||||||
|
|
||||||
cover_loader_options_.desired_height_ = width() - kWidgetSpacing;
|
cover_loader_options_.desired_height_ = width() - kWidgetSpacing;
|
||||||
pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_).first);
|
QImage image = ImageUtils::ScaleAndPad(image_original_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
||||||
|
if (image.isNull()) pixmap_current_ = QPixmap();
|
||||||
|
else pixmap_current_ = QPixmap::fromImage(image);
|
||||||
prev_width_ = width();
|
prev_width_ = width();
|
||||||
update();
|
update();
|
||||||
|
|
||||||
|
|
|
@ -67,9 +67,10 @@ ContextAlbumsModel::ContextAlbumsModel(CollectionBackend *backend, Application *
|
||||||
|
|
||||||
root_->lazy_loaded = true;
|
root_->lazy_loaded = true;
|
||||||
|
|
||||||
cover_loader_options_.desired_height_ = kPrettyCoverSize;
|
cover_loader_options_.get_image_data_ = false;
|
||||||
cover_loader_options_.pad_output_image_ = true;
|
|
||||||
cover_loader_options_.scale_output_image_ = true;
|
cover_loader_options_.scale_output_image_ = true;
|
||||||
|
cover_loader_options_.pad_output_image_ = true;
|
||||||
|
cover_loader_options_.desired_height_ = kPrettyCoverSize;
|
||||||
|
|
||||||
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &ContextAlbumsModel::AlbumCoverLoaded);
|
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &ContextAlbumsModel::AlbumCoverLoaded);
|
||||||
|
|
||||||
|
@ -159,7 +160,7 @@ void ContextAlbumsModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoad
|
||||||
pending_cache_keys_.remove(cache_key);
|
pending_cache_keys_.remove(cache_key);
|
||||||
|
|
||||||
// Insert this image in the cache.
|
// Insert this image in the cache.
|
||||||
if (result.image_scaled.isNull()) {
|
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type_ManuallyUnset) {
|
||||||
// Set the no_cover image so we don't continually try to load art.
|
// Set the no_cover image so we don't continually try to load art.
|
||||||
QPixmapCache::insert(cache_key, no_cover_icon_);
|
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||||
}
|
}
|
||||||
|
|
|
@ -603,13 +603,13 @@ void ContextView::SetSong() {
|
||||||
const QueryOptions opt;
|
const QueryOptions opt;
|
||||||
CollectionBackend::AlbumList albumlist;
|
CollectionBackend::AlbumList albumlist;
|
||||||
widget_albums_->albums_model()->Reset();
|
widget_albums_->albums_model()->Reset();
|
||||||
albumlist = app_->collection_backend()->GetAlbumsByArtist(song_playing_.artist(), opt);
|
albumlist = app_->collection_backend()->GetAlbumsByArtist(song_playing_.effective_albumartist(), opt);
|
||||||
if (albumlist.count() > 1) {
|
if (albumlist.count() > 1) {
|
||||||
label_play_albums_->show();
|
label_play_albums_->show();
|
||||||
widget_albums_->show();
|
widget_albums_->show();
|
||||||
label_play_albums_->setText("<b>" + tr("Albums by %1").arg( song_playing_.artist().toHtmlEscaped()) + "</b>");
|
label_play_albums_->setText("<b>" + tr("Albums by %1").arg(song_playing_.effective_albumartist().toHtmlEscaped()) + "</b>");
|
||||||
for (CollectionBackend::Album album : albumlist) {
|
for (const CollectionBackend::Album &album : albumlist) {
|
||||||
SongList songs = app_->collection_backend()->GetSongs(song_playing_.artist(), album.album_name, opt);
|
SongList songs = app_->collection_backend()->GetAlbumSongs(song_playing_.effective_albumartist(), album.album, opt);
|
||||||
widget_albums_->albums_model()->AddSongs(songs);
|
widget_albums_->albums_model()->AddSongs(songs);
|
||||||
}
|
}
|
||||||
spacer_play_albums_->changeSize(20, 10, QSizePolicy::Fixed);
|
spacer_play_albums_->changeSize(20, 10, QSizePolicy::Fixed);
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry 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 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QImageReader>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
|
#include "imageutils.h"
|
||||||
|
#include "core/utilities.h"
|
||||||
|
#include "core/tagreaderclient.h"
|
||||||
|
|
||||||
|
QStringList ImageUtils::kSupportedImageMimeTypes;
|
||||||
|
QStringList ImageUtils::kSupportedImageFormats;
|
||||||
|
|
||||||
|
QStringList ImageUtils::SupportedImageMimeTypes() {
|
||||||
|
|
||||||
|
if (kSupportedImageMimeTypes.isEmpty()) {
|
||||||
|
for (const QByteArray &mimetype : QImageReader::supportedMimeTypes()) {
|
||||||
|
kSupportedImageMimeTypes << mimetype;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kSupportedImageMimeTypes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ImageUtils::SupportedImageFormats() {
|
||||||
|
|
||||||
|
if (kSupportedImageFormats.isEmpty()) {
|
||||||
|
for (const QByteArray &filetype : QImageReader::supportedImageFormats()) {
|
||||||
|
kSupportedImageFormats << filetype;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return kSupportedImageFormats;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QByteArray> ImageUtils::ImageFormatsForMimeType(const QByteArray &mimetype) {
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||||
|
return QImageReader::imageFormatsForMimeType(mimetype);
|
||||||
|
#else
|
||||||
|
if (mimetype == "image/bmp") return QList<QByteArray>() << "BMP";
|
||||||
|
else if (mimetype == "image/gif") return QList<QByteArray>() << "GIF";
|
||||||
|
else if (mimetype == "image/jpeg") return QList<QByteArray>() << "JPG";
|
||||||
|
else if (mimetype == "image/png") return QList<QByteArray>() << "PNG";
|
||||||
|
else return QList<QByteArray>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap ImageUtils::TryLoadPixmap(const QUrl &art_automatic, const QUrl &art_manual, const QUrl &url) {
|
||||||
|
|
||||||
|
QPixmap ret;
|
||||||
|
|
||||||
|
if (!art_manual.path().isEmpty()) {
|
||||||
|
if (art_manual.path() == Song::kManuallyUnsetCover) return ret;
|
||||||
|
else if (art_manual.isLocalFile()) {
|
||||||
|
ret.load(art_manual.toLocalFile());
|
||||||
|
}
|
||||||
|
else if (art_manual.scheme().isEmpty()) {
|
||||||
|
ret.load(art_manual.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ret.isNull() && !art_automatic.path().isEmpty()) {
|
||||||
|
if (art_automatic.path() == Song::kEmbeddedCover && !url.isEmpty() && url.isLocalFile()) {
|
||||||
|
ret = QPixmap::fromImage(TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(url.toLocalFile()));
|
||||||
|
}
|
||||||
|
else if (art_automatic.isLocalFile()) {
|
||||||
|
ret.load(art_automatic.toLocalFile());
|
||||||
|
}
|
||||||
|
else if (art_automatic.scheme().isEmpty()) {
|
||||||
|
ret.load(art_automatic.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ImageUtils::SaveImageToJpegData(const QImage &image) {
|
||||||
|
|
||||||
|
if (image.isNull()) return QByteArray();
|
||||||
|
|
||||||
|
QByteArray image_data;
|
||||||
|
QBuffer buffer(&image_data);
|
||||||
|
if (buffer.open(QIODevice::WriteOnly)) {
|
||||||
|
image.save(&buffer, "JPEG");
|
||||||
|
buffer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return image_data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage ImageUtils::ScaleAndPad(const QImage &image, const bool scale, const bool pad, const int desired_height) {
|
||||||
|
|
||||||
|
if (image.isNull()) return image;
|
||||||
|
|
||||||
|
// Scale the image down
|
||||||
|
QImage image_scaled;
|
||||||
|
if (scale) {
|
||||||
|
image_scaled = image.scaled(QSize(desired_height, desired_height), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
image_scaled = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pad the image to height x height
|
||||||
|
if (pad) {
|
||||||
|
QImage image_padded(desired_height, desired_height, QImage::Format_ARGB32);
|
||||||
|
image_padded.fill(0);
|
||||||
|
|
||||||
|
QPainter p(&image_padded);
|
||||||
|
p.drawImage((desired_height - image_scaled.width()) / 2, (desired_height - image_scaled.height()) / 2, image_scaled);
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
image_scaled = image_padded;
|
||||||
|
}
|
||||||
|
|
||||||
|
return image_scaled;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage ImageUtils::CreateThumbnail(const QImage &image, const bool pad, const QSize size) {
|
||||||
|
|
||||||
|
if (image.isNull()) return image;
|
||||||
|
|
||||||
|
QImage image_thumbnail;
|
||||||
|
if (pad) {
|
||||||
|
image_thumbnail = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
QImage image_padded(size, QImage::Format_ARGB32_Premultiplied);
|
||||||
|
image_padded.fill(0);
|
||||||
|
|
||||||
|
QPainter p(&image_padded);
|
||||||
|
p.drawImage((image_padded.width() - image_thumbnail.width()) / 2, (image_padded.height() - image_thumbnail.height()) / 2, image_thumbnail);
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
image_thumbnail = image_padded;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
image_thumbnail = image.scaledToHeight(size.height(), Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return image_thumbnail;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage ImageUtils::GenerateNoCoverImage(const QSize size) {
|
||||||
|
|
||||||
|
QImage image(":/pictures/cdcase.png");
|
||||||
|
|
||||||
|
// Get a square version of the nocover image with some transparency:
|
||||||
|
QImage image_scaled = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
|
||||||
|
QImage image_square(size, QImage::Format_ARGB32);
|
||||||
|
image_square.fill(0);
|
||||||
|
QPainter p(&image_square);
|
||||||
|
p.setOpacity(0.4);
|
||||||
|
p.drawImage((size.width() - image_scaled.width()) / 2, (size.height() - image_scaled.height()) / 2, image_scaled);
|
||||||
|
p.end();
|
||||||
|
|
||||||
|
return image_square;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry 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 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef IMAGEUTILS_H
|
||||||
|
#define IMAGEUTILS_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QPixmap>
|
||||||
|
|
||||||
|
class ImageUtils {
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QStringList kSupportedImageMimeTypes;
|
||||||
|
static QStringList kSupportedImageFormats;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static QStringList SupportedImageMimeTypes();
|
||||||
|
static QStringList SupportedImageFormats();
|
||||||
|
static QList<QByteArray> ImageFormatsForMimeType(const QByteArray &mimetype);
|
||||||
|
static QByteArray SaveImageToJpegData(const QImage &image = QImage());
|
||||||
|
static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QUrl &url = QUrl());
|
||||||
|
static QImage ScaleAndPad(const QImage &image, const bool scale, const bool pad, const int desired_height);
|
||||||
|
static QImage CreateThumbnail(const QImage &image, const bool pad, const QSize size);
|
||||||
|
static QImage GenerateNoCoverImage(const QSize size = QSize());
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // IMAGEUTILS_H
|
|
@ -143,6 +143,7 @@
|
||||||
#include "covermanager/albumcoverloaderresult.h"
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "covermanager/currentalbumcoverloader.h"
|
#include "covermanager/currentalbumcoverloader.h"
|
||||||
#include "covermanager/coverproviders.h"
|
#include "covermanager/coverproviders.h"
|
||||||
|
#include "covermanager/albumcoverimageresult.h"
|
||||||
#include "lyrics/lyricsproviders.h"
|
#include "lyrics/lyricsproviders.h"
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
# include "device/devicemanager.h"
|
# include "device/devicemanager.h"
|
||||||
|
@ -613,6 +614,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
||||||
QObject::connect(album_cover_choice_controller_->cover_from_url_action(), &QAction::triggered, this, &MainWindow::LoadCoverFromURL);
|
QObject::connect(album_cover_choice_controller_->cover_from_url_action(), &QAction::triggered, this, &MainWindow::LoadCoverFromURL);
|
||||||
QObject::connect(album_cover_choice_controller_->search_for_cover_action(), &QAction::triggered, this, &MainWindow::SearchForCover);
|
QObject::connect(album_cover_choice_controller_->search_for_cover_action(), &QAction::triggered, this, &MainWindow::SearchForCover);
|
||||||
QObject::connect(album_cover_choice_controller_->unset_cover_action(), &QAction::triggered, this, &MainWindow::UnsetCover);
|
QObject::connect(album_cover_choice_controller_->unset_cover_action(), &QAction::triggered, this, &MainWindow::UnsetCover);
|
||||||
|
QObject::connect(album_cover_choice_controller_->clear_cover_action(), &QAction::triggered, this, &MainWindow::ClearCover);
|
||||||
|
QObject::connect(album_cover_choice_controller_->delete_cover_action(), &QAction::triggered, this, &MainWindow::DeleteCover);
|
||||||
QObject::connect(album_cover_choice_controller_->show_cover_action(), &QAction::triggered, this, &MainWindow::ShowCover);
|
QObject::connect(album_cover_choice_controller_->show_cover_action(), &QAction::triggered, this, &MainWindow::ShowCover);
|
||||||
QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::triggered, this, &MainWindow::SearchCoverAutomatically);
|
QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::triggered, this, &MainWindow::SearchCoverAutomatically);
|
||||||
QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::toggled, this, &MainWindow::ToggleSearchCoverAuto);
|
QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::toggled, this, &MainWindow::ToggleSearchCoverAuto);
|
||||||
|
@ -1234,7 +1237,7 @@ void MainWindow::MediaStopped() {
|
||||||
|
|
||||||
song_playing_ = Song();
|
song_playing_ = Song();
|
||||||
song_ = Song();
|
song_ = Song();
|
||||||
image_original_ = QImage();
|
album_cover_ = AlbumCoverImageResult();
|
||||||
|
|
||||||
app_->scrobbler()->ClearPlaying();
|
app_->scrobbler()->ClearPlaying();
|
||||||
|
|
||||||
|
@ -1312,6 +1315,14 @@ void MainWindow::SongChanged(const Song &song) {
|
||||||
|
|
||||||
SendNowPlaying();
|
SendNowPlaying();
|
||||||
|
|
||||||
|
const bool enable_cover_options = song.url().isLocalFile() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty();
|
||||||
|
album_cover_choice_controller_->cover_from_file_action()->setEnabled(enable_cover_options);
|
||||||
|
album_cover_choice_controller_->cover_from_url_action()->setEnabled(enable_cover_options);
|
||||||
|
album_cover_choice_controller_->search_for_cover_action()->setEnabled(enable_cover_options);
|
||||||
|
album_cover_choice_controller_->unset_cover_action()->setEnabled(enable_cover_options);
|
||||||
|
album_cover_choice_controller_->delete_cover_action()->setEnabled(enable_cover_options);
|
||||||
|
album_cover_choice_controller_->clear_cover_action()->setEnabled(enable_cover_options);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::TrackSkipped(PlaylistItemPtr item) {
|
void MainWindow::TrackSkipped(PlaylistItemPtr item) {
|
||||||
|
@ -2073,7 +2084,7 @@ void MainWindow::RenumberTracks() {
|
||||||
song.set_track(track);
|
song.set_track(track);
|
||||||
TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
|
TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
|
||||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
||||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); });
|
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
++track;
|
++track;
|
||||||
}
|
}
|
||||||
|
@ -2085,7 +2096,7 @@ void MainWindow::SongSaveComplete(TagReaderReply *reply, const QPersistentModelI
|
||||||
if (reply->is_successful() && idx.isValid()) {
|
if (reply->is_successful() && idx.isValid()) {
|
||||||
app_->playlist_manager()->current()->ReloadItems(QList<int>()<< idx.row());
|
app_->playlist_manager()->current()->ReloadItems(QList<int>()<< idx.row());
|
||||||
}
|
}
|
||||||
reply->deleteLater();
|
metaObject()->invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2104,7 +2115,7 @@ void MainWindow::SelectionSetValue() {
|
||||||
if (Playlist::set_column_value(song, column, column_value)) {
|
if (Playlist::set_column_value(song, column, column_value)) {
|
||||||
TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
|
TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
|
||||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
||||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); });
|
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2914,15 +2925,23 @@ void MainWindow::SearchForCover() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::SaveCoverToFile() {
|
void MainWindow::SaveCoverToFile() {
|
||||||
album_cover_choice_controller_->SaveCoverToFileManual(song_, image_original_);
|
album_cover_choice_controller_->SaveCoverToFileManual(song_, album_cover_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::UnsetCover() {
|
void MainWindow::UnsetCover() {
|
||||||
album_cover_choice_controller_->UnsetCover(&song_);
|
album_cover_choice_controller_->UnsetCover(&song_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::ClearCover() {
|
||||||
|
album_cover_choice_controller_->ClearCover(&song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::DeleteCover() {
|
||||||
|
album_cover_choice_controller_->DeleteCover(&song_);
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::ShowCover() {
|
void MainWindow::ShowCover() {
|
||||||
album_cover_choice_controller_->ShowCover(song_, image_original_);
|
album_cover_choice_controller_->ShowCover(song_, album_cover_.image);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::SearchCoverAutomatically() {
|
void MainWindow::SearchCoverAutomatically() {
|
||||||
|
@ -2936,9 +2955,9 @@ void MainWindow::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult
|
||||||
if (song != song_playing_) return;
|
if (song != song_playing_) return;
|
||||||
|
|
||||||
song_ = song;
|
song_ = song;
|
||||||
image_original_ = result.image_original;
|
album_cover_ = result.album_cover;
|
||||||
|
|
||||||
emit AlbumCoverReady(song, result.image_original);
|
emit AlbumCoverReady(song, result.album_cover.image);
|
||||||
|
|
||||||
GetCoverAutomatically();
|
GetCoverAutomatically();
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@
|
||||||
#include "settings/settingsdialog.h"
|
#include "settings/settingsdialog.h"
|
||||||
#include "settings/behavioursettingspage.h"
|
#include "settings/behavioursettingspage.h"
|
||||||
#include "covermanager/albumcoverloaderresult.h"
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
#include "covermanager/albumcoverimageresult.h"
|
||||||
|
|
||||||
class About;
|
class About;
|
||||||
class Console;
|
class Console;
|
||||||
|
@ -255,6 +256,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||||
void LoadCoverFromURL();
|
void LoadCoverFromURL();
|
||||||
void SearchForCover();
|
void SearchForCover();
|
||||||
void UnsetCover();
|
void UnsetCover();
|
||||||
|
void ClearCover();
|
||||||
|
void DeleteCover();
|
||||||
void ShowCover();
|
void ShowCover();
|
||||||
void SearchCoverAutomatically();
|
void SearchCoverAutomatically();
|
||||||
void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result);
|
void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result);
|
||||||
|
@ -386,7 +389,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||||
|
|
||||||
Song song_;
|
Song song_;
|
||||||
Song song_playing_;
|
Song song_playing_;
|
||||||
QImage image_original_;
|
AlbumCoverImageResult album_cover_;
|
||||||
int exit_count_;
|
int exit_count_;
|
||||||
bool delete_files_;
|
bool delete_files_;
|
||||||
|
|
||||||
|
|
|
@ -115,10 +115,10 @@ void RegisterMetaTypes() {
|
||||||
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
|
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
|
||||||
qRegisterMetaType<AlbumCoverLoaderResult>("AlbumCoverLoaderResult");
|
qRegisterMetaType<AlbumCoverLoaderResult>("AlbumCoverLoaderResult");
|
||||||
qRegisterMetaType<AlbumCoverLoaderResult::Type>("AlbumCoverLoaderResult::Type");
|
qRegisterMetaType<AlbumCoverLoaderResult::Type>("AlbumCoverLoaderResult::Type");
|
||||||
qRegisterMetaType<CoverSearchResult>("CoverSearchResult");
|
qRegisterMetaType<CoverProviderSearchResult>("CoverProviderSearchResult");
|
||||||
qRegisterMetaType<CoverSearchStatistics>("CoverSearchStatistics");
|
qRegisterMetaType<CoverSearchStatistics>("CoverSearchStatistics");
|
||||||
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
|
qRegisterMetaType<QList<CoverProviderSearchResult> >("QList<CoverProviderSearchResult>");
|
||||||
qRegisterMetaType<CoverSearchResults>("CoverSearchResults");
|
qRegisterMetaType<CoverProviderSearchResults>("CoverProviderSearchResults");
|
||||||
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
|
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
|
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
|
||||||
|
|
|
@ -394,8 +394,8 @@ void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &re
|
||||||
AddMetadata("mpris:trackid", current_track_id(), &last_metadata_);
|
AddMetadata("mpris:trackid", current_track_id(), &last_metadata_);
|
||||||
|
|
||||||
QUrl cover_url;
|
QUrl cover_url;
|
||||||
if (result.cover_url.isValid() && result.cover_url.isLocalFile() && QFile(result.cover_url.toLocalFile()).exists()) {
|
if (result.album_cover.cover_url.isValid() && result.album_cover.cover_url.isLocalFile() && QFile(result.album_cover.cover_url.toLocalFile()).exists()) {
|
||||||
cover_url = result.cover_url;
|
cover_url = result.album_cover.cover_url;
|
||||||
}
|
}
|
||||||
else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
|
else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
|
||||||
cover_url = result.temp_cover_url;
|
cover_url = result.temp_cover_url;
|
||||||
|
|
|
@ -183,7 +183,7 @@ class Mpris2 : public QObject {
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
void ActivatePlaylist(const QDBusObjectPath &playlist_id);
|
void ActivatePlaylist(const QDBusObjectPath &playlist_id);
|
||||||
QList<MprisPlaylist> GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order);
|
MprisPlaylistList GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
// Player
|
// Player
|
||||||
|
|
|
@ -341,10 +341,22 @@ bool Song::compilation_on() const { return d->compilation_on_; }
|
||||||
const QUrl &Song::art_automatic() const { return d->art_automatic_; }
|
const QUrl &Song::art_automatic() const { return d->art_automatic_; }
|
||||||
const QUrl &Song::art_manual() const { return d->art_manual_; }
|
const QUrl &Song::art_manual() const { return d->art_manual_; }
|
||||||
bool Song::has_manually_unset_cover() const { return d->art_manual_.path() == kManuallyUnsetCover; }
|
bool Song::has_manually_unset_cover() const { return d->art_manual_.path() == kManuallyUnsetCover; }
|
||||||
void Song::manually_unset_cover() { d->art_manual_ = QUrl::fromLocalFile(kManuallyUnsetCover); }
|
void Song::set_manually_unset_cover() { d->art_manual_ = QUrl::fromLocalFile(kManuallyUnsetCover); }
|
||||||
bool Song::has_embedded_cover() const { return d->art_automatic_.path() == kEmbeddedCover; }
|
bool Song::has_embedded_cover() const { return d->art_automatic_.path() == kEmbeddedCover; }
|
||||||
void Song::set_embedded_cover() { d->art_automatic_ = QUrl::fromLocalFile(kEmbeddedCover); }
|
void Song::set_embedded_cover() { d->art_automatic_ = QUrl::fromLocalFile(kEmbeddedCover); }
|
||||||
|
|
||||||
|
void Song::clear_art_automatic() { d->art_automatic_.clear(); }
|
||||||
|
void Song::clear_art_manual() { d->art_manual_.clear(); }
|
||||||
|
|
||||||
|
bool Song::save_embedded_cover_supported(const FileType filetype) {
|
||||||
|
|
||||||
|
return filetype == FileType_FLAC ||
|
||||||
|
filetype == FileType_OggVorbis ||
|
||||||
|
filetype == FileType_MPEG ||
|
||||||
|
filetype == FileType_MP4;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const QUrl &Song::stream_url() const { return d->stream_url_; }
|
const QUrl &Song::stream_url() const { return d->stream_url_; }
|
||||||
const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
|
const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
|
||||||
const QImage &Song::image() const { return d->image_; }
|
const QImage &Song::image() const { return d->image_; }
|
||||||
|
@ -382,6 +394,8 @@ bool Song::art_manual_is_valid() const {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Song::has_valid_art() const { return art_automatic_is_valid() || art_manual_is_valid(); }
|
||||||
|
|
||||||
const QString &Song::error() const { return d->error_; }
|
const QString &Song::error() const { return d->error_; }
|
||||||
|
|
||||||
void Song::set_id(int id) { d->id_ = id; }
|
void Song::set_id(int id) { d->id_ = id; }
|
||||||
|
@ -1054,13 +1068,19 @@ void Song::InitArtManual() {
|
||||||
if (QFile::exists(path)) {
|
if (QFile::exists(path)) {
|
||||||
d->art_manual_ = QUrl::fromLocalFile(path);
|
d->art_manual_ = QUrl::fromLocalFile(path);
|
||||||
}
|
}
|
||||||
else if (d->url_.isLocalFile()) { // Pick the first image file in the album directory.
|
}
|
||||||
QFileInfo file(d->url_.toLocalFile());
|
|
||||||
QDir dir(file.path());
|
}
|
||||||
QStringList files = dir.entryList(QStringList() << "*.jpg" << "*.png" << "*.gif" << "*.jpeg", QDir::Files|QDir::Readable, QDir::Name);
|
|
||||||
if (files.count() > 0) {
|
void Song::InitArtAutomatic() {
|
||||||
d->art_manual_ = QUrl::fromLocalFile(file.path() + QDir::separator() + files.first());
|
|
||||||
}
|
if (d->source_ == Source_LocalFile && d->url_.isLocalFile() && d->art_automatic_.isEmpty()) {
|
||||||
|
// Pick the first image file in the album directory.
|
||||||
|
QFileInfo file(d->url_.toLocalFile());
|
||||||
|
QDir dir(file.path());
|
||||||
|
QStringList files = dir.entryList(QStringList() << "*.jpg" << "*.png" << "*.gif" << "*.jpeg", QDir::Files|QDir::Readable, QDir::Name);
|
||||||
|
if (files.count() > 0) {
|
||||||
|
d->art_automatic_ = QUrl::fromLocalFile(file.path() + QDir::separator() + files.first());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1491,11 +1511,15 @@ bool Song::IsMetadataEqual(const Song &other) const {
|
||||||
d->bitrate_ == other.d->bitrate_ &&
|
d->bitrate_ == other.d->bitrate_ &&
|
||||||
d->samplerate_ == other.d->samplerate_ &&
|
d->samplerate_ == other.d->samplerate_ &&
|
||||||
d->bitdepth_ == other.d->bitdepth_ &&
|
d->bitdepth_ == other.d->bitdepth_ &&
|
||||||
d->art_automatic_ == other.d->art_automatic_ &&
|
|
||||||
d->art_manual_ == other.d->art_manual_ &&
|
|
||||||
d->cue_path_ == other.d->cue_path_;
|
d->cue_path_ == other.d->cue_path_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Song::IsMetadataAndArtEqual(const Song &other) const {
|
||||||
|
|
||||||
|
return IsMetadataEqual(other) && d->art_automatic_ == other.d->art_automatic_ && d->art_manual_ == other.d->art_manual_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool Song::IsEditable() const {
|
bool Song::IsEditable() const {
|
||||||
return d->valid_ && !d->url_.isEmpty() && !is_stream() && d->source_ != Source_Unknown && d->filetype_ != FileType_Unknown && !has_cue();
|
return d->valid_ && !d->url_.isEmpty() && !is_stream() && d->source_ != Source_Unknown && d->filetype_ != FileType_Unknown && !has_cue();
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,7 @@ class Song {
|
||||||
void InitFromQuery(const SqlRow &query, bool reliable_metadata, int col = 0);
|
void InitFromQuery(const SqlRow &query, bool reliable_metadata, int col = 0);
|
||||||
void InitFromFilePartial(const QString &filename); // Just store the filename: incomplete but fast
|
void InitFromFilePartial(const QString &filename); // Just store the filename: incomplete but fast
|
||||||
void InitArtManual(); // Check if there is already a art in the cache and store the filename in art_manual
|
void InitArtManual(); // Check if there is already a art in the cache and store the filename in art_manual
|
||||||
|
void InitArtAutomatic();
|
||||||
|
|
||||||
bool MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle);
|
bool MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle);
|
||||||
|
|
||||||
|
@ -255,6 +256,7 @@ class Song {
|
||||||
bool is_metadata_good() const;
|
bool is_metadata_good() const;
|
||||||
bool art_automatic_is_valid() const;
|
bool art_automatic_is_valid() const;
|
||||||
bool art_manual_is_valid() const;
|
bool art_manual_is_valid() const;
|
||||||
|
bool has_valid_art() const;
|
||||||
bool is_compilation() const;
|
bool is_compilation() const;
|
||||||
|
|
||||||
// Playlist views are special because you don't want to fill in album artists automatically for compilations, but you do for normal albums:
|
// Playlist views are special because you don't want to fill in album artists automatically for compilations, but you do for normal albums:
|
||||||
|
@ -264,13 +266,19 @@ class Song {
|
||||||
// Returns true if this Song had it's cover manually unset by user.
|
// Returns true if this Song had it's cover manually unset by user.
|
||||||
bool has_manually_unset_cover() const;
|
bool has_manually_unset_cover() const;
|
||||||
// This method represents an explicit request to unset this song's cover.
|
// This method represents an explicit request to unset this song's cover.
|
||||||
void manually_unset_cover();
|
void set_manually_unset_cover();
|
||||||
|
|
||||||
// Returns true if this song (it's media file) has an embedded cover.
|
// Returns true if this song (it's media file) has an embedded cover.
|
||||||
bool has_embedded_cover() const;
|
bool has_embedded_cover() const;
|
||||||
// Sets a flag saying that this song (it's media file) has an embedded cover.
|
// Sets a flag saying that this song (it's media file) has an embedded cover.
|
||||||
void set_embedded_cover();
|
void set_embedded_cover();
|
||||||
|
|
||||||
|
void clear_art_automatic();
|
||||||
|
void clear_art_manual();
|
||||||
|
|
||||||
|
static bool save_embedded_cover_supported(const FileType filetype);
|
||||||
|
bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); };
|
||||||
|
|
||||||
const QUrl &stream_url() const;
|
const QUrl &stream_url() const;
|
||||||
const QUrl &effective_stream_url() const;
|
const QUrl &effective_stream_url() const;
|
||||||
const QImage &image() const;
|
const QImage &image() const;
|
||||||
|
@ -355,6 +363,7 @@ class Song {
|
||||||
|
|
||||||
// Comparison functions
|
// Comparison functions
|
||||||
bool IsMetadataEqual(const Song &other) const;
|
bool IsMetadataEqual(const Song &other) const;
|
||||||
|
bool IsMetadataAndArtEqual(const Song &other) const;
|
||||||
bool IsOnSameAlbum(const Song &other) const;
|
bool IsOnSameAlbum(const Song &other) const;
|
||||||
bool IsSimilar(const Song &other) const;
|
bool IsSimilar(const Song &other) const;
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,7 @@ void StandardItemIconLoader::AlbumCoverLoaded(const quint64 id, const AlbumCover
|
||||||
QStandardItem *item = pending_covers_.take(id);
|
QStandardItem *item = pending_covers_.take(id);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
if (!result.image_scaled.isNull()) {
|
if (result.success && !result.image_scaled.isNull() && result.type != AlbumCoverLoaderResult::Type_ManuallyUnset) {
|
||||||
item->setIcon(QIcon(QPixmap::fromImage(result.image_scaled)));
|
item->setIcon(QIcon(QPixmap::fromImage(result.image_scaled)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -172,7 +172,24 @@ bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) {
|
QByteArray TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) {
|
||||||
|
|
||||||
|
Q_ASSERT(QThread::currentThread() != thread());
|
||||||
|
|
||||||
|
QByteArray ret;
|
||||||
|
|
||||||
|
TagReaderReply *reply = LoadEmbeddedArt(filename);
|
||||||
|
if (reply->WaitForFinished()) {
|
||||||
|
const std::string &data_str = reply->message().load_embedded_art_response().data();
|
||||||
|
ret = QByteArray(data_str.data(), data_str.size());
|
||||||
|
}
|
||||||
|
metaObject()->invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage TagReaderClient::LoadEmbeddedArtAsImageBlocking(const QString &filename) {
|
||||||
|
|
||||||
Q_ASSERT(QThread::currentThread() != thread());
|
Q_ASSERT(QThread::currentThread() != thread());
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,8 @@ class TagReaderClient : public QObject {
|
||||||
void ReadFileBlocking(const QString &filename, Song *song);
|
void ReadFileBlocking(const QString &filename, Song *song);
|
||||||
bool SaveFileBlocking(const QString &filename, const Song &metadata);
|
bool SaveFileBlocking(const QString &filename, const Song &metadata);
|
||||||
bool IsMediaFileBlocking(const QString &filename);
|
bool IsMediaFileBlocking(const QString &filename);
|
||||||
QImage LoadEmbeddedArtBlocking(const QString &filename);
|
QByteArray LoadEmbeddedArtBlocking(const QString &filename);
|
||||||
|
QImage LoadEmbeddedArtAsImageBlocking(const QString &filename);
|
||||||
bool SaveEmbeddedArtBlocking(const QString &filename, const QByteArray &data);
|
bool SaveEmbeddedArtBlocking(const QString &filename, const QByteArray &data);
|
||||||
|
|
||||||
// TODO: Make this not a singleton
|
// TODO: Make this not a singleton
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
#include <QtEvents>
|
#include <QtEvents>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
#include <QImageReader>
|
#include <QMimeDatabase>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
# include <QRandomGenerator>
|
# include <QRandomGenerator>
|
||||||
|
@ -102,9 +102,6 @@
|
||||||
|
|
||||||
namespace Utilities {
|
namespace Utilities {
|
||||||
|
|
||||||
QStringList kSupportedImageMimeTypes;
|
|
||||||
QStringList kSupportedImageFormats;
|
|
||||||
|
|
||||||
static QString tr(const char *str) {
|
static QString tr(const char *str) {
|
||||||
return QCoreApplication::translate("", str);
|
return QCoreApplication::translate("", str);
|
||||||
}
|
}
|
||||||
|
@ -945,41 +942,23 @@ bool IsColorDark(const QColor &color) {
|
||||||
return ((30 * color.red() + 59 * color.green() + 11 * color.blue()) / 100) <= 130;
|
return ((30 * color.red() + 59 * color.green() + 11 * color.blue()) / 100) <= 130;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList SupportedImageMimeTypes() {
|
QByteArray ReadDataFromFile(const QString &filename) {
|
||||||
|
|
||||||
if (kSupportedImageMimeTypes.isEmpty()) {
|
QFile file(filename);
|
||||||
for (const QByteArray &mimetype : QImageReader::supportedMimeTypes()) {
|
QByteArray data;
|
||||||
kSupportedImageMimeTypes << mimetype;
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
}
|
data = file.readAll();
|
||||||
|
file.close();
|
||||||
}
|
}
|
||||||
|
return data;
|
||||||
return kSupportedImageMimeTypes;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList SupportedImageFormats() {
|
QString MimeTypeFromData(const QByteArray &data) {
|
||||||
|
|
||||||
if (kSupportedImageFormats.isEmpty()) {
|
if (data.isEmpty()) return QString();
|
||||||
for (const QByteArray &filetype : QImageReader::supportedImageFormats()) {
|
|
||||||
kSupportedImageFormats << filetype;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return kSupportedImageFormats;
|
return QMimeDatabase().mimeTypeForData(data).name();
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QByteArray> ImageFormatsForMimeType(const QByteArray &mimetype) {
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
|
||||||
return QImageReader::imageFormatsForMimeType(mimetype);
|
|
||||||
#else
|
|
||||||
if (mimetype == "image/bmp") return QList<QByteArray>() << "BMP";
|
|
||||||
else if (mimetype == "image/gif") return QList<QByteArray>() << "GIF";
|
|
||||||
else if (mimetype == "image/jpeg") return QList<QByteArray>() << "JPG";
|
|
||||||
else if (mimetype == "image/png") return QList<QByteArray>() << "PNG";
|
|
||||||
else return QList<QByteArray>();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -143,9 +143,8 @@ QString ReplaceVariable(const QString &variable, const Song &song, const QString
|
||||||
|
|
||||||
bool IsColorDark(const QColor &color);
|
bool IsColorDark(const QColor &color);
|
||||||
|
|
||||||
QStringList SupportedImageMimeTypes();
|
QByteArray ReadDataFromFile(const QString &filename);
|
||||||
QStringList SupportedImageFormats();
|
QString MimeTypeFromData(const QByteArray &data);
|
||||||
QList<QByteArray> ImageFormatsForMimeType(const QByteArray &mimetype);
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
|
#include <QtConcurrentRun>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QFutureWatcher>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
@ -45,12 +48,16 @@
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
|
#include <QActionGroup>
|
||||||
|
#include <QMenu>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QtEvents>
|
#include <QtEvents>
|
||||||
|
|
||||||
|
#include "core/utilities.h"
|
||||||
|
#include "core/imageutils.h"
|
||||||
|
#include "core/application.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/application.h"
|
|
||||||
|
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "settings/collectionsettingspage.h"
|
#include "settings/collectionsettingspage.h"
|
||||||
|
@ -61,6 +68,7 @@
|
||||||
#include "albumcoverfetcher.h"
|
#include "albumcoverfetcher.h"
|
||||||
#include "albumcoverloader.h"
|
#include "albumcoverloader.h"
|
||||||
#include "albumcoversearcher.h"
|
#include "albumcoversearcher.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
#include "coverfromurldialog.h"
|
#include "coverfromurldialog.h"
|
||||||
#include "currentalbumcoverloader.h"
|
#include "currentalbumcoverloader.h"
|
||||||
|
|
||||||
|
@ -77,11 +85,23 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) :
|
||||||
cover_fetcher_(nullptr),
|
cover_fetcher_(nullptr),
|
||||||
save_file_dialog_(nullptr),
|
save_file_dialog_(nullptr),
|
||||||
cover_from_url_dialog_(nullptr),
|
cover_from_url_dialog_(nullptr),
|
||||||
cover_album_dir_(false),
|
cover_from_file_(nullptr),
|
||||||
cover_filename_(CollectionSettingsPage::SaveCover_Hash),
|
cover_to_file_(nullptr),
|
||||||
|
cover_from_url_(nullptr),
|
||||||
|
search_for_cover_(nullptr),
|
||||||
|
separator1_(nullptr),
|
||||||
|
unset_cover_(nullptr),
|
||||||
|
delete_cover_(nullptr),
|
||||||
|
clear_cover_(nullptr),
|
||||||
|
separator2_(nullptr),
|
||||||
|
show_cover_(nullptr),
|
||||||
|
search_cover_auto_(nullptr),
|
||||||
|
save_cover_type_(CollectionSettingsPage::SaveCoverType_Cache),
|
||||||
|
save_cover_filename_(CollectionSettingsPage::SaveCoverFilename_Hash),
|
||||||
cover_overwrite_(false),
|
cover_overwrite_(false),
|
||||||
cover_lowercase_(true),
|
cover_lowercase_(true),
|
||||||
cover_replace_spaces_(true)
|
cover_replace_spaces_(true),
|
||||||
|
save_embedded_cover_override_(false)
|
||||||
{
|
{
|
||||||
|
|
||||||
cover_from_file_ = new QAction(IconLoader::Load("document-open"), tr("Load cover from disk..."), this);
|
cover_from_file_ = new QAction(IconLoader::Load("document-open"), tr("Load cover from disk..."), this);
|
||||||
|
@ -89,14 +109,18 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) :
|
||||||
cover_from_url_ = new QAction(IconLoader::Load("download"), tr("Load cover from URL..."), this);
|
cover_from_url_ = new QAction(IconLoader::Load("download"), tr("Load cover from URL..."), this);
|
||||||
search_for_cover_ = new QAction(IconLoader::Load("search"), tr("Search for album covers..."), this);
|
search_for_cover_ = new QAction(IconLoader::Load("search"), tr("Search for album covers..."), this);
|
||||||
unset_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Unset cover"), this);
|
unset_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Unset cover"), this);
|
||||||
|
delete_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Delete cover"), this);
|
||||||
|
clear_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Clear cover"), this);
|
||||||
|
separator1_ = new QAction(this);
|
||||||
|
separator1_->setSeparator(true);
|
||||||
show_cover_ = new QAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this);
|
show_cover_ = new QAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this);
|
||||||
|
|
||||||
search_cover_auto_ = new QAction(tr("Search automatically"), this);
|
search_cover_auto_ = new QAction(tr("Search automatically"), this);
|
||||||
search_cover_auto_->setCheckable(true);
|
search_cover_auto_->setCheckable(true);
|
||||||
search_cover_auto_->setChecked(false);
|
search_cover_auto_->setChecked(false);
|
||||||
|
|
||||||
separator_ = new QAction(this);
|
separator2_ = new QAction(this);
|
||||||
separator_->setSeparator(true);
|
separator2_->setSeparator(true);
|
||||||
|
|
||||||
ReloadSettings();
|
ReloadSettings();
|
||||||
|
|
||||||
|
@ -113,6 +137,7 @@ void AlbumCoverChoiceController::Init(Application *app) {
|
||||||
cover_searcher_->Init(cover_fetcher_);
|
cover_searcher_->Init(cover_fetcher_);
|
||||||
|
|
||||||
QObject::connect(cover_fetcher_, &AlbumCoverFetcher::AlbumCoverFetched, this, &AlbumCoverChoiceController::AlbumCoverFetched);
|
QObject::connect(cover_fetcher_, &AlbumCoverFetcher::AlbumCoverFetched, this, &AlbumCoverChoiceController::AlbumCoverFetched);
|
||||||
|
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::SaveEmbeddedCoverAsyncFinished, this, &AlbumCoverChoiceController::SaveEmbeddedCoverAsyncFinished);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,8 +145,8 @@ void AlbumCoverChoiceController::ReloadSettings() {
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
cover_album_dir_ = s.value("cover_album_dir", false).toBool();
|
save_cover_type_ = CollectionSettingsPage::SaveCoverType(s.value("save_cover_type", CollectionSettingsPage::SaveCoverType_Cache).toInt());
|
||||||
cover_filename_ = CollectionSettingsPage::SaveCover(s.value("cover_filename", CollectionSettingsPage::SaveCover_Hash).toInt());
|
save_cover_filename_ = CollectionSettingsPage::SaveCoverFilename(s.value("save_cover_filename", CollectionSettingsPage::SaveCoverFilename_Hash).toInt());
|
||||||
cover_pattern_ = s.value("cover_pattern", "%albumartist-%album").toString();
|
cover_pattern_ = s.value("cover_pattern", "%albumartist-%album").toString();
|
||||||
cover_overwrite_ = s.value("cover_overwrite", false).toBool();
|
cover_overwrite_ = s.value("cover_overwrite", false).toBool();
|
||||||
cover_lowercase_ = s.value("cover_lowercase", false).toBool();
|
cover_lowercase_ = s.value("cover_lowercase", false).toBool();
|
||||||
|
@ -131,30 +156,74 @@ void AlbumCoverChoiceController::ReloadSettings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
|
QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
|
||||||
return QList<QAction*>() << cover_from_file_ << cover_to_file_ << separator_ << cover_from_url_ << search_for_cover_ << unset_cover_ << separator_ << show_cover_;
|
|
||||||
|
return QList<QAction*>() << show_cover_
|
||||||
|
<< cover_to_file_
|
||||||
|
<< separator1_
|
||||||
|
<< cover_from_file_
|
||||||
|
<< cover_from_url_
|
||||||
|
<< search_for_cover_
|
||||||
|
<< separator2_
|
||||||
|
<< unset_cover_
|
||||||
|
<< clear_cover_
|
||||||
|
<< delete_cover_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromFile(Song *song) {
|
||||||
|
|
||||||
|
if (!song->url().isLocalFile()) return AlbumCoverImageResult();
|
||||||
|
|
||||||
|
QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
||||||
|
|
||||||
|
if (cover_file.isEmpty()) return AlbumCoverImageResult();
|
||||||
|
|
||||||
|
AlbumCoverImageResult result;
|
||||||
|
QFile file(cover_file);
|
||||||
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
|
result.image_data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
if (!result.image_data.isEmpty()) {
|
||||||
|
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
|
||||||
|
result.image.loadFromData(result.image_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
|
QUrl AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
|
||||||
|
|
||||||
|
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
|
||||||
|
|
||||||
QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
||||||
|
|
||||||
if (cover_file.isNull()) return QUrl();
|
if (cover_file.isEmpty()) return QUrl();
|
||||||
|
|
||||||
// Can we load the image?
|
if (QImage(cover_file).isNull()) return QUrl();
|
||||||
QImage image(cover_file);
|
|
||||||
|
|
||||||
if (image.isNull()) {
|
switch(get_save_album_cover_type()) {
|
||||||
return QUrl();
|
case CollectionSettingsPage::SaveCoverType_Embedded:
|
||||||
}
|
if (song->save_embedded_cover_supported()) {
|
||||||
else {
|
SaveCoverEmbeddedAutomatic(*song, cover_file);
|
||||||
QUrl cover_url(QUrl::fromLocalFile(cover_file));
|
return QUrl::fromLocalFile(Song::kEmbeddedCover);
|
||||||
SaveCoverToSong(song, cover_url);
|
}
|
||||||
return cover_url;
|
// fallthrough
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Cache:
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Album:{
|
||||||
|
QUrl cover_url = QUrl::fromLocalFile(cover_file);
|
||||||
|
SaveArtManualToSong(song, cover_url);
|
||||||
|
return cover_url;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return QUrl();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverChoiceController::SaveCoverToFileManual(const Song &song, const QImage &image) {
|
void AlbumCoverChoiceController::SaveCoverToFileManual(const Song &song, const AlbumCoverImageResult &result) {
|
||||||
|
|
||||||
QString initial_file_name = "/";
|
QString initial_file_name = "/";
|
||||||
|
|
||||||
|
@ -168,14 +237,29 @@ void AlbumCoverChoiceController::SaveCoverToFileManual(const Song &song, const Q
|
||||||
|
|
||||||
QString save_filename = QFileDialog::getSaveFileName(this, tr("Save album cover"), GetInitialPathForFileDialog(song, initial_file_name), tr(kSaveImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
QString save_filename = QFileDialog::getSaveFileName(this, tr("Save album cover"), GetInitialPathForFileDialog(song, initial_file_name), tr(kSaveImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
||||||
|
|
||||||
if (save_filename.isNull()) return;
|
if (save_filename.isEmpty()) return;
|
||||||
|
|
||||||
QString extension = save_filename.right(4);
|
QFileInfo fileinfo(save_filename);
|
||||||
if (!extension.startsWith('.') || !QImageWriter::supportedImageFormats().contains(extension.right(3).toUtf8())) {
|
if (fileinfo.suffix().isEmpty()) {
|
||||||
save_filename.append(".jpg");
|
save_filename.append(".jpg");
|
||||||
|
fileinfo.setFile(save_filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
image.save(save_filename);
|
if (!QImageWriter::supportedImageFormats().contains(fileinfo.completeSuffix())) {
|
||||||
|
save_filename = Utilities::PathWithoutFilenameExtension(save_filename) + ".jpg";
|
||||||
|
fileinfo.setFile(save_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.is_jpeg() && fileinfo.completeSuffix().toLower() == "jpg") {
|
||||||
|
QFile file(save_filename);
|
||||||
|
if (file.open(QIODevice::WriteOnly)) {
|
||||||
|
file.write(result.image_data);
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.image.save(save_filename);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,67 +287,138 @@ QString AlbumCoverChoiceController::GetInitialPathForFileDialog(const Song &song
|
||||||
|
|
||||||
QUrl AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
|
QUrl AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
|
||||||
|
|
||||||
if (!cover_from_url_dialog_) { cover_from_url_dialog_ = new CoverFromURLDialog(this); }
|
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
|
||||||
|
|
||||||
QImage image = cover_from_url_dialog_->Exec();
|
AlbumCoverImageResult result = LoadImageFromURL();
|
||||||
|
|
||||||
if (image.isNull()) {
|
if (result.image.isNull()) {
|
||||||
return QUrl();
|
return QUrl();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
|
return SaveCoverAutomatic(song, result);
|
||||||
if (cover_url.isEmpty()) return QUrl();
|
|
||||||
SaveCoverToSong(song, cover_url);
|
|
||||||
return cover_url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromURL() {
|
||||||
|
|
||||||
|
if (!cover_from_url_dialog_) { cover_from_url_dialog_ = new CoverFromURLDialog(this); }
|
||||||
|
|
||||||
|
return cover_from_url_dialog_->Exec();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
QUrl AlbumCoverChoiceController::SearchForCover(Song *song) {
|
QUrl AlbumCoverChoiceController::SearchForCover(Song *song) {
|
||||||
|
|
||||||
QString album = song->effective_album();
|
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
|
||||||
album.remove(Song::kAlbumRemoveDisc);
|
|
||||||
album.remove(Song::kAlbumRemoveMisc);
|
|
||||||
|
|
||||||
// Get something sensible to stick in the search box
|
// Get something sensible to stick in the search box
|
||||||
QImage image = cover_searcher_->Exec(song->effective_albumartist(), album);
|
AlbumCoverImageResult result = SearchForImage(song);
|
||||||
|
if (result.is_valid()) {
|
||||||
if (image.isNull()) {
|
return SaveCoverAutomatic(song, result);
|
||||||
return QUrl();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
|
return QUrl();
|
||||||
if (cover_url.isEmpty()) return QUrl();
|
|
||||||
SaveCoverToSong(song, cover_url);
|
|
||||||
return cover_url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AlbumCoverImageResult AlbumCoverChoiceController::SearchForImage(Song *song) {
|
||||||
|
|
||||||
|
if (!song->url().isLocalFile()) return AlbumCoverImageResult();
|
||||||
|
|
||||||
|
QString album = song->effective_album();
|
||||||
|
album = album.remove(Song::kAlbumRemoveDisc).remove(Song::kAlbumRemoveMisc);
|
||||||
|
|
||||||
|
// Get something sensible to stick in the search box
|
||||||
|
return cover_searcher_->Exec(song->effective_albumartist(), album);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
QUrl AlbumCoverChoiceController::UnsetCover(Song *song) {
|
QUrl AlbumCoverChoiceController::UnsetCover(Song *song) {
|
||||||
|
|
||||||
QUrl cover_url(QUrl::fromLocalFile(Song::kManuallyUnsetCover));
|
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
|
||||||
SaveCoverToSong(song, cover_url);
|
|
||||||
|
QUrl cover_url = QUrl::fromLocalFile(Song::kManuallyUnsetCover);
|
||||||
|
SaveArtManualToSong(song, cover_url);
|
||||||
|
|
||||||
return cover_url;
|
return cover_url;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverChoiceController::ShowCover(const Song &song) {
|
void AlbumCoverChoiceController::ClearCover(Song *song) {
|
||||||
|
|
||||||
QPixmap pixmap = AlbumCoverLoader::TryLoadPixmap(song.art_automatic(), song.art_manual(), song.url());
|
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
|
||||||
if (pixmap.isNull()) return;
|
|
||||||
ShowCover(song, pixmap);
|
song->clear_art_manual();
|
||||||
|
SaveArtManualToSong(song, QUrl());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AlbumCoverChoiceController::DeleteCover(Song *song) {
|
||||||
|
|
||||||
|
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return false;
|
||||||
|
|
||||||
|
if (song->has_embedded_cover() && song->save_embedded_cover_supported()) {
|
||||||
|
SaveCoverEmbeddedAutomatic(*song, AlbumCoverImageResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString art_automatic;
|
||||||
|
QString art_manual;
|
||||||
|
if (song->art_automatic().isValid() && song->art_automatic().isLocalFile()) {
|
||||||
|
art_automatic = song->art_automatic().toLocalFile();
|
||||||
|
}
|
||||||
|
if (song->art_manual().isValid() && song->art_manual().isLocalFile()) {
|
||||||
|
art_manual = song->art_manual().toLocalFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
if (!art_automatic.isEmpty()) {
|
||||||
|
if (QFile::exists(art_automatic)) {
|
||||||
|
if (QFile::remove(art_automatic)) {
|
||||||
|
song->clear_art_automatic();
|
||||||
|
if (art_automatic == art_manual) song->clear_art_manual();
|
||||||
|
}
|
||||||
|
else success = false;
|
||||||
|
}
|
||||||
|
else song->clear_art_automatic();
|
||||||
|
}
|
||||||
|
else song->clear_art_automatic();
|
||||||
|
|
||||||
|
if (!art_manual.isEmpty()) {
|
||||||
|
if (QFile::exists(art_manual)) {
|
||||||
|
if (QFile::remove(art_manual)) {
|
||||||
|
song->clear_art_manual();
|
||||||
|
if (art_automatic == art_manual) song->clear_art_automatic();
|
||||||
|
}
|
||||||
|
else success = false;
|
||||||
|
}
|
||||||
|
else song->clear_art_manual();
|
||||||
|
}
|
||||||
|
else song->clear_art_manual();
|
||||||
|
|
||||||
|
if (success) UnsetCover(song);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverChoiceController::ShowCover(const Song &song, const QImage &image) {
|
void AlbumCoverChoiceController::ShowCover(const Song &song, const QImage &image) {
|
||||||
|
|
||||||
if (song.art_manual().isLocalFile() || song.art_automatic().isLocalFile()) {
|
if (image.isNull()) {
|
||||||
QPixmap pixmap = AlbumCoverLoader::TryLoadPixmap(song.art_automatic(), song.art_manual(), song.url());
|
if ((song.art_manual().isValid() && song.art_manual().isLocalFile() && QFile::exists(song.art_manual().toLocalFile())) ||
|
||||||
|
(song.art_automatic().isValid() && song.art_automatic().isLocalFile() && QFile::exists(song.art_automatic().toLocalFile())) ||
|
||||||
|
song.has_embedded_cover()
|
||||||
|
) {
|
||||||
|
QPixmap pixmap = ImageUtils::TryLoadPixmap(song.art_automatic(), song.art_manual(), song.url());
|
||||||
|
if (!pixmap.isNull()) ShowCover(song, pixmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
QPixmap pixmap = QPixmap::fromImage(image);
|
||||||
if (!pixmap.isNull()) ShowCover(song, pixmap);
|
if (!pixmap.isNull()) ShowCover(song, pixmap);
|
||||||
}
|
}
|
||||||
else if (!image.isNull()) ShowCover(song, QPixmap::fromImage(image));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,7 +481,7 @@ qint64 AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
|
void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics) {
|
||||||
|
|
||||||
Q_UNUSED(statistics);
|
Q_UNUSED(statistics);
|
||||||
|
|
||||||
|
@ -335,46 +490,22 @@ void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl
|
||||||
song = cover_fetching_tasks_.take(id);
|
song = cover_fetching_tasks_.take(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!image.isNull()) {
|
if (result.is_valid()) {
|
||||||
QUrl new_cover_url = SaveCoverToFileAutomatic(&song, cover_url, image, false);
|
SaveCoverAutomatic(&song, result);
|
||||||
if (!new_cover_url.isEmpty()) SaveCoverToSong(&song, new_cover_url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emit AutomaticCoverSearchDone();
|
emit AutomaticCoverSearchDone();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverChoiceController::SaveCoverToSong(Song *song, const QUrl &cover_url) {
|
void AlbumCoverChoiceController::SaveArtAutomaticToSong(Song *song, const QUrl &art_automatic) {
|
||||||
|
|
||||||
if (!song->is_valid()) return;
|
if (!song->is_valid()) return;
|
||||||
|
|
||||||
song->set_art_manual(cover_url);
|
song->set_art_automatic(art_automatic);
|
||||||
|
|
||||||
if (song->id() != -1) { // Update the backends.
|
|
||||||
switch (song->source()) {
|
|
||||||
case Song::Source_Collection:
|
|
||||||
app_->collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
|
|
||||||
break;
|
|
||||||
case Song::Source_LocalFile:
|
|
||||||
case Song::Source_CDDA:
|
|
||||||
case Song::Source_Device:
|
|
||||||
case Song::Source_Stream:
|
|
||||||
case Song::Source_Unknown:
|
|
||||||
break;
|
|
||||||
case Song::Source_Tidal:
|
|
||||||
case Song::Source_Qobuz:
|
|
||||||
case Song::Source_Subsonic:
|
|
||||||
InternetService *service = app_->internet_services()->ServiceBySource(song->source());
|
|
||||||
if (!service) break;
|
|
||||||
if (service->artists_collection_backend())
|
|
||||||
service->artists_collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
|
|
||||||
if (service->albums_collection_backend())
|
|
||||||
service->albums_collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
|
|
||||||
if (service->songs_collection_backend())
|
|
||||||
service->songs_collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (song->source() == Song::Source_Collection) {
|
||||||
|
app_->collection_backend()->UpdateAutomaticAlbumArtAsync(song->effective_albumartist(), song->album(), art_automatic);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*song == app_->current_albumcover_loader()->last_song()) {
|
if (*song == app_->current_albumcover_loader()->last_song()) {
|
||||||
|
@ -383,27 +514,168 @@ void AlbumCoverChoiceController::SaveCoverToSong(Song *song, const QUrl &cover_u
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const QUrl &cover_url, const QImage &image, const bool overwrite) {
|
void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art_manual, const bool clear_art_automatic) {
|
||||||
|
|
||||||
return SaveCoverToFileAutomatic(song->source(), song->effective_albumartist(), song->effective_album(), song->album_id(), song->url().adjusted(QUrl::RemoveFilename).path(), cover_url, image, overwrite);
|
if (!song->is_valid()) return;
|
||||||
|
|
||||||
|
song->set_art_manual(art_manual);
|
||||||
|
if (clear_art_automatic) song->clear_art_automatic();
|
||||||
|
|
||||||
|
// Update the backends.
|
||||||
|
switch (song->source()) {
|
||||||
|
case Song::Source_Collection:
|
||||||
|
app_->collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
|
||||||
|
break;
|
||||||
|
case Song::Source_LocalFile:
|
||||||
|
case Song::Source_CDDA:
|
||||||
|
case Song::Source_Device:
|
||||||
|
case Song::Source_Stream:
|
||||||
|
case Song::Source_Unknown:
|
||||||
|
break;
|
||||||
|
case Song::Source_Tidal:
|
||||||
|
case Song::Source_Qobuz:
|
||||||
|
case Song::Source_Subsonic:
|
||||||
|
InternetService *service = app_->internet_services()->ServiceBySource(song->source());
|
||||||
|
if (!service) break;
|
||||||
|
if (service->artists_collection_backend()) {
|
||||||
|
service->artists_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
|
||||||
|
}
|
||||||
|
if (service->albums_collection_backend()) {
|
||||||
|
service->albums_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
|
||||||
|
}
|
||||||
|
if (service->songs_collection_backend()) {
|
||||||
|
service->songs_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*song == app_->current_albumcover_loader()->last_song()) {
|
||||||
|
app_->current_albumcover_loader()->LoadAlbumCover(*song);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QImage &image, const bool overwrite) {
|
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const AlbumCoverImageResult &result, const bool force_overwrite) {
|
||||||
|
|
||||||
QString filepath = app_->album_cover_loader()->CoverFilePath(source, artist, album, album_id, album_dir, cover_url, "jpg");
|
return SaveCoverToFileAutomatic(song->source(),
|
||||||
|
song->effective_albumartist(),
|
||||||
|
song->effective_album(),
|
||||||
|
song->album_id(),
|
||||||
|
song->url().adjusted(QUrl::RemoveFilename).path(),
|
||||||
|
result,
|
||||||
|
force_overwrite);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source source,
|
||||||
|
const QString &artist,
|
||||||
|
const QString &album,
|
||||||
|
const QString &album_id,
|
||||||
|
const QString &album_dir,
|
||||||
|
const AlbumCoverImageResult &result,
|
||||||
|
const bool force_overwrite) {
|
||||||
|
|
||||||
|
QString filepath = app_->album_cover_loader()->CoverFilePath(source, artist, album, album_id, album_dir, result.cover_url, "jpg");
|
||||||
if (filepath.isEmpty()) return QUrl();
|
if (filepath.isEmpty()) return QUrl();
|
||||||
|
|
||||||
QUrl new_cover_url(QUrl::fromLocalFile(filepath));
|
QFile file(filepath);
|
||||||
|
// Don't overwrite when saving in album dir if the filename is set to pattern unless "force_overwrite" is set.
|
||||||
// Don't overwrite when saving in album dir if the filename is set to pattern unless the "overwrite" is set.
|
if (source == Song::Source_Collection && !cover_overwrite_ && !force_overwrite && get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType_Album && save_cover_filename_ == CollectionSettingsPage::SaveCoverFilename_Pattern && file.exists()) {
|
||||||
if (source == Song::Source_Collection && QFile::exists(filepath) && !cover_overwrite_ && !overwrite && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern) {
|
while (file.exists()) {
|
||||||
return new_cover_url;
|
QFileInfo fileinfo(file.fileName());
|
||||||
|
file.setFileName(fileinfo.path() + "/0" + fileinfo.fileName());
|
||||||
|
}
|
||||||
|
filepath = file.fileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!image.save(filepath, "JPG") && !QFile::exists(filepath)) return QUrl();
|
QUrl cover_url;
|
||||||
|
if (result.is_jpeg()) {
|
||||||
|
if (file.open(QIODevice::WriteOnly)) {
|
||||||
|
if (file.write(result.image_data)) cover_url = QUrl::fromLocalFile(filepath);
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (result.image.save(filepath, "JPG")) cover_url = QUrl::fromLocalFile(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
return new_cover_url;
|
return cover_url;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const AlbumCoverImageResult &result) {
|
||||||
|
|
||||||
|
if (song.source() == Song::Source_Collection) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
QFuture<SongList> future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), song.effective_albumartist(), song.effective_album(), QueryOptions());
|
||||||
|
#else
|
||||||
|
QFuture<SongList> future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, song.effective_albumartist(), song.effective_album(), QueryOptions());
|
||||||
|
#endif
|
||||||
|
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
|
||||||
|
watcher->setFuture(future);
|
||||||
|
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [=]() {
|
||||||
|
SongList songs = watcher->result();
|
||||||
|
watcher->deleteLater();
|
||||||
|
QList<QUrl> urls;
|
||||||
|
for (const Song &s : songs) urls << s.url();
|
||||||
|
if (result.is_jpeg()) {
|
||||||
|
qint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image_data);
|
||||||
|
QMutexLocker l(&mutex_cover_save_tasks_);
|
||||||
|
cover_save_tasks_.insert(id, song);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
|
||||||
|
QMutexLocker l(&mutex_cover_save_tasks_);
|
||||||
|
cover_save_tasks_.insert(id, song);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (result.is_jpeg()) {
|
||||||
|
app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), result.image_data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), result.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const QUrl &cover_url) {
|
||||||
|
|
||||||
|
SaveCoverEmbeddedAutomatic(song, cover_url.toLocalFile());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const QString &cover_filename) {
|
||||||
|
|
||||||
|
if (song.source() == Song::Source_Collection) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
QFuture<SongList> future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), song.effective_albumartist(), song.effective_album(), QueryOptions());
|
||||||
|
#else
|
||||||
|
QFuture<SongList> future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, song->effective_albumartist(), song->effective_album(), QueryOptions());
|
||||||
|
#endif
|
||||||
|
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
|
||||||
|
watcher->setFuture(future);
|
||||||
|
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [=]() {
|
||||||
|
SongList songs = watcher->result();
|
||||||
|
watcher->deleteLater();
|
||||||
|
QList<QUrl> urls;
|
||||||
|
for (const Song &s : songs) urls << s.url();
|
||||||
|
qint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, cover_filename);
|
||||||
|
QMutexLocker l(&mutex_cover_save_tasks_);
|
||||||
|
cover_save_tasks_.insert(id, song);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), cover_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const QList<QUrl> urls, const QImage &image) {
|
||||||
|
|
||||||
|
app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, image);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,7 +708,13 @@ QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
|
||||||
const QString suffix = QFileInfo(filename).suffix().toLower();
|
const QString suffix = QFileInfo(filename).suffix().toLower();
|
||||||
|
|
||||||
if (IsKnownImageExtension(suffix)) {
|
if (IsKnownImageExtension(suffix)) {
|
||||||
SaveCoverToSong(song, url);
|
if (get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType_Embedded && song->save_embedded_cover_supported()) {
|
||||||
|
SaveCoverEmbeddedAutomatic(*song, filename);
|
||||||
|
return QUrl::fromLocalFile(Song::kEmbeddedCover);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SaveArtManualToSong(song, url);
|
||||||
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,13 +722,43 @@ QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
|
||||||
if (e->mimeData()->hasImage()) {
|
if (e->mimeData()->hasImage()) {
|
||||||
QImage image = qvariant_cast<QImage>(e->mimeData()->imageData());
|
QImage image = qvariant_cast<QImage>(e->mimeData()->imageData());
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
|
return SaveCoverAutomatic(song, AlbumCoverImageResult(image));
|
||||||
if (cover_url.isEmpty()) return QUrl();
|
|
||||||
SaveCoverToSong(song, cover_url);
|
|
||||||
return cover_url;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QUrl();
|
return QUrl();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCoverImageResult &result) {
|
||||||
|
|
||||||
|
QUrl cover_url;
|
||||||
|
switch(get_save_album_cover_type()) {
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Embedded:{
|
||||||
|
if (song->save_embedded_cover_supported()) {
|
||||||
|
SaveCoverEmbeddedAutomatic(*song, result);
|
||||||
|
cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Cache:
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Album:{
|
||||||
|
cover_url = SaveCoverToFileAutomatic(song, result);
|
||||||
|
if (!cover_url.isEmpty()) SaveArtManualToSong(song, cover_url);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cover_url;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverChoiceController::SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success) {
|
||||||
|
|
||||||
|
if (!cover_save_tasks_.contains(id)) return;
|
||||||
|
|
||||||
|
Song song = cover_save_tasks_.take(id);
|
||||||
|
if (success) SaveArtAutomaticToSong(&song, QUrl::fromLocalFile(Song::kEmbeddedCover));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -27,18 +27,24 @@
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <QPair>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "settings/collectionsettingspage.h"
|
#include "settings/collectionsettingspage.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
|
|
||||||
class QFileDialog;
|
class QFileDialog;
|
||||||
class QAction;
|
class QAction;
|
||||||
|
class QActionGroup;
|
||||||
|
class QMenu;
|
||||||
class QDragEnterEvent;
|
class QDragEnterEvent;
|
||||||
class QDropEvent;
|
class QDropEvent;
|
||||||
|
|
||||||
|
@ -63,6 +69,9 @@ class AlbumCoverChoiceController : public QWidget {
|
||||||
void Init(Application *app);
|
void Init(Application *app);
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
|
CollectionSettingsPage::SaveCoverType get_save_album_cover_type() const { return (save_embedded_cover_override_ ? CollectionSettingsPage::SaveCoverType_Embedded : save_cover_type_); }
|
||||||
|
void set_save_embedded_cover_override(const bool value) { save_embedded_cover_override_ = value; }
|
||||||
|
|
||||||
// Getters for all QActions implemented by this controller.
|
// Getters for all QActions implemented by this controller.
|
||||||
|
|
||||||
QAction *cover_from_file_action() const { return cover_from_file_; }
|
QAction *cover_from_file_action() const { return cover_from_file_; }
|
||||||
|
@ -70,6 +79,8 @@ class AlbumCoverChoiceController : public QWidget {
|
||||||
QAction *cover_from_url_action() const { return cover_from_url_; }
|
QAction *cover_from_url_action() const { return cover_from_url_; }
|
||||||
QAction *search_for_cover_action() const { return search_for_cover_; }
|
QAction *search_for_cover_action() const { return search_for_cover_; }
|
||||||
QAction *unset_cover_action() const { return unset_cover_; }
|
QAction *unset_cover_action() const { return unset_cover_; }
|
||||||
|
QAction *delete_cover_action() const { return delete_cover_; }
|
||||||
|
QAction *clear_cover_action() const { return clear_cover_; }
|
||||||
QAction *show_cover_action() const { return show_cover_; }
|
QAction *show_cover_action() const { return show_cover_; }
|
||||||
QAction *search_cover_auto_action() const { return search_cover_auto_; }
|
QAction *search_cover_auto_action() const { return search_cover_auto_; }
|
||||||
|
|
||||||
|
@ -87,40 +98,54 @@ class AlbumCoverChoiceController : public QWidget {
|
||||||
|
|
||||||
// Lets the user choose a cover from disk. If no cover will be chosen or the chosen cover will not be a proper image, this returns an empty string.
|
// Lets the user choose a cover from disk. If no cover will be chosen or the chosen cover will not be a proper image, this returns an empty string.
|
||||||
// Otherwise, the path to the chosen cover will be returned.
|
// Otherwise, the path to the chosen cover will be returned.
|
||||||
|
AlbumCoverImageResult LoadImageFromFile(Song *song);
|
||||||
QUrl LoadCoverFromFile(Song *song);
|
QUrl LoadCoverFromFile(Song *song);
|
||||||
|
|
||||||
// Shows a dialog that allows user to save the given image on disk.
|
// Shows a dialog that allows user to save the given image on disk.
|
||||||
// The image is supposed to be the cover of the given song's album.
|
// The image is supposed to be the cover of the given song's album.
|
||||||
void SaveCoverToFileManual(const Song &song, const QImage &image);
|
void SaveCoverToFileManual(const Song &song, const AlbumCoverImageResult &result);
|
||||||
|
|
||||||
// Downloads the cover from an URL given by user.
|
// Downloads the cover from an URL given by user.
|
||||||
// This returns the downloaded image or null image if something went wrong for example when user cancelled the dialog.
|
// This returns the downloaded image or null image if something went wrong for example when user cancelled the dialog.
|
||||||
QUrl LoadCoverFromURL(Song *song);
|
QUrl LoadCoverFromURL(Song *song);
|
||||||
|
AlbumCoverImageResult LoadImageFromURL();
|
||||||
|
|
||||||
// Lets the user choose a cover among all that have been found on last.fm.
|
// Lets the user choose a cover among all that have been found on last.fm.
|
||||||
// Returns the chosen cover or null cover if user didn't choose anything.
|
// Returns the chosen cover or null cover if user didn't choose anything.
|
||||||
QUrl SearchForCover(Song *song);
|
QUrl SearchForCover(Song *song);
|
||||||
|
AlbumCoverImageResult SearchForImage(Song *song);
|
||||||
|
|
||||||
// Returns a path which indicates that the cover has been unset manually.
|
// Returns a path which indicates that the cover has been unset manually.
|
||||||
QUrl UnsetCover(Song *song);
|
QUrl UnsetCover(Song *song);
|
||||||
|
|
||||||
|
// Clears any album cover art associated with the song.
|
||||||
|
void ClearCover(Song *song);
|
||||||
|
|
||||||
|
// Physically deletes associated album covers from disk.
|
||||||
|
bool DeleteCover(Song *song);
|
||||||
|
|
||||||
// Shows the cover of given song in it's original size.
|
// Shows the cover of given song in it's original size.
|
||||||
void ShowCover(const Song &song);
|
void ShowCover(const Song &song, const QImage &image = QImage());
|
||||||
void ShowCover(const Song &song, const QImage &image);
|
|
||||||
void ShowCover(const Song &song, const QPixmap &pixmap);
|
void ShowCover(const Song &song, const QPixmap &pixmap);
|
||||||
|
|
||||||
// Search for covers automatically
|
// Search for covers automatically
|
||||||
qint64 SearchCoverAutomatically(const Song &song);
|
qint64 SearchCoverAutomatically(const Song &song);
|
||||||
|
|
||||||
// Saves the chosen cover as manual cover path of this song in collection.
|
// Saves the chosen cover as manual cover path of this song in collection.
|
||||||
void SaveCoverToSong(Song *song, const QUrl &cover_url);
|
void SaveArtAutomaticToSong(Song *song, const QUrl &art_automatic);
|
||||||
|
void SaveArtManualToSong(Song *song, const QUrl &art_manual, const bool clear_art_automatic = false);
|
||||||
|
|
||||||
// Saves the cover that the user picked through a drag and drop operation.
|
// Saves the cover that the user picked through a drag and drop operation.
|
||||||
QUrl SaveCover(Song *song, const QDropEvent *e);
|
QUrl SaveCover(Song *song, const QDropEvent *e);
|
||||||
|
|
||||||
// Saves the given image in album directory or cache as a cover for 'album artist' - 'album'. The method returns path of the image.
|
// Saves the given image in album directory or cache as a cover for 'album artist' - 'album'. The method returns path of the image.
|
||||||
QUrl SaveCoverToFileAutomatic(const Song *song, const QUrl &cover_url, const QImage &image, const bool overwrite = false);
|
QUrl SaveCoverAutomatic(Song *song, const AlbumCoverImageResult &result);
|
||||||
QUrl SaveCoverToFileAutomatic(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QImage &image, const bool overwrite = false);
|
QUrl SaveCoverToFileAutomatic(const Song *song, const AlbumCoverImageResult &result, const bool force_overwrite = false);
|
||||||
|
QUrl SaveCoverToFileAutomatic(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const AlbumCoverImageResult &result, const bool force_overwrite = false);
|
||||||
|
void SaveCoverEmbeddedAutomatic(const Song &song, const AlbumCoverImageResult &result);
|
||||||
|
void SaveCoverEmbeddedAutomatic(const Song &song, const QUrl &cover_url);
|
||||||
|
void SaveCoverEmbeddedAutomatic(const Song &song, const QString &cover_filename);
|
||||||
|
void SaveCoverEmbeddedAutomatic(const QList<QUrl> urls, const QImage &image);
|
||||||
|
|
||||||
static bool CanAcceptDrag(const QDragEnterEvent *e);
|
static bool CanAcceptDrag(const QDragEnterEvent *e);
|
||||||
|
|
||||||
|
@ -128,9 +153,11 @@ class AlbumCoverChoiceController : public QWidget {
|
||||||
void AutomaticCoverSearchDone();
|
void AutomaticCoverSearchDone();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics);
|
void AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics);
|
||||||
|
void SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
QString GetInitialPathForFileDialog(const Song &song, const QString &filename);
|
QString GetInitialPathForFileDialog(const Song &song, const QString &filename);
|
||||||
|
|
||||||
static bool IsKnownImageExtension(const QString &suffix);
|
static bool IsKnownImageExtension(const QString &suffix);
|
||||||
|
@ -145,21 +172,27 @@ class AlbumCoverChoiceController : public QWidget {
|
||||||
|
|
||||||
QAction *cover_from_file_;
|
QAction *cover_from_file_;
|
||||||
QAction *cover_to_file_;
|
QAction *cover_to_file_;
|
||||||
QAction *separator_;
|
|
||||||
QAction *cover_from_url_;
|
QAction *cover_from_url_;
|
||||||
QAction *search_for_cover_;
|
QAction *search_for_cover_;
|
||||||
|
QAction *separator1_;
|
||||||
QAction *unset_cover_;
|
QAction *unset_cover_;
|
||||||
|
QAction *delete_cover_;
|
||||||
|
QAction *clear_cover_;
|
||||||
|
QAction *separator2_;
|
||||||
QAction *show_cover_;
|
QAction *show_cover_;
|
||||||
QAction *search_cover_auto_;
|
QAction *search_cover_auto_;
|
||||||
|
|
||||||
QMap<quint64, Song> cover_fetching_tasks_;
|
QMap<quint64, Song> cover_fetching_tasks_;
|
||||||
|
QMap<qint64, Song> cover_save_tasks_;
|
||||||
|
QMutex mutex_cover_save_tasks_;
|
||||||
|
|
||||||
bool cover_album_dir_;
|
CollectionSettingsPage::SaveCoverType save_cover_type_;
|
||||||
CollectionSettingsPage::SaveCover cover_filename_;
|
CollectionSettingsPage::SaveCoverFilename save_cover_filename_;
|
||||||
QString cover_pattern_;
|
QString cover_pattern_;
|
||||||
bool cover_overwrite_;
|
bool cover_overwrite_;
|
||||||
bool cover_lowercase_;
|
bool cover_lowercase_;
|
||||||
bool cover_replace_spaces_;
|
bool cover_replace_spaces_;
|
||||||
|
bool save_embedded_cover_override_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ AlbumCoverFetcher::~AlbumCoverFetcher() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, const QString &title, bool fetchall) {
|
quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, const QString &title, const bool batch) {
|
||||||
|
|
||||||
CoverSearchRequest request;
|
CoverSearchRequest request;
|
||||||
request.id = next_id_++;
|
request.id = next_id_++;
|
||||||
|
@ -66,7 +66,7 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString
|
||||||
request.album = request.album.remove(Song::kAlbumRemoveMisc);
|
request.album = request.album.remove(Song::kAlbumRemoveMisc);
|
||||||
request.title = title;
|
request.title = title;
|
||||||
request.search = false;
|
request.search = false;
|
||||||
request.fetchall = fetchall;
|
request.batch = batch;
|
||||||
|
|
||||||
AddRequest(request);
|
AddRequest(request);
|
||||||
return request.id;
|
return request.id;
|
||||||
|
@ -83,7 +83,7 @@ quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString
|
||||||
request.album = request.album.remove(Song::kAlbumRemoveMisc);
|
request.album = request.album.remove(Song::kAlbumRemoveMisc);
|
||||||
request.title = title;
|
request.title = title;
|
||||||
request.search = true;
|
request.search = true;
|
||||||
request.fetchall = false;
|
request.batch = false;
|
||||||
|
|
||||||
AddRequest(request);
|
AddRequest(request);
|
||||||
return request.id;
|
return request.id;
|
||||||
|
@ -135,7 +135,7 @@ void AlbumCoverFetcher::StartRequests() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverFetcher::SingleSearchFinished(const quint64 request_id, const CoverSearchResults results) {
|
void AlbumCoverFetcher::SingleSearchFinished(const quint64 request_id, const CoverProviderSearchResults &results) {
|
||||||
|
|
||||||
if (!active_requests_.contains(request_id)) return;
|
if (!active_requests_.contains(request_id)) return;
|
||||||
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
||||||
|
@ -145,12 +145,12 @@ void AlbumCoverFetcher::SingleSearchFinished(const quint64 request_id, const Cov
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverFetcher::SingleCoverFetched(const quint64 request_id, const QUrl &cover_url, const QImage &image) {
|
void AlbumCoverFetcher::SingleCoverFetched(const quint64 request_id, const AlbumCoverImageResult &result) {
|
||||||
|
|
||||||
if (!active_requests_.contains(request_id)) return;
|
if (!active_requests_.contains(request_id)) return;
|
||||||
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
||||||
|
|
||||||
search->deleteLater();
|
search->deleteLater();
|
||||||
emit AlbumCoverFetched(request_id, cover_url, image, search->statistics());
|
emit AlbumCoverFetched(request_id, result, search->statistics());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,13 @@
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QQueue>
|
#include <QQueue>
|
||||||
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
|
||||||
#include "coversearchstatistics.h"
|
#include "coversearchstatistics.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
|
|
||||||
class QTimer;
|
class QTimer;
|
||||||
class NetworkAccessManager;
|
class NetworkAccessManager;
|
||||||
|
@ -44,7 +46,7 @@ class AlbumCoverFetcherSearch;
|
||||||
|
|
||||||
// This class represents a single search-for-cover request. It identifies and describes the request.
|
// This class represents a single search-for-cover request. It identifies and describes the request.
|
||||||
struct CoverSearchRequest {
|
struct CoverSearchRequest {
|
||||||
explicit CoverSearchRequest() : id(-1), search(false), fetchall(false) {}
|
explicit CoverSearchRequest() : id(-1), search(false), batch(false) {}
|
||||||
|
|
||||||
// An unique (for one AlbumCoverFetcher) request identifier
|
// An unique (for one AlbumCoverFetcher) request identifier
|
||||||
quint64 id;
|
quint64 id;
|
||||||
|
@ -57,13 +59,13 @@ struct CoverSearchRequest {
|
||||||
// Is this only a search request or should we also fetch the first cover that's found?
|
// Is this only a search request or should we also fetch the first cover that's found?
|
||||||
bool search;
|
bool search;
|
||||||
|
|
||||||
// Is the request part of fetchall (fetching all missing covers)
|
// Is the request part of a batch (fetching all missing covers)
|
||||||
bool fetchall;
|
bool batch;
|
||||||
};
|
};
|
||||||
|
|
||||||
// This structure represents a single result of some album's cover search request.
|
// This structure represents a single result of some album's cover search request.
|
||||||
struct CoverSearchResult {
|
struct CoverProviderSearchResult {
|
||||||
explicit CoverSearchResult() : score_provider(0.0), score_match(0.0), score_quality(0.0), number(0) {}
|
explicit CoverProviderSearchResult() : score_provider(0.0), score_match(0.0), score_quality(0.0), number(0) {}
|
||||||
|
|
||||||
// Used for grouping in the user interface.
|
// Used for grouping in the user interface.
|
||||||
QString provider;
|
QString provider;
|
||||||
|
@ -94,11 +96,11 @@ struct CoverSearchResult {
|
||||||
float score() const { return score_provider + score_match + score_quality; }
|
float score() const { return score_provider + score_match + score_quality; }
|
||||||
|
|
||||||
};
|
};
|
||||||
Q_DECLARE_METATYPE(CoverSearchResult)
|
Q_DECLARE_METATYPE(CoverProviderSearchResult)
|
||||||
|
|
||||||
// This is a complete result of a single search request (a list of results, each describing one image, actually).
|
// This is a complete result of a single search request (a list of results, each describing one image, actually).
|
||||||
typedef QList<CoverSearchResult> CoverSearchResults;
|
typedef QList<CoverProviderSearchResult> CoverProviderSearchResults;
|
||||||
Q_DECLARE_METATYPE(QList<CoverSearchResult>)
|
Q_DECLARE_METATYPE(QList<CoverProviderSearchResult>)
|
||||||
|
|
||||||
// This class searches for album covers for a given query or artist/album and returns URLs. It's NOT thread-safe.
|
// This class searches for album covers for a given query or artist/album and returns URLs. It's NOT thread-safe.
|
||||||
class AlbumCoverFetcher : public QObject {
|
class AlbumCoverFetcher : public QObject {
|
||||||
|
@ -111,17 +113,17 @@ class AlbumCoverFetcher : public QObject {
|
||||||
static const int kMaxConcurrentRequests;
|
static const int kMaxConcurrentRequests;
|
||||||
|
|
||||||
quint64 SearchForCovers(const QString &artist, const QString &album, const QString &title = QString());
|
quint64 SearchForCovers(const QString &artist, const QString &album, const QString &title = QString());
|
||||||
quint64 FetchAlbumCover(const QString &artist, const QString &album, const QString &title, const bool fetchall);
|
quint64 FetchAlbumCover(const QString &artist, const QString &album, const QString &title, const bool batch);
|
||||||
|
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void AlbumCoverFetched(quint64 request_id, QUrl cover_url, QImage cover, CoverSearchStatistics statistics);
|
void AlbumCoverFetched(quint64 request_id, AlbumCoverImageResult result, CoverSearchStatistics statistics);
|
||||||
void SearchFinished(quint64 request_id, CoverSearchResults results, CoverSearchStatistics statistics);
|
void SearchFinished(quint64 request_id, CoverProviderSearchResults results, CoverSearchStatistics statistics);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void SingleSearchFinished(const quint64, const CoverSearchResults results);
|
void SingleSearchFinished(const quint64, const CoverProviderSearchResults &results);
|
||||||
void SingleCoverFetched(const quint64, const QUrl &cover_url, const QImage &image);
|
void SingleCoverFetched(const quint64, const AlbumCoverImageResult &result);
|
||||||
void StartRequests();
|
void StartRequests();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -40,12 +40,14 @@
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/utilities.h"
|
#include "core/utilities.h"
|
||||||
|
#include "core/imageutils.h"
|
||||||
#include "core/networkaccessmanager.h"
|
#include "core/networkaccessmanager.h"
|
||||||
#include "core/networktimeouts.h"
|
#include "core/networktimeouts.h"
|
||||||
#include "albumcoverfetcher.h"
|
#include "albumcoverfetcher.h"
|
||||||
#include "albumcoverfetchersearch.h"
|
#include "albumcoverfetchersearch.h"
|
||||||
#include "coverprovider.h"
|
#include "coverprovider.h"
|
||||||
#include "coverproviders.h"
|
#include "coverproviders.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
|
|
||||||
const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 20000;
|
const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 20000;
|
||||||
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 6000;
|
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 6000;
|
||||||
|
@ -99,8 +101,8 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip provider if it does not have fetchall set and we are doing fetchall - "Fetch Missing Covers".
|
// Skip provider if it does not have batch set and we are doing a batch - "Fetch Missing Covers".
|
||||||
if (!provider->fetchall() && request_.fetchall) {
|
if (!provider->batch() && request_.batch) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +111,7 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject::connect(provider, &CoverProvider::SearchResults, this, QOverload<const int, const CoverSearchResults&>::of(&AlbumCoverFetcherSearch::ProviderSearchResults));
|
QObject::connect(provider, &CoverProvider::SearchResults, this, QOverload<const int, const CoverProviderSearchResults&>::of(&AlbumCoverFetcherSearch::ProviderSearchResults));
|
||||||
QObject::connect(provider, &CoverProvider::SearchFinished, this, &AlbumCoverFetcherSearch::ProviderSearchFinished);
|
QObject::connect(provider, &CoverProvider::SearchFinished, this, &AlbumCoverFetcherSearch::ProviderSearchFinished);
|
||||||
const int id = cover_providers->NextId();
|
const int id = cover_providers->NextId();
|
||||||
const bool success = provider->StartSearch(request_.artist, request_.album, request_.title, id);
|
const bool success = provider->StartSearch(request_.artist, request_.album, request_.title, id);
|
||||||
|
@ -127,7 +129,7 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverFetcherSearch::ProviderSearchResults(const int id, const CoverSearchResults &results) {
|
void AlbumCoverFetcherSearch::ProviderSearchResults(const int id, const CoverProviderSearchResults &results) {
|
||||||
|
|
||||||
if (!pending_requests_.contains(id)) return;
|
if (!pending_requests_.contains(id)) return;
|
||||||
CoverProvider *provider = pending_requests_[id];
|
CoverProvider *provider = pending_requests_[id];
|
||||||
|
@ -135,9 +137,9 @@ void AlbumCoverFetcherSearch::ProviderSearchResults(const int id, const CoverSea
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverFetcherSearch::ProviderSearchResults(CoverProvider *provider, const CoverSearchResults &results) {
|
void AlbumCoverFetcherSearch::ProviderSearchResults(CoverProvider *provider, const CoverProviderSearchResults &results) {
|
||||||
|
|
||||||
CoverSearchResults results_copy(results);
|
CoverProviderSearchResults results_copy(results);
|
||||||
for (int i = 0 ; i < results_copy.count() ; ++i) {
|
for (int i = 0 ; i < results_copy.count() ; ++i) {
|
||||||
|
|
||||||
results_copy[i].provider = provider->name();
|
results_copy[i].provider = provider->name();
|
||||||
|
@ -225,7 +227,7 @@ void AlbumCoverFetcherSearch::ProviderSearchResults(CoverProvider *provider, con
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverFetcherSearch::ProviderSearchFinished(const int id, const CoverSearchResults &results) {
|
void AlbumCoverFetcherSearch::ProviderSearchFinished(const int id, const CoverProviderSearchResults &results) {
|
||||||
|
|
||||||
if (!pending_requests_.contains(id)) return;
|
if (!pending_requests_.contains(id)) return;
|
||||||
|
|
||||||
|
@ -256,7 +258,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
|
||||||
// No results?
|
// No results?
|
||||||
if (results_.isEmpty()) {
|
if (results_.isEmpty()) {
|
||||||
statistics_.missing_images_++;
|
statistics_.missing_images_++;
|
||||||
emit AlbumCoverFetched(request_.id, QUrl(), QImage());
|
emit AlbumCoverFetched(request_.id, AlbumCoverImageResult());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +266,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
|
||||||
// We'll sort the list of results by current score, then load the first 3 images from each category and use some heuristics for additional score.
|
// We'll sort the list of results by current score, then load the first 3 images from each category and use some heuristics for additional score.
|
||||||
// If no images are good enough we'll keep loading more images until we find one that is or we run out of results.
|
// If no images are good enough we'll keep loading more images until we find one that is or we run out of results.
|
||||||
|
|
||||||
std::stable_sort(results_.begin(), results_.end(), CoverSearchResultCompareScore);
|
std::stable_sort(results_.begin(), results_.end(), CoverProviderSearchResultCompareScore);
|
||||||
|
|
||||||
FetchMoreImages();
|
FetchMoreImages();
|
||||||
|
|
||||||
|
@ -275,7 +277,7 @@ void AlbumCoverFetcherSearch::FetchMoreImages() {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (!results_.isEmpty()) {
|
while (!results_.isEmpty()) {
|
||||||
++i;
|
++i;
|
||||||
CoverSearchResult result = results_.takeFirst();
|
CoverProviderSearchResult result = results_.takeFirst();
|
||||||
|
|
||||||
qLog(Debug) << "Loading" << result.artist << result.album << result.image_url << "from" << result.provider << "with current score" << result.score();
|
qLog(Debug) << "Loading" << result.artist << result.album << result.image_url << "from" << result.provider << "with current score" << result.score();
|
||||||
|
|
||||||
|
@ -309,13 +311,11 @@ void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(QNetworkReply *reply) {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
if (!pending_image_loads_.contains(reply)) return;
|
if (!pending_image_loads_.contains(reply)) return;
|
||||||
CoverSearchResult result = pending_image_loads_.take(reply);
|
CoverProviderSearchResult result = pending_image_loads_.take(reply);
|
||||||
|
|
||||||
statistics_.bytes_transferred_ += reply->bytesAvailable();
|
statistics_.bytes_transferred_ += reply->bytesAvailable();
|
||||||
|
|
||||||
if (cancel_requested_) {
|
if (cancel_requested_) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
qLog(Error) << "Error requesting" << reply->url() << reply->errorString();
|
qLog(Error) << "Error requesting" << reply->url() << reply->errorString();
|
||||||
|
@ -325,15 +325,17 @@ void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(QNetworkReply *reply) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
||||||
if (Utilities::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) || Utilities::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
|
if (ImageUtils::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) || ImageUtils::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
|
||||||
|
QByteArray image_data = reply->readAll();
|
||||||
|
QString mime_type = Utilities::MimeTypeFromData(image_data);
|
||||||
QImage image;
|
QImage image;
|
||||||
if (image.loadFromData(reply->readAll())) {
|
if (image.loadFromData(image_data)) {
|
||||||
if (result.image_size != QSize(0,0) && result.image_size != image.size()) {
|
if (result.image_size != QSize(0, 0) && result.image_size != image.size()) {
|
||||||
qLog(Debug) << "API size for image" << result.image_size << "for" << reply->url() << "from" << result.provider << "did not match retrieved size" << image.size();
|
qLog(Debug) << "API size for image" << result.image_size << "for" << reply->url() << "from" << result.provider << "did not match retrieved size" << image.size();
|
||||||
}
|
}
|
||||||
result.image_size = image.size();
|
result.image_size = image.size();
|
||||||
result.score_quality = ScoreImage(image.size());
|
result.score_quality = ScoreImage(image.size());
|
||||||
candidate_images_.insert(result.score(), CandidateImage(result, image));
|
candidate_images_.insert(result.score(), CandidateImage(result, AlbumCoverImageResult(result.image_url, mime_type, image_data, image)));
|
||||||
qLog(Debug) << reply->url() << "from" << result.provider << "scored" << result.score();
|
qLog(Debug) << reply->url() << "from" << result.provider << "scored" << result.score();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -380,26 +382,24 @@ float AlbumCoverFetcherSearch::ScoreImage(const QSize size) const {
|
||||||
|
|
||||||
void AlbumCoverFetcherSearch::SendBestImage() {
|
void AlbumCoverFetcherSearch::SendBestImage() {
|
||||||
|
|
||||||
QUrl cover_url;
|
AlbumCoverImageResult result;
|
||||||
QImage image;
|
|
||||||
|
|
||||||
if (!candidate_images_.isEmpty()) {
|
if (!candidate_images_.isEmpty()) {
|
||||||
const CandidateImage best_image = candidate_images_.values().back();
|
const CandidateImage best_image = candidate_images_.values().back();
|
||||||
cover_url = best_image.first.image_url;
|
result = best_image.album_cover;
|
||||||
image = best_image.second;
|
|
||||||
|
|
||||||
qLog(Info) << "Using" << best_image.first.image_url << "from" << best_image.first.provider << "with score" << best_image.first.score();
|
qLog(Info) << "Using" << best_image.result.image_url << "from" << best_image.result.provider << "with score" << best_image.result.score();
|
||||||
|
|
||||||
statistics_.chosen_images_by_provider_[best_image.first.provider]++;
|
statistics_.chosen_images_by_provider_[best_image.result.provider]++;
|
||||||
statistics_.chosen_images_++;
|
statistics_.chosen_images_++;
|
||||||
statistics_.chosen_width_ += image.width();
|
statistics_.chosen_width_ += result.image.width();
|
||||||
statistics_.chosen_height_ += image.height();
|
statistics_.chosen_height_ += result.image.height();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
statistics_.missing_images_++;
|
statistics_.missing_images_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit AlbumCoverFetched(request_.id, cover_url, image);
|
emit AlbumCoverFetched(request_.id, result);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,10 +425,10 @@ bool AlbumCoverFetcherSearch::ProviderCompareOrder(CoverProvider *a, CoverProvid
|
||||||
return a->order() < b->order();
|
return a->order() < b->order();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AlbumCoverFetcherSearch::CoverSearchResultCompareScore(const CoverSearchResult &a, const CoverSearchResult &b) {
|
bool AlbumCoverFetcherSearch::CoverProviderSearchResultCompareScore(const CoverProviderSearchResult &a, const CoverProviderSearchResult &b) {
|
||||||
return a.score() > b.score();
|
return a.score() > b.score();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AlbumCoverFetcherSearch::CoverSearchResultCompareNumber(const CoverSearchResult &a, const CoverSearchResult &b) {
|
bool AlbumCoverFetcherSearch::CoverProviderSearchResultCompareNumber(const CoverProviderSearchResult &a, const CoverProviderSearchResult &b) {
|
||||||
return a.number < b.number;
|
return a.number < b.number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,14 @@
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QMultiMap>
|
#include <QMultiMap>
|
||||||
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
|
||||||
#include "albumcoverfetcher.h"
|
#include "albumcoverfetcher.h"
|
||||||
#include "coversearchstatistics.h"
|
#include "coversearchstatistics.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
|
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class CoverProvider;
|
class CoverProvider;
|
||||||
|
@ -59,23 +61,23 @@ class AlbumCoverFetcherSearch : public QObject {
|
||||||
|
|
||||||
CoverSearchStatistics statistics() const { return statistics_; }
|
CoverSearchStatistics statistics() const { return statistics_; }
|
||||||
|
|
||||||
static bool CoverSearchResultCompareNumber(const CoverSearchResult &a, const CoverSearchResult &b);
|
static bool CoverProviderSearchResultCompareNumber(const CoverProviderSearchResult &a, const CoverProviderSearchResult &b);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
// It's the end of search (when there was no fetch-me-a-cover request).
|
// It's the end of search (when there was no fetch-me-a-cover request).
|
||||||
void SearchFinished(quint64, CoverSearchResults results);
|
void SearchFinished(quint64, CoverProviderSearchResults results);
|
||||||
|
|
||||||
// It's the end of search and we've fetched a cover.
|
// It's the end of search and we've fetched a cover.
|
||||||
void AlbumCoverFetched(const quint64, const QUrl &cover_url, const QImage &cover);
|
void AlbumCoverFetched(const quint64, AlbumCoverImageResult result);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ProviderSearchResults(const int id, const CoverSearchResults &results);
|
void ProviderSearchResults(const int id, const CoverProviderSearchResults &results);
|
||||||
void ProviderSearchFinished(const int id, const CoverSearchResults &results);
|
void ProviderSearchFinished(const int id, const CoverProviderSearchResults &results);
|
||||||
void ProviderCoverFetchFinished(QNetworkReply *reply);
|
void ProviderCoverFetchFinished(QNetworkReply *reply);
|
||||||
void TerminateSearch();
|
void TerminateSearch();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ProviderSearchResults(CoverProvider *provider, const CoverSearchResults &results);
|
void ProviderSearchResults(CoverProvider *provider, const CoverProviderSearchResults &results);
|
||||||
void AllProvidersFinished();
|
void AllProvidersFinished();
|
||||||
|
|
||||||
void FetchMoreImages();
|
void FetchMoreImages();
|
||||||
|
@ -83,7 +85,7 @@ class AlbumCoverFetcherSearch : public QObject {
|
||||||
void SendBestImage();
|
void SendBestImage();
|
||||||
|
|
||||||
static bool ProviderCompareOrder(CoverProvider *a, CoverProvider *b);
|
static bool ProviderCompareOrder(CoverProvider *a, CoverProvider *b);
|
||||||
static bool CoverSearchResultCompareScore(const CoverSearchResult &a, const CoverSearchResult &b);
|
static bool CoverProviderSearchResultCompareScore(const CoverProviderSearchResult &a, const CoverProviderSearchResult &b);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int kSearchTimeoutMs;
|
static const int kSearchTimeoutMs;
|
||||||
|
@ -97,14 +99,18 @@ class AlbumCoverFetcherSearch : public QObject {
|
||||||
CoverSearchRequest request_;
|
CoverSearchRequest request_;
|
||||||
|
|
||||||
// Complete results (from all of the available providers).
|
// Complete results (from all of the available providers).
|
||||||
CoverSearchResults results_;
|
CoverProviderSearchResults results_;
|
||||||
|
|
||||||
QMap<int, CoverProvider*> pending_requests_;
|
QMap<int, CoverProvider*> pending_requests_;
|
||||||
QMap<QNetworkReply*, CoverSearchResult> pending_image_loads_;
|
QMap<QNetworkReply*, CoverProviderSearchResult> pending_image_loads_;
|
||||||
NetworkTimeouts* image_load_timeout_;
|
NetworkTimeouts* image_load_timeout_;
|
||||||
|
|
||||||
// QMap is sorted by key (score). Values are (result, image)
|
// QMap is sorted by key (score).
|
||||||
typedef QPair<CoverSearchResult, QImage> CandidateImage;
|
struct CandidateImage {
|
||||||
|
CandidateImage(const CoverProviderSearchResult &_result, const AlbumCoverImageResult &_album_cover) : result(_result), album_cover(_album_cover) {}
|
||||||
|
CoverProviderSearchResult result;
|
||||||
|
AlbumCoverImageResult album_cover;
|
||||||
|
};
|
||||||
QMultiMap<float, CandidateImage> candidate_images_;
|
QMultiMap<float, CandidateImage> candidate_images_;
|
||||||
|
|
||||||
NetworkAccessManager *network_;
|
NetworkAccessManager *network_;
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry 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 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ALBUMCOVERIMAGERESULT_H
|
||||||
|
#define ALBUMCOVERIMAGERESULT_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QImage>
|
||||||
|
|
||||||
|
struct AlbumCoverImageResult {
|
||||||
|
explicit AlbumCoverImageResult(const QUrl &_cover_url = QUrl(),
|
||||||
|
const QString &_mime_type = QString(),
|
||||||
|
const QByteArray &_image_data = QByteArray(),
|
||||||
|
const QImage &_image = QImage()) :
|
||||||
|
cover_url(_cover_url),
|
||||||
|
mime_type(_mime_type),
|
||||||
|
image_data(_image_data), image(_image) {}
|
||||||
|
|
||||||
|
explicit AlbumCoverImageResult(const QImage &_image) : image(_image) {}
|
||||||
|
|
||||||
|
QUrl cover_url;
|
||||||
|
QString mime_type;
|
||||||
|
QByteArray image_data;
|
||||||
|
QImage image;
|
||||||
|
|
||||||
|
bool is_valid() const { return !image_data.isNull() || !image.isNull(); }
|
||||||
|
bool is_jpeg() const { return mime_type == "image/jpeg" && !image_data.isEmpty(); }
|
||||||
|
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(AlbumCoverImageResult)
|
||||||
|
|
||||||
|
#endif // ALBUMCOVERIMAGERESULT_H
|
|
@ -27,10 +27,12 @@
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#include <QBuffer>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QQueue>
|
#include <QQueue>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
@ -40,25 +42,29 @@
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
#include <QMimeDatabase>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
#include "core/networkaccessmanager.h"
|
#include "core/networkaccessmanager.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
#include "core/utilities.h"
|
#include "core/utilities.h"
|
||||||
|
#include "core/imageutils.h"
|
||||||
#include "settings/collectionsettingspage.h"
|
#include "settings/collectionsettingspage.h"
|
||||||
#include "organize/organizeformat.h"
|
#include "organize/organizeformat.h"
|
||||||
#include "albumcoverloader.h"
|
#include "albumcoverloader.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
#include "albumcoverloaderresult.h"
|
#include "albumcoverloaderresult.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
|
|
||||||
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
|
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
stop_requested_(false),
|
stop_requested_(false),
|
||||||
next_id_(1),
|
load_image_async_id_(1),
|
||||||
|
save_image_async_id_(1),
|
||||||
network_(new NetworkAccessManager(this)),
|
network_(new NetworkAccessManager(this)),
|
||||||
cover_album_dir_(false),
|
save_cover_type_(CollectionSettingsPage::SaveCoverType_Cache),
|
||||||
cover_filename_(CollectionSettingsPage::SaveCover_Hash),
|
save_cover_filename_(CollectionSettingsPage::SaveCoverFilename_Hash),
|
||||||
cover_overwrite_(false),
|
cover_overwrite_(false),
|
||||||
cover_lowercase_(true),
|
cover_lowercase_(true),
|
||||||
cover_replace_spaces_(true),
|
cover_replace_spaces_(true),
|
||||||
|
@ -89,8 +95,8 @@ void AlbumCoverLoader::ReloadSettings() {
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
cover_album_dir_ = s.value("cover_album_dir", false).toBool();
|
save_cover_type_ = CollectionSettingsPage::SaveCoverType(s.value("save_cover_type", CollectionSettingsPage::SaveCoverType_Cache).toInt());
|
||||||
cover_filename_ = CollectionSettingsPage::SaveCover(s.value("cover_filename", CollectionSettingsPage::SaveCover_Hash).toInt());
|
save_cover_filename_ = CollectionSettingsPage::SaveCoverFilename(s.value("save_cover_filename", CollectionSettingsPage::SaveCoverFilename_Hash).toInt());
|
||||||
cover_pattern_ = s.value("cover_pattern", "%albumartist-%album").toString();
|
cover_pattern_ = s.value("cover_pattern", "%albumartist-%album").toString();
|
||||||
cover_overwrite_ = s.value("cover_overwrite", false).toBool();
|
cover_overwrite_ = s.value("cover_overwrite", false).toBool();
|
||||||
cover_lowercase_ = s.value("cover_lowercase", false).toBool();
|
cover_lowercase_ = s.value("cover_lowercase", false).toBool();
|
||||||
|
@ -106,10 +112,10 @@ QString AlbumCoverLoader::AlbumCoverFilename(QString artist, QString album, cons
|
||||||
|
|
||||||
QString filename = artist + "-" + album;
|
QString filename = artist + "-" + album;
|
||||||
filename = Utilities::UnicodeToAscii(filename.toLower());
|
filename = Utilities::UnicodeToAscii(filename.toLower());
|
||||||
filename = filename.replace(' ', '-');
|
filename = filename.replace(' ', '-')
|
||||||
filename = filename.replace("--", "-");
|
.replace("--", "-")
|
||||||
filename = filename.remove(OrganizeFormat::kInvalidFatCharacters);
|
.remove(OrganizeFormat::kInvalidFatCharacters)
|
||||||
filename = filename.simplified();
|
.simplified();
|
||||||
|
|
||||||
if (!extension.isEmpty()) {
|
if (!extension.isEmpty()) {
|
||||||
filename.append('.');
|
filename.append('.');
|
||||||
|
@ -129,7 +135,7 @@ QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString
|
||||||
album.remove(Song::kAlbumRemoveDisc);
|
album.remove(Song::kAlbumRemoveDisc);
|
||||||
|
|
||||||
QString path;
|
QString path;
|
||||||
if (source == Song::Source_Collection && cover_album_dir_ && !album_dir.isEmpty()) {
|
if (source == Song::Source_Collection && save_cover_type_ == CollectionSettingsPage::SaveCoverType_Album && !album_dir.isEmpty()) {
|
||||||
path = album_dir;
|
path = album_dir;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -147,7 +153,10 @@ QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString
|
||||||
}
|
}
|
||||||
|
|
||||||
QString filename;
|
QString filename;
|
||||||
if (source == Song::Source_Collection && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) {
|
if (source == Song::Source_Collection &&
|
||||||
|
save_cover_type_ == CollectionSettingsPage::SaveCoverType_Album &&
|
||||||
|
save_cover_filename_ == CollectionSettingsPage::SaveCoverFilename_Pattern &&
|
||||||
|
!cover_pattern_.isEmpty()) {
|
||||||
filename = CoverFilenameFromVariable(artist, album);
|
filename = CoverFilenameFromVariable(artist, album);
|
||||||
filename.remove(OrganizeFormat::kInvalidFatCharacters);
|
filename.remove(OrganizeFormat::kInvalidFatCharacters);
|
||||||
if (cover_lowercase_) filename = filename.toLower();
|
if (cover_lowercase_) filename = filename.toLower();
|
||||||
|
@ -157,7 +166,8 @@ QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString
|
||||||
filename.append(extension);
|
filename.append(extension);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
if (filename.isEmpty()) {
|
||||||
filename = CoverFilenameFromSource(source, cover_url, artist, album, album_id, extension);
|
filename = CoverFilenameFromSource(source, cover_url, artist, album, album_id, extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +230,7 @@ QString AlbumCoverLoader::CoverFilenameFromVariable(const QString &artist, const
|
||||||
|
|
||||||
void AlbumCoverLoader::CancelTask(const quint64 id) {
|
void AlbumCoverLoader::CancelTask(const quint64 id) {
|
||||||
|
|
||||||
QMutexLocker l(&mutex_);
|
QMutexLocker l(&mutex_load_image_async_);
|
||||||
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
|
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
|
||||||
if (it->id == id) {
|
if (it->id == id) {
|
||||||
tasks_.erase(it);
|
tasks_.erase(it);
|
||||||
|
@ -232,7 +242,7 @@ void AlbumCoverLoader::CancelTask(const quint64 id) {
|
||||||
|
|
||||||
void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
|
void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
|
||||||
|
|
||||||
QMutexLocker l(&mutex_);
|
QMutexLocker l(&mutex_load_image_async_);
|
||||||
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end();) {
|
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end();) {
|
||||||
if (ids.contains(it->id)) {
|
if (ids.contains(it->id)) {
|
||||||
it = tasks_.erase(it);
|
it = tasks_.erase(it);
|
||||||
|
@ -244,26 +254,58 @@ void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions& options, const Song &song) {
|
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song) {
|
||||||
return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url(), song, song.image());
|
|
||||||
}
|
|
||||||
|
|
||||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url, const Song song, const QImage &embedded_image) {
|
|
||||||
|
|
||||||
Task task;
|
Task task;
|
||||||
task.options = options;
|
task.options = options;
|
||||||
task.song = song;
|
task.song = song;
|
||||||
task.song_url = song_url;
|
|
||||||
task.art_manual = art_manual;
|
|
||||||
task.art_automatic = art_automatic;
|
|
||||||
task.art_updated = false;
|
|
||||||
task.embedded_image = embedded_image;
|
|
||||||
task.type = AlbumCoverLoaderResult::Type_None;
|
|
||||||
task.state = State_Manual;
|
task.state = State_Manual;
|
||||||
|
|
||||||
|
return EnqueueTask(task);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url, const Song::Source song_source) {
|
||||||
|
|
||||||
|
Song song(song_source);
|
||||||
|
song.set_url(song_url);
|
||||||
|
song.set_art_automatic(art_automatic);
|
||||||
|
song.set_art_manual(art_manual);
|
||||||
|
|
||||||
|
Task task;
|
||||||
|
task.options = options;
|
||||||
|
task.song = song;
|
||||||
|
task.state = State_Manual;
|
||||||
|
|
||||||
|
return EnqueueTask(task);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover) {
|
||||||
|
|
||||||
|
Task task;
|
||||||
|
task.options = options;
|
||||||
|
task.album_cover = album_cover;
|
||||||
|
|
||||||
|
return EnqueueTask(task);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image) {
|
||||||
|
|
||||||
|
Task task;
|
||||||
|
task.options = options;
|
||||||
|
task.album_cover.image = image;
|
||||||
|
|
||||||
|
return EnqueueTask(task);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
quint64 AlbumCoverLoader::EnqueueTask(Task &task) {
|
||||||
|
|
||||||
{
|
{
|
||||||
QMutexLocker l(&mutex_);
|
QMutexLocker l(&mutex_load_image_async_);
|
||||||
task.id = next_id_++;
|
task.id = load_image_async_id_++;
|
||||||
tasks_.enqueue(task);
|
tasks_.enqueue(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,7 +321,7 @@ void AlbumCoverLoader::ProcessTasks() {
|
||||||
// Get the next task
|
// Get the next task
|
||||||
Task task;
|
Task task;
|
||||||
{
|
{
|
||||||
QMutexLocker l(&mutex_);
|
QMutexLocker l(&mutex_load_image_async_);
|
||||||
if (tasks_.isEmpty()) return;
|
if (tasks_.isEmpty()) return;
|
||||||
task = tasks_.dequeue();
|
task = tasks_.dequeue();
|
||||||
}
|
}
|
||||||
|
@ -298,8 +340,16 @@ void AlbumCoverLoader::ProcessTask(Task *task) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.loaded_success) {
|
if (result.loaded_success) {
|
||||||
QPair<QImage, QImage> images = ScaleAndPad(task->options, result.image);
|
result.album_cover.mime_type = Utilities::MimeTypeFromData(result.album_cover.image_data);
|
||||||
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(result.type, result.cover_url, result.image, images.first, images.second, task->art_updated));
|
QImage image_scaled;
|
||||||
|
QImage image_thumbnail;
|
||||||
|
if (task->options.get_image_ && task->options.scale_output_image_) {
|
||||||
|
image_scaled = ImageUtils::ScaleAndPad(result.album_cover.image, task->options.scale_output_image_, task->options.pad_output_image_, task->options.desired_height_);
|
||||||
|
}
|
||||||
|
if (task->options.get_image_ && task->options.create_thumbnail_) {
|
||||||
|
image_thumbnail = ImageUtils::CreateThumbnail(result.album_cover.image, task->options.pad_thumbnail_image_, task->options.thumbnail_size_);
|
||||||
|
}
|
||||||
|
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(result.loaded_success, result.type, result.album_cover, image_scaled, image_thumbnail, task->art_updated));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,30 +366,33 @@ void AlbumCoverLoader::NextState(Task *task) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Give up
|
// Give up
|
||||||
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(AlbumCoverLoaderResult::Type_None, QUrl(), task->options.default_output_image_, task->options.default_output_image_, task->options.default_thumbnail_image_, task->art_updated));
|
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(false, AlbumCoverLoaderResult::Type_None, AlbumCoverImageResult(task->options.default_output_image_), task->options.default_scaled_image_, task->options.default_thumbnail_image_, task->art_updated));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
|
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
|
||||||
|
|
||||||
// An image embedded in the song itself takes priority
|
// Only scale and pad.
|
||||||
if (!task->embedded_image.isNull()) {
|
if (task->album_cover.is_valid()) {
|
||||||
QPair<QImage, QImage> images = ScaleAndPad(task->options, task->embedded_image);
|
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, task->album_cover);
|
||||||
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, QUrl(), images.first);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use cached album cover if possible.
|
// For local files and streams initialize art if found.
|
||||||
if (task->state == State_Manual &&
|
if ((task->song.source() == Song::Source_LocalFile || task->song.source() == Song::Source_Stream) && !task->song.art_manual_is_valid() && !task->song.art_automatic_is_valid()) {
|
||||||
!task->song.art_manual_is_valid() &&
|
switch (task->state) {
|
||||||
task->art_manual.isEmpty() &&
|
case State_None:
|
||||||
task->song.source() != Song::Source_Collection &&
|
break;
|
||||||
!task->options.scale_output_image_ &&
|
case State_Manual:
|
||||||
!task->options.pad_output_image_) {
|
task->song.InitArtManual();
|
||||||
task->song.InitArtManual();
|
if (task->song.art_manual_is_valid()) task->art_updated = true;
|
||||||
if (task->song.art_manual_is_valid() && task->art_manual != task->song.art_manual()) {
|
break;
|
||||||
task->art_manual = task->song.art_manual();
|
case State_Automatic:
|
||||||
task->art_updated = true;
|
if (task->song.url().isLocalFile()) {
|
||||||
|
task->song.InitArtAutomatic();
|
||||||
|
if (task->song.art_automatic_is_valid()) task->art_updated = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,32 +402,64 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
|
||||||
case State_None:
|
case State_None:
|
||||||
case State_Automatic:
|
case State_Automatic:
|
||||||
type = AlbumCoverLoaderResult::Type_Automatic;
|
type = AlbumCoverLoaderResult::Type_Automatic;
|
||||||
cover_url = task->art_automatic;
|
cover_url = task->song.art_automatic();
|
||||||
break;
|
break;
|
||||||
case State_Manual:
|
case State_Manual:
|
||||||
type = AlbumCoverLoaderResult::Type_Manual;
|
type = AlbumCoverLoaderResult::Type_Manual;
|
||||||
cover_url = task->art_manual;
|
cover_url = task->song.art_manual();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
task->type = type;
|
task->type = type;
|
||||||
|
|
||||||
if (!cover_url.isEmpty() && !cover_url.path().isEmpty()) {
|
if (!cover_url.isEmpty() && !cover_url.path().isEmpty()) {
|
||||||
if (cover_url.path() == Song::kManuallyUnsetCover) {
|
if (cover_url.path() == Song::kManuallyUnsetCover) {
|
||||||
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_ManuallyUnset, cover_url, task->options.default_output_image_);
|
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_ManuallyUnset, AlbumCoverImageResult(cover_url, QString(), QByteArray(), task->options.default_output_image_));
|
||||||
}
|
}
|
||||||
else if (cover_url.path() == Song::kEmbeddedCover && task->song_url.isLocalFile()) {
|
else if (cover_url.path() == Song::kEmbeddedCover && task->song.url().isLocalFile()) {
|
||||||
const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile());
|
QByteArray image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song.url().toLocalFile());
|
||||||
if (!taglib_image.isNull()) {
|
if (!image_data.isEmpty()) {
|
||||||
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, cover_url, ScaleAndPad(task->options, taglib_image).first);
|
QImage image;
|
||||||
|
if (task->options.get_image_ && image.loadFromData(image_data)) {
|
||||||
|
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, AlbumCoverImageResult(cover_url, QString(), image_data, image));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, AlbumCoverImageResult(cover_url, QString(), image_data, image));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (cover_url.isLocalFile()) {
|
|
||||||
QImage image(cover_url.toLocalFile());
|
if (cover_url.isLocalFile()) {
|
||||||
return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : image);
|
QFile file(cover_url.toLocalFile());
|
||||||
|
if (file.exists()) {
|
||||||
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
|
QByteArray image_data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
QImage image;
|
||||||
|
if (!image_data.isEmpty() && task->options.get_image_ && image.loadFromData(image_data)) {
|
||||||
|
return TryLoadResult(false, !image.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return TryLoadResult(false, !image.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qLog(Error) << "Failed to open album cover file" << cover_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (cover_url.scheme().isEmpty()) { // Assume a local file with no scheme.
|
else if (cover_url.scheme().isEmpty()) { // Assume a local file with no scheme.
|
||||||
QImage image(cover_url.path());
|
QFile file(cover_url.path());
|
||||||
return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : image);
|
if (file.exists() && file.open(QIODevice::ReadOnly)) {
|
||||||
|
QByteArray image_data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
QImage image;
|
||||||
|
if (!image_data.isEmpty() && task->options.get_image_ && image.loadFromData(image_data)) {
|
||||||
|
return TryLoadResult(false, !image.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return TryLoadResult(false, !image.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (network_->supportedSchemes().contains(cover_url.scheme())) { // Remote URL
|
else if (network_->supportedSchemes().contains(cover_url.scheme())) { // Remote URL
|
||||||
qLog(Debug) << "Loading remote cover from" << cover_url;
|
qLog(Debug) << "Loading remote cover from" << cover_url;
|
||||||
|
@ -388,11 +473,11 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply, cover_url]() { RemoteFetchFinished(reply, cover_url); });
|
QObject::connect(reply, &QNetworkReply::finished, [this, reply, cover_url]() { RemoteFetchFinished(reply, cover_url); });
|
||||||
|
|
||||||
remote_tasks_.insert(reply, *task);
|
remote_tasks_.insert(reply, *task);
|
||||||
return TryLoadResult(true, false, type, cover_url, QImage());
|
return TryLoadResult(true, false, type, AlbumCoverImageResult(cover_url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TryLoadResult(false, false, AlbumCoverLoaderResult::Type_None, cover_url, task->options.default_output_image_);
|
return TryLoadResult(false, false, AlbumCoverLoaderResult::Type_None, AlbumCoverImageResult(cover_url, QString(), QByteArray(), task->options.default_output_image_));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,14 +510,24 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cov
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
// Try to load the image
|
// Try to load the image
|
||||||
|
QByteArray image_data = reply->readAll();
|
||||||
|
QString mime_type = Utilities::MimeTypeFromData(image_data);
|
||||||
QImage image;
|
QImage image;
|
||||||
if (image.load(reply, nullptr)) {
|
if (task.options.get_image_data_) {
|
||||||
QPair<QImage, QImage> images = ScaleAndPad(task.options, image);
|
if (image.loadFromData(image_data)) {
|
||||||
emit AlbumCoverLoaded(task.id, AlbumCoverLoaderResult(task.type, cover_url, image, images.first, images.second, task.art_updated));
|
QImage image_scaled;
|
||||||
return;
|
QImage image_thumbnail;
|
||||||
|
if (task.options.scale_output_image_) image_scaled = ImageUtils::ScaleAndPad(image, task.options.scale_output_image_, task.options.pad_output_image_, task.options.desired_height_);
|
||||||
|
if (task.options.create_thumbnail_) image_thumbnail = ImageUtils::CreateThumbnail(image, task.options.pad_thumbnail_image_, task.options.thumbnail_size_);
|
||||||
|
emit AlbumCoverLoaded(task.id, AlbumCoverLoaderResult(true, task.type, AlbumCoverImageResult(cover_url, mime_type, (task.options.get_image_data_ ? image_data : QByteArray()), image), image_scaled, image_thumbnail, task.art_updated));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qLog(Error) << "Unable to load album cover image" << cover_url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qLog(Error) << "Unable to load album cover image" << cover_url;
|
emit AlbumCoverLoaded(task.id, AlbumCoverLoaderResult(true, task.type, AlbumCoverImageResult(cover_url, mime_type, image_data, QImage()), QImage(), QImage(), task.art_updated));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -443,79 +538,158 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cov
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QPair<QImage, QImage> AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {
|
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString song_filename, const QString &cover_filename) {
|
||||||
|
|
||||||
if (image.isNull()) return qMakePair(image, image);
|
QMutexLocker l(&mutex_save_image_async_);
|
||||||
|
qint64 id = ++save_image_async_id_;
|
||||||
|
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QString, song_filename), Q_ARG(QString, cover_filename));
|
||||||
|
return id;
|
||||||
|
|
||||||
// Scale the image down
|
}
|
||||||
QImage image_scaled;
|
|
||||||
if (options.scale_output_image_) {
|
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString song_filename, const QImage &image) {
|
||||||
image_scaled = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
||||||
|
QMutexLocker l(&mutex_save_image_async_);
|
||||||
|
qint64 id = ++save_image_async_id_;
|
||||||
|
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QString, song_filename), Q_ARG(QImage, image));
|
||||||
|
return id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString song_filename, const QByteArray &image_data) {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_save_image_async_);
|
||||||
|
qint64 id = ++save_image_async_id_;
|
||||||
|
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QString, song_filename), Q_ARG(QByteArray, image_data));
|
||||||
|
return id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QString &cover_filename) {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_save_image_async_);
|
||||||
|
qint64 id = ++save_image_async_id_;
|
||||||
|
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QString, cover_filename));
|
||||||
|
return id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QImage &image) {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_save_image_async_);
|
||||||
|
qint64 id = ++save_image_async_id_;
|
||||||
|
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QImage, image));
|
||||||
|
return id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QByteArray &image_data) {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_save_image_async_);
|
||||||
|
qint64 id = ++save_image_async_id_;
|
||||||
|
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QByteArray, image_data));
|
||||||
|
return id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QString song_filename, const QByteArray &image_data) {
|
||||||
|
|
||||||
|
TagReaderReply *reply = TagReaderClient::Instance()->SaveEmbeddedArt(song_filename, image_data);
|
||||||
|
tagreader_save_embedded_art_requests_.insert(id, reply);
|
||||||
|
QObject::connect(reply, &TagReaderReply::Finished, this, [this, id, reply]() { SaveEmbeddedArtFinished(id, reply); }, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QString song_filename, const QImage &image) {
|
||||||
|
|
||||||
|
QByteArray image_data;
|
||||||
|
|
||||||
|
if (!image.isNull()) {
|
||||||
|
QBuffer buffer(&image_data);
|
||||||
|
if (buffer.open(QIODevice::WriteOnly)) {
|
||||||
|
image.save(&buffer, "JPEG");
|
||||||
|
buffer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveEmbeddedCover(id, song_filename, image_data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QString song_filename, const QString &cover_filename) {
|
||||||
|
|
||||||
|
QFile file(cover_filename);
|
||||||
|
|
||||||
|
if (file.size() >= 209715200 || !file.open(QIODevice::ReadOnly)) { // Max 200 MB.
|
||||||
|
emit SaveEmbeddedCoverAsyncFinished(id, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray image_data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
SaveEmbeddedCover(id, song_filename, image_data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QImage &image) {
|
||||||
|
|
||||||
|
if (image.isNull()) {
|
||||||
|
for (const QUrl &url : urls) {
|
||||||
|
SaveEmbeddedCover(id, url.toLocalFile(), QByteArray());
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
image_scaled = image;
|
QByteArray image_data;
|
||||||
}
|
QBuffer buffer(&image_data);
|
||||||
|
if (buffer.open(QIODevice::WriteOnly)) {
|
||||||
// Pad the image to height x height
|
if (image.save(&buffer, "JPEG")) {
|
||||||
if (options.pad_output_image_) {
|
SaveEmbeddedCover(id, urls, image_data);
|
||||||
QImage image_padded(options.desired_height_, options.desired_height_, QImage::Format_ARGB32);
|
buffer.close();
|
||||||
image_padded.fill(0);
|
return;
|
||||||
|
}
|
||||||
QPainter p(&image_padded);
|
buffer.close();
|
||||||
p.drawImage((options.desired_height_ - image_scaled.width()) / 2, (options.desired_height_ - image_scaled.height()) / 2, image_scaled);
|
|
||||||
p.end();
|
|
||||||
|
|
||||||
image_scaled = image_padded;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create thumbnail
|
|
||||||
QImage image_thumbnail;
|
|
||||||
if (options.create_thumbnail_) {
|
|
||||||
if (options.pad_thumbnail_image_) {
|
|
||||||
image_thumbnail = image.scaled(options.thumbnail_size_, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
||||||
QImage image_padded(options.thumbnail_size_, QImage::Format_ARGB32_Premultiplied);
|
|
||||||
image_padded.fill(0);
|
|
||||||
|
|
||||||
QPainter p(&image_padded);
|
|
||||||
p.drawImage((image_padded.width() - image_thumbnail.width()) / 2, (image_padded.height() - image_thumbnail.height()) / 2, image_thumbnail);
|
|
||||||
p.end();
|
|
||||||
|
|
||||||
image_thumbnail = image_padded;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
image_thumbnail = image.scaledToHeight(options.thumbnail_size_.height(), Qt::SmoothTransformation);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return qMakePair(image_scaled, image_thumbnail);
|
emit SaveEmbeddedCoverAsyncFinished(id, false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap AlbumCoverLoader::TryLoadPixmap(const QUrl &art_automatic, const QUrl &art_manual, const QUrl &url) {
|
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QString &cover_filename) {
|
||||||
|
|
||||||
QPixmap ret;
|
QFile file(cover_filename);
|
||||||
|
|
||||||
if (!art_manual.path().isEmpty()) {
|
if (file.size() >= 209715200 || !file.open(QIODevice::ReadOnly)) { // Max 200 MB.
|
||||||
if (art_manual.path() == Song::kManuallyUnsetCover) return ret;
|
emit SaveEmbeddedCoverAsyncFinished(id, false);
|
||||||
else if (art_manual.isLocalFile()) {
|
return;
|
||||||
ret.load(art_manual.toLocalFile());
|
|
||||||
}
|
|
||||||
else if (art_manual.scheme().isEmpty()) {
|
|
||||||
ret.load(art_manual.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ret.isNull() && !art_automatic.path().isEmpty()) {
|
|
||||||
if (art_automatic.path() == Song::kEmbeddedCover && !url.isEmpty() && url.isLocalFile()) {
|
|
||||||
ret = QPixmap::fromImage(TagReaderClient::Instance()->LoadEmbeddedArtBlocking(url.toLocalFile()));
|
|
||||||
}
|
|
||||||
else if (art_automatic.isLocalFile()) {
|
|
||||||
ret.load(art_automatic.toLocalFile());
|
|
||||||
}
|
|
||||||
else if (art_automatic.scheme().isEmpty()) {
|
|
||||||
ret.load(art_automatic.path());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
QByteArray image_data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
SaveEmbeddedCover(id, urls, image_data);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QByteArray &image_data) {
|
||||||
|
|
||||||
|
for (const QUrl &url : urls) {
|
||||||
|
SaveEmbeddedCover(id, url.toLocalFile(), image_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverLoader::SaveEmbeddedArtFinished(const qint64 id, TagReaderReply *reply) {
|
||||||
|
|
||||||
|
if (tagreader_save_embedded_art_requests_.contains(id)) {
|
||||||
|
tagreader_save_embedded_art_requests_.remove(id, reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tagreader_save_embedded_art_requests_.contains(id)) {
|
||||||
|
emit SaveEmbeddedCoverAsyncFinished(id, reply->is_successful());
|
||||||
|
}
|
||||||
|
|
||||||
|
metaObject()->invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,15 +30,19 @@
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <QMultiMap>
|
||||||
#include <QQueue>
|
#include <QQueue>
|
||||||
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
#include "core/tagreaderclient.h"
|
||||||
#include "settings/collectionsettingspage.h"
|
#include "settings/collectionsettingspage.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
#include "albumcoverloaderresult.h"
|
#include "albumcoverloaderresult.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
|
|
||||||
class QThread;
|
class QThread;
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
|
@ -70,23 +74,39 @@ class AlbumCoverLoader : public QObject {
|
||||||
QString CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QString &extension = QString());
|
QString CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QString &extension = QString());
|
||||||
|
|
||||||
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song);
|
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song);
|
||||||
virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url = QUrl(), const Song song = Song(), const QImage &embedded_image = QImage());
|
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url = QUrl(), const Song::Source song_source = Song::Source_Unknown);
|
||||||
|
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover);
|
||||||
|
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image);
|
||||||
|
|
||||||
void CancelTask(const quint64 id);
|
void CancelTask(const quint64 id);
|
||||||
void CancelTasks(const QSet<quint64> &ids);
|
void CancelTasks(const QSet<quint64> &ids);
|
||||||
|
|
||||||
static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QUrl &url = QUrl());
|
qint64 SaveEmbeddedCoverAsync(const QString song_filename, const QString &cover_filename);
|
||||||
static QPair<QImage, QImage> ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image);
|
qint64 SaveEmbeddedCoverAsync(const QString song_filename, const QImage &image);
|
||||||
|
qint64 SaveEmbeddedCoverAsync(const QString song_filename, const QByteArray &image_data);
|
||||||
|
qint64 SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QString &cover_filename);
|
||||||
|
qint64 SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QImage &image);
|
||||||
|
qint64 SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QByteArray &image_data);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
void AlbumCoverLoaded(quint64 id, AlbumCoverLoaderResult result);
|
void AlbumCoverLoaded(quint64 id, AlbumCoverLoaderResult result);
|
||||||
|
void SaveEmbeddedCoverAsyncFinished(quint64 id, bool success);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void Exit();
|
void Exit();
|
||||||
void ProcessTasks();
|
void ProcessTasks();
|
||||||
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
|
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
|
||||||
|
|
||||||
|
void SaveEmbeddedCover(const qint64 id, const QString song_filename, const QString &cover_filename);
|
||||||
|
void SaveEmbeddedCover(const qint64 id, const QString song_filename, const QImage &image);
|
||||||
|
void SaveEmbeddedCover(const qint64 id, const QString song_filename, const QByteArray &image_data);
|
||||||
|
void SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QImage &image);
|
||||||
|
void SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QString &cover_filename);
|
||||||
|
void SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QByteArray &image_data);
|
||||||
|
|
||||||
|
void SaveEmbeddedArtFinished(const qint64 id, TagReaderReply *reply);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
struct Task {
|
struct Task {
|
||||||
|
@ -95,11 +115,8 @@ class AlbumCoverLoader : public QObject {
|
||||||
AlbumCoverLoaderOptions options;
|
AlbumCoverLoaderOptions options;
|
||||||
|
|
||||||
quint64 id;
|
quint64 id;
|
||||||
QUrl art_manual;
|
|
||||||
QUrl art_automatic;
|
|
||||||
QUrl song_url;
|
|
||||||
Song song;
|
Song song;
|
||||||
QImage embedded_image;
|
AlbumCoverImageResult album_cover;
|
||||||
State state;
|
State state;
|
||||||
AlbumCoverLoaderResult::Type type;
|
AlbumCoverLoaderResult::Type type;
|
||||||
bool art_updated;
|
bool art_updated;
|
||||||
|
@ -107,33 +124,42 @@ class AlbumCoverLoader : public QObject {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TryLoadResult {
|
struct TryLoadResult {
|
||||||
explicit TryLoadResult(const bool _started_async = false, const bool _loaded_success = false, const AlbumCoverLoaderResult::Type _type = AlbumCoverLoaderResult::Type_None, const QUrl &_cover_url = QUrl(), const QImage &_image = QImage()) : started_async(_started_async), loaded_success(_loaded_success), type(_type), cover_url(_cover_url), image(_image) {}
|
explicit TryLoadResult(const bool _started_async = false,
|
||||||
|
const bool _loaded_success = false,
|
||||||
|
const AlbumCoverLoaderResult::Type _type = AlbumCoverLoaderResult::Type_None,
|
||||||
|
const AlbumCoverImageResult &_album_cover = AlbumCoverImageResult()) :
|
||||||
|
started_async(_started_async),
|
||||||
|
loaded_success(_loaded_success),
|
||||||
|
type(_type),
|
||||||
|
album_cover(_album_cover) {}
|
||||||
|
|
||||||
bool started_async;
|
bool started_async;
|
||||||
bool loaded_success;
|
bool loaded_success;
|
||||||
|
|
||||||
AlbumCoverLoaderResult::Type type;
|
AlbumCoverLoaderResult::Type type;
|
||||||
QUrl cover_url;
|
AlbumCoverImageResult album_cover;
|
||||||
QImage image;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
quint64 EnqueueTask(Task &task);
|
||||||
void ProcessTask(Task *task);
|
void ProcessTask(Task *task);
|
||||||
void NextState(Task *task);
|
void NextState(Task *task);
|
||||||
TryLoadResult TryLoadImage(Task *task);
|
TryLoadResult TryLoadImage(Task *task);
|
||||||
|
|
||||||
bool stop_requested_;
|
bool stop_requested_;
|
||||||
|
|
||||||
QMutex mutex_;
|
QMutex mutex_load_image_async_;
|
||||||
|
QMutex mutex_save_image_async_;
|
||||||
QQueue<Task> tasks_;
|
QQueue<Task> tasks_;
|
||||||
QMap<QNetworkReply*, Task> remote_tasks_;
|
QMap<QNetworkReply*, Task> remote_tasks_;
|
||||||
quint64 next_id_;
|
quint64 load_image_async_id_;
|
||||||
|
quint64 save_image_async_id_;
|
||||||
|
|
||||||
NetworkAccessManager *network_;
|
NetworkAccessManager *network_;
|
||||||
|
|
||||||
static const int kMaxRedirects = 3;
|
static const int kMaxRedirects = 3;
|
||||||
|
|
||||||
bool cover_album_dir_;
|
CollectionSettingsPage::SaveCoverType save_cover_type_;
|
||||||
CollectionSettingsPage::SaveCover cover_filename_;
|
CollectionSettingsPage::SaveCoverFilename save_cover_filename_;
|
||||||
QString cover_pattern_;
|
QString cover_pattern_;
|
||||||
bool cover_overwrite_;
|
bool cover_overwrite_;
|
||||||
bool cover_lowercase_;
|
bool cover_lowercase_;
|
||||||
|
@ -141,6 +167,8 @@ class AlbumCoverLoader : public QObject {
|
||||||
|
|
||||||
QThread *original_thread_;
|
QThread *original_thread_;
|
||||||
|
|
||||||
|
QMultiMap<qint64, TagReaderReply*> tagreader_save_embedded_art_requests_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ALBUMCOVERLOADER_H
|
#endif // ALBUMCOVERLOADER_H
|
||||||
|
|
|
@ -29,19 +29,25 @@
|
||||||
|
|
||||||
struct AlbumCoverLoaderOptions {
|
struct AlbumCoverLoaderOptions {
|
||||||
explicit AlbumCoverLoaderOptions()
|
explicit AlbumCoverLoaderOptions()
|
||||||
: desired_height_(120),
|
: get_image_data_(true),
|
||||||
|
get_image_(true),
|
||||||
scale_output_image_(true),
|
scale_output_image_(true),
|
||||||
pad_output_image_(true),
|
pad_output_image_(true),
|
||||||
create_thumbnail_(false),
|
create_thumbnail_(false),
|
||||||
pad_thumbnail_image_(false) {}
|
pad_thumbnail_image_(false),
|
||||||
|
desired_height_(120),
|
||||||
|
thumbnail_size_(120, 120) {}
|
||||||
|
|
||||||
int desired_height_;
|
bool get_image_data_;
|
||||||
QSize thumbnail_size_;
|
bool get_image_;
|
||||||
bool scale_output_image_;
|
bool scale_output_image_;
|
||||||
bool pad_output_image_;
|
bool pad_output_image_;
|
||||||
bool create_thumbnail_;
|
bool create_thumbnail_;
|
||||||
bool pad_thumbnail_image_;
|
bool pad_thumbnail_image_;
|
||||||
|
int desired_height_;
|
||||||
|
QSize thumbnail_size_;
|
||||||
QImage default_output_image_;
|
QImage default_output_image_;
|
||||||
|
QImage default_scaled_image_;
|
||||||
QImage default_thumbnail_image_;
|
QImage default_thumbnail_image_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
|
|
||||||
struct AlbumCoverLoaderResult {
|
struct AlbumCoverLoaderResult {
|
||||||
|
|
||||||
enum Type {
|
enum Type {
|
||||||
|
@ -36,11 +38,22 @@ struct AlbumCoverLoaderResult {
|
||||||
Type_Remote,
|
Type_Remote,
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit AlbumCoverLoaderResult(const Type _type = Type_None, const QUrl &_cover_url = QUrl(), const QImage &_image_original = QImage(), const QImage &_image_scaled = QImage(), const QImage &_image_thumbnail = QImage(), const bool _updated = false) : type(_type), cover_url(_cover_url), image_original(_image_original), image_scaled(_image_scaled), image_thumbnail(_image_thumbnail), updated(_updated) {}
|
explicit AlbumCoverLoaderResult(const bool _success = false,
|
||||||
|
const Type _type = Type_None,
|
||||||
|
const AlbumCoverImageResult &_album_cover = AlbumCoverImageResult(),
|
||||||
|
const QImage &_image_scaled = QImage(),
|
||||||
|
const QImage &_image_thumbnail = QImage(),
|
||||||
|
const bool _updated = false) :
|
||||||
|
success(_success),
|
||||||
|
type(_type),
|
||||||
|
album_cover(_album_cover),
|
||||||
|
image_scaled(_image_scaled),
|
||||||
|
image_thumbnail(_image_thumbnail),
|
||||||
|
updated(_updated) {}
|
||||||
|
|
||||||
|
bool success;
|
||||||
Type type;
|
Type type;
|
||||||
QUrl cover_url;
|
AlbumCoverImageResult album_cover;
|
||||||
QImage image_original;
|
|
||||||
QImage image_scaled;
|
QImage image_scaled;
|
||||||
QImage image_thumbnail;
|
QImage image_thumbnail;
|
||||||
bool updated;
|
bool updated;
|
||||||
|
|
|
@ -28,6 +28,9 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QFutureWatcher>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
#include <QItemSelectionModel>
|
#include <QItemSelectionModel>
|
||||||
|
@ -64,12 +67,15 @@
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/utilities.h"
|
#include "core/utilities.h"
|
||||||
|
#include "core/imageutils.h"
|
||||||
|
#include "core/tagreaderclient.h"
|
||||||
#include "widgets/forcescrollperpixel.h"
|
#include "widgets/forcescrollperpixel.h"
|
||||||
#include "widgets/qsearchfield.h"
|
#include "widgets/qsearchfield.h"
|
||||||
#include "collection/sqlrow.h"
|
#include "collection/sqlrow.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectionquery.h"
|
#include "collection/collectionquery.h"
|
||||||
#include "playlist/songmimedata.h"
|
#include "playlist/songmimedata.h"
|
||||||
|
#include "settings/collectionsettingspage.h"
|
||||||
#include "coverproviders.h"
|
#include "coverproviders.h"
|
||||||
#include "albumcovermanager.h"
|
#include "albumcovermanager.h"
|
||||||
#include "albumcoversearcher.h"
|
#include "albumcoversearcher.h"
|
||||||
|
@ -83,6 +89,7 @@
|
||||||
#include "albumcovermanagerlist.h"
|
#include "albumcovermanagerlist.h"
|
||||||
#include "coversearchstatistics.h"
|
#include "coversearchstatistics.h"
|
||||||
#include "coversearchstatisticsdialog.h"
|
#include "coversearchstatisticsdialog.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
|
|
||||||
#include "ui_albumcovermanager.h"
|
#include "ui_albumcovermanager.h"
|
||||||
|
|
||||||
|
@ -93,6 +100,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
||||||
ui_(new Ui_CoverManager),
|
ui_(new Ui_CoverManager),
|
||||||
mainwindow_(mainwindow),
|
mainwindow_(mainwindow),
|
||||||
app_(app),
|
app_(app),
|
||||||
|
collection_backend_(collection_backend),
|
||||||
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
||||||
cover_fetcher_(new AlbumCoverFetcher(app_->cover_providers(), this)),
|
cover_fetcher_(new AlbumCoverFetcher(app_->cover_providers(), this)),
|
||||||
cover_searcher_(nullptr),
|
cover_searcher_(nullptr),
|
||||||
|
@ -100,14 +108,13 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
||||||
cover_exporter_(new AlbumCoverExporter(this)),
|
cover_exporter_(new AlbumCoverExporter(this)),
|
||||||
artist_icon_(IconLoader::Load("folder-sound")),
|
artist_icon_(IconLoader::Load("folder-sound")),
|
||||||
all_artists_icon_(IconLoader::Load("library-music")),
|
all_artists_icon_(IconLoader::Load("library-music")),
|
||||||
no_cover_icon_(":/pictures/cdcase.png"),
|
image_nocover_thumbnail_(ImageUtils::GenerateNoCoverImage(QSize(120, 120))),
|
||||||
no_cover_image_(GenerateNoCoverImage(no_cover_icon_)),
|
icon_nocover_item_(QPixmap::fromImage(image_nocover_thumbnail_)),
|
||||||
no_cover_item_icon_(QPixmap::fromImage(no_cover_image_)),
|
|
||||||
context_menu_(new QMenu(this)),
|
context_menu_(new QMenu(this)),
|
||||||
progress_bar_(new QProgressBar(this)),
|
progress_bar_(new QProgressBar(this)),
|
||||||
abort_progress_(new QPushButton(this)),
|
abort_progress_(new QPushButton(this)),
|
||||||
jobs_(0),
|
jobs_(0),
|
||||||
collection_backend_(collection_backend) {
|
all_artists_(nullptr) {
|
||||||
|
|
||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
ui_->albums->set_cover_manager(this);
|
ui_->albums->set_cover_manager(this);
|
||||||
|
@ -122,7 +129,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
||||||
|
|
||||||
album_cover_choice_controller_->Init(app_);
|
album_cover_choice_controller_->Init(app_);
|
||||||
|
|
||||||
cover_searcher_ = new AlbumCoverSearcher(no_cover_item_icon_, app_, this);
|
cover_searcher_ = new AlbumCoverSearcher(icon_nocover_item_, app_, this);
|
||||||
cover_export_ = new AlbumCoverExport(this);
|
cover_export_ = new AlbumCoverExport(this);
|
||||||
|
|
||||||
// Set up the status bar
|
// Set up the status bar
|
||||||
|
@ -139,8 +146,15 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
||||||
QShortcut *close = new QShortcut(QKeySequence::Close, this);
|
QShortcut *close = new QShortcut(QKeySequence::Close, this);
|
||||||
QObject::connect(close, &QShortcut::activated, this, &AlbumCoverManager::close);
|
QObject::connect(close, &QShortcut::activated, this, &AlbumCoverManager::close);
|
||||||
|
|
||||||
|
cover_loader_options_.scale_output_image_ = true;
|
||||||
|
cover_loader_options_.pad_output_image_ = true;
|
||||||
|
cover_loader_options_.desired_height_ = 120;
|
||||||
|
cover_loader_options_.create_thumbnail_ = false;
|
||||||
|
|
||||||
EnableCoversButtons();
|
EnableCoversButtons();
|
||||||
|
|
||||||
|
ReloadSettings();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AlbumCoverManager::~AlbumCoverManager() {
|
AlbumCoverManager::~AlbumCoverManager() {
|
||||||
|
@ -151,11 +165,10 @@ AlbumCoverManager::~AlbumCoverManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverManager::ReloadSettings() {
|
void AlbumCoverManager::ReloadSettings() {
|
||||||
app_->album_cover_loader()->ReloadSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
CollectionBackend *AlbumCoverManager::backend() const {
|
app_->album_cover_loader()->ReloadSettings();
|
||||||
return collection_backend_;
|
album_cover_choice_controller_->ReloadSettings();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverManager::Init() {
|
void AlbumCoverManager::Init() {
|
||||||
|
@ -185,6 +198,8 @@ void AlbumCoverManager::Init() {
|
||||||
QObject::connect(album_cover_choice_controller_->cover_from_url_action(), &QAction::triggered, this, &AlbumCoverManager::LoadCoverFromURL);
|
QObject::connect(album_cover_choice_controller_->cover_from_url_action(), &QAction::triggered, this, &AlbumCoverManager::LoadCoverFromURL);
|
||||||
QObject::connect(album_cover_choice_controller_->search_for_cover_action(), &QAction::triggered, this, &AlbumCoverManager::SearchForCover);
|
QObject::connect(album_cover_choice_controller_->search_for_cover_action(), &QAction::triggered, this, &AlbumCoverManager::SearchForCover);
|
||||||
QObject::connect(album_cover_choice_controller_->unset_cover_action(), &QAction::triggered, this, &AlbumCoverManager::UnsetCover);
|
QObject::connect(album_cover_choice_controller_->unset_cover_action(), &QAction::triggered, this, &AlbumCoverManager::UnsetCover);
|
||||||
|
QObject::connect(album_cover_choice_controller_->clear_cover_action(), &QAction::triggered, this, &AlbumCoverManager::ClearCover);
|
||||||
|
QObject::connect(album_cover_choice_controller_->delete_cover_action(), &QAction::triggered, this, &AlbumCoverManager::DeleteCover);
|
||||||
QObject::connect(album_cover_choice_controller_->show_cover_action(), &QAction::triggered, this, &AlbumCoverManager::ShowCover);
|
QObject::connect(album_cover_choice_controller_->show_cover_action(), &QAction::triggered, this, &AlbumCoverManager::ShowCover);
|
||||||
|
|
||||||
QObject::connect(cover_exporter_, &AlbumCoverExporter::AlbumCoversExportUpdate, this, &AlbumCoverManager::UpdateExportStatus);
|
QObject::connect(cover_exporter_, &AlbumCoverExporter::AlbumCoversExportUpdate, this, &AlbumCoverManager::UpdateExportStatus);
|
||||||
|
@ -213,13 +228,19 @@ void AlbumCoverManager::Init() {
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
|
|
||||||
restoreGeometry(s.value("geometry").toByteArray());
|
if (s.contains("geometry")) {
|
||||||
if (!ui_->splitter->restoreState(s.value("splitter_state").toByteArray())) {
|
restoreGeometry(s.value("geometry").toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s.contains("splitter_state") || (s.contains("splitter_state") && !ui_->splitter->restoreState(s.value("splitter_state").toByteArray()))) {
|
||||||
// Sensible default size for the artists view
|
// Sensible default size for the artists view
|
||||||
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
|
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.endGroup();
|
||||||
|
|
||||||
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &AlbumCoverManager::AlbumCoverLoaded);
|
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &AlbumCoverManager::AlbumCoverLoaded);
|
||||||
|
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::SaveEmbeddedCoverAsyncFinished, this, &AlbumCoverManager::SaveEmbeddedCoverAsyncFinished);
|
||||||
|
|
||||||
cover_searcher_->Init(cover_fetcher_);
|
cover_searcher_->Init(cover_fetcher_);
|
||||||
|
|
||||||
|
@ -227,10 +248,12 @@ void AlbumCoverManager::Init() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverManager::showEvent(QShowEvent *) {
|
void AlbumCoverManager::showEvent(QShowEvent *e) {
|
||||||
|
|
||||||
LoadGeometry();
|
if (!e->spontaneous()) {
|
||||||
Reset();
|
LoadGeometry();
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +269,7 @@ void AlbumCoverManager::closeEvent(QCloseEvent *e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveGeometry();
|
SaveSettings();
|
||||||
|
|
||||||
// Cancel any outstanding requests
|
// Cancel any outstanding requests
|
||||||
CancelRequests();
|
CancelRequests();
|
||||||
|
@ -287,12 +310,13 @@ void AlbumCoverManager::LoadGeometry() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverManager::SaveGeometry() {
|
void AlbumCoverManager::SaveSettings() {
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
s.setValue("geometry", saveGeometry());
|
s.setValue("geometry", saveGeometry());
|
||||||
s.setValue("splitter_state", ui_->splitter->saveState());
|
s.setValue("splitter_state", ui_->splitter->saveState());
|
||||||
|
s.setValue("save_cover_type", album_cover_choice_controller_->get_save_album_cover_type());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -305,6 +329,8 @@ void AlbumCoverManager::CancelRequests() {
|
||||||
app_->album_cover_loader()->CancelTasks(QSet<quint64>::fromList(cover_loading_tasks_.keys()));
|
app_->album_cover_loader()->CancelTasks(QSet<quint64>::fromList(cover_loading_tasks_.keys()));
|
||||||
#endif
|
#endif
|
||||||
cover_loading_tasks_.clear();
|
cover_loading_tasks_.clear();
|
||||||
|
cover_save_tasks_.clear();
|
||||||
|
cover_save_tasks2_.clear();
|
||||||
|
|
||||||
cover_exporter_->Cancel();
|
cover_exporter_->Cancel();
|
||||||
|
|
||||||
|
@ -322,7 +348,7 @@ static bool CompareNocase(const QString &left, const QString &right) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool CompareAlbumNameNocase(const CollectionBackend::Album &left, const CollectionBackend::Album &right) {
|
static bool CompareAlbumNameNocase(const CollectionBackend::Album &left, const CollectionBackend::Album &right) {
|
||||||
return CompareNocase(left.album_name, right.album_name);
|
return CompareNocase(left.album, right.album);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverManager::Reset() {
|
void AlbumCoverManager::Reset() {
|
||||||
|
@ -330,8 +356,8 @@ void AlbumCoverManager::Reset() {
|
||||||
EnableCoversButtons();
|
EnableCoversButtons();
|
||||||
|
|
||||||
ui_->artists->clear();
|
ui_->artists->clear();
|
||||||
new QListWidgetItem(all_artists_icon_, tr("All artists"), ui_->artists, All_Artists);
|
all_artists_ = new QListWidgetItem(all_artists_icon_, tr("All artists"), ui_->artists, All_Artists);
|
||||||
new QListWidgetItem(artist_icon_, tr("Various artists"), ui_->artists, Various_Artists);
|
new AlbumItem(artist_icon_, tr("Various artists"), ui_->artists, Various_Artists);
|
||||||
|
|
||||||
QStringList artists(collection_backend_->GetAllArtistsWithAlbums());
|
QStringList artists(collection_backend_->GetAllArtistsWithAlbums());
|
||||||
std::stable_sort(artists.begin(), artists.end(), CompareNocase);
|
std::stable_sort(artists.begin(), artists.end(), CompareNocase);
|
||||||
|
@ -341,6 +367,10 @@ void AlbumCoverManager::Reset() {
|
||||||
new QListWidgetItem(artist_icon_, artist, ui_->artists, Specific_Artist);
|
new QListWidgetItem(artist_icon_, artist, ui_->artists, Specific_Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ui_->artists->selectedItems().isEmpty()) {
|
||||||
|
ui_->artists->selectionModel()->setCurrentIndex(ui_->artists->indexFromItem(all_artists_), QItemSelectionModel::Clear);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverManager::EnableCoversButtons() {
|
void AlbumCoverManager::EnableCoversButtons() {
|
||||||
|
@ -357,9 +387,6 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
|
||||||
|
|
||||||
if (!current) return;
|
if (!current) return;
|
||||||
|
|
||||||
QString artist;
|
|
||||||
if (current->type() == Specific_Artist) artist = current->text();
|
|
||||||
|
|
||||||
ui_->albums->clear();
|
ui_->albums->clear();
|
||||||
context_menu_items_.clear();
|
context_menu_items_.clear();
|
||||||
CancelRequests();
|
CancelRequests();
|
||||||
|
@ -377,32 +404,42 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
|
||||||
std::stable_sort(albums.begin(), albums.end(), CompareAlbumNameNocase);
|
std::stable_sort(albums.begin(), albums.end(), CompareAlbumNameNocase);
|
||||||
|
|
||||||
for (const CollectionBackend::Album &info : albums) {
|
for (const CollectionBackend::Album &info : albums) {
|
||||||
|
|
||||||
// Don't show songs without an album, obviously
|
// Don't show songs without an album, obviously
|
||||||
if (info.album_name.isEmpty()) continue;
|
if (info.album.isEmpty()) continue;
|
||||||
|
|
||||||
QListWidgetItem *item = new QListWidgetItem(no_cover_item_icon_, info.album_name, ui_->albums);
|
QString display_text;
|
||||||
item->setData(Role_ArtistName, info.artist);
|
|
||||||
item->setData(Role_AlbumArtistName, info.album_artist);
|
|
||||||
item->setData(Role_AlbumName, info.album_name);
|
|
||||||
item->setData(Role_FirstUrl, info.first_url);
|
|
||||||
item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter));
|
|
||||||
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
|
|
||||||
item->setToolTip(info.artist + " - " + info.album_name);
|
|
||||||
|
|
||||||
QString effective_artist = EffectiveAlbumArtistName(*item);
|
if (current->type() == Specific_Artist) {
|
||||||
if (!artist.isEmpty()) {
|
display_text = info.album;
|
||||||
item->setToolTip(effective_artist + " - " + info.album_name);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
item->setToolTip(info.album_name);
|
display_text = info.album_artist + " - " + info.album;
|
||||||
|
}
|
||||||
|
|
||||||
|
AlbumItem *item = new AlbumItem(icon_nocover_item_, display_text, ui_->albums);
|
||||||
|
item->setData(Role_AlbumArtist, info.album_artist);
|
||||||
|
item->setData(Role_Album, info.album);
|
||||||
|
item->setData(Role_Filetype, info.filetype);
|
||||||
|
item->setData(Role_CuePath, info.cue_path);
|
||||||
|
item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter));
|
||||||
|
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
|
||||||
|
item->urls = info.urls;
|
||||||
|
|
||||||
|
if (info.album_artist.isEmpty()) {
|
||||||
|
item->setToolTip(info.album);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item->setToolTip(info.album_artist + " - " + info.album);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) {
|
if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) {
|
||||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.first_url);
|
|
||||||
item->setData(Role_PathAutomatic, info.art_automatic);
|
item->setData(Role_PathAutomatic, info.art_automatic);
|
||||||
item->setData(Role_PathManual, info.art_manual);
|
item->setData(Role_PathManual, info.art_manual);
|
||||||
|
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.urls.first());
|
||||||
cover_loading_tasks_[id] = item;
|
cover_loading_tasks_[id] = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateFilter();
|
UpdateFilter();
|
||||||
|
@ -413,11 +450,18 @@ void AlbumCoverManager::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoade
|
||||||
|
|
||||||
if (!cover_loading_tasks_.contains(id)) return;
|
if (!cover_loading_tasks_.contains(id)) return;
|
||||||
|
|
||||||
QListWidgetItem *item = cover_loading_tasks_.take(id);
|
AlbumItem *item = cover_loading_tasks_.take(id);
|
||||||
|
|
||||||
if (result.image_scaled.isNull()) return;
|
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type_ManuallyUnset) {
|
||||||
|
item->setIcon(icon_nocover_item_);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item->setIcon(QPixmap::fromImage(result.image_scaled));
|
||||||
|
}
|
||||||
|
|
||||||
|
//item->setData(Role_Image, result.image_original);
|
||||||
|
//item->setData(Role_ImageData, result.image_data);
|
||||||
|
|
||||||
item->setIcon(QPixmap::fromImage(result.image_scaled));
|
|
||||||
UpdateFilter();
|
UpdateFilter();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -440,14 +484,14 @@ void AlbumCoverManager::UpdateFilter() {
|
||||||
qint32 without_cover = 0;
|
qint32 without_cover = 0;
|
||||||
|
|
||||||
for (int i = 0; i < ui_->albums->count(); ++i) {
|
for (int i = 0; i < ui_->albums->count(); ++i) {
|
||||||
QListWidgetItem *item = ui_->albums->item(i);
|
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i));
|
||||||
bool should_hide = ShouldHide(*item, filter, hide);
|
bool should_hide = ShouldHide(*item, filter, hide);
|
||||||
item->setHidden(should_hide);
|
item->setHidden(should_hide);
|
||||||
|
|
||||||
if (!should_hide) {
|
if (!should_hide) {
|
||||||
total_count++;
|
++total_count;
|
||||||
if (!ItemHasCover(*item)) {
|
if (!ItemHasCover(*item)) {
|
||||||
without_cover++;
|
++without_cover;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -457,7 +501,7 @@ void AlbumCoverManager::UpdateFilter() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AlbumCoverManager::ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const {
|
bool AlbumCoverManager::ShouldHide(const AlbumItem &item, const QString &filter, HideCovers hide) const {
|
||||||
|
|
||||||
bool has_cover = ItemHasCover(item);
|
bool has_cover = ItemHasCover(item);
|
||||||
if (hide == Hide_WithCovers && has_cover) {
|
if (hide == Hide_WithCovers && has_cover) {
|
||||||
|
@ -474,9 +518,8 @@ bool AlbumCoverManager::ShouldHide(const QListWidgetItem &item, const QString &f
|
||||||
QStringList query = filter.split(' ');
|
QStringList query = filter.split(' ');
|
||||||
for (const QString &s : query) {
|
for (const QString &s : query) {
|
||||||
bool in_text = item.text().contains(s, Qt::CaseInsensitive);
|
bool in_text = item.text().contains(s, Qt::CaseInsensitive);
|
||||||
bool in_artist = item.data(Role_ArtistName).toString().contains(s, Qt::CaseInsensitive);
|
bool in_albumartist = item.data(Role_AlbumArtist).toString().contains(s, Qt::CaseInsensitive);
|
||||||
bool in_albumartist = item.data(Role_AlbumArtistName).toString().contains(s, Qt::CaseInsensitive);
|
if (!in_text && !in_albumartist) {
|
||||||
if (!in_text && !in_artist && !in_albumartist) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -488,11 +531,11 @@ bool AlbumCoverManager::ShouldHide(const QListWidgetItem &item, const QString &f
|
||||||
void AlbumCoverManager::FetchAlbumCovers() {
|
void AlbumCoverManager::FetchAlbumCovers() {
|
||||||
|
|
||||||
for (int i = 0; i < ui_->albums->count(); ++i) {
|
for (int i = 0; i < ui_->albums->count(); ++i) {
|
||||||
QListWidgetItem *item = ui_->albums->item(i);
|
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i));
|
||||||
if (item->isHidden()) continue;
|
if (item->isHidden()) continue;
|
||||||
if (ItemHasCover(*item)) continue;
|
if (ItemHasCover(*item)) continue;
|
||||||
|
|
||||||
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), true);
|
quint64 id = cover_fetcher_->FetchAlbumCover(item->data(Role_AlbumArtist).toString(), item->data(Role_Album).toString(), QString(), true);
|
||||||
cover_fetching_tasks_[id] = item;
|
cover_fetching_tasks_[id] = item;
|
||||||
jobs_++;
|
jobs_++;
|
||||||
}
|
}
|
||||||
|
@ -507,14 +550,13 @@ void AlbumCoverManager::FetchAlbumCovers() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverManager::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
|
void AlbumCoverManager::AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics) {
|
||||||
|
|
||||||
if (!cover_fetching_tasks_.contains(id))
|
if (!cover_fetching_tasks_.contains(id)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
QListWidgetItem *item = cover_fetching_tasks_.take(id);
|
AlbumItem *item = cover_fetching_tasks_.take(id);
|
||||||
if (!image.isNull()) {
|
if (!result.image.isNull()) {
|
||||||
SaveAndSetCover(item, cover_url, image);
|
SaveAndSetCover(item, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cover_fetching_tasks_.isEmpty()) {
|
if (cover_fetching_tasks_.isEmpty()) {
|
||||||
|
@ -554,29 +596,34 @@ void AlbumCoverManager::UpdateStatusText() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *event) {
|
bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *e) {
|
||||||
|
|
||||||
if (obj == ui_->albums && event->type() == QEvent::ContextMenu) {
|
if (obj == ui_->albums && e->type() == QEvent::ContextMenu) {
|
||||||
context_menu_items_ = ui_->albums->selectedItems();
|
context_menu_items_ = ui_->albums->selectedItems();
|
||||||
if (context_menu_items_.isEmpty()) return false;
|
if (context_menu_items_.isEmpty()) return false;
|
||||||
|
|
||||||
bool some_with_covers = false;
|
bool some_with_covers = false;
|
||||||
|
|
||||||
for (QListWidgetItem *item : context_menu_items_) {
|
for (QListWidgetItem *item : context_menu_items_) {
|
||||||
if (ItemHasCover(*item)) some_with_covers = true;
|
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||||
|
if (ItemHasCover(*album_item)) some_with_covers = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
album_cover_choice_controller_->show_cover_action()->setEnabled(some_with_covers && context_menu_items_.size() == 1);
|
||||||
|
album_cover_choice_controller_->cover_to_file_action()->setEnabled(some_with_covers);
|
||||||
album_cover_choice_controller_->cover_from_file_action()->setEnabled(context_menu_items_.size() == 1);
|
album_cover_choice_controller_->cover_from_file_action()->setEnabled(context_menu_items_.size() == 1);
|
||||||
album_cover_choice_controller_->cover_from_url_action()->setEnabled(context_menu_items_.size() == 1);
|
album_cover_choice_controller_->cover_from_url_action()->setEnabled(context_menu_items_.size() == 1);
|
||||||
album_cover_choice_controller_->show_cover_action()->setEnabled(some_with_covers && context_menu_items_.size() == 1);
|
|
||||||
album_cover_choice_controller_->unset_cover_action()->setEnabled(some_with_covers);
|
|
||||||
album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders());
|
album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders());
|
||||||
|
album_cover_choice_controller_->unset_cover_action()->setEnabled(some_with_covers);
|
||||||
|
album_cover_choice_controller_->clear_cover_action()->setEnabled(some_with_covers);
|
||||||
|
album_cover_choice_controller_->delete_cover_action()->setEnabled(some_with_covers);
|
||||||
|
|
||||||
QContextMenuEvent *e = static_cast<QContextMenuEvent*>(event);
|
QContextMenuEvent *context_menu_event = static_cast<QContextMenuEvent*>(e);
|
||||||
context_menu_->popup(e->globalPos());
|
context_menu_->popup(context_menu_event->globalPos());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return QMainWindow::eventFilter(obj, event);
|
return QMainWindow::eventFilter(obj, e);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Song AlbumCoverManager::GetSingleSelectionAsSong() {
|
Song AlbumCoverManager::GetSingleSelectionAsSong() {
|
||||||
|
@ -587,12 +634,12 @@ Song AlbumCoverManager::GetFirstSelectedAsSong() {
|
||||||
return context_menu_items_.isEmpty() ? Song() : ItemAsSong(context_menu_items_[0]);
|
return context_menu_items_.isEmpty() ? Song() : ItemAsSong(context_menu_items_[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) {
|
Song AlbumCoverManager::ItemAsSong(AlbumItem *item) {
|
||||||
|
|
||||||
Song result(Song::Source_Collection);
|
Song result(Song::Source_Collection);
|
||||||
|
|
||||||
QString title = item->data(Role_AlbumName).toString();
|
QString title = item->data(Role_Album).toString();
|
||||||
QString artist_name = EffectiveAlbumArtistName(*item);
|
QString artist_name = item->data(Role_AlbumArtist).toString();
|
||||||
if (!artist_name.isEmpty()) {
|
if (!artist_name.isEmpty()) {
|
||||||
result.set_title(artist_name + " - " + title);
|
result.set_title(artist_name + " - " + title);
|
||||||
}
|
}
|
||||||
|
@ -600,11 +647,13 @@ Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) {
|
||||||
result.set_title(title);
|
result.set_title(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.set_artist(item->data(Role_ArtistName).toString());
|
result.set_artist(item->data(Role_AlbumArtist).toString());
|
||||||
result.set_albumartist(item->data(Role_AlbumArtistName).toString());
|
result.set_albumartist(item->data(Role_AlbumArtist).toString());
|
||||||
result.set_album(item->data(Role_AlbumName).toString());
|
result.set_album(item->data(Role_Album).toString());
|
||||||
|
|
||||||
result.set_url(item->data(Role_FirstUrl).toUrl());
|
result.set_filetype(Song::FileType(item->data(Role_Filetype).toInt()));
|
||||||
|
result.set_url(item->urls.first());
|
||||||
|
result.set_cue_path(item->data(Role_CuePath).toString());
|
||||||
|
|
||||||
result.set_art_automatic(item->data(Role_PathAutomatic).toUrl());
|
result.set_art_automatic(item->data(Role_PathAutomatic).toUrl());
|
||||||
result.set_art_manual(item->data(Role_PathManual).toUrl());
|
result.set_art_manual(item->data(Role_PathManual).toUrl());
|
||||||
|
@ -628,8 +677,9 @@ void AlbumCoverManager::ShowCover() {
|
||||||
void AlbumCoverManager::FetchSingleCover() {
|
void AlbumCoverManager::FetchSingleCover() {
|
||||||
|
|
||||||
for (QListWidgetItem *item : context_menu_items_) {
|
for (QListWidgetItem *item : context_menu_items_) {
|
||||||
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), false);
|
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||||
cover_fetching_tasks_[id] = item;
|
quint64 id = cover_fetcher_->FetchAlbumCover(album_item->data(Role_AlbumArtist).toString(), album_item->data(Role_Album).toString(), QString(), false);
|
||||||
|
cover_fetching_tasks_[id] = album_item;
|
||||||
jobs_++;
|
jobs_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,7 +690,7 @@ void AlbumCoverManager::FetchSingleCover() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverManager::UpdateCoverInList(QListWidgetItem *item, const QUrl &cover_url) {
|
void AlbumCoverManager::UpdateCoverInList(AlbumItem *item, const QUrl &cover_url) {
|
||||||
|
|
||||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), cover_url);
|
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), cover_url);
|
||||||
item->setData(Role_PathManual, cover_url);
|
item->setData(Role_PathManual, cover_url);
|
||||||
|
@ -653,12 +703,9 @@ void AlbumCoverManager::LoadCoverFromFile() {
|
||||||
Song song = GetSingleSelectionAsSong();
|
Song song = GetSingleSelectionAsSong();
|
||||||
if (!song.is_valid()) return;
|
if (!song.is_valid()) return;
|
||||||
|
|
||||||
QListWidgetItem *item = context_menu_items_[0];
|
AlbumCoverImageResult result = album_cover_choice_controller_->LoadImageFromFile(&song);
|
||||||
|
if (!result.image.isNull()) {
|
||||||
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromFile(&song);
|
SaveImageToAlbums(&song, result);
|
||||||
|
|
||||||
if (!cover_url.isEmpty()) {
|
|
||||||
UpdateCoverInList(item, cover_url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -666,33 +713,37 @@ void AlbumCoverManager::LoadCoverFromFile() {
|
||||||
void AlbumCoverManager::SaveCoverToFile() {
|
void AlbumCoverManager::SaveCoverToFile() {
|
||||||
|
|
||||||
Song song = GetSingleSelectionAsSong();
|
Song song = GetSingleSelectionAsSong();
|
||||||
if (!song.is_valid()) return;
|
if (!song.is_valid() || song.has_manually_unset_cover()) return;
|
||||||
|
|
||||||
QImage image;
|
AlbumCoverImageResult result;
|
||||||
|
|
||||||
// Load the image from disk
|
// Load the image from disk
|
||||||
if (song.has_manually_unset_cover()) {
|
|
||||||
image = no_cover_image_;
|
if (!song.art_manual().isEmpty() && !song.has_manually_unset_cover() && song.art_manual().isLocalFile() && QFile::exists(song.art_manual().toLocalFile())) {
|
||||||
|
result.image_data = Utilities::ReadDataFromFile(song.art_manual().toLocalFile());
|
||||||
}
|
}
|
||||||
else {
|
else if (!song.art_manual().isEmpty() && !song.art_manual().path().isEmpty() && song.art_manual().scheme().isEmpty() && QFile::exists(song.art_manual().path())) {
|
||||||
if (!song.art_manual().isEmpty() && song.art_manual().isLocalFile() && QFile::exists(song.art_manual().toLocalFile())) {
|
result.image_data = Utilities::ReadDataFromFile(song.art_manual().path());
|
||||||
image = QImage(song.art_manual().toLocalFile());
|
}
|
||||||
}
|
else if (song.has_embedded_cover()) {
|
||||||
else if (!song.art_manual().isEmpty() && !song.art_manual().path().isEmpty() && song.art_manual().scheme().isEmpty() && QFile::exists(song.art_manual().path())) {
|
result.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile());
|
||||||
image = QImage(song.art_manual().path());
|
}
|
||||||
}
|
else if (!song.art_automatic().isEmpty() && song.art_automatic().isLocalFile() && QFile::exists(song.art_automatic().toLocalFile())) {
|
||||||
else if (!song.art_automatic().isEmpty() && song.art_automatic().isLocalFile() && QFile::exists(song.art_automatic().toLocalFile())) {
|
result.image_data = Utilities::ReadDataFromFile(song.art_automatic().toLocalFile());
|
||||||
image = QImage(song.art_automatic().toLocalFile());
|
}
|
||||||
}
|
else if (!song.art_automatic().isEmpty() && !song.art_automatic().path().isEmpty() && song.art_automatic().scheme().isEmpty() && QFile::exists(song.art_automatic().path())) {
|
||||||
else if (!song.art_automatic().isEmpty() && !song.art_automatic().path().isEmpty() && song.art_automatic().scheme().isEmpty() && QFile::exists(song.art_automatic().path())) {
|
result.image_data = Utilities::ReadDataFromFile(song.art_automatic().path());
|
||||||
image = QImage(song.art_automatic().path());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
image = no_cover_image_;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
album_cover_choice_controller_->SaveCoverToFileManual(song, image);
|
if (!result.is_valid()) return;
|
||||||
|
|
||||||
|
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
|
||||||
|
|
||||||
|
if (!result.image_data.isEmpty()) {
|
||||||
|
result.image.loadFromData(result.image_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
album_cover_choice_controller_->SaveCoverToFileManual(song, result);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -701,12 +752,9 @@ void AlbumCoverManager::LoadCoverFromURL() {
|
||||||
Song song = GetSingleSelectionAsSong();
|
Song song = GetSingleSelectionAsSong();
|
||||||
if (!song.is_valid()) return;
|
if (!song.is_valid()) return;
|
||||||
|
|
||||||
QListWidgetItem *item = context_menu_items_[0];
|
AlbumCoverImageResult result = album_cover_choice_controller_->LoadImageFromURL();
|
||||||
|
if (result.is_valid()) {
|
||||||
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromURL(&song);
|
SaveImageToAlbums(&song, result);
|
||||||
|
|
||||||
if (!cover_url.isEmpty()) {
|
|
||||||
UpdateCoverInList(item, cover_url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -716,20 +764,58 @@ void AlbumCoverManager::SearchForCover() {
|
||||||
Song song = GetFirstSelectedAsSong();
|
Song song = GetFirstSelectedAsSong();
|
||||||
if (!song.is_valid()) return;
|
if (!song.is_valid()) return;
|
||||||
|
|
||||||
QListWidgetItem *item = context_menu_items_[0];
|
AlbumCoverImageResult result = album_cover_choice_controller_->SearchForImage(&song);
|
||||||
|
if (result.is_valid()) {
|
||||||
|
SaveImageToAlbums(&song, result);
|
||||||
|
}
|
||||||
|
|
||||||
QUrl cover_url = album_cover_choice_controller_->SearchForCover(&song);
|
}
|
||||||
if (cover_url.isEmpty()) return;
|
|
||||||
|
void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResult &result) {
|
||||||
|
|
||||||
|
QUrl cover_url;
|
||||||
|
switch (album_cover_choice_controller_->get_save_album_cover_type()) {
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Cache:
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Album:
|
||||||
|
cover_url = album_cover_choice_controller_->SaveCoverToFileAutomatic(song, result);
|
||||||
|
break;
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Embedded:
|
||||||
|
cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Force the found cover on all of the selected items
|
// Force the found cover on all of the selected items
|
||||||
for (QListWidgetItem *current : context_menu_items_) {
|
QList<QUrl> urls;
|
||||||
// Don't save the first one twice
|
QList<AlbumItem*> album_items;
|
||||||
if (current != item) {
|
for (QListWidgetItem *item : context_menu_items_) {
|
||||||
Song current_song = ItemAsSong(current);
|
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||||
album_cover_choice_controller_->SaveCoverToSong(¤t_song, cover_url);
|
switch (album_cover_choice_controller_->get_save_album_cover_type()) {
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Cache:
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Album:{
|
||||||
|
Song current_song = ItemAsSong(album_item);
|
||||||
|
album_cover_choice_controller_->SaveArtManualToSong(¤t_song, cover_url);
|
||||||
|
UpdateCoverInList(album_item, cover_url);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CollectionSettingsPage::SaveCoverType_Embedded:{
|
||||||
|
urls << album_item->urls;
|
||||||
|
album_items << album_item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UpdateCoverInList(current, cover_url);
|
if (album_cover_choice_controller_->get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType_Embedded && !urls.isEmpty()) {
|
||||||
|
quint64 id = -1;
|
||||||
|
if (result.is_jpeg()) {
|
||||||
|
id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image_data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
|
||||||
|
}
|
||||||
|
for (AlbumItem *album_item : album_items) {
|
||||||
|
cover_save_tasks_.insert(id, album_item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -739,19 +825,68 @@ void AlbumCoverManager::UnsetCover() {
|
||||||
Song song = GetFirstSelectedAsSong();
|
Song song = GetFirstSelectedAsSong();
|
||||||
if (!song.is_valid()) return;
|
if (!song.is_valid()) return;
|
||||||
|
|
||||||
QListWidgetItem *item = context_menu_items_[0];
|
AlbumItem *first_album_item = static_cast<AlbumItem*>(context_menu_items_[0]);
|
||||||
|
|
||||||
QUrl cover_url = album_cover_choice_controller_->UnsetCover(&song);
|
QUrl cover_url = album_cover_choice_controller_->UnsetCover(&song);
|
||||||
|
|
||||||
// Force the 'none' cover on all of the selected items
|
// Force the 'none' cover on all of the selected items
|
||||||
for (QListWidgetItem *current : context_menu_items_) {
|
for (QListWidgetItem *item : context_menu_items_) {
|
||||||
current->setIcon(no_cover_item_icon_);
|
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||||
current->setData(Role_PathManual, cover_url);
|
album_item->setIcon(icon_nocover_item_);
|
||||||
|
album_item->setData(Role_PathManual, cover_url);
|
||||||
|
|
||||||
// Don't save the first one twice
|
// Don't save the first one twice
|
||||||
if (current != item) {
|
if (album_item != first_album_item) {
|
||||||
Song current_song = ItemAsSong(current);
|
Song current_song = ItemAsSong(album_item);
|
||||||
album_cover_choice_controller_->SaveCoverToSong(¤t_song, cover_url);
|
album_cover_choice_controller_->SaveArtManualToSong(¤t_song, cover_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverManager::ClearCover() {
|
||||||
|
|
||||||
|
Song song = GetFirstSelectedAsSong();
|
||||||
|
if (!song.is_valid()) return;
|
||||||
|
|
||||||
|
AlbumItem *first_album_item = static_cast<AlbumItem*>(context_menu_items_[0]);
|
||||||
|
|
||||||
|
album_cover_choice_controller_->ClearCover(&song);
|
||||||
|
|
||||||
|
// Force the 'none' cover on all of the selected items
|
||||||
|
for (QListWidgetItem *item : context_menu_items_) {
|
||||||
|
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||||
|
album_item->setIcon(icon_nocover_item_);
|
||||||
|
album_item->setData(Role_PathManual, QUrl());
|
||||||
|
|
||||||
|
// Don't save the first one twice
|
||||||
|
if (album_item != first_album_item) {
|
||||||
|
Song current_song = ItemAsSong(album_item);
|
||||||
|
album_cover_choice_controller_->SaveArtManualToSong(¤t_song, QUrl(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverManager::DeleteCover() {
|
||||||
|
|
||||||
|
Song song = GetFirstSelectedAsSong();
|
||||||
|
if (!song.is_valid()) return;
|
||||||
|
|
||||||
|
AlbumItem *first_album_item = static_cast<AlbumItem*>(context_menu_items_[0]);
|
||||||
|
|
||||||
|
album_cover_choice_controller_->DeleteCover(&song);
|
||||||
|
|
||||||
|
// Force the 'none' cover on all of the selected items
|
||||||
|
for (QListWidgetItem *item : context_menu_items_) {
|
||||||
|
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||||
|
album_item->setIcon(icon_nocover_item_);
|
||||||
|
album_item->setData(Role_PathManual, QUrl());
|
||||||
|
|
||||||
|
// Don't save the first one twice
|
||||||
|
if (album_item != first_album_item) {
|
||||||
|
Song current_song = ItemAsSong(album_item);
|
||||||
|
album_cover_choice_controller_->SaveArtManualToSong(¤t_song, QUrl(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -763,20 +898,15 @@ SongList AlbumCoverManager::GetSongsInAlbum(const QModelIndex &idx) const {
|
||||||
|
|
||||||
CollectionQuery q;
|
CollectionQuery q;
|
||||||
q.SetColumnSpec("ROWID," + Song::kColumnSpec);
|
q.SetColumnSpec("ROWID," + Song::kColumnSpec);
|
||||||
q.AddWhere("album", idx.data(Role_AlbumName).toString());
|
q.AddWhere("album", idx.data(Role_Album).toString());
|
||||||
q.SetOrderBy("disc, track, title");
|
q.SetOrderBy("disc, track, title");
|
||||||
|
|
||||||
QString artist = idx.data(Role_ArtistName).toString();
|
QString albumartist = idx.data(Role_AlbumArtist).toString();
|
||||||
QString albumartist = idx.data(Role_AlbumArtistName).toString();
|
|
||||||
|
|
||||||
if (!albumartist.isEmpty()) {
|
if (!albumartist.isEmpty()) {
|
||||||
q.AddWhere("albumartist", albumartist);
|
q.AddWhere("effective_albumartist", albumartist);
|
||||||
}
|
|
||||||
else if (!artist.isEmpty()) {
|
|
||||||
q.AddWhere("artist", artist);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
q.AddCompilationRequirement(artist.isEmpty() && albumartist.isEmpty());
|
q.AddCompilationRequirement(albumartist.isEmpty());
|
||||||
|
|
||||||
if (!collection_backend_->ExecQuery(&q)) return ret;
|
if (!collection_backend_->ExecQuery(&q)) return ret;
|
||||||
|
|
||||||
|
@ -813,7 +943,7 @@ SongMimeData *AlbumCoverManager::GetMimeDataForAlbums(const QModelIndexList &ind
|
||||||
|
|
||||||
void AlbumCoverManager::AlbumDoubleClicked(const QModelIndex &idx) {
|
void AlbumCoverManager::AlbumDoubleClicked(const QModelIndex &idx) {
|
||||||
|
|
||||||
QListWidgetItem *item = static_cast<QListWidgetItem*>(idx.internalPointer());
|
AlbumItem *item = static_cast<AlbumItem*>(idx.internalPointer());
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
album_cover_choice_controller_->ShowCover(ItemAsSong(item));
|
album_cover_choice_controller_->ShowCover(ItemAsSong(item));
|
||||||
|
|
||||||
|
@ -833,23 +963,35 @@ void AlbumCoverManager::LoadSelectedToPlaylist() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverManager::SaveAndSetCover(QListWidgetItem *item, const QUrl &cover_url, const QImage &image) {
|
void AlbumCoverManager::SaveAndSetCover(AlbumItem *item, const AlbumCoverImageResult &result) {
|
||||||
|
|
||||||
const QString artist = item->data(Role_ArtistName).toString();
|
const QString albumartist = item->data(Role_AlbumArtist).toString();
|
||||||
const QString albumartist = item->data(Role_AlbumArtistName).toString();
|
const QString album = item->data(Role_Album).toString();
|
||||||
const QString album = item->data(Role_AlbumName).toString();
|
const QList<QUrl> &urls = item->urls;
|
||||||
const QUrl url = item->data(Role_FirstUrl).toUrl();
|
const Song::FileType filetype = Song::FileType(item->data(Role_Filetype).toInt());
|
||||||
|
const bool has_cue = !item->data(Role_CuePath).toString().isEmpty();
|
||||||
|
|
||||||
QUrl new_cover_url = album_cover_choice_controller_->SaveCoverToFileAutomatic(Song::Source_Collection, (!albumartist.isEmpty() ? albumartist : artist), album, QString(), url.adjusted(QUrl::RemoveFilename).path(), cover_url, image, false);
|
if (album_cover_choice_controller_->get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType_Embedded && Song::save_embedded_cover_supported(filetype) && !has_cue) {
|
||||||
if (new_cover_url.isEmpty()) return;
|
if (result.is_jpeg()) {
|
||||||
|
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image_data);
|
||||||
|
cover_save_tasks_.insert(id, item);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
|
||||||
|
cover_save_tasks_.insert(id, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
QUrl cover_url = album_cover_choice_controller_->SaveCoverToFileAutomatic(Song::Source_Collection, albumartist, album, QString(), urls.first().adjusted(QUrl::RemoveFilename).path(), result, false);
|
||||||
|
|
||||||
// Save the image in the database
|
if (cover_url.isEmpty()) return;
|
||||||
collection_backend_->UpdateManualAlbumArtAsync(artist, albumartist, album, new_cover_url);
|
|
||||||
|
|
||||||
// Update the icon in our list
|
// Save the image in the database
|
||||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), new_cover_url);
|
collection_backend_->UpdateManualAlbumArtAsync(albumartist, album, cover_url);
|
||||||
item->setData(Role_PathManual, new_cover_url);
|
|
||||||
cover_loading_tasks_[id] = item;
|
// Update the icon in our list
|
||||||
|
UpdateCoverInList(item, cover_url);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -866,7 +1008,7 @@ void AlbumCoverManager::ExportCovers() {
|
||||||
cover_exporter_->SetDialogResult(result);
|
cover_exporter_->SetDialogResult(result);
|
||||||
|
|
||||||
for (int i = 0; i < ui_->albums->count(); ++i) {
|
for (int i = 0; i < ui_->albums->count(); ++i) {
|
||||||
QListWidgetItem *item = ui_->albums->item(i);
|
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i));
|
||||||
|
|
||||||
// skip hidden and coverless albums
|
// skip hidden and coverless albums
|
||||||
if (item->isHidden() || !ItemHasCover(*item)) {
|
if (item->isHidden() || !ItemHasCover(*item)) {
|
||||||
|
@ -918,31 +1060,36 @@ void AlbumCoverManager::UpdateExportStatus(const int exported, const int skipped
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AlbumCoverManager::EffectiveAlbumArtistName(const QListWidgetItem &item) const {
|
QImage AlbumCoverManager::GenerateNoCoverImage(const QImage &image) const {
|
||||||
QString albumartist = item.data(Role_AlbumArtistName).toString();
|
|
||||||
if (!albumartist.isEmpty()) {
|
|
||||||
return albumartist;
|
|
||||||
}
|
|
||||||
return item.data(Role_ArtistName).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage AlbumCoverManager::GenerateNoCoverImage(const QIcon &no_cover_icon) const {
|
// Get a square version of the nocover image with some transparency:
|
||||||
|
QImage image_scaled = image.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
|
||||||
// Get a square version of cdcase.png with some transparency:
|
QImage image_square(120, 120, QImage::Format_ARGB32);
|
||||||
QImage nocover = no_cover_icon.pixmap(no_cover_icon.availableSizes().last()).toImage();
|
image_square.fill(0);
|
||||||
nocover = nocover.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
QPainter p(&image_square);
|
||||||
|
|
||||||
QImage square_nocover(120, 120, QImage::Format_ARGB32);
|
|
||||||
square_nocover.fill(0);
|
|
||||||
QPainter p(&square_nocover);
|
|
||||||
p.setOpacity(0.4);
|
p.setOpacity(0.4);
|
||||||
p.drawImage((120 - nocover.width()) / 2, (120 - nocover.height()) / 2, nocover);
|
p.drawImage((120 - image_scaled.width()) / 2, (120 - image_scaled.height()) / 2, image_scaled);
|
||||||
p.end();
|
p.end();
|
||||||
|
|
||||||
return square_nocover;
|
return image_square;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AlbumCoverManager::ItemHasCover(const QListWidgetItem &item) const {
|
bool AlbumCoverManager::ItemHasCover(const AlbumItem &item) const {
|
||||||
return item.icon().cacheKey() != no_cover_item_icon_.cacheKey();
|
return item.icon().cacheKey() != icon_nocover_item_.cacheKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlbumCoverManager::SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success) {
|
||||||
|
|
||||||
|
while (cover_save_tasks_.contains(id)) {
|
||||||
|
AlbumItem *album_item = cover_save_tasks_.take(id);
|
||||||
|
if (!success) continue;
|
||||||
|
album_item->setData(Role_PathAutomatic, QUrl::fromLocalFile(Song::kEmbeddedCover));
|
||||||
|
Song song = ItemAsSong(album_item);
|
||||||
|
album_cover_choice_controller_->SaveArtAutomaticToSong(&song, QUrl::fromLocalFile(Song::kEmbeddedCover));
|
||||||
|
quint64 cover_load_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, album_item->data(Role_PathAutomatic).toUrl(), album_item->data(Role_PathManual).toUrl(), album_item->urls.first());
|
||||||
|
cover_loading_tasks_[cover_load_id] = album_item;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QListWidgetItem>
|
#include <QListWidgetItem>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <QMultiMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
@ -38,7 +39,9 @@
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
#include "albumcoverloaderresult.h"
|
#include "albumcoverloaderresult.h"
|
||||||
|
#include "albumcoverchoicecontroller.h"
|
||||||
#include "coversearchstatistics.h"
|
#include "coversearchstatistics.h"
|
||||||
|
#include "settings/collectionsettingspage.h"
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
class QMimeData;
|
class QMimeData;
|
||||||
|
@ -53,7 +56,6 @@ class QShowEvent;
|
||||||
class Application;
|
class Application;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class SongMimeData;
|
class SongMimeData;
|
||||||
class AlbumCoverChoiceController;
|
|
||||||
class AlbumCoverExport;
|
class AlbumCoverExport;
|
||||||
class AlbumCoverExporter;
|
class AlbumCoverExporter;
|
||||||
class AlbumCoverFetcher;
|
class AlbumCoverFetcher;
|
||||||
|
@ -61,17 +63,21 @@ class AlbumCoverSearcher;
|
||||||
|
|
||||||
class Ui_CoverManager;
|
class Ui_CoverManager;
|
||||||
|
|
||||||
|
class AlbumItem : public QListWidgetItem {
|
||||||
|
public:
|
||||||
|
AlbumItem(const QIcon &icon, const QString &text, QListWidget *parent = nullptr, int type = Type) : QListWidgetItem(icon, text, parent, type) {};
|
||||||
|
QList<QUrl> urls;
|
||||||
|
};
|
||||||
|
|
||||||
class AlbumCoverManager : public QMainWindow {
|
class AlbumCoverManager : public QMainWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent = nullptr);
|
explicit AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent = nullptr);
|
||||||
~AlbumCoverManager() override;
|
~AlbumCoverManager() override;
|
||||||
|
|
||||||
static const char *kSettingsGroup;
|
static const char *kSettingsGroup;
|
||||||
|
|
||||||
CollectionBackend *backend() const;
|
|
||||||
QIcon no_cover_icon() const { return no_cover_icon_; }
|
|
||||||
|
|
||||||
void Reset();
|
void Reset();
|
||||||
void Init();
|
void Init();
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
@ -80,15 +86,15 @@ class AlbumCoverManager : public QMainWindow {
|
||||||
void DisableCoversButtons();
|
void DisableCoversButtons();
|
||||||
|
|
||||||
SongList GetSongsInAlbum(const QModelIndex &idx) const;
|
SongList GetSongsInAlbum(const QModelIndex &idx) const;
|
||||||
SongList GetSongsInAlbums(const QModelIndexList &indexes) const;
|
|
||||||
SongMimeData *GetMimeDataForAlbums(const QModelIndexList &indexes) const;
|
CollectionBackend *backend() const { return collection_backend_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent*) override;
|
void showEvent(QShowEvent *e) override;
|
||||||
void closeEvent(QCloseEvent*) override;
|
void closeEvent(QCloseEvent *e) override;
|
||||||
|
|
||||||
// For the album view context menu events
|
// For the album view context menu events
|
||||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
bool eventFilter(QObject *obj, QEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum ArtistItemType {
|
enum ArtistItemType {
|
||||||
|
@ -98,12 +104,14 @@ class AlbumCoverManager : public QMainWindow {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Role {
|
enum Role {
|
||||||
Role_ArtistName = Qt::UserRole + 1,
|
Role_AlbumArtist = Qt::UserRole + 1,
|
||||||
Role_AlbumArtistName,
|
Role_Album,
|
||||||
Role_AlbumName,
|
|
||||||
Role_PathAutomatic,
|
Role_PathAutomatic,
|
||||||
Role_PathManual,
|
Role_PathManual,
|
||||||
Role_FirstUrl
|
Role_Filetype,
|
||||||
|
Role_CuePath,
|
||||||
|
Role_ImageData,
|
||||||
|
Role_Image
|
||||||
};
|
};
|
||||||
|
|
||||||
enum HideCovers {
|
enum HideCovers {
|
||||||
|
@ -113,21 +121,29 @@ class AlbumCoverManager : public QMainWindow {
|
||||||
};
|
};
|
||||||
|
|
||||||
void LoadGeometry();
|
void LoadGeometry();
|
||||||
void SaveGeometry();
|
void SaveSettings();
|
||||||
|
|
||||||
QString InitialPathForOpenCoverDialog(const QString &path_automatic, const QString &first_file_name) const;
|
QString InitialPathForOpenCoverDialog(const QString &path_automatic, const QString &first_file_name) const;
|
||||||
QString EffectiveAlbumArtistName(const QListWidgetItem &item) const;
|
|
||||||
|
|
||||||
// Returns the selected element in form of a Song ready to be used by AlbumCoverChoiceController or invalid song if there's nothing or multiple elements selected.
|
// Returns the selected element in form of a Song ready to be used by AlbumCoverChoiceController or invalid song if there's nothing or multiple elements selected.
|
||||||
Song GetSingleSelectionAsSong();
|
Song GetSingleSelectionAsSong();
|
||||||
// Returns the first of the selected elements in form of a Song ready to be used by AlbumCoverChoiceController or invalid song if there's nothing selected.
|
// Returns the first of the selected elements in form of a Song ready to be used by AlbumCoverChoiceController or invalid song if there's nothing selected.
|
||||||
Song GetFirstSelectedAsSong();
|
Song GetFirstSelectedAsSong();
|
||||||
|
|
||||||
Song ItemAsSong(QListWidgetItem *item);
|
Song ItemAsSong(QListWidgetItem *item) { return ItemAsSong(static_cast<AlbumItem*>(item)); }
|
||||||
|
Song ItemAsSong(AlbumItem *item);
|
||||||
|
|
||||||
void UpdateStatusText();
|
void UpdateStatusText();
|
||||||
bool ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const;
|
bool ShouldHide(const AlbumItem &item, const QString &filter, HideCovers hide) const;
|
||||||
void SaveAndSetCover(QListWidgetItem *item, const QUrl &cover_url, const QImage &image);
|
void SaveAndSetCover(AlbumItem *item, const AlbumCoverImageResult &result);
|
||||||
|
|
||||||
|
void SaveImageToAlbums(Song *song, const AlbumCoverImageResult &result);
|
||||||
|
|
||||||
|
SongList GetSongsInAlbums(const QModelIndexList &indexes) const;
|
||||||
|
SongMimeData *GetMimeDataForAlbums(const QModelIndexList &indexes) const;
|
||||||
|
|
||||||
|
QImage GenerateNoCoverImage(const QImage &image) const;
|
||||||
|
bool ItemHasCover(const AlbumItem &item) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void AddToPlaylist(QMimeData *data);
|
void AddToPlaylist(QMimeData *data);
|
||||||
|
@ -138,7 +154,7 @@ class AlbumCoverManager : public QMainWindow {
|
||||||
void UpdateFilter();
|
void UpdateFilter();
|
||||||
void FetchAlbumCovers();
|
void FetchAlbumCovers();
|
||||||
void ExportCovers();
|
void ExportCovers();
|
||||||
void AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics);
|
void AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics);
|
||||||
void CancelRequests();
|
void CancelRequests();
|
||||||
|
|
||||||
// On the context menu
|
// On the context menu
|
||||||
|
@ -149,6 +165,8 @@ class AlbumCoverManager : public QMainWindow {
|
||||||
void LoadCoverFromURL();
|
void LoadCoverFromURL();
|
||||||
void SearchForCover();
|
void SearchForCover();
|
||||||
void UnsetCover();
|
void UnsetCover();
|
||||||
|
void ClearCover();
|
||||||
|
void DeleteCover();
|
||||||
void ShowCover();
|
void ShowCover();
|
||||||
|
|
||||||
// For adding albums to the playlist
|
// For adding albums to the playlist
|
||||||
|
@ -156,14 +174,16 @@ class AlbumCoverManager : public QMainWindow {
|
||||||
void AddSelectedToPlaylist();
|
void AddSelectedToPlaylist();
|
||||||
void LoadSelectedToPlaylist();
|
void LoadSelectedToPlaylist();
|
||||||
|
|
||||||
void UpdateCoverInList(QListWidgetItem *item, const QUrl &cover);
|
void UpdateCoverInList(AlbumItem *item, const QUrl &cover);
|
||||||
void UpdateExportStatus(const int exported, const int skipped, const int max);
|
void UpdateExportStatus(const int exported, const int skipped, const int max);
|
||||||
|
|
||||||
|
void SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui_CoverManager *ui_;
|
Ui_CoverManager *ui_;
|
||||||
QMainWindow *mainwindow_;
|
QMainWindow *mainwindow_;
|
||||||
Application *app_;
|
Application *app_;
|
||||||
|
CollectionBackend *collection_backend_;
|
||||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||||
|
|
||||||
QAction *filter_all_;
|
QAction *filter_all_;
|
||||||
|
@ -171,24 +191,20 @@ class AlbumCoverManager : public QMainWindow {
|
||||||
QAction *filter_without_covers_;
|
QAction *filter_without_covers_;
|
||||||
|
|
||||||
AlbumCoverLoaderOptions cover_loader_options_;
|
AlbumCoverLoaderOptions cover_loader_options_;
|
||||||
QMap<quint64, QListWidgetItem*> cover_loading_tasks_;
|
QMap<quint64, AlbumItem*> cover_loading_tasks_;
|
||||||
|
|
||||||
AlbumCoverFetcher *cover_fetcher_;
|
AlbumCoverFetcher *cover_fetcher_;
|
||||||
QMap<quint64, QListWidgetItem*> cover_fetching_tasks_;
|
QMap<quint64, AlbumItem*> cover_fetching_tasks_;
|
||||||
CoverSearchStatistics fetch_statistics_;
|
CoverSearchStatistics fetch_statistics_;
|
||||||
|
|
||||||
AlbumCoverSearcher *cover_searcher_;
|
AlbumCoverSearcher *cover_searcher_;
|
||||||
AlbumCoverExport *cover_export_;
|
AlbumCoverExport *cover_export_;
|
||||||
AlbumCoverExporter *cover_exporter_;
|
AlbumCoverExporter *cover_exporter_;
|
||||||
|
|
||||||
QImage GenerateNoCoverImage(const QIcon &no_cover_icon) const;
|
|
||||||
bool ItemHasCover(const QListWidgetItem &item) const;
|
|
||||||
|
|
||||||
QIcon artist_icon_;
|
QIcon artist_icon_;
|
||||||
QIcon all_artists_icon_;
|
QIcon all_artists_icon_;
|
||||||
const QIcon no_cover_icon_;
|
const QImage image_nocover_thumbnail_;
|
||||||
const QImage no_cover_image_;
|
const QIcon icon_nocover_item_;
|
||||||
const QIcon no_cover_item_icon_;
|
|
||||||
|
|
||||||
QMenu *context_menu_;
|
QMenu *context_menu_;
|
||||||
QList<QListWidgetItem*> context_menu_items_;
|
QList<QListWidgetItem*> context_menu_items_;
|
||||||
|
@ -197,7 +213,10 @@ class AlbumCoverManager : public QMainWindow {
|
||||||
QPushButton *abort_progress_;
|
QPushButton *abort_progress_;
|
||||||
int jobs_;
|
int jobs_;
|
||||||
|
|
||||||
CollectionBackend *collection_backend_;
|
QMultiMap<qint64, AlbumItem*> cover_save_tasks_;
|
||||||
|
QList<AlbumItem*> cover_save_tasks2_;
|
||||||
|
|
||||||
|
QListWidgetItem *all_artists_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,9 @@ QMimeData *AlbumCoverManagerList::mimeData(const QList<QListWidgetItem*> items)
|
||||||
mime_data->songs = songs;
|
mime_data->songs = songs;
|
||||||
mime_data->setUrls(urls);
|
mime_data->setUrls(urls);
|
||||||
mime_data->setData(orig_data->formats()[0], orig_data->data(orig_data->formats()[0]));
|
mime_data->setData(orig_data->formats()[0], orig_data->data(orig_data->formats()[0]));
|
||||||
|
|
||||||
return mime_data;
|
return mime_data;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverManagerList::dropEvent(QDropEvent *e) {
|
void AlbumCoverManagerList::dropEvent(QDropEvent *e) {
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include <QStandardItem>
|
#include <QStandardItem>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
@ -58,6 +59,7 @@
|
||||||
#include "albumcoverloader.h"
|
#include "albumcoverloader.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
#include "albumcoverloaderresult.h"
|
#include "albumcoverloaderresult.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
#include "ui_albumcoversearcher.h"
|
#include "ui_albumcoversearcher.h"
|
||||||
|
|
||||||
const int SizeOverlayDelegate::kMargin = 4;
|
const int SizeOverlayDelegate::kMargin = 4;
|
||||||
|
@ -130,6 +132,8 @@ AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *
|
||||||
ui_->covers->setItemDelegate(new SizeOverlayDelegate(this));
|
ui_->covers->setItemDelegate(new SizeOverlayDelegate(this));
|
||||||
ui_->covers->setModel(model_);
|
ui_->covers->setModel(model_);
|
||||||
|
|
||||||
|
options_.get_image_data_ = true;
|
||||||
|
options_.get_image_ = true;
|
||||||
options_.scale_output_image_ = false;
|
options_.scale_output_image_ = false;
|
||||||
options_.pad_output_image_ = false;
|
options_.pad_output_image_ = false;
|
||||||
options_.create_thumbnail_ = true;
|
options_.create_thumbnail_ = true;
|
||||||
|
@ -157,7 +161,7 @@ void AlbumCoverSearcher::Init(AlbumCoverFetcher *fetcher) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage AlbumCoverSearcher::Exec(const QString &artist, const QString &album) {
|
AlbumCoverImageResult AlbumCoverSearcher::Exec(const QString &artist, const QString &album) {
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
ui_->artist->clear();
|
ui_->artist->clear();
|
||||||
|
@ -175,16 +179,18 @@ QImage AlbumCoverSearcher::Exec(const QString &artist, const QString &album) {
|
||||||
Search();
|
Search();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exec() == QDialog::Rejected) return QImage();
|
if (exec() == QDialog::Rejected) return AlbumCoverImageResult();
|
||||||
|
|
||||||
QModelIndex selected = ui_->covers->currentIndex();
|
QModelIndex selected = ui_->covers->currentIndex();
|
||||||
if (!selected.isValid() || !selected.data(Role_ImageFetchFinished).toBool())
|
if (!selected.isValid() || !selected.data(Role_ImageFetchFinished).toBool())
|
||||||
return QImage();
|
return AlbumCoverImageResult();
|
||||||
|
|
||||||
QIcon icon = selected.data(Qt::DecorationRole).value<QIcon>();
|
AlbumCoverImageResult result;
|
||||||
if (icon.cacheKey() == no_cover_icon_.cacheKey()) return QImage();
|
result.image_data = selected.data(Role_ImageData).value<QByteArray>();
|
||||||
|
result.image = selected.data(Role_Image).value<QImage>();
|
||||||
|
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
|
||||||
|
|
||||||
return icon.pixmap(icon.availableSizes()[0]).toImage();
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,10 +219,9 @@ void AlbumCoverSearcher::Search() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverSearchResults &results) {
|
void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverProviderSearchResults &results) {
|
||||||
|
|
||||||
if (id != id_)
|
if (id != id_) return;
|
||||||
return;
|
|
||||||
|
|
||||||
ui_->search->setEnabled(true);
|
ui_->search->setEnabled(true);
|
||||||
ui_->artist->setEnabled(true);
|
ui_->artist->setEnabled(true);
|
||||||
|
@ -225,7 +230,8 @@ void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverSearchResul
|
||||||
ui_->search->setText(tr("Search"));
|
ui_->search->setText(tr("Search"));
|
||||||
id_ = 0;
|
id_ = 0;
|
||||||
|
|
||||||
for (const CoverSearchResult &result : results) {
|
for (const CoverProviderSearchResult &result : results) {
|
||||||
|
|
||||||
if (result.image_url.isEmpty()) continue;
|
if (result.image_url.isEmpty()) continue;
|
||||||
|
|
||||||
quint64 new_id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url, QUrl());
|
quint64 new_id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url, QUrl());
|
||||||
|
@ -255,19 +261,25 @@ void AlbumCoverSearcher::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoad
|
||||||
|
|
||||||
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
|
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
|
||||||
|
|
||||||
if (result.image_original.isNull()) {
|
if (!result.success || result.album_cover.image_data.isNull() || result.album_cover.image.isNull() || result.image_thumbnail.isNull()) {
|
||||||
model_->removeRow(item->row());
|
model_->removeRow(item->row());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon icon;
|
QPixmap pixmap = QPixmap::fromImage(result.image_thumbnail);
|
||||||
icon.addPixmap(QPixmap::fromImage(result.image_original));
|
if (pixmap.isNull()) {
|
||||||
icon.addPixmap(QPixmap::fromImage(result.image_thumbnail));
|
model_->removeRow(item->row());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QIcon icon(pixmap);
|
||||||
|
|
||||||
item->setData(true, Role_ImageFetchFinished);
|
item->setData(true, Role_ImageFetchFinished);
|
||||||
item->setData(result.image_original.width() * result.image_original.height(), Role_ImageDimensions);
|
item->setData(result.album_cover.image_data, Role_ImageData);
|
||||||
item->setData(result.image_original.size(), Role_ImageSize);
|
item->setData(result.album_cover.image, Role_Image);
|
||||||
item->setIcon(icon);
|
item->setData(result.album_cover.image.width() * result.album_cover.image.height(), Role_ImageDimensions);
|
||||||
|
item->setData(result.album_cover.image.size(), Role_ImageSize);
|
||||||
|
if (!icon.isNull()) item->setIcon(icon);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include <QStyledItemDelegate>
|
#include <QStyledItemDelegate>
|
||||||
#include <QStyleOptionViewItem>
|
#include <QStyleOptionViewItem>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
@ -39,6 +40,7 @@
|
||||||
#include "albumcoverfetcher.h"
|
#include "albumcoverfetcher.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
#include "albumcoverloaderresult.h"
|
#include "albumcoverloaderresult.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
class QStandardItem;
|
class QStandardItem;
|
||||||
|
@ -77,20 +79,22 @@ class AlbumCoverSearcher : public QDialog {
|
||||||
Role_ImageURL = Qt::UserRole + 1,
|
Role_ImageURL = Qt::UserRole + 1,
|
||||||
Role_ImageRequestId,
|
Role_ImageRequestId,
|
||||||
Role_ImageFetchFinished,
|
Role_ImageFetchFinished,
|
||||||
Role_ImageDimensions, // width * height
|
Role_ImageData,
|
||||||
|
Role_Image,
|
||||||
|
Role_ImageDimensions,
|
||||||
Role_ImageSize,
|
Role_ImageSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
void Init(AlbumCoverFetcher *fetcher);
|
void Init(AlbumCoverFetcher *fetcher);
|
||||||
|
|
||||||
QImage Exec(const QString &artist, const QString &album);
|
AlbumCoverImageResult Exec(const QString &artist, const QString &album);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void keyPressEvent(QKeyEvent*) override;
|
void keyPressEvent(QKeyEvent*) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void Search();
|
void Search();
|
||||||
void SearchFinished(const quint64 id, const CoverSearchResults &results);
|
void SearchFinished(const quint64 id, const CoverProviderSearchResults &results);
|
||||||
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
||||||
|
|
||||||
void CoverDoubleClicked(const QModelIndex &idx);
|
void CoverDoubleClicked(const QModelIndex &idx);
|
||||||
|
|
|
@ -89,7 +89,7 @@ void CoverExportRunnable::ProcessAndExportCover() {
|
||||||
|
|
||||||
// load embedded cover if any
|
// load embedded cover if any
|
||||||
if (song_.has_embedded_cover()) {
|
if (song_.has_embedded_cover()) {
|
||||||
embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song_.url().toLocalFile());
|
embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
|
||||||
|
|
||||||
if (embedded_cover.isNull()) {
|
if (embedded_cover.isNull()) {
|
||||||
EmitCoverSkipped();
|
EmitCoverSkipped();
|
||||||
|
@ -179,7 +179,7 @@ void CoverExportRunnable::ExportCover() {
|
||||||
|
|
||||||
if (cover_path == Song::kEmbeddedCover) {
|
if (cover_path == Song::kEmbeddedCover) {
|
||||||
// an embedded cover
|
// an embedded cover
|
||||||
QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song_.url().toLocalFile());
|
QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
|
||||||
if (!embedded.save(new_file)) {
|
if (!embedded.save(new_file)) {
|
||||||
EmitCoverSkipped();
|
EmitCoverSkipped();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -31,8 +31,10 @@
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "core/utilities.h"
|
||||||
#include "core/networkaccessmanager.h"
|
#include "core/networkaccessmanager.h"
|
||||||
#include "widgets/busyindicator.h"
|
#include "widgets/busyindicator.h"
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
#include "coverfromurldialog.h"
|
#include "coverfromurldialog.h"
|
||||||
#include "ui_coverfromurldialog.h"
|
#include "ui_coverfromurldialog.h"
|
||||||
|
|
||||||
|
@ -47,17 +49,17 @@ CoverFromURLDialog::~CoverFromURLDialog() {
|
||||||
delete ui_;
|
delete ui_;
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage CoverFromURLDialog::Exec() {
|
AlbumCoverImageResult CoverFromURLDialog::Exec() {
|
||||||
|
|
||||||
// reset state
|
// reset state
|
||||||
ui_->url->setText("");;
|
ui_->url->setText("");;
|
||||||
last_image_ = QImage();
|
last_album_cover_ = AlbumCoverImageResult();
|
||||||
|
|
||||||
QClipboard *clipboard = QApplication::clipboard();
|
QClipboard *clipboard = QApplication::clipboard();
|
||||||
ui_->url->setText(clipboard->text());
|
ui_->url->setText(clipboard->text());
|
||||||
|
|
||||||
exec();
|
exec();
|
||||||
return last_image_;
|
return last_album_cover_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,11 +91,13 @@ void CoverFromURLDialog::LoadCoverFromURLFinished() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage image;
|
AlbumCoverImageResult result;
|
||||||
image.loadFromData(reply->readAll());
|
result.image_data = reply->readAll();
|
||||||
|
result.image.loadFromData(result.image_data);
|
||||||
|
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
|
||||||
|
|
||||||
if (!image.isNull()) {
|
if (!result.image.isNull()) {
|
||||||
last_image_ = image;
|
last_album_cover_ = result;
|
||||||
QDialog::accept();
|
QDialog::accept();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
|
||||||
|
#include "albumcoverimageresult.h"
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
|
|
||||||
class NetworkAccessManager;
|
class NetworkAccessManager;
|
||||||
|
@ -42,7 +44,7 @@ class CoverFromURLDialog : public QDialog {
|
||||||
~CoverFromURLDialog() override;
|
~CoverFromURLDialog() override;
|
||||||
|
|
||||||
// Opens the dialog. This returns an image found at the URL chosen by user or null image if the dialog got rejected.
|
// Opens the dialog. This returns an image found at the URL chosen by user or null image if the dialog got rejected.
|
||||||
QImage Exec();
|
AlbumCoverImageResult Exec();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void accept() override;
|
void accept() override;
|
||||||
|
@ -52,7 +54,7 @@ class CoverFromURLDialog : public QDialog {
|
||||||
Ui_CoverFromURLDialog *ui_;
|
Ui_CoverFromURLDialog *ui_;
|
||||||
|
|
||||||
NetworkAccessManager *network_;
|
NetworkAccessManager *network_;
|
||||||
QImage last_image_;
|
AlbumCoverImageResult last_album_cover_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COVERFROMURLDIALOG_H
|
#endif // COVERFROMURLDIALOG_H
|
||||||
|
|
|
@ -27,4 +27,4 @@
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "coverprovider.h"
|
#include "coverprovider.h"
|
||||||
|
|
||||||
CoverProvider::CoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent) : QObject(parent), app_(app), name_(name), enabled_(enabled), order_(0), authentication_required_(authentication_required), quality_(quality), fetchall_(fetchall), allow_missing_album_(allow_missing_album) {}
|
CoverProvider::CoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool batch, const bool allow_missing_album, Application *app, QObject *parent) : QObject(parent), app_(app), name_(name), enabled_(enabled), order_(0), authentication_required_(authentication_required), quality_(quality), batch_(batch), allow_missing_album_(allow_missing_album) {}
|
||||||
|
|
|
@ -40,14 +40,14 @@ class CoverProvider : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent);
|
explicit CoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool batch, const bool allow_missing_album, Application *app, QObject *parent);
|
||||||
|
|
||||||
// A name (very short description) of this provider, like "last.fm".
|
// A name (very short description) of this provider, like "last.fm".
|
||||||
QString name() const { return name_; }
|
QString name() const { return name_; }
|
||||||
bool is_enabled() const { return enabled_; }
|
bool is_enabled() const { return enabled_; }
|
||||||
int order() const { return order_; }
|
int order() const { return order_; }
|
||||||
bool quality() const { return quality_; }
|
bool quality() const { return quality_; }
|
||||||
bool fetchall() const { return fetchall_; }
|
bool batch() const { return batch_; }
|
||||||
bool allow_missing_album() const { return allow_missing_album_; }
|
bool allow_missing_album() const { return allow_missing_album_; }
|
||||||
|
|
||||||
void set_enabled(const bool enabled) { enabled_ = enabled; }
|
void set_enabled(const bool enabled) { enabled_ = enabled; }
|
||||||
|
@ -70,8 +70,8 @@ class CoverProvider : public QObject {
|
||||||
void AuthenticationComplete(bool, QStringList = QStringList());
|
void AuthenticationComplete(bool, QStringList = QStringList());
|
||||||
void AuthenticationSuccess();
|
void AuthenticationSuccess();
|
||||||
void AuthenticationFailure(QStringList);
|
void AuthenticationFailure(QStringList);
|
||||||
void SearchResults(int, CoverSearchResults);
|
void SearchResults(int, CoverProviderSearchResults);
|
||||||
void SearchFinished(int, CoverSearchResults);
|
void SearchFinished(int, CoverProviderSearchResults);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application *app_;
|
Application *app_;
|
||||||
|
@ -80,7 +80,7 @@ class CoverProvider : public QObject {
|
||||||
int order_;
|
int order_;
|
||||||
bool authentication_required_;
|
bool authentication_required_;
|
||||||
float quality_;
|
float quality_;
|
||||||
bool fetchall_;
|
bool batch_;
|
||||||
bool allow_missing_album_;
|
bool allow_missing_album_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
|
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
#include "core/song.h"
|
||||||
#include "playlist/playlistmanager.h"
|
#include "playlist/playlistmanager.h"
|
||||||
#include "albumcoverloader.h"
|
#include "albumcoverloader.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
|
@ -43,6 +44,8 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare
|
||||||
id_(0)
|
id_(0)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
options_.get_image_data_ = true;
|
||||||
|
options_.get_image_ = true;
|
||||||
options_.scale_output_image_ = false;
|
options_.scale_output_image_ = false;
|
||||||
options_.pad_output_image_ = false;
|
options_.pad_output_image_ = false;
|
||||||
options_.create_thumbnail_ = true;
|
options_.create_thumbnail_ = true;
|
||||||
|
@ -74,11 +77,11 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
|
||||||
if (id != id_) return;
|
if (id != id_) return;
|
||||||
id_ = 0;
|
id_ = 0;
|
||||||
|
|
||||||
if (!result.image_scaled.isNull()) {
|
if (!result.album_cover.image.isNull()) {
|
||||||
temp_cover_.reset(new QTemporaryFile(temp_file_pattern_));
|
temp_cover_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||||
temp_cover_->setAutoRemove(true);
|
temp_cover_->setAutoRemove(true);
|
||||||
if (temp_cover_->open()) {
|
if (temp_cover_->open()) {
|
||||||
if (result.image_scaled.save(temp_cover_->fileName(), "JPEG")) {
|
if (result.album_cover.image.save(temp_cover_->fileName(), "JPEG")) {
|
||||||
result.temp_cover_url = QUrl::fromLocalFile(temp_cover_->fileName());
|
result.temp_cover_url = QUrl::fromLocalFile(temp_cover_->fileName());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -108,7 +111,7 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.updated) {
|
if (result.updated) {
|
||||||
last_song_.set_art_manual(result.cover_url);
|
last_song_.set_art_manual(result.album_cover.cover_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit AlbumCoverLoaded(last_song_, result);
|
emit AlbumCoverLoaded(last_song_, result);
|
||||||
|
|
|
@ -205,23 +205,23 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
|
||||||
|
|
||||||
QByteArray data = GetReplyData(reply);
|
QByteArray data = GetReplyData(reply);
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonValue value_data = ExtractData(data);
|
QJsonValue value_data = ExtractData(data);
|
||||||
if (!value_data.isArray()) {
|
if (!value_data.isArray()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray array_data = value_data.toArray();
|
QJsonArray array_data = value_data.toArray();
|
||||||
if (array_data.isEmpty()) {
|
if (array_data.isEmpty()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<QUrl, CoverSearchResult> results;
|
QMap<QUrl, CoverProviderSearchResult> results;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const QJsonValue json_value : array_data) {
|
for (const QJsonValue json_value : array_data) {
|
||||||
|
|
||||||
|
@ -279,7 +279,7 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
|
||||||
album = album.remove(Song::kAlbumRemoveDisc);
|
album = album.remove(Song::kAlbumRemoveDisc);
|
||||||
album = album.remove(Song::kAlbumRemoveMisc);
|
album = album.remove(Song::kAlbumRemoveMisc);
|
||||||
|
|
||||||
CoverSearchResult cover_result;
|
CoverProviderSearchResult cover_result;
|
||||||
cover_result.artist = artist;
|
cover_result.artist = artist;
|
||||||
cover_result.album = album;
|
cover_result.album = album;
|
||||||
|
|
||||||
|
@ -309,11 +309,11 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
CoverSearchResults cover_results = results.values();
|
CoverProviderSearchResults cover_results = results.values();
|
||||||
std::stable_sort(cover_results.begin(), cover_results.end(), AlbumCoverFetcherSearch::CoverSearchResultCompareNumber);
|
std::stable_sort(cover_results.begin(), cover_results.end(), AlbumCoverFetcherSearch::CoverProviderSearchResultCompareNumber);
|
||||||
emit SearchFinished(id, cover_results);
|
emit SearchFinished(id, cover_results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -441,7 +441,7 @@ void DiscogsCoverProvider::HandleReleaseReply(QNetworkReply *reply, const int se
|
||||||
if (width < 300 || height < 300) continue;
|
if (width < 300 || height < 300) continue;
|
||||||
const float aspect_score = 1.0 - float(std::max(width, height) - std::min(width, height)) / std::max(height, width);
|
const float aspect_score = 1.0 - float(std::max(width, height) - std::min(width, height)) / std::max(height, width);
|
||||||
if (aspect_score < 0.85) continue;
|
if (aspect_score < 0.85) continue;
|
||||||
CoverSearchResult result;
|
CoverProviderSearchResult result;
|
||||||
result.artist = artist;
|
result.artist = artist;
|
||||||
result.album = album;
|
result.album = album;
|
||||||
result.image_url = QUrl(obj_image["resource_url"].toString());
|
result.image_url = QUrl(obj_image["resource_url"].toString());
|
||||||
|
|
|
@ -73,7 +73,7 @@ class DiscogsCoverProvider : public JsonCoverProvider {
|
||||||
QString album;
|
QString album;
|
||||||
DiscogsCoverType type;
|
DiscogsCoverType type;
|
||||||
QMap<quint64, DiscogsCoverReleaseContext> requests_release_;
|
QMap<quint64, DiscogsCoverReleaseContext> requests_release_;
|
||||||
CoverSearchResults results;
|
CoverProviderSearchResults results;
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
#include "coverprovider.h"
|
#include "coverprovider.h"
|
||||||
#include "jsoncoverprovider.h"
|
#include "jsoncoverprovider.h"
|
||||||
|
|
||||||
JsonCoverProvider::JsonCoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent) : CoverProvider(name, enabled, authentication_required, quality, fetchall, allow_missing_album, app, parent) {}
|
JsonCoverProvider::JsonCoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool batch, const bool allow_missing_album, Application *app, QObject *parent) : CoverProvider(name, enabled, authentication_required, quality, batch, allow_missing_album, app, parent) {}
|
||||||
|
|
||||||
QJsonObject JsonCoverProvider::ExtractJsonObj(const QByteArray &data) {
|
QJsonObject JsonCoverProvider::ExtractJsonObj(const QByteArray &data) {
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ class JsonCoverProvider : public CoverProvider {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit JsonCoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent);
|
explicit JsonCoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool batch, const bool allow_missing_album, Application *app, QObject *parent);
|
||||||
|
|
||||||
QJsonObject ExtractJsonObj(const QByteArray &data);
|
QJsonObject ExtractJsonObj(const QByteArray &data);
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, const int id, cons
|
||||||
QObject::disconnect(reply, nullptr, this, nullptr);
|
QObject::disconnect(reply, nullptr, this, nullptr);
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
CoverSearchResults results;
|
CoverProviderSearchResults results;
|
||||||
|
|
||||||
QByteArray data = GetReplyData(reply);
|
QByteArray data = GetReplyData(reply);
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
|
@ -275,7 +275,7 @@ void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, const int id, cons
|
||||||
|
|
||||||
if (!url.isValid()) continue;
|
if (!url.isValid()) continue;
|
||||||
|
|
||||||
CoverSearchResult cover_result;
|
CoverProviderSearchResult cover_result;
|
||||||
cover_result.artist = artist;
|
cover_result.artist = artist;
|
||||||
cover_result.album = album;
|
cover_result.album = album;
|
||||||
cover_result.image_url = url;
|
cover_result.image_url = url;
|
||||||
|
|
|
@ -128,7 +128,7 @@ void MusicbrainzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
|
||||||
QObject::disconnect(reply, nullptr, this, nullptr);
|
QObject::disconnect(reply, nullptr, this, nullptr);
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
CoverSearchResults results;
|
CoverProviderSearchResults results;
|
||||||
|
|
||||||
QByteArray data = GetReplyData(reply);
|
QByteArray data = GetReplyData(reply);
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
|
@ -217,7 +217,7 @@ void MusicbrainzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
|
||||||
QString id = obj_release["id"].toString();
|
QString id = obj_release["id"].toString();
|
||||||
QString album = obj_release["title"].toString();
|
QString album = obj_release["title"].toString();
|
||||||
|
|
||||||
CoverSearchResult cover_result;
|
CoverProviderSearchResult cover_result;
|
||||||
QUrl url(QString(kAlbumCoverUrl).arg(id));
|
QUrl url(QString(kAlbumCoverUrl).arg(id));
|
||||||
cover_result.artist = artist;
|
cover_result.artist = artist;
|
||||||
cover_result.album = album;
|
cover_result.album = album;
|
||||||
|
|
|
@ -105,7 +105,7 @@ void MusixmatchCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
|
||||||
QObject::disconnect(reply, nullptr, this, nullptr);
|
QObject::disconnect(reply, nullptr, this, nullptr);
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
CoverSearchResults results;
|
CoverProviderSearchResults results;
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||||
|
@ -195,7 +195,7 @@ void MusixmatchCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CoverSearchResult result;
|
CoverProviderSearchResult result;
|
||||||
result.artist = obj_album["artistName"].toString();
|
result.artist = obj_album["artistName"].toString();
|
||||||
result.album = obj_album["name"].toString();
|
result.album = obj_album["name"].toString();
|
||||||
|
|
||||||
|
|
|
@ -170,7 +170,7 @@ void QobuzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
|
||||||
QObject::disconnect(reply, nullptr, this, nullptr);
|
QObject::disconnect(reply, nullptr, this, nullptr);
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
CoverSearchResults results;
|
CoverProviderSearchResults results;
|
||||||
|
|
||||||
QByteArray data = GetReplyData(reply);
|
QByteArray data = GetReplyData(reply);
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
|
@ -274,7 +274,7 @@ void QobuzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
|
||||||
album = album.remove(Song::kAlbumRemoveDisc);
|
album = album.remove(Song::kAlbumRemoveDisc);
|
||||||
album = album.remove(Song::kAlbumRemoveMisc);
|
album = album.remove(Song::kAlbumRemoveMisc);
|
||||||
|
|
||||||
CoverSearchResult cover_result;
|
CoverProviderSearchResult cover_result;
|
||||||
cover_result.artist = artist;
|
cover_result.artist = artist;
|
||||||
cover_result.album = album;
|
cover_result.album = album;
|
||||||
cover_result.image_url = cover_url;
|
cover_result.image_url = cover_url;
|
||||||
|
|
|
@ -466,36 +466,36 @@ void SpotifyCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id,
|
||||||
|
|
||||||
QByteArray data = GetReplyData(reply);
|
QByteArray data = GetReplyData(reply);
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject json_obj = ExtractJsonObj(data);
|
QJsonObject json_obj = ExtractJsonObj(data);
|
||||||
if (json_obj.isEmpty()) {
|
if (json_obj.isEmpty()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!json_obj.contains(extract) || !json_obj[extract].isObject()) {
|
if (!json_obj.contains(extract) || !json_obj[extract].isObject()) {
|
||||||
Error(QString("Json object is missing %1 object.").arg(extract), json_obj);
|
Error(QString("Json object is missing %1 object.").arg(extract), json_obj);
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
json_obj = json_obj[extract].toObject();
|
json_obj = json_obj[extract].toObject();
|
||||||
|
|
||||||
if (!json_obj.contains("items") || !json_obj["items"].isArray()) {
|
if (!json_obj.contains("items") || !json_obj["items"].isArray()) {
|
||||||
Error(QString("%1 object is missing items array.").arg(extract), json_obj);
|
Error(QString("%1 object is missing items array.").arg(extract), json_obj);
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray array_items = json_obj["items"].toArray();
|
QJsonArray array_items = json_obj["items"].toArray();
|
||||||
if (array_items.isEmpty()) {
|
if (array_items.isEmpty()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CoverSearchResults results;
|
CoverProviderSearchResults results;
|
||||||
for (const QJsonValue value_item : array_items) {
|
for (const QJsonValue value_item : array_items) {
|
||||||
|
|
||||||
if (!value_item.isObject()) {
|
if (!value_item.isObject()) {
|
||||||
|
@ -531,7 +531,7 @@ void SpotifyCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id,
|
||||||
int height = obj_image["height"].toInt();
|
int height = obj_image["height"].toInt();
|
||||||
if (width < 300 || height < 300) continue;
|
if (width < 300 || height < 300) continue;
|
||||||
QUrl url(obj_image["url"].toString());
|
QUrl url(obj_image["url"].toString());
|
||||||
CoverSearchResult result;
|
CoverProviderSearchResult result;
|
||||||
result.album = album;
|
result.album = album;
|
||||||
result.image_url = url;
|
result.image_url = url;
|
||||||
result.image_size = QSize(width, height);
|
result.image_size = QSize(width, height);
|
||||||
|
|
|
@ -183,34 +183,34 @@ void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
|
||||||
|
|
||||||
QByteArray data = GetReplyData(reply);
|
QByteArray data = GetReplyData(reply);
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject json_obj = ExtractJsonObj(data);
|
QJsonObject json_obj = ExtractJsonObj(data);
|
||||||
if (json_obj.isEmpty()) {
|
if (json_obj.isEmpty()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!json_obj.contains("items")) {
|
if (!json_obj.contains("items")) {
|
||||||
Error("Json object is missing items.", json_obj);
|
Error("Json object is missing items.", json_obj);
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QJsonValue value_items = json_obj["items"];
|
QJsonValue value_items = json_obj["items"];
|
||||||
|
|
||||||
if (!value_items.isArray()) {
|
if (!value_items.isArray()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QJsonArray array_items = value_items.toArray();
|
QJsonArray array_items = value_items.toArray();
|
||||||
if (array_items.isEmpty()) {
|
if (array_items.isEmpty()) {
|
||||||
emit SearchFinished(id, CoverSearchResults());
|
emit SearchFinished(id, CoverProviderSearchResults());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CoverSearchResults results;
|
CoverProviderSearchResults results;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const QJsonValue value_item : array_items) {
|
for (const QJsonValue value_item : array_items) {
|
||||||
|
|
||||||
|
@ -262,7 +262,7 @@ void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
|
||||||
album = album.remove(Song::kAlbumRemoveMisc);
|
album = album.remove(Song::kAlbumRemoveMisc);
|
||||||
cover = cover.replace("-", "/");
|
cover = cover.replace("-", "/");
|
||||||
|
|
||||||
CoverSearchResult cover_result;
|
CoverProviderSearchResult cover_result;
|
||||||
cover_result.artist = artist;
|
cover_result.artist = artist;
|
||||||
cover_result.album = album;
|
cover_result.album = album;
|
||||||
cover_result.number = ++i;
|
cover_result.number = ++i;
|
||||||
|
|
|
@ -41,8 +41,10 @@ AddStreamDialog::~AddStreamDialog() { delete ui_; }
|
||||||
|
|
||||||
void AddStreamDialog::showEvent(QShowEvent *e) {
|
void AddStreamDialog::showEvent(QShowEvent *e) {
|
||||||
|
|
||||||
ui_->url->setFocus();
|
if (!e->spontaneous()) {
|
||||||
ui_->url->selectAll();
|
ui_->url->setFocus();
|
||||||
|
ui_->url->selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
QDialog::showEvent(e);
|
QDialog::showEvent(e);
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -28,10 +28,11 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QAbstractItemModel>
|
#include <QAbstractItemModel>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QList>
|
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QList>
|
||||||
|
#include <QMap>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
@ -39,6 +40,7 @@
|
||||||
#include "playlist/playlistitem.h"
|
#include "playlist/playlistitem.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
#include "covermanager/albumcoverloaderresult.h"
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
#include "covermanager/albumcoverimageresult.h"
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
class QMenu;
|
class QMenu;
|
||||||
|
@ -51,9 +53,9 @@ class QHideEvent;
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
class AlbumCoverChoiceController;
|
class AlbumCoverChoiceController;
|
||||||
class TrackSelectionDialog;
|
|
||||||
class Ui_EditTagDialog;
|
class Ui_EditTagDialog;
|
||||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||||
|
class TrackSelectionDialog;
|
||||||
class TagFetcher;
|
class TagFetcher;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -64,8 +66,9 @@ class EditTagDialog : public QDialog {
|
||||||
explicit EditTagDialog(Application *app, QWidget *parent = nullptr);
|
explicit EditTagDialog(Application *app, QWidget *parent = nullptr);
|
||||||
~EditTagDialog() override;
|
~EditTagDialog() override;
|
||||||
|
|
||||||
static const char *kHintText;
|
|
||||||
static const char *kSettingsGroup;
|
static const char *kSettingsGroup;
|
||||||
|
static const char *kTagsDifferentHintText;
|
||||||
|
static const char *kArtDifferentHintText;
|
||||||
|
|
||||||
void SetSongs(const SongList &songs, const PlaylistItemList &items = PlaylistItemList());
|
void SetSongs(const SongList &songs, const PlaylistItemList &items = PlaylistItemList());
|
||||||
|
|
||||||
|
@ -78,25 +81,30 @@ class EditTagDialog : public QDialog {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool eventFilter(QObject *o, QEvent *e) override;
|
bool eventFilter(QObject *o, QEvent *e) override;
|
||||||
void showEvent(QShowEvent*) override;
|
void showEvent(QShowEvent *e) override;
|
||||||
void hideEvent(QHideEvent*) override;
|
void hideEvent(QHideEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum UpdateCoverAction {
|
||||||
|
UpdateCoverAction_None = 0,
|
||||||
|
UpdateCoverAction_Clear,
|
||||||
|
UpdateCoverAction_Unset,
|
||||||
|
UpdateCoverAction_Delete,
|
||||||
|
UpdateCoverAction_New,
|
||||||
|
};
|
||||||
struct Data {
|
struct Data {
|
||||||
explicit Data(const Song &song = Song()) : original_(song), current_(song) {}
|
explicit Data(const Song &song = Song()) : original_(song), current_(song), cover_action_(UpdateCoverAction_None) {}
|
||||||
|
|
||||||
static QVariant value(const Song &song, const QString &id);
|
static QVariant value(const Song &song, const QString &id);
|
||||||
QVariant original_value(const QString &id) const {
|
QVariant original_value(const QString &id) const { return value(original_, id); }
|
||||||
return value(original_, id);
|
QVariant current_value(const QString &id) const { return value(current_, id); }
|
||||||
}
|
|
||||||
QVariant current_value(const QString &id) const {
|
|
||||||
return value(current_, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_value(const QString &id, const QVariant &value);
|
void set_value(const QString &id, const QVariant &value);
|
||||||
|
|
||||||
Song original_;
|
Song original_;
|
||||||
Song current_;
|
Song current_;
|
||||||
|
UpdateCoverAction cover_action_;
|
||||||
|
AlbumCoverImageResult cover_result_;
|
||||||
};
|
};
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
@ -118,12 +126,15 @@ class EditTagDialog : public QDialog {
|
||||||
void LoadCoverFromURL();
|
void LoadCoverFromURL();
|
||||||
void SearchForCover();
|
void SearchForCover();
|
||||||
void UnsetCover();
|
void UnsetCover();
|
||||||
|
void ClearCover();
|
||||||
|
void DeleteCover();
|
||||||
void ShowCover();
|
void ShowCover();
|
||||||
|
|
||||||
void PreviousSong();
|
void PreviousSong();
|
||||||
void NextSong();
|
void NextSong();
|
||||||
|
|
||||||
void SongSaveComplete(TagReaderReply *reply, const QString &filename, const Song &song);
|
void SongSaveTagsComplete(TagReaderReply *reply, const QString &filename, Song song);
|
||||||
|
void SongSaveArtComplete(TagReaderReply *reply, const QString &filename, Song song, const UpdateCoverAction cover_action);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct FieldData {
|
struct FieldData {
|
||||||
|
@ -136,7 +147,7 @@ class EditTagDialog : public QDialog {
|
||||||
};
|
};
|
||||||
|
|
||||||
Song *GetFirstSelected();
|
Song *GetFirstSelected();
|
||||||
void UpdateCoverOf(const Song &selected, const QModelIndexList &sel, const QUrl &cover_url);
|
void UpdateCover(const UpdateCoverAction action, const AlbumCoverImageResult &result = AlbumCoverImageResult());
|
||||||
|
|
||||||
bool DoesValueVary(const QModelIndexList &sel, const QString &id) const;
|
bool DoesValueVary(const QModelIndexList &sel, const QString &id) const;
|
||||||
bool IsValueModified(const QModelIndexList &sel, const QString &id) const;
|
bool IsValueModified(const QModelIndexList &sel, const QString &id) const;
|
||||||
|
@ -146,23 +157,34 @@ class EditTagDialog : public QDialog {
|
||||||
void UpdateModifiedField(const FieldData &field, const QModelIndexList &sel);
|
void UpdateModifiedField(const FieldData &field, const QModelIndexList &sel);
|
||||||
void ResetFieldValue(const FieldData &field, const QModelIndexList &sel);
|
void ResetFieldValue(const FieldData &field, const QModelIndexList &sel);
|
||||||
|
|
||||||
void UpdateSummaryTab(const Song &song);
|
void UpdateSummaryTab(const Song &song, const UpdateCoverAction cover_action);
|
||||||
void UpdateStatisticsTab(const Song &song);
|
void UpdateStatisticsTab(const Song &song);
|
||||||
|
|
||||||
void UpdateUI(const QModelIndexList &sel);
|
QString GetArtSummary(const Song &song, const UpdateCoverAction cover_action);
|
||||||
|
|
||||||
|
void UpdateUI(const QModelIndexList &indexes);
|
||||||
|
|
||||||
bool SetLoading(const QString &message);
|
bool SetLoading(const QString &message);
|
||||||
void SetSongListVisibility(bool visible);
|
void SetSongListVisibility(bool visible);
|
||||||
|
|
||||||
// Called by QtConcurrentRun
|
// Called by QtConcurrentRun
|
||||||
QList<Data> LoadData(const SongList &songs) const;
|
QList<Data> LoadData(const SongList &songs) const;
|
||||||
void SaveData(const QList<Data> &tag_data);
|
void SaveData();
|
||||||
|
|
||||||
|
static void SetText(QLabel *label, const int value, const QString &suffix, const QString &def = QString());
|
||||||
|
static void SetDate(QLabel *label, const uint time);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui_EditTagDialog *ui_;
|
Ui_EditTagDialog *ui_;
|
||||||
|
|
||||||
Application *app_;
|
Application *app_;
|
||||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||||
|
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||||
|
TagFetcher *tag_fetcher_;
|
||||||
|
TrackSelectionDialog *results_dialog_;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const QImage image_no_cover_thumbnail_;
|
||||||
|
|
||||||
bool loading_;
|
bool loading_;
|
||||||
|
|
||||||
|
@ -172,25 +194,20 @@ class EditTagDialog : public QDialog {
|
||||||
|
|
||||||
bool ignore_edits_;
|
bool ignore_edits_;
|
||||||
|
|
||||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
|
||||||
TagFetcher *tag_fetcher_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AlbumCoverLoaderOptions cover_options_;
|
AlbumCoverLoaderOptions cover_options_;
|
||||||
quint64 cover_art_id_;
|
quint64 summary_cover_art_id_;
|
||||||
|
quint64 tags_cover_art_id_;
|
||||||
bool cover_art_is_set_;
|
bool cover_art_is_set_;
|
||||||
|
|
||||||
// A copy of the original, unscaled album cover.
|
|
||||||
QImage original_;
|
|
||||||
|
|
||||||
QMenu *cover_menu_;
|
QMenu *cover_menu_;
|
||||||
|
|
||||||
QPushButton *previous_button_;
|
QPushButton *previous_button_;
|
||||||
QPushButton *next_button_;
|
QPushButton *next_button_;
|
||||||
|
|
||||||
TrackSelectionDialog *results_dialog_;
|
int save_tag_pending_;
|
||||||
|
int save_art_pending_;
|
||||||
|
|
||||||
int pending_;
|
QMap<int, Song> collection_songs_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // EDITTAGDIALOG_H
|
#endif // EDITTAGDIALOG_H
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -849,23 +849,22 @@ void InternetSearchView::LazyLoadAlbumCover(const QModelIndex &proxy_index) {
|
||||||
|
|
||||||
void InternetSearchView::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &albumcover_result) {
|
void InternetSearchView::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &albumcover_result) {
|
||||||
|
|
||||||
if (!cover_loader_tasks_.contains(id)) {
|
if (!cover_loader_tasks_.contains(id)) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QPair<QModelIndex, QString> cover_loader_task = cover_loader_tasks_.take(id);
|
QPair<QModelIndex, QString> cover_loader_task = cover_loader_tasks_.take(id);
|
||||||
QModelIndex idx = cover_loader_task.first;
|
QModelIndex idx = cover_loader_task.first;
|
||||||
QString key = cover_loader_task.second;
|
QString key = cover_loader_task.second;
|
||||||
|
|
||||||
QPixmap pixmap = QPixmap::fromImage(albumcover_result.image_scaled);
|
if (albumcover_result.success && !albumcover_result.image_scaled.isNull()) {
|
||||||
if (!pixmap.isNull()) {
|
QPixmap pixmap = QPixmap::fromImage(albumcover_result.image_scaled);
|
||||||
pixmap_cache_.insert(key, pixmap);
|
if (!pixmap.isNull()) {
|
||||||
}
|
pixmap_cache_.insert(key, pixmap);
|
||||||
|
}
|
||||||
if (idx.isValid()) {
|
if (idx.isValid()) {
|
||||||
QStandardItem *item = front_model_->itemFromIndex(idx);
|
QStandardItem *item = front_model_->itemFromIndex(idx);
|
||||||
if (item) {
|
if (item) {
|
||||||
item->setData(albumcover_result.image_scaled, Qt::DecorationRole);
|
item->setData(albumcover_result.image_scaled, Qt::DecorationRole);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,7 +160,7 @@ void Organize::ProcessSomeFiles() {
|
||||||
if (!song.is_valid()) continue;
|
if (!song.is_valid()) continue;
|
||||||
|
|
||||||
// Get embedded album cover
|
// Get embedded album cover
|
||||||
QImage cover = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task.song_info_.song_.url().toLocalFile());
|
QImage cover = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(task.song_info_.song_.url().toLocalFile());
|
||||||
if (!cover.isNull()) song.set_image(cover);
|
if (!cover.isNull()) song.set_image(cover);
|
||||||
|
|
||||||
#ifdef HAVE_GSTREAMER
|
#ifdef HAVE_GSTREAMER
|
||||||
|
@ -212,7 +212,7 @@ void Organize::ProcessSomeFiles() {
|
||||||
job.remove_original_ = !copy_;
|
job.remove_original_ = !copy_;
|
||||||
job.playlist_ = playlist_;
|
job.playlist_ = playlist_;
|
||||||
|
|
||||||
if (task.song_info_.song_.art_manual_is_valid() && task.song_info_.song_.art_manual().path() != Song::kManuallyUnsetCover) {
|
if (task.song_info_.song_.art_manual_is_valid() && !task.song_info_.song_.has_manually_unset_cover()) {
|
||||||
if (task.song_info_.song_.art_manual().isLocalFile() && QFile::exists(task.song_info_.song_.art_manual().toLocalFile())) {
|
if (task.song_info_.song_.art_manual().isLocalFile() && QFile::exists(task.song_info_.song_.art_manual().toLocalFile())) {
|
||||||
job.cover_source_ = task.song_info_.song_.art_manual().toLocalFile();
|
job.cover_source_ = task.song_info_.song_.art_manual().toLocalFile();
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,7 @@ void Organize::ProcessSomeFiles() {
|
||||||
job.cover_source_ = task.song_info_.song_.art_manual().path();
|
job.cover_source_ = task.song_info_.song_.art_manual().path();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (task.song_info_.song_.art_automatic_is_valid() && task.song_info_.song_.art_automatic().path() != Song::kEmbeddedCover) {
|
else if (task.song_info_.song_.art_automatic_is_valid() && !task.song_info_.song_.has_embedded_cover()) {
|
||||||
if (task.song_info_.song_.art_automatic().isLocalFile() && QFile::exists(task.song_info_.song_.art_automatic().toLocalFile())) {
|
if (task.song_info_.song_.art_automatic().isLocalFile() && QFile::exists(task.song_info_.song_.art_automatic().toLocalFile())) {
|
||||||
job.cover_source_ = task.song_info_.song_.art_automatic().toLocalFile();
|
job.cover_source_ = task.song_info_.song_.art_automatic().toLocalFile();
|
||||||
}
|
}
|
||||||
|
|
|
@ -393,7 +393,7 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, int role)
|
||||||
|
|
||||||
TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
|
TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
|
||||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(idx);
|
QPersistentModelIndex persistent_index = QPersistentModelIndex(idx);
|
||||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); });
|
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -418,7 +418,8 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd
|
||||||
emit Error(tr("An error occurred writing metadata to '%1'").arg(QString::fromStdString(reply->request_message().save_file_request().filename())));
|
emit Error(tr("An error occurred writing metadata to '%1'").arg(QString::fromStdString(reply->request_message().save_file_request().filename())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reply->deleteLater();
|
|
||||||
|
metaObject()->invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,7 +756,7 @@ bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, int ro
|
||||||
else if (const PlaylistItemMimeData *item_data = qobject_cast<const PlaylistItemMimeData*>(data)) {
|
else if (const PlaylistItemMimeData *item_data = qobject_cast<const PlaylistItemMimeData*>(data)) {
|
||||||
InsertItems(item_data->items_, row, play_now, enqueue_now, enqueue_next_now);
|
InsertItems(item_data->items_, row, play_now, enqueue_now, enqueue_next_now);
|
||||||
}
|
}
|
||||||
else if (const InternetSongMimeData* internet_song_data = qobject_cast<const InternetSongMimeData*>(data)) {
|
else if (const InternetSongMimeData *internet_song_data = qobject_cast<const InternetSongMimeData*>(data)) {
|
||||||
InsertInternetItems(internet_song_data->service, internet_song_data->songs, row, play_now, enqueue_now, enqueue_next_now);
|
InsertInternetItems(internet_song_data->service, internet_song_data->songs, row, play_now, enqueue_now, enqueue_next_now);
|
||||||
}
|
}
|
||||||
else if (const PlaylistGeneratorMimeData *generator_data = qobject_cast<const PlaylistGeneratorMimeData*>(data)) {
|
else if (const PlaylistGeneratorMimeData *generator_data = qobject_cast<const PlaylistGeneratorMimeData*>(data)) {
|
||||||
|
@ -2186,11 +2187,11 @@ void Playlist::UpdateScrobblePoint(const qint64 seek_point_nanosec) {
|
||||||
void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
|
void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
|
||||||
|
|
||||||
// Update art_manual for local songs that are not in the collection.
|
// Update art_manual for local songs that are not in the collection.
|
||||||
if (((result.type == AlbumCoverLoaderResult::Type_Manual && result.cover_url.isLocalFile()) || result.type == AlbumCoverLoaderResult::Type_ManuallyUnset) && (song.source() == Song::Source_LocalFile || song.source() == Song::Source_CDDA || song.source() == Song::Source_Device)) {
|
if (((result.type == AlbumCoverLoaderResult::Type_Manual && result.album_cover.cover_url.isLocalFile()) || result.type == AlbumCoverLoaderResult::Type_ManuallyUnset) && (song.source() == Song::Source_LocalFile || song.source() == Song::Source_CDDA || song.source() == Song::Source_Device)) {
|
||||||
PlaylistItemPtr item = current_item();
|
PlaylistItemPtr item = current_item();
|
||||||
if (item && item->Metadata() == song && (!item->Metadata().art_manual_is_valid() || (result.type == AlbumCoverLoaderResult::Type_ManuallyUnset && !item->Metadata().has_manually_unset_cover()))) {
|
if (item && item->Metadata() == song && (!item->Metadata().art_manual_is_valid() || (result.type == AlbumCoverLoaderResult::Type_ManuallyUnset && !item->Metadata().has_manually_unset_cover()))) {
|
||||||
qLog(Debug) << "Updating art manual for local song" << song.title() << song.album() << song.title() << "to" << result.cover_url << "in playlist.";
|
qLog(Debug) << "Updating art manual for local song" << song.title() << song.album() << song.title() << "to" << result.album_cover.cover_url << "in playlist.";
|
||||||
item->SetArtManual(result.cover_url);
|
item->SetArtManual(result.album_cover.cover_url);
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -600,15 +600,20 @@ void PlaylistView::StartGlowing() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistView::hideEvent(QHideEvent *) { glow_timer_.stop(); }
|
void PlaylistView::hideEvent(QHideEvent *e) {
|
||||||
|
glow_timer_.stop();
|
||||||
|
QTreeView::hideEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
void PlaylistView::showEvent(QShowEvent *) {
|
void PlaylistView::showEvent(QShowEvent *e) {
|
||||||
|
|
||||||
if (currently_glowing_ && glow_enabled_)
|
if (currently_glowing_ && glow_enabled_)
|
||||||
glow_timer_.start(1500 / kGlowIntensitySteps, this);
|
glow_timer_.start(1500 / kGlowIntensitySteps, this);
|
||||||
|
|
||||||
MaybeAutoscroll(Playlist::AutoScroll_Maybe);
|
MaybeAutoscroll(Playlist::AutoScroll_Maybe);
|
||||||
|
|
||||||
|
QTreeView::showEvent(e);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -1087,6 +1092,8 @@ void PlaylistView::paintEvent(QPaintEvent *event) {
|
||||||
p.setPen(line_pen);
|
p.setPen(line_pen);
|
||||||
p.drawLine(QPoint(0, drop_pos), QPoint(width(), drop_pos));
|
p.drawLine(QPoint(0, drop_pos), QPoint(width(), drop_pos));
|
||||||
|
|
||||||
|
QTreeView::paintEvent(event);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistView::dragMoveEvent(QDragMoveEvent *event) {
|
void PlaylistView::dragMoveEvent(QDragMoveEvent *event) {
|
||||||
|
@ -1260,6 +1267,7 @@ void PlaylistView::StretchChanged(const bool stretch) {
|
||||||
void PlaylistView::resizeEvent(QResizeEvent *e) {
|
void PlaylistView::resizeEvent(QResizeEvent *e) {
|
||||||
|
|
||||||
QTreeView::resizeEvent(e);
|
QTreeView::resizeEvent(e);
|
||||||
|
|
||||||
if (dynamic_controls_->isVisible()) {
|
if (dynamic_controls_->isVisible()) {
|
||||||
RepositionDynamicControls();
|
RepositionDynamicControls();
|
||||||
}
|
}
|
||||||
|
@ -1379,9 +1387,9 @@ void PlaylistView::Stopped() {
|
||||||
|
|
||||||
void PlaylistView::AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result) {
|
void PlaylistView::AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result) {
|
||||||
|
|
||||||
if ((song != Song() && song_playing_ == Song()) || result.image_original == current_song_cover_art_) return;
|
if ((song != Song() && song_playing_ == Song()) || result.album_cover.image == current_song_cover_art_) return;
|
||||||
|
|
||||||
current_song_cover_art_ = result.image_original;
|
current_song_cover_art_ = result.album_cover.image;
|
||||||
if (background_image_type_ == AppearanceSettingsPage::BackgroundImageType_Album) {
|
if (background_image_type_ == AppearanceSettingsPage::BackgroundImageType_Album) {
|
||||||
if (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) {
|
if (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) {
|
||||||
set_background_image(QImage());
|
set_background_image(QImage());
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
#include "playlistitem.h"
|
#include "playlistitem.h"
|
||||||
#include "songplaylistitem.h"
|
#include "songplaylistitem.h"
|
||||||
|
|
||||||
SongPlaylistItem::SongPlaylistItem(const Song::Source &source) : PlaylistItem(source) {}
|
SongPlaylistItem::SongPlaylistItem(const Song::Source source) : PlaylistItem(source) {}
|
||||||
SongPlaylistItem::SongPlaylistItem(const Song &song) : PlaylistItem(song.source()), song_(song) {}
|
SongPlaylistItem::SongPlaylistItem(const Song &song) : PlaylistItem(song.source()), song_(song) {}
|
||||||
|
|
||||||
bool SongPlaylistItem::InitFromQuery(const SqlRow &query) {
|
bool SongPlaylistItem::InitFromQuery(const SqlRow &query) {
|
||||||
|
|
|
@ -32,12 +32,12 @@
|
||||||
|
|
||||||
class SongPlaylistItem : public PlaylistItem {
|
class SongPlaylistItem : public PlaylistItem {
|
||||||
public:
|
public:
|
||||||
explicit SongPlaylistItem(const Song::Source &source);
|
explicit SongPlaylistItem(const Song::Source source);
|
||||||
explicit SongPlaylistItem(const Song &song);
|
explicit SongPlaylistItem(const Song &song);
|
||||||
|
|
||||||
// Restores a stream- or file-related playlist item using query row.
|
// Restores a stream- or file-related playlist item using query row.
|
||||||
// If it's a file related playlist item, this will restore it's CUE attributes (if any) but won't parse the CUE!
|
// If it's a file related playlist item, this will restore it's CUE attributes (if any) but won't parse the CUE!
|
||||||
bool InitFromQuery(const SqlRow& query) override;
|
bool InitFromQuery(const SqlRow &query) override;
|
||||||
void Reload() override;
|
void Reload() override;
|
||||||
|
|
||||||
Song Metadata() const override;
|
Song Metadata() const override;
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/timeconstants.h"
|
#include "core/timeconstants.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/utilities.h"
|
#include "core/imageutils.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
#include "qobuzservice.h"
|
#include "qobuzservice.h"
|
||||||
#include "qobuzurlhandler.h"
|
#include "qobuzurlhandler.h"
|
||||||
|
@ -1154,7 +1154,7 @@ void QobuzRequest::GetAlbumCovers() {
|
||||||
|
|
||||||
void QobuzRequest::AddAlbumCoverRequest(Song &song) {
|
void QobuzRequest::AddAlbumCoverRequest(Song &song) {
|
||||||
|
|
||||||
QUrl cover_url(song.art_automatic());
|
QUrl cover_url = song.art_automatic();
|
||||||
if (!cover_url.isValid()) return;
|
if (!cover_url.isValid()) return;
|
||||||
|
|
||||||
if (album_covers_requests_sent_.contains(cover_url)) {
|
if (album_covers_requests_sent_.contains(cover_url)) {
|
||||||
|
@ -1233,7 +1233,7 @@ void QobuzRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl &cover_ur
|
||||||
}
|
}
|
||||||
|
|
||||||
QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
||||||
if (!Utilities::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) && !Utilities::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
|
if (!ImageUtils::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) && !ImageUtils::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
|
||||||
Error(QString("Unsupported mimetype for image reader %1 for %2").arg(mimetype).arg(cover_url.toString()));
|
Error(QString("Unsupported mimetype for image reader %1 for %2").arg(mimetype).arg(cover_url.toString()));
|
||||||
if (album_covers_requests_sent_.contains(cover_url)) album_covers_requests_sent_.remove(cover_url);
|
if (album_covers_requests_sent_.contains(cover_url)) album_covers_requests_sent_.remove(cover_url);
|
||||||
AlbumCoverFinishCheck();
|
AlbumCoverFinishCheck();
|
||||||
|
@ -1248,7 +1248,7 @@ void QobuzRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl &cover_ur
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QByteArray> format_list = Utilities::ImageFormatsForMimeType(mimetype.toUtf8());
|
QList<QByteArray> format_list = ImageUtils::ImageFormatsForMimeType(mimetype.toUtf8());
|
||||||
char *format = nullptr;
|
char *format = nullptr;
|
||||||
if (!format_list.isEmpty()) {
|
if (!format_list.isEmpty()) {
|
||||||
format = format_list.first().data();
|
format = format_list.first().data();
|
||||||
|
|
|
@ -80,7 +80,7 @@ CollectionSettingsPage::CollectionSettingsPage(SettingsDialog *dialog)
|
||||||
QObject::connect(ui_->add, &QPushButton::clicked, this, &CollectionSettingsPage::Add);
|
QObject::connect(ui_->add, &QPushButton::clicked, this, &CollectionSettingsPage::Add);
|
||||||
QObject::connect(ui_->remove, &QPushButton::clicked, this, &CollectionSettingsPage::Remove);
|
QObject::connect(ui_->remove, &QPushButton::clicked, this, &CollectionSettingsPage::Remove);
|
||||||
|
|
||||||
QObject::connect(ui_->checkbox_cover_album_dir, &QCheckBox::toggled, this, &CollectionSettingsPage::CoverSaveInAlbumDirChanged);
|
QObject::connect(ui_->radiobutton_save_albumcover_albumdir, &QRadioButton::toggled, this, &CollectionSettingsPage::CoverSaveInAlbumDirChanged);
|
||||||
QObject::connect(ui_->radiobutton_cover_hash, &QRadioButton::toggled, this, &CollectionSettingsPage::CoverSaveInAlbumDirChanged);
|
QObject::connect(ui_->radiobutton_cover_hash, &QRadioButton::toggled, this, &CollectionSettingsPage::CoverSaveInAlbumDirChanged);
|
||||||
QObject::connect(ui_->radiobutton_cover_pattern, &QRadioButton::toggled, this, &CollectionSettingsPage::CoverSaveInAlbumDirChanged);
|
QObject::connect(ui_->radiobutton_cover_pattern, &QRadioButton::toggled, this, &CollectionSettingsPage::CoverSaveInAlbumDirChanged);
|
||||||
|
|
||||||
|
@ -162,11 +162,17 @@ void CollectionSettingsPage::Load() {
|
||||||
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
||||||
ui_->cover_art_patterns->setText(filters.join(","));
|
ui_->cover_art_patterns->setText(filters.join(","));
|
||||||
|
|
||||||
ui_->checkbox_cover_album_dir->setChecked(s.value("cover_album_dir", false).toBool());
|
SaveCoverType save_cover_type = SaveCoverType(s.value("save_cover_type", SaveCoverType_Cache).toInt());
|
||||||
SaveCover save_cover = SaveCover(s.value("cover_filename", SaveCover_Hash).toInt());
|
switch (save_cover_type) {
|
||||||
switch (save_cover) {
|
case SaveCoverType_Cache: ui_->radiobutton_save_albumcover_cache->setChecked(true); break;
|
||||||
case SaveCover_Hash: ui_->radiobutton_cover_hash->setChecked(true); break;
|
case SaveCoverType_Album: ui_->radiobutton_save_albumcover_albumdir->setChecked(true); break;
|
||||||
case SaveCover_Pattern: ui_->radiobutton_cover_pattern->setChecked(true); break;
|
case SaveCoverType_Embedded: ui_->radiobutton_save_albumcover_embedded->setChecked(true); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveCoverFilename save_cover_filename = SaveCoverFilename(s.value("save_cover_filename", SaveCoverFilename_Hash).toInt());
|
||||||
|
switch (save_cover_filename) {
|
||||||
|
case SaveCoverFilename_Hash: ui_->radiobutton_cover_hash->setChecked(true); break;
|
||||||
|
case SaveCoverFilename_Pattern: ui_->radiobutton_cover_pattern->setChecked(true); break;
|
||||||
}
|
}
|
||||||
QString cover_pattern = s.value("cover_pattern").toString();
|
QString cover_pattern = s.value("cover_pattern").toString();
|
||||||
if (!cover_pattern.isEmpty()) ui_->lineedit_cover_pattern->setText(cover_pattern);
|
if (!cover_pattern.isEmpty()) ui_->lineedit_cover_pattern->setText(cover_pattern);
|
||||||
|
@ -222,11 +228,17 @@ void CollectionSettingsPage::Save() {
|
||||||
|
|
||||||
s.setValue("cover_art_patterns", filters);
|
s.setValue("cover_art_patterns", filters);
|
||||||
|
|
||||||
s.setValue("cover_album_dir", ui_->checkbox_cover_album_dir->isChecked());
|
SaveCoverType save_cover_type = SaveCoverType_Cache;
|
||||||
SaveCover save_cover = SaveCover_Hash;
|
if (ui_->radiobutton_save_albumcover_cache->isChecked()) save_cover_type = SaveCoverType_Cache;
|
||||||
if (ui_->radiobutton_cover_hash->isChecked()) save_cover = SaveCover_Hash;
|
else if (ui_->radiobutton_save_albumcover_albumdir->isChecked()) save_cover_type = SaveCoverType_Album;
|
||||||
if (ui_->radiobutton_cover_pattern->isChecked()) save_cover = SaveCover_Pattern;
|
else if (ui_->radiobutton_save_albumcover_embedded->isChecked()) save_cover_type = SaveCoverType_Embedded;
|
||||||
s.setValue("cover_filename", int(save_cover));
|
s.setValue("save_cover_type", int(save_cover_type));
|
||||||
|
|
||||||
|
SaveCoverFilename save_cover_filename = SaveCoverFilename_Hash;
|
||||||
|
if (ui_->radiobutton_cover_hash->isChecked()) save_cover_filename = SaveCoverFilename_Hash;
|
||||||
|
else if (ui_->radiobutton_cover_pattern->isChecked()) save_cover_filename = SaveCoverFilename_Pattern;
|
||||||
|
s.setValue("save_cover_filename", int(save_cover_filename));
|
||||||
|
|
||||||
s.setValue("cover_pattern", ui_->lineedit_cover_pattern->text());
|
s.setValue("cover_pattern", ui_->lineedit_cover_pattern->text());
|
||||||
s.setValue("cover_overwrite", ui_->checkbox_cover_overwrite->isChecked());
|
s.setValue("cover_overwrite", ui_->checkbox_cover_overwrite->isChecked());
|
||||||
s.setValue("cover_lowercase", ui_->checkbox_cover_lowercase->isChecked());
|
s.setValue("cover_lowercase", ui_->checkbox_cover_lowercase->isChecked());
|
||||||
|
@ -246,7 +258,7 @@ void CollectionSettingsPage::Save() {
|
||||||
|
|
||||||
void CollectionSettingsPage::CoverSaveInAlbumDirChanged() {
|
void CollectionSettingsPage::CoverSaveInAlbumDirChanged() {
|
||||||
|
|
||||||
if (ui_->checkbox_cover_album_dir->isChecked()) {
|
if (ui_->radiobutton_save_albumcover_albumdir->isChecked()) {
|
||||||
if (!ui_->groupbox_cover_filename->isEnabled()) {
|
if (!ui_->groupbox_cover_filename->isEnabled()) {
|
||||||
ui_->groupbox_cover_filename->setEnabled(true);
|
ui_->groupbox_cover_filename->setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,17 @@ class CollectionSettingsPage : public SettingsPage {
|
||||||
static const int kSettingsCacheSizeDefault;
|
static const int kSettingsCacheSizeDefault;
|
||||||
static const int kSettingsDiskCacheSizeDefault;
|
static const int kSettingsDiskCacheSizeDefault;
|
||||||
|
|
||||||
|
enum SaveCoverType {
|
||||||
|
SaveCoverType_Cache = 1,
|
||||||
|
SaveCoverType_Album = 2,
|
||||||
|
SaveCoverType_Embedded = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SaveCoverFilename {
|
||||||
|
SaveCoverFilename_Hash = 1,
|
||||||
|
SaveCoverFilename_Pattern = 2
|
||||||
|
};
|
||||||
|
|
||||||
enum CacheSizeUnit {
|
enum CacheSizeUnit {
|
||||||
CacheSizeUnit_KB,
|
CacheSizeUnit_KB,
|
||||||
CacheSizeUnit_MB,
|
CacheSizeUnit_MB,
|
||||||
|
@ -57,11 +68,6 @@ class CollectionSettingsPage : public SettingsPage {
|
||||||
CacheSizeUnit_TB,
|
CacheSizeUnit_TB,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SaveCover {
|
|
||||||
SaveCover_Hash = 1,
|
|
||||||
SaveCover_Pattern = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
void Load() override;
|
void Load() override;
|
||||||
void Save() override;
|
void Save() override;
|
||||||
|
|
||||||
|
|
|
@ -157,10 +157,33 @@ If there are no matches then it will use the largest image in the directory.</st
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkbox_cover_album_dir">
|
<widget class="QGroupBox" name="groupbox_save_options">
|
||||||
<property name="text">
|
<property name="title">
|
||||||
<string>Save album covers in album directory</string>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="radiobutton_save_albumcover_albumdir">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save album covers in album directory</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="radiobutton_save_albumcover_cache">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save album covers in cache directory</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="radiobutton_save_albumcover_embedded">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save album covers as embedded cover</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -185,16 +208,16 @@ If there are no matches then it will use the largest image in the directory.</st
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QRadioButton" name="radiobutton_cover_hash">
|
<widget class="QRadioButton" name="radiobutton_cover_pattern">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Use hash</string>
|
<string>Pattern</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QRadioButton" name="radiobutton_cover_pattern">
|
<widget class="QRadioButton" name="radiobutton_cover_hash">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Use pattern</string>
|
<string>Random</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -463,9 +486,7 @@ If there are no matches then it will use the largest image in the directory.</st
|
||||||
<tabstop>auto_open</tabstop>
|
<tabstop>auto_open</tabstop>
|
||||||
<tabstop>pretty_covers</tabstop>
|
<tabstop>pretty_covers</tabstop>
|
||||||
<tabstop>show_dividers</tabstop>
|
<tabstop>show_dividers</tabstop>
|
||||||
<tabstop>checkbox_cover_album_dir</tabstop>
|
|
||||||
<tabstop>radiobutton_cover_hash</tabstop>
|
<tabstop>radiobutton_cover_hash</tabstop>
|
||||||
<tabstop>radiobutton_cover_pattern</tabstop>
|
|
||||||
<tabstop>lineedit_cover_pattern</tabstop>
|
<tabstop>lineedit_cover_pattern</tabstop>
|
||||||
<tabstop>checkbox_cover_overwrite</tabstop>
|
<tabstop>checkbox_cover_overwrite</tabstop>
|
||||||
<tabstop>checkbox_cover_lowercase</tabstop>
|
<tabstop>checkbox_cover_lowercase</tabstop>
|
||||||
|
|
|
@ -189,14 +189,15 @@ SettingsDialog::~SettingsDialog() {
|
||||||
|
|
||||||
void SettingsDialog::showEvent(QShowEvent *e) {
|
void SettingsDialog::showEvent(QShowEvent *e) {
|
||||||
|
|
||||||
LoadGeometry();
|
if (!e->spontaneous()) {
|
||||||
|
LoadGeometry();
|
||||||
// Load settings
|
// Load settings
|
||||||
loading_settings_ = true;
|
loading_settings_ = true;
|
||||||
for (const PageData &page : pages_.values()) {
|
for (const PageData &page : pages_.values()) {
|
||||||
page.page_->Load();
|
page.page_->Load();
|
||||||
|
}
|
||||||
|
loading_settings_ = false;
|
||||||
}
|
}
|
||||||
loading_settings_ = false;
|
|
||||||
|
|
||||||
QDialog::showEvent(e);
|
QDialog::showEvent(e);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QShowEvent>
|
||||||
|
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "settingspage.h"
|
#include "settingspage.h"
|
||||||
#include "transcoder/transcoderoptionsflac.h"
|
#include "transcoder/transcoderoptionsflac.h"
|
||||||
|
@ -49,7 +51,7 @@ TranscoderSettingsPage::~TranscoderSettingsPage() {
|
||||||
|
|
||||||
void TranscoderSettingsPage::showEvent(QShowEvent *e) {
|
void TranscoderSettingsPage::showEvent(QShowEvent *e) {
|
||||||
|
|
||||||
set_changed();
|
if (!e->spontaneous()) set_changed();
|
||||||
|
|
||||||
QWidget::showEvent(e);
|
QWidget::showEvent(e);
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
|
|
||||||
#include "settingspage.h"
|
#include "settingspage.h"
|
||||||
|
|
||||||
|
class QShowEvent;
|
||||||
|
|
||||||
class SettingsDialog;
|
class SettingsDialog;
|
||||||
class Ui_TranscoderSettingsPage;
|
class Ui_TranscoderSettingsPage;
|
||||||
|
|
||||||
|
@ -35,7 +37,7 @@ class TranscoderSettingsPage : public SettingsPage {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TranscoderSettingsPage(SettingsDialog* dialog);
|
explicit TranscoderSettingsPage(SettingsDialog *dialog);
|
||||||
~TranscoderSettingsPage() override;
|
~TranscoderSettingsPage() override;
|
||||||
|
|
||||||
static const char *kSettingsGroup;
|
static const char *kSettingsGroup;
|
||||||
|
@ -47,7 +49,7 @@ class TranscoderSettingsPage : public SettingsPage {
|
||||||
void showEvent(QShowEvent *e) override;
|
void showEvent(QShowEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui_TranscoderSettingsPage* ui_;
|
Ui_TranscoderSettingsPage *ui_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TRANSCODERSETTINGSPAGE_H
|
#endif // TRANSCODERSETTINGSPAGE_H
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/timeconstants.h"
|
#include "core/timeconstants.h"
|
||||||
#include "core/utilities.h"
|
#include "core/imageutils.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
#include "subsonicservice.h"
|
#include "subsonicservice.h"
|
||||||
#include "subsonicurlhandler.h"
|
#include "subsonicurlhandler.h"
|
||||||
|
@ -788,7 +788,7 @@ void SubsonicRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl url, c
|
||||||
}
|
}
|
||||||
|
|
||||||
QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
||||||
if (!Utilities::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) && !Utilities::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
|
if (!ImageUtils::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) && !ImageUtils::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
|
||||||
Error(QString("Unsupported mimetype for image reader %1 for %2").arg(mimetype).arg(url.toString()));
|
Error(QString("Unsupported mimetype for image reader %1 for %2").arg(mimetype).arg(url.toString()));
|
||||||
if (album_covers_requests_sent_.contains(url)) album_covers_requests_sent_.remove(url);
|
if (album_covers_requests_sent_.contains(url)) album_covers_requests_sent_.remove(url);
|
||||||
AlbumCoverFinishCheck();
|
AlbumCoverFinishCheck();
|
||||||
|
@ -803,7 +803,7 @@ void SubsonicRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl url, c
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QByteArray> format_list = Utilities::ImageFormatsForMimeType(mimetype.toUtf8());
|
QList<QByteArray> format_list = ImageUtils::ImageFormatsForMimeType(mimetype.toUtf8());
|
||||||
char *format = nullptr;
|
char *format = nullptr;
|
||||||
if (!format_list.isEmpty()) {
|
if (!format_list.isEmpty()) {
|
||||||
format = format_list.first().data();
|
format = format_list.first().data();
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/timeconstants.h"
|
#include "core/timeconstants.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/utilities.h"
|
#include "core/imageutils.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
#include "tidalservice.h"
|
#include "tidalservice.h"
|
||||||
#include "tidalurlhandler.h"
|
#include "tidalurlhandler.h"
|
||||||
|
@ -1116,7 +1116,7 @@ void TidalRequest::AddAlbumCoverRequest(Song &song) {
|
||||||
|
|
||||||
AlbumCoverRequest request;
|
AlbumCoverRequest request;
|
||||||
request.album_id = song.album_id();
|
request.album_id = song.album_id();
|
||||||
request.url = QUrl(song.art_automatic());
|
request.url = song.art_automatic();
|
||||||
request.filename = app_->album_cover_loader()->CoverFilePath(song.source(), song.effective_albumartist(), song.effective_album(), song.album_id(), QString(), request.url);
|
request.filename = app_->album_cover_loader()->CoverFilePath(song.source(), song.effective_albumartist(), song.effective_album(), song.album_id(), QString(), request.url);
|
||||||
if (request.filename.isEmpty()) return;
|
if (request.filename.isEmpty()) return;
|
||||||
|
|
||||||
|
@ -1187,7 +1187,7 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
|
||||||
}
|
}
|
||||||
|
|
||||||
QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
||||||
if (!Utilities::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) && !Utilities::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
|
if (!ImageUtils::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) && !ImageUtils::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
|
||||||
Error(QString("Unsupported mimetype for image reader %1 for %2").arg(mimetype).arg(url.toString()));
|
Error(QString("Unsupported mimetype for image reader %1 for %2").arg(mimetype).arg(url.toString()));
|
||||||
if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
|
if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
|
||||||
AlbumCoverFinishCheck();
|
AlbumCoverFinishCheck();
|
||||||
|
@ -1202,7 +1202,7 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QByteArray> format_list = Utilities::ImageFormatsForMimeType(mimetype.toUtf8());
|
QList<QByteArray> format_list = ImageUtils::ImageFormatsForMimeType(mimetype.toUtf8());
|
||||||
char *format = nullptr;
|
char *format = nullptr;
|
||||||
if (!format_list.isEmpty()) {
|
if (!format_list.isEmpty()) {
|
||||||
format = format_list.first().data();
|
format = format_list.first().data();
|
||||||
|
|
|
@ -154,16 +154,20 @@ TranscodeDialog::~TranscodeDialog() {
|
||||||
delete ui_;
|
delete ui_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TranscodeDialog::showEvent(QShowEvent*) {
|
void TranscodeDialog::showEvent(QShowEvent *e) {
|
||||||
|
|
||||||
LoadGeometry();
|
if (!e->spontaneous()) LoadGeometry();
|
||||||
|
|
||||||
|
QDialog::showEvent(e);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TranscodeDialog::closeEvent(QCloseEvent*) {
|
void TranscodeDialog::closeEvent(QCloseEvent *e) {
|
||||||
|
|
||||||
SaveGeometry();
|
SaveGeometry();
|
||||||
|
|
||||||
|
QDialog::closeEvent(e);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TranscodeDialog::accept() {
|
void TranscodeDialog::accept() {
|
||||||
|
|
|
@ -54,8 +54,8 @@ class TranscodeDialog : public QDialog {
|
||||||
void SetFilenames(const QStringList &filenames);
|
void SetFilenames(const QStringList &filenames);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent*) override;
|
void showEvent(QShowEvent *e) override;
|
||||||
void closeEvent(QCloseEvent*) override;
|
void closeEvent(QCloseEvent *e) override;
|
||||||
void timerEvent(QTimerEvent *e) override;
|
void timerEvent(QTimerEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -44,8 +44,8 @@
|
||||||
#include <QtEvents>
|
#include <QtEvents>
|
||||||
|
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
|
#include "core/imageutils.h"
|
||||||
#include "covermanager/albumcoverchoicecontroller.h"
|
#include "covermanager/albumcoverchoicecontroller.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
|
||||||
#include "playingwidget.h"
|
#include "playingwidget.h"
|
||||||
|
|
||||||
const char *PlayingWidget::kSettingsGroup = "PlayingWidget";
|
const char *PlayingWidget::kSettingsGroup = "PlayingWidget";
|
||||||
|
@ -133,9 +133,10 @@ void PlayingWidget::Init(Application *app, AlbumCoverChoiceController *album_cov
|
||||||
album_cover_choice_controller_ = album_cover_choice_controller;
|
album_cover_choice_controller_ = album_cover_choice_controller;
|
||||||
album_cover_choice_controller_->Init(app_);
|
album_cover_choice_controller_->Init(app_);
|
||||||
QList<QAction*> cover_actions = album_cover_choice_controller_->GetAllActions();
|
QList<QAction*> cover_actions = album_cover_choice_controller_->GetAllActions();
|
||||||
cover_actions.append(album_cover_choice_controller_->search_cover_auto_action());
|
|
||||||
menu_->addActions(cover_actions);
|
menu_->addActions(cover_actions);
|
||||||
menu_->addSeparator();
|
menu_->addSeparator();
|
||||||
|
menu_->addAction(album_cover_choice_controller_->search_cover_auto_action());
|
||||||
|
menu_->addSeparator();
|
||||||
|
|
||||||
above_statusbar_action_ = menu_->addAction(tr("Show above status bar"));
|
above_statusbar_action_ = menu_->addAction(tr("Show above status bar"));
|
||||||
above_statusbar_action_->setCheckable(true);
|
above_statusbar_action_->setCheckable(true);
|
||||||
|
@ -333,7 +334,7 @@ void PlayingWidget::SetImage(const QImage &image) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayingWidget::ScaleCover() {
|
void PlayingWidget::ScaleCover() {
|
||||||
pixmap_cover_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_).first);
|
pixmap_cover_ = QPixmap::fromImage(ImageUtils::ScaleAndPad(image_original_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_));
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -193,7 +193,7 @@ TEST_F(SingleSong, GetSongWithNoAlbum) {
|
||||||
CollectionBackend::AlbumList albums = backend_->GetAllAlbums();
|
CollectionBackend::AlbumList albums = backend_->GetAllAlbums();
|
||||||
//EXPECT_EQ(1, albums.size());
|
//EXPECT_EQ(1, albums.size());
|
||||||
//EXPECT_EQ("Artist", albums[0].artist);
|
//EXPECT_EQ("Artist", albums[0].artist);
|
||||||
//EXPECT_EQ("", albums[0].album_name);
|
//EXPECT_EQ("", albums[0].album);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,8 +213,8 @@ TEST_F(SingleSong, GetAllAlbums) {
|
||||||
|
|
||||||
CollectionBackend::AlbumList albums = backend_->GetAllAlbums();
|
CollectionBackend::AlbumList albums = backend_->GetAllAlbums();
|
||||||
ASSERT_EQ(1, albums.size());
|
ASSERT_EQ(1, albums.size());
|
||||||
EXPECT_EQ(song_.album(), albums[0].album_name);
|
EXPECT_EQ(song_.album(), albums[0].album);
|
||||||
EXPECT_EQ(song_.artist(), albums[0].artist);
|
EXPECT_EQ(song_.artist(), albums[0].album_artist);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,8 +224,8 @@ TEST_F(SingleSong, GetAlbumsByArtist) {
|
||||||
|
|
||||||
CollectionBackend::AlbumList albums = backend_->GetAlbumsByArtist("Artist");
|
CollectionBackend::AlbumList albums = backend_->GetAlbumsByArtist("Artist");
|
||||||
ASSERT_EQ(1, albums.size());
|
ASSERT_EQ(1, albums.size());
|
||||||
EXPECT_EQ(song_.album(), albums[0].album_name);
|
EXPECT_EQ(song_.album(), albums[0].album);
|
||||||
EXPECT_EQ(song_.artist(), albums[0].artist);
|
EXPECT_EQ(song_.artist(), albums[0].album_artist);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,9 +233,9 @@ TEST_F(SingleSong, GetAlbumArt) {
|
||||||
|
|
||||||
AddDummySong(); if (HasFatalFailure()) return;
|
AddDummySong(); if (HasFatalFailure()) return;
|
||||||
|
|
||||||
CollectionBackend::Album album = backend_->GetAlbumArt("Artist", "AlbumArtist", "Album");
|
CollectionBackend::Album album = backend_->GetAlbumArt("Artist", "Album");
|
||||||
EXPECT_EQ(song_.album(), album.album_name);
|
EXPECT_EQ(song_.album(), album.album);
|
||||||
EXPECT_EQ(song_.artist(), album.artist);
|
EXPECT_EQ(song_.effective_albumartist(), album.album_artist);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ TEST_F(SingleSong, GetSongs) {
|
||||||
|
|
||||||
AddDummySong(); if (HasFatalFailure()) return;
|
AddDummySong(); if (HasFatalFailure()) return;
|
||||||
|
|
||||||
SongList songs = backend_->GetSongs("Artist", "Album");
|
SongList songs = backend_->GetAlbumSongs("Artist", "Album");
|
||||||
ASSERT_EQ(1, songs.size());
|
ASSERT_EQ(1, songs.size());
|
||||||
EXPECT_EQ(song_.album(), songs[0].album());
|
EXPECT_EQ(song_.album(), songs[0].album());
|
||||||
EXPECT_EQ(song_.artist(), songs[0].artist());
|
EXPECT_EQ(song_.artist(), songs[0].artist());
|
||||||
|
|
Loading…
Reference in New Issue