parent
e4c89c1aed
commit
133f094d72
|
@ -34,6 +34,7 @@ set(SOURCES
|
|||
core/thread.cpp
|
||||
core/urlhandler.cpp
|
||||
core/utilities.cpp
|
||||
core/imageutils.cpp
|
||||
core/iconloader.cpp
|
||||
core/qtsystemtrayicon.cpp
|
||||
core/standarditemiconloader.cpp
|
||||
|
|
|
@ -52,8 +52,6 @@
|
|||
#include "collectionquery.h"
|
||||
#include "sqlrow.h"
|
||||
|
||||
const char *CollectionBackend::kSettingsGroup = "Collection";
|
||||
|
||||
CollectionBackend::CollectionBackend(QObject *parent) :
|
||||
CollectionBackendInterface(parent),
|
||||
db_(nullptr),
|
||||
|
@ -671,19 +669,32 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbumsByArtist(const QString
|
|||
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);
|
||||
query.AddCompilationRequirement(false);
|
||||
query.AddWhere("album", album);
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
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);
|
||||
query.AddCompilationRequirement(false);
|
||||
query.AddWhere("artist", artist);
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
query.AddWhere("album", album);
|
||||
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) {
|
||||
|
@ -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) {
|
||||
|
||||
CollectionQuery query(opt);
|
||||
query.SetColumnSpec("url, artist, albumartist, album, compilation_effective, art_automatic, art_manual");
|
||||
query.SetOrderBy("album");
|
||||
query.SetColumnSpec("url, effective_albumartist, album, compilation_effective, art_automatic, art_manual, filetype, cue_path");
|
||||
query.SetOrderBy("effective_albumartist, album, url");
|
||||
|
||||
if (compilation_required) {
|
||||
query.AddCompilationRequirement(true);
|
||||
}
|
||||
else if (!artist.isEmpty()) {
|
||||
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;
|
||||
while (query.Next()) {
|
||||
bool is_compilation = query.Value(4).toBool();
|
||||
bool is_compilation = query.Value(3).toBool();
|
||||
|
||||
Album info;
|
||||
info.first_url = QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||
QUrl url = QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||
if (!is_compilation) {
|
||||
info.artist = query.Value(1).toString();
|
||||
info.album_artist = query.Value(2).toString();
|
||||
info.album_artist = query.Value(1).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("..+:.*"))) {
|
||||
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);
|
||||
}
|
||||
|
||||
QString art_manual = query.Value(6).toString();
|
||||
QString art_manual = query.Value(5).toString();
|
||||
if (art_manual.contains(QRegularExpression("..+:.*"))) {
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
if (!effective_albumartist.isEmpty()) {
|
||||
key.append(effective_albumartist);
|
||||
if (!info.album_artist.isEmpty()) {
|
||||
key.append(info.album_artist);
|
||||
}
|
||||
if (!info.album_name.isEmpty()) {
|
||||
if (!info.album.isEmpty()) {
|
||||
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 (!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;
|
||||
ret.album_name = album;
|
||||
ret.artist = artist;
|
||||
ret.album_artist = albumartist;
|
||||
ret.album = album;
|
||||
ret.album_artist = effective_albumartist;
|
||||
|
||||
CollectionQuery query = CollectionQuery(QueryOptions());
|
||||
query.SetColumnSpec("art_automatic, art_manual, url");
|
||||
if (!albumartist.isEmpty()) {
|
||||
query.AddWhere("albumartist", albumartist);
|
||||
}
|
||||
else if (!artist.isEmpty()) {
|
||||
query.AddWhere("artist", artist);
|
||||
if (!effective_albumartist.isEmpty()) {
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
}
|
||||
query.AddWhere("album", album);
|
||||
|
||||
|
@ -1093,20 +1111,20 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &artist, c
|
|||
if (query.Next()) {
|
||||
ret.art_automatic = QUrl::fromEncoded(query.Value(0).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;
|
||||
|
||||
}
|
||||
|
||||
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());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
@ -1114,15 +1132,9 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QStrin
|
|||
// 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 (!albumartist.isEmpty()) {
|
||||
query.AddWhere("albumartist", albumartist);
|
||||
}
|
||||
else if (!artist.isEmpty()) {
|
||||
query.AddWhere("artist", artist);
|
||||
}
|
||||
|
||||
if (!ExecQuery(&query)) return;
|
||||
|
||||
SongList deleted_songs;
|
||||
|
@ -1133,26 +1145,73 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QStrin
|
|||
}
|
||||
|
||||
// Update the songs
|
||||
QString sql(QString("UPDATE %1 SET art_manual = :cover WHERE album = :album AND unavailable = 0").arg(songs_table_));
|
||||
|
||||
if (!albumartist.isEmpty()) {
|
||||
sql += " AND albumartist = :albumartist";
|
||||
}
|
||||
else if (!artist.isNull()) {
|
||||
sql += " AND artist = :artist";
|
||||
QString sql(QString("UPDATE %1 SET art_manual = :cover ").arg(songs_table_));
|
||||
if (clear_art_automatic) {
|
||||
sql += "AND art_automatic = '' ";
|
||||
}
|
||||
sql += "WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
|
||||
|
||||
QSqlQuery q(db);
|
||||
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);
|
||||
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();
|
||||
db_->CheckErrors(q);
|
||||
|
||||
|
|
|
@ -50,25 +50,23 @@ class CollectionBackendInterface : public QObject {
|
|||
|
||||
struct 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) :
|
||||
artist(_artist),
|
||||
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) :
|
||||
album_artist(_album_artist),
|
||||
album_name(_album_name),
|
||||
album(_album),
|
||||
art_automatic(_art_automatic),
|
||||
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_name;
|
||||
QString album;
|
||||
|
||||
QUrl art_automatic;
|
||||
QUrl art_manual;
|
||||
QUrl first_url;
|
||||
QList<QUrl> urls;
|
||||
Song::FileType filetype;
|
||||
QString cue_path;
|
||||
};
|
||||
typedef QList<Album> AlbumList;
|
||||
|
||||
|
@ -88,8 +86,9 @@ class CollectionBackendInterface : public QObject {
|
|||
|
||||
virtual QStringList GetAllArtists(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 GetSongs(const QString &artist, 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 GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
||||
|
||||
virtual void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url) = 0;
|
||||
virtual Album GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album) = 0;
|
||||
virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) = 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;
|
||||
|
||||
|
@ -118,7 +119,6 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
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 GetAllArtists(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 GetSongs(const QString &artist, 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 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;
|
||||
Album GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album) override;
|
||||
void UpdateManualAlbumArtAsync(const QString &album_artist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) 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;
|
||||
SongList GetSongsById(const QList<int> &ids);
|
||||
|
@ -205,7 +208,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||
void AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
|
||||
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 IncrementPlayCount(const int id);
|
||||
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_[2] = GroupBy_None;
|
||||
|
||||
cover_loader_options_.desired_height_ = kPrettyCoverSize;
|
||||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.get_image_data_ = false;
|
||||
cover_loader_options_.scale_output_image_ = true;
|
||||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.desired_height_ = kPrettyCoverSize;
|
||||
|
||||
if (app_) {
|
||||
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);
|
||||
|
||||
// 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.
|
||||
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 (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)));
|
||||
if (!cached_img && !result.image_scaled.isNull()) {
|
||||
if (!cached_img) {
|
||||
QNetworkCacheMetaData item_metadata;
|
||||
item_metadata.setSaveToDisk(true);
|
||||
item_metadata.setUrl(QUrl(cache_key));
|
||||
|
|
|
@ -471,11 +471,7 @@ void CollectionView::SetShowInVarious(const bool on) {
|
|||
}
|
||||
}
|
||||
if (other_artists.count() > 0) {
|
||||
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) {
|
||||
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) {
|
||||
for (const QString &s : other_artists) {
|
||||
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 (cue_deleted) {
|
||||
for (const Song &song : backend_->GetSongsByUrl(QUrl::fromLocalFile(file))) {
|
||||
if (!song.IsMetadataEqual(matching_song)) {
|
||||
if (!song.IsMetadataAndArtEqual(matching_song)) {
|
||||
t->deleted_songs << song;
|
||||
}
|
||||
}
|
||||
|
@ -611,7 +611,7 @@ void CollectionWatcher::PreserveUserSetData(const QString &file, const QUrl &ima
|
|||
|
||||
t->new_songs << *out;
|
||||
}
|
||||
else if (!matching_song.IsMetadataEqual(*out)) {
|
||||
else if (!matching_song.IsMetadataAndArtEqual(*out)) {
|
||||
qLog(Debug) << file << "metadata changed";
|
||||
|
||||
// Update the song in the DB
|
||||
|
|
|
@ -37,8 +37,8 @@
|
|||
#include <QContextMenuEvent>
|
||||
#include <QPaintEvent>
|
||||
|
||||
#include "core/imageutils.h"
|
||||
#include "covermanager/albumcoverchoicecontroller.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
|
||||
#include "contextview.h"
|
||||
#include "contextalbum.h"
|
||||
|
@ -62,8 +62,8 @@ ContextAlbum::ContextAlbum(QWidget *parent) :
|
|||
cover_loader_options_.desired_height_ = 600;
|
||||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.scale_output_image_ = true;
|
||||
QPair<QImage, QImage> images = AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_strawberry_);
|
||||
pixmap_current_ = QPixmap::fromImage(images.first);
|
||||
QImage image = ImageUtils::ScaleAndPad(image_strawberry_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
||||
if (!image.isNull()) pixmap_current_ = QPixmap::fromImage(image);
|
||||
|
||||
QObject::connect(timeline_fade_, &QTimeLine::valueChanged, this, &ContextAlbum::FadePreviousTrack);
|
||||
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);
|
||||
|
||||
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_->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_) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -144,7 +147,9 @@ void ContextAlbum::FadePreviousTrack(const qreal value) {
|
|||
void ContextAlbum::ScaleCover() {
|
||||
|
||||
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();
|
||||
update();
|
||||
|
||||
|
|
|
@ -67,9 +67,10 @@ ContextAlbumsModel::ContextAlbumsModel(CollectionBackend *backend, Application *
|
|||
|
||||
root_->lazy_loaded = true;
|
||||
|
||||
cover_loader_options_.desired_height_ = kPrettyCoverSize;
|
||||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.get_image_data_ = false;
|
||||
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);
|
||||
|
||||
|
@ -159,7 +160,7 @@ void ContextAlbumsModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoad
|
|||
pending_cache_keys_.remove(cache_key);
|
||||
|
||||
// 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.
|
||||
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||
}
|
||||
|
|
|
@ -603,13 +603,13 @@ void ContextView::SetSong() {
|
|||
const QueryOptions opt;
|
||||
CollectionBackend::AlbumList albumlist;
|
||||
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) {
|
||||
label_play_albums_->show();
|
||||
widget_albums_->show();
|
||||
label_play_albums_->setText("<b>" + tr("Albums by %1").arg( song_playing_.artist().toHtmlEscaped()) + "</b>");
|
||||
for (CollectionBackend::Album album : albumlist) {
|
||||
SongList songs = app_->collection_backend()->GetSongs(song_playing_.artist(), album.album_name, opt);
|
||||
label_play_albums_->setText("<b>" + tr("Albums by %1").arg(song_playing_.effective_albumartist().toHtmlEscaped()) + "</b>");
|
||||
for (const CollectionBackend::Album &album : albumlist) {
|
||||
SongList songs = app_->collection_backend()->GetAlbumSongs(song_playing_.effective_albumartist(), album.album, opt);
|
||||
widget_albums_->albums_model()->AddSongs(songs);
|
||||
}
|
||||
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/currentalbumcoverloader.h"
|
||||
#include "covermanager/coverproviders.h"
|
||||
#include "covermanager/albumcoverimageresult.h"
|
||||
#include "lyrics/lyricsproviders.h"
|
||||
#ifndef Q_OS_WIN
|
||||
# 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_->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_->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_->search_cover_auto_action(), &QAction::triggered, this, &MainWindow::SearchCoverAutomatically);
|
||||
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_ = Song();
|
||||
image_original_ = QImage();
|
||||
album_cover_ = AlbumCoverImageResult();
|
||||
|
||||
app_->scrobbler()->ClearPlaying();
|
||||
|
||||
|
@ -1312,6 +1315,14 @@ void MainWindow::SongChanged(const Song &song) {
|
|||
|
||||
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) {
|
||||
|
@ -2073,7 +2084,7 @@ void MainWindow::RenumberTracks() {
|
|||
song.set_track(track);
|
||||
TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
|
||||
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;
|
||||
}
|
||||
|
@ -2085,7 +2096,7 @@ void MainWindow::SongSaveComplete(TagReaderReply *reply, const QPersistentModelI
|
|||
if (reply->is_successful() && idx.isValid()) {
|
||||
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)) {
|
||||
TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(song.url().toLocalFile(), song);
|
||||
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() {
|
||||
album_cover_choice_controller_->SaveCoverToFileManual(song_, image_original_);
|
||||
album_cover_choice_controller_->SaveCoverToFileManual(song_, album_cover_);
|
||||
}
|
||||
|
||||
void MainWindow::UnsetCover() {
|
||||
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() {
|
||||
album_cover_choice_controller_->ShowCover(song_, image_original_);
|
||||
album_cover_choice_controller_->ShowCover(song_, album_cover_.image);
|
||||
}
|
||||
|
||||
void MainWindow::SearchCoverAutomatically() {
|
||||
|
@ -2936,9 +2955,9 @@ void MainWindow::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult
|
|||
if (song != song_playing_) return;
|
||||
|
||||
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();
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
#include "settings/settingsdialog.h"
|
||||
#include "settings/behavioursettingspage.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "covermanager/albumcoverimageresult.h"
|
||||
|
||||
class About;
|
||||
class Console;
|
||||
|
@ -255,6 +256,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||
void LoadCoverFromURL();
|
||||
void SearchForCover();
|
||||
void UnsetCover();
|
||||
void ClearCover();
|
||||
void DeleteCover();
|
||||
void ShowCover();
|
||||
void SearchCoverAutomatically();
|
||||
void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result);
|
||||
|
@ -386,7 +389,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||
|
||||
Song song_;
|
||||
Song song_playing_;
|
||||
QImage image_original_;
|
||||
AlbumCoverImageResult album_cover_;
|
||||
int exit_count_;
|
||||
bool delete_files_;
|
||||
|
||||
|
|
|
@ -115,10 +115,10 @@ void RegisterMetaTypes() {
|
|||
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
|
||||
qRegisterMetaType<AlbumCoverLoaderResult>("AlbumCoverLoaderResult");
|
||||
qRegisterMetaType<AlbumCoverLoaderResult::Type>("AlbumCoverLoaderResult::Type");
|
||||
qRegisterMetaType<CoverSearchResult>("CoverSearchResult");
|
||||
qRegisterMetaType<CoverProviderSearchResult>("CoverProviderSearchResult");
|
||||
qRegisterMetaType<CoverSearchStatistics>("CoverSearchStatistics");
|
||||
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
|
||||
qRegisterMetaType<CoverSearchResults>("CoverSearchResults");
|
||||
qRegisterMetaType<QList<CoverProviderSearchResult> >("QList<CoverProviderSearchResult>");
|
||||
qRegisterMetaType<CoverProviderSearchResults>("CoverProviderSearchResults");
|
||||
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
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_);
|
||||
|
||||
QUrl cover_url;
|
||||
if (result.cover_url.isValid() && result.cover_url.isLocalFile() && QFile(result.cover_url.toLocalFile()).exists()) {
|
||||
cover_url = result.cover_url;
|
||||
if (result.album_cover.cover_url.isValid() && result.album_cover.cover_url.isLocalFile() && QFile(result.album_cover.cover_url.toLocalFile()).exists()) {
|
||||
cover_url = result.album_cover.cover_url;
|
||||
}
|
||||
else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
|
||||
cover_url = result.temp_cover_url;
|
||||
|
|
|
@ -183,7 +183,7 @@ class Mpris2 : public QObject {
|
|||
|
||||
// Methods
|
||||
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:
|
||||
// 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_manual() const { return d->art_manual_; }
|
||||
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; }
|
||||
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::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_; }
|
||||
|
@ -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_; }
|
||||
|
||||
void Song::set_id(int id) { d->id_ = id; }
|
||||
|
@ -1054,13 +1068,19 @@ void Song::InitArtManual() {
|
|||
if (QFile::exists(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) {
|
||||
d->art_manual_ = QUrl::fromLocalFile(file.path() + QDir::separator() + files.first());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Song::InitArtAutomatic() {
|
||||
|
||||
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->samplerate_ == other.d->samplerate_ &&
|
||||
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_;
|
||||
}
|
||||
|
||||
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 {
|
||||
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 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 InitArtAutomatic();
|
||||
|
||||
bool MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle);
|
||||
|
||||
|
@ -255,6 +256,7 @@ class Song {
|
|||
bool is_metadata_good() const;
|
||||
bool art_automatic_is_valid() const;
|
||||
bool art_manual_is_valid() const;
|
||||
bool has_valid_art() 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:
|
||||
|
@ -264,13 +266,19 @@ class Song {
|
|||
// Returns true if this Song had it's cover manually unset by user.
|
||||
bool has_manually_unset_cover() const;
|
||||
// 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.
|
||||
bool has_embedded_cover() const;
|
||||
// Sets a flag saying that this song (it's media file) has an 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 &effective_stream_url() const;
|
||||
const QImage &image() const;
|
||||
|
@ -355,6 +363,7 @@ class Song {
|
|||
|
||||
// Comparison functions
|
||||
bool IsMetadataEqual(const Song &other) const;
|
||||
bool IsMetadataAndArtEqual(const Song &other) const;
|
||||
bool IsOnSameAlbum(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);
|
||||
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)));
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -63,7 +63,8 @@ class TagReaderClient : public QObject {
|
|||
void ReadFileBlocking(const QString &filename, Song *song);
|
||||
bool SaveFileBlocking(const QString &filename, const Song &metadata);
|
||||
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);
|
||||
|
||||
// TODO: Make this not a singleton
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
#include <QtEvents>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkInterface>
|
||||
#include <QImageReader>
|
||||
#include <QMimeDatabase>
|
||||
#include <QtDebug>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
# include <QRandomGenerator>
|
||||
|
@ -102,9 +102,6 @@
|
|||
|
||||
namespace Utilities {
|
||||
|
||||
QStringList kSupportedImageMimeTypes;
|
||||
QStringList kSupportedImageFormats;
|
||||
|
||||
static QString tr(const char *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;
|
||||
}
|
||||
|
||||
QStringList SupportedImageMimeTypes() {
|
||||
QByteArray ReadDataFromFile(const QString &filename) {
|
||||
|
||||
if (kSupportedImageMimeTypes.isEmpty()) {
|
||||
for (const QByteArray &mimetype : QImageReader::supportedMimeTypes()) {
|
||||
kSupportedImageMimeTypes << mimetype;
|
||||
}
|
||||
QFile file(filename);
|
||||
QByteArray data;
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
data = file.readAll();
|
||||
file.close();
|
||||
}
|
||||
|
||||
return kSupportedImageMimeTypes;
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
QStringList SupportedImageFormats() {
|
||||
QString MimeTypeFromData(const QByteArray &data) {
|
||||
|
||||
if (kSupportedImageFormats.isEmpty()) {
|
||||
for (const QByteArray &filetype : QImageReader::supportedImageFormats()) {
|
||||
kSupportedImageFormats << filetype;
|
||||
}
|
||||
}
|
||||
if (data.isEmpty()) return QString();
|
||||
|
||||
return kSupportedImageFormats;
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
return QMimeDatabase().mimeTypeForData(data).name();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -143,9 +143,8 @@ QString ReplaceVariable(const QString &variable, const Song &song, const QString
|
|||
|
||||
bool IsColorDark(const QColor &color);
|
||||
|
||||
QStringList SupportedImageMimeTypes();
|
||||
QStringList SupportedImageFormats();
|
||||
QList<QByteArray> ImageFormatsForMimeType(const QByteArray &mimetype);
|
||||
QByteArray ReadDataFromFile(const QString &filename);
|
||||
QString MimeTypeFromData(const QByteArray &data);
|
||||
|
||||
} // namespace
|
||||
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
|
||||
#include <QtGlobal>
|
||||
#include <QGuiApplication>
|
||||
#include <QtConcurrentRun>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
#include <QWidget>
|
||||
|
@ -45,12 +48,16 @@
|
|||
#include <QFileDialog>
|
||||
#include <QLabel>
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
#include <QMenu>
|
||||
#include <QSettings>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "core/utilities.h"
|
||||
#include "core/imageutils.h"
|
||||
#include "core/application.h"
|
||||
#include "core/song.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/application.h"
|
||||
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
|
@ -61,6 +68,7 @@
|
|||
#include "albumcoverfetcher.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "albumcoversearcher.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
#include "coverfromurldialog.h"
|
||||
#include "currentalbumcoverloader.h"
|
||||
|
||||
|
@ -77,11 +85,23 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) :
|
|||
cover_fetcher_(nullptr),
|
||||
save_file_dialog_(nullptr),
|
||||
cover_from_url_dialog_(nullptr),
|
||||
cover_album_dir_(false),
|
||||
cover_filename_(CollectionSettingsPage::SaveCover_Hash),
|
||||
cover_from_file_(nullptr),
|
||||
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_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);
|
||||
|
@ -89,14 +109,18 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) :
|
|||
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);
|
||||
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);
|
||||
|
||||
search_cover_auto_ = new QAction(tr("Search automatically"), this);
|
||||
search_cover_auto_->setCheckable(true);
|
||||
search_cover_auto_->setChecked(false);
|
||||
|
||||
separator_ = new QAction(this);
|
||||
separator_->setSeparator(true);
|
||||
separator2_ = new QAction(this);
|
||||
separator2_->setSeparator(true);
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
|
@ -113,6 +137,7 @@ void AlbumCoverChoiceController::Init(Application *app) {
|
|||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
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;
|
||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||
cover_album_dir_ = s.value("cover_album_dir", false).toBool();
|
||||
cover_filename_ = CollectionSettingsPage::SaveCover(s.value("cover_filename", CollectionSettingsPage::SaveCover_Hash).toInt());
|
||||
save_cover_type_ = CollectionSettingsPage::SaveCoverType(s.value("save_cover_type", CollectionSettingsPage::SaveCoverType_Cache).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_overwrite_ = s.value("cover_overwrite", false).toBool();
|
||||
cover_lowercase_ = s.value("cover_lowercase", false).toBool();
|
||||
|
@ -131,30 +156,74 @@ void AlbumCoverChoiceController::ReloadSettings() {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
||||
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));
|
||||
|
||||
if (cover_file.isNull()) return QUrl();
|
||||
if (cover_file.isEmpty()) return QUrl();
|
||||
|
||||
// Can we load the image?
|
||||
QImage image(cover_file);
|
||||
if (QImage(cover_file).isNull()) return QUrl();
|
||||
|
||||
if (image.isNull()) {
|
||||
return QUrl();
|
||||
}
|
||||
else {
|
||||
QUrl cover_url(QUrl::fromLocalFile(cover_file));
|
||||
SaveCoverToSong(song, cover_url);
|
||||
return cover_url;
|
||||
switch(get_save_album_cover_type()) {
|
||||
case CollectionSettingsPage::SaveCoverType_Embedded:
|
||||
if (song->save_embedded_cover_supported()) {
|
||||
SaveCoverEmbeddedAutomatic(*song, cover_file);
|
||||
return QUrl::fromLocalFile(Song::kEmbeddedCover);
|
||||
}
|
||||
// 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 = "/";
|
||||
|
||||
|
@ -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));
|
||||
|
||||
if (save_filename.isNull()) return;
|
||||
if (save_filename.isEmpty()) return;
|
||||
|
||||
QString extension = save_filename.right(4);
|
||||
if (!extension.startsWith('.') || !QImageWriter::supportedImageFormats().contains(extension.right(3).toUtf8())) {
|
||||
QFileInfo fileinfo(save_filename);
|
||||
if (fileinfo.suffix().isEmpty()) {
|
||||
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) {
|
||||
|
||||
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();
|
||||
}
|
||||
else {
|
||||
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
|
||||
if (cover_url.isEmpty()) return QUrl();
|
||||
SaveCoverToSong(song, cover_url);
|
||||
return cover_url;
|
||||
return SaveCoverAutomatic(song, result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
QString album = song->effective_album();
|
||||
album.remove(Song::kAlbumRemoveDisc);
|
||||
album.remove(Song::kAlbumRemoveMisc);
|
||||
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
|
||||
|
||||
// Get something sensible to stick in the search box
|
||||
QImage image = cover_searcher_->Exec(song->effective_albumartist(), album);
|
||||
|
||||
if (image.isNull()) {
|
||||
return QUrl();
|
||||
AlbumCoverImageResult result = SearchForImage(song);
|
||||
if (result.is_valid()) {
|
||||
return SaveCoverAutomatic(song, result);
|
||||
}
|
||||
else {
|
||||
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
|
||||
if (cover_url.isEmpty()) return QUrl();
|
||||
SaveCoverToSong(song, cover_url);
|
||||
return cover_url;
|
||||
return QUrl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 cover_url(QUrl::fromLocalFile(Song::kManuallyUnsetCover));
|
||||
SaveCoverToSong(song, cover_url);
|
||||
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
|
||||
|
||||
QUrl cover_url = QUrl::fromLocalFile(Song::kManuallyUnsetCover);
|
||||
SaveArtManualToSong(song, 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 (pixmap.isNull()) return;
|
||||
ShowCover(song, pixmap);
|
||||
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
|
||||
|
||||
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) {
|
||||
|
||||
if (song.art_manual().isLocalFile() || song.art_automatic().isLocalFile()) {
|
||||
QPixmap pixmap = AlbumCoverLoader::TryLoadPixmap(song.art_automatic(), song.art_manual(), song.url());
|
||||
if (image.isNull()) {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
|
||||
|
@ -335,46 +490,22 @@ void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl
|
|||
song = cover_fetching_tasks_.take(id);
|
||||
}
|
||||
|
||||
if (!image.isNull()) {
|
||||
QUrl new_cover_url = SaveCoverToFileAutomatic(&song, cover_url, image, false);
|
||||
if (!new_cover_url.isEmpty()) SaveCoverToSong(&song, new_cover_url);
|
||||
if (result.is_valid()) {
|
||||
SaveCoverAutomatic(&song, result);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
song->set_art_manual(cover_url);
|
||||
|
||||
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;
|
||||
}
|
||||
song->set_art_automatic(art_automatic);
|
||||
|
||||
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()) {
|
||||
|
@ -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();
|
||||
|
||||
QUrl new_cover_url(QUrl::fromLocalFile(filepath));
|
||||
|
||||
// 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 && QFile::exists(filepath) && !cover_overwrite_ && !overwrite && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern) {
|
||||
return new_cover_url;
|
||||
QFile file(filepath);
|
||||
// Don't overwrite when saving in album dir if the filename is set to pattern unless "force_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()) {
|
||||
while (file.exists()) {
|
||||
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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -444,13 +722,43 @@ QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
|
|||
if (e->mimeData()->hasImage()) {
|
||||
QImage image = qvariant_cast<QImage>(e->mimeData()->imageData());
|
||||
if (!image.isNull()) {
|
||||
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
|
||||
if (cover_url.isEmpty()) return QUrl();
|
||||
SaveCoverToSong(song, cover_url);
|
||||
return cover_url;
|
||||
return SaveCoverAutomatic(song, AlbumCoverImageResult(image));
|
||||
}
|
||||
}
|
||||
|
||||
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 <QObject>
|
||||
#include <QWidget>
|
||||
#include <QPair>
|
||||
#include <QSet>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
class QFileDialog;
|
||||
class QAction;
|
||||
class QActionGroup;
|
||||
class QMenu;
|
||||
class QDragEnterEvent;
|
||||
class QDropEvent;
|
||||
|
||||
|
@ -63,6 +69,9 @@ class AlbumCoverChoiceController : public QWidget {
|
|||
void Init(Application *app);
|
||||
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.
|
||||
|
||||
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 *search_for_cover_action() const { return search_for_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 *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.
|
||||
// Otherwise, the path to the chosen cover will be returned.
|
||||
AlbumCoverImageResult LoadImageFromFile(Song *song);
|
||||
QUrl LoadCoverFromFile(Song *song);
|
||||
|
||||
// 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.
|
||||
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.
|
||||
// This returns the downloaded image or null image if something went wrong for example when user cancelled the dialog.
|
||||
QUrl LoadCoverFromURL(Song *song);
|
||||
AlbumCoverImageResult LoadImageFromURL();
|
||||
|
||||
// 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.
|
||||
QUrl SearchForCover(Song *song);
|
||||
AlbumCoverImageResult SearchForImage(Song *song);
|
||||
|
||||
// Returns a path which indicates that the cover has been unset manually.
|
||||
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.
|
||||
void ShowCover(const Song &song);
|
||||
void ShowCover(const Song &song, const QImage &image);
|
||||
void ShowCover(const Song &song, const QImage &image = QImage());
|
||||
void ShowCover(const Song &song, const QPixmap &pixmap);
|
||||
|
||||
// Search for covers automatically
|
||||
qint64 SearchCoverAutomatically(const Song &song);
|
||||
|
||||
// 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.
|
||||
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.
|
||||
QUrl SaveCoverToFileAutomatic(const Song *song, const QUrl &cover_url, const QImage &image, const bool overwrite = false);
|
||||
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 SaveCoverAutomatic(Song *song, const AlbumCoverImageResult &result);
|
||||
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);
|
||||
|
||||
|
@ -128,9 +153,11 @@ class AlbumCoverChoiceController : public QWidget {
|
|||
void AutomaticCoverSearchDone();
|
||||
|
||||
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:
|
||||
|
||||
QString GetInitialPathForFileDialog(const Song &song, const QString &filename);
|
||||
|
||||
static bool IsKnownImageExtension(const QString &suffix);
|
||||
|
@ -145,21 +172,27 @@ class AlbumCoverChoiceController : public QWidget {
|
|||
|
||||
QAction *cover_from_file_;
|
||||
QAction *cover_to_file_;
|
||||
QAction *separator_;
|
||||
QAction *cover_from_url_;
|
||||
QAction *search_for_cover_;
|
||||
QAction *separator1_;
|
||||
QAction *unset_cover_;
|
||||
QAction *delete_cover_;
|
||||
QAction *clear_cover_;
|
||||
QAction *separator2_;
|
||||
QAction *show_cover_;
|
||||
QAction *search_cover_auto_;
|
||||
|
||||
QMap<quint64, Song> cover_fetching_tasks_;
|
||||
QMap<qint64, Song> cover_save_tasks_;
|
||||
QMutex mutex_cover_save_tasks_;
|
||||
|
||||
bool cover_album_dir_;
|
||||
CollectionSettingsPage::SaveCover cover_filename_;
|
||||
CollectionSettingsPage::SaveCoverType save_cover_type_;
|
||||
CollectionSettingsPage::SaveCoverFilename save_cover_filename_;
|
||||
QString cover_pattern_;
|
||||
bool cover_overwrite_;
|
||||
bool cover_lowercase_;
|
||||
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;
|
||||
request.id = next_id_++;
|
||||
|
@ -66,7 +66,7 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString
|
|||
request.album = request.album.remove(Song::kAlbumRemoveMisc);
|
||||
request.title = title;
|
||||
request.search = false;
|
||||
request.fetchall = fetchall;
|
||||
request.batch = batch;
|
||||
|
||||
AddRequest(request);
|
||||
return request.id;
|
||||
|
@ -83,7 +83,7 @@ quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString
|
|||
request.album = request.album.remove(Song::kAlbumRemoveMisc);
|
||||
request.title = title;
|
||||
request.search = true;
|
||||
request.fetchall = false;
|
||||
request.batch = false;
|
||||
|
||||
AddRequest(request);
|
||||
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;
|
||||
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;
|
||||
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
||||
|
||||
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 <QHash>
|
||||
#include <QQueue>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
|
||||
#include "coversearchstatistics.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
class QTimer;
|
||||
class NetworkAccessManager;
|
||||
|
@ -44,7 +46,7 @@ class AlbumCoverFetcherSearch;
|
|||
|
||||
// This class represents a single search-for-cover request. It identifies and describes the request.
|
||||
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
|
||||
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?
|
||||
bool search;
|
||||
|
||||
// Is the request part of fetchall (fetching all missing covers)
|
||||
bool fetchall;
|
||||
// Is the request part of a batch (fetching all missing covers)
|
||||
bool batch;
|
||||
};
|
||||
|
||||
// This structure represents a single result of some album's cover search request.
|
||||
struct CoverSearchResult {
|
||||
explicit CoverSearchResult() : score_provider(0.0), score_match(0.0), score_quality(0.0), number(0) {}
|
||||
struct CoverProviderSearchResult {
|
||||
explicit CoverProviderSearchResult() : score_provider(0.0), score_match(0.0), score_quality(0.0), number(0) {}
|
||||
|
||||
// Used for grouping in the user interface.
|
||||
QString provider;
|
||||
|
@ -94,11 +96,11 @@ struct CoverSearchResult {
|
|||
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).
|
||||
typedef QList<CoverSearchResult> CoverSearchResults;
|
||||
Q_DECLARE_METATYPE(QList<CoverSearchResult>)
|
||||
typedef QList<CoverProviderSearchResult> CoverProviderSearchResults;
|
||||
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.
|
||||
class AlbumCoverFetcher : public QObject {
|
||||
|
@ -111,17 +113,17 @@ class AlbumCoverFetcher : public QObject {
|
|||
static const int kMaxConcurrentRequests;
|
||||
|
||||
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();
|
||||
|
||||
signals:
|
||||
void AlbumCoverFetched(quint64 request_id, QUrl cover_url, QImage cover, CoverSearchStatistics statistics);
|
||||
void SearchFinished(quint64 request_id, CoverSearchResults results, CoverSearchStatistics statistics);
|
||||
void AlbumCoverFetched(quint64 request_id, AlbumCoverImageResult result, CoverSearchStatistics statistics);
|
||||
void SearchFinished(quint64 request_id, CoverProviderSearchResults results, CoverSearchStatistics statistics);
|
||||
|
||||
private slots:
|
||||
void SingleSearchFinished(const quint64, const CoverSearchResults results);
|
||||
void SingleCoverFetched(const quint64, const QUrl &cover_url, const QImage &image);
|
||||
void SingleSearchFinished(const quint64, const CoverProviderSearchResults &results);
|
||||
void SingleCoverFetched(const quint64, const AlbumCoverImageResult &result);
|
||||
void StartRequests();
|
||||
|
||||
private:
|
||||
|
|
|
@ -40,12 +40,14 @@
|
|||
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/imageutils.h"
|
||||
#include "core/networkaccessmanager.h"
|
||||
#include "core/networktimeouts.h"
|
||||
#include "albumcoverfetcher.h"
|
||||
#include "albumcoverfetchersearch.h"
|
||||
#include "coverprovider.h"
|
||||
#include "coverproviders.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 20000;
|
||||
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 6000;
|
||||
|
@ -99,8 +101,8 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Skip provider if it does not have fetchall set and we are doing fetchall - "Fetch Missing Covers".
|
||||
if (!provider->fetchall() && request_.fetchall) {
|
||||
// Skip provider if it does not have batch set and we are doing a batch - "Fetch Missing Covers".
|
||||
if (!provider->batch() && request_.batch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -109,7 +111,7 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
|
|||
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);
|
||||
const int id = cover_providers->NextId();
|
||||
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;
|
||||
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) {
|
||||
|
||||
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;
|
||||
|
||||
|
@ -256,7 +258,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
|
|||
// No results?
|
||||
if (results_.isEmpty()) {
|
||||
statistics_.missing_images_++;
|
||||
emit AlbumCoverFetched(request_.id, QUrl(), QImage());
|
||||
emit AlbumCoverFetched(request_.id, AlbumCoverImageResult());
|
||||
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.
|
||||
// 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();
|
||||
|
||||
|
@ -275,7 +277,7 @@ void AlbumCoverFetcherSearch::FetchMoreImages() {
|
|||
int i = 0;
|
||||
while (!results_.isEmpty()) {
|
||||
++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();
|
||||
|
||||
|
@ -309,13 +311,11 @@ void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(QNetworkReply *reply) {
|
|||
reply->deleteLater();
|
||||
|
||||
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();
|
||||
|
||||
if (cancel_requested_) {
|
||||
return;
|
||||
}
|
||||
if (cancel_requested_) return;
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qLog(Error) << "Error requesting" << reply->url() << reply->errorString();
|
||||
|
@ -325,15 +325,17 @@ void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(QNetworkReply *reply) {
|
|||
}
|
||||
else {
|
||||
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;
|
||||
if (image.loadFromData(reply->readAll())) {
|
||||
if (result.image_size != QSize(0,0) && result.image_size != image.size()) {
|
||||
if (image.loadFromData(image_data)) {
|
||||
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();
|
||||
}
|
||||
result.image_size = 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();
|
||||
}
|
||||
else {
|
||||
|
@ -380,26 +382,24 @@ float AlbumCoverFetcherSearch::ScoreImage(const QSize size) const {
|
|||
|
||||
void AlbumCoverFetcherSearch::SendBestImage() {
|
||||
|
||||
QUrl cover_url;
|
||||
QImage image;
|
||||
AlbumCoverImageResult result;
|
||||
|
||||
if (!candidate_images_.isEmpty()) {
|
||||
const CandidateImage best_image = candidate_images_.values().back();
|
||||
cover_url = best_image.first.image_url;
|
||||
image = best_image.second;
|
||||
result = best_image.album_cover;
|
||||
|
||||
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_width_ += image.width();
|
||||
statistics_.chosen_height_ += image.height();
|
||||
statistics_.chosen_width_ += result.image.width();
|
||||
statistics_.chosen_height_ += result.image.height();
|
||||
}
|
||||
else {
|
||||
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();
|
||||
}
|
||||
|
||||
bool AlbumCoverFetcherSearch::CoverSearchResultCompareScore(const CoverSearchResult &a, const CoverSearchResult &b) {
|
||||
bool AlbumCoverFetcherSearch::CoverProviderSearchResultCompareScore(const CoverProviderSearchResult &a, const CoverProviderSearchResult &b) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -29,12 +29,14 @@
|
|||
#include <QPair>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
|
||||
#include "albumcoverfetcher.h"
|
||||
#include "coversearchstatistics.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
class QNetworkReply;
|
||||
class CoverProvider;
|
||||
|
@ -59,23 +61,23 @@ class AlbumCoverFetcherSearch : public QObject {
|
|||
|
||||
CoverSearchStatistics statistics() const { return statistics_; }
|
||||
|
||||
static bool CoverSearchResultCompareNumber(const CoverSearchResult &a, const CoverSearchResult &b);
|
||||
static bool CoverProviderSearchResultCompareNumber(const CoverProviderSearchResult &a, const CoverProviderSearchResult &b);
|
||||
|
||||
signals:
|
||||
// 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.
|
||||
void AlbumCoverFetched(const quint64, const QUrl &cover_url, const QImage &cover);
|
||||
void AlbumCoverFetched(const quint64, AlbumCoverImageResult result);
|
||||
|
||||
private slots:
|
||||
void ProviderSearchResults(const int id, const CoverSearchResults &results);
|
||||
void ProviderSearchFinished(const int id, const CoverSearchResults &results);
|
||||
void ProviderSearchResults(const int id, const CoverProviderSearchResults &results);
|
||||
void ProviderSearchFinished(const int id, const CoverProviderSearchResults &results);
|
||||
void ProviderCoverFetchFinished(QNetworkReply *reply);
|
||||
void TerminateSearch();
|
||||
|
||||
private:
|
||||
void ProviderSearchResults(CoverProvider *provider, const CoverSearchResults &results);
|
||||
void ProviderSearchResults(CoverProvider *provider, const CoverProviderSearchResults &results);
|
||||
void AllProvidersFinished();
|
||||
|
||||
void FetchMoreImages();
|
||||
|
@ -83,7 +85,7 @@ class AlbumCoverFetcherSearch : public QObject {
|
|||
void SendBestImage();
|
||||
|
||||
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:
|
||||
static const int kSearchTimeoutMs;
|
||||
|
@ -97,14 +99,18 @@ class AlbumCoverFetcherSearch : public QObject {
|
|||
CoverSearchRequest request_;
|
||||
|
||||
// Complete results (from all of the available providers).
|
||||
CoverSearchResults results_;
|
||||
CoverProviderSearchResults results_;
|
||||
|
||||
QMap<int, CoverProvider*> pending_requests_;
|
||||
QMap<QNetworkReply*, CoverSearchResult> pending_image_loads_;
|
||||
QMap<QNetworkReply*, CoverProviderSearchResult> pending_image_loads_;
|
||||
NetworkTimeouts* image_load_timeout_;
|
||||
|
||||
// QMap is sorted by key (score). Values are (result, image)
|
||||
typedef QPair<CoverSearchResult, QImage> CandidateImage;
|
||||
// QMap is sorted by key (score).
|
||||
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_;
|
||||
|
||||
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 <QThread>
|
||||
#include <QMutex>
|
||||
#include <QBuffer>
|
||||
#include <QSet>
|
||||
#include <QList>
|
||||
#include <QQueue>
|
||||
#include <QVariant>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrl>
|
||||
|
@ -40,25 +42,29 @@
|
|||
#include <QSize>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QMimeDatabase>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/networkaccessmanager.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/imageutils.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "organize/organizeformat.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
|
||||
: QObject(parent),
|
||||
stop_requested_(false),
|
||||
next_id_(1),
|
||||
load_image_async_id_(1),
|
||||
save_image_async_id_(1),
|
||||
network_(new NetworkAccessManager(this)),
|
||||
cover_album_dir_(false),
|
||||
cover_filename_(CollectionSettingsPage::SaveCover_Hash),
|
||||
save_cover_type_(CollectionSettingsPage::SaveCoverType_Cache),
|
||||
save_cover_filename_(CollectionSettingsPage::SaveCoverFilename_Hash),
|
||||
cover_overwrite_(false),
|
||||
cover_lowercase_(true),
|
||||
cover_replace_spaces_(true),
|
||||
|
@ -89,8 +95,8 @@ void AlbumCoverLoader::ReloadSettings() {
|
|||
|
||||
QSettings s;
|
||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||
cover_album_dir_ = s.value("cover_album_dir", false).toBool();
|
||||
cover_filename_ = CollectionSettingsPage::SaveCover(s.value("cover_filename", CollectionSettingsPage::SaveCover_Hash).toInt());
|
||||
save_cover_type_ = CollectionSettingsPage::SaveCoverType(s.value("save_cover_type", CollectionSettingsPage::SaveCoverType_Cache).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_overwrite_ = s.value("cover_overwrite", 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;
|
||||
filename = Utilities::UnicodeToAscii(filename.toLower());
|
||||
filename = filename.replace(' ', '-');
|
||||
filename = filename.replace("--", "-");
|
||||
filename = filename.remove(OrganizeFormat::kInvalidFatCharacters);
|
||||
filename = filename.simplified();
|
||||
filename = filename.replace(' ', '-')
|
||||
.replace("--", "-")
|
||||
.remove(OrganizeFormat::kInvalidFatCharacters)
|
||||
.simplified();
|
||||
|
||||
if (!extension.isEmpty()) {
|
||||
filename.append('.');
|
||||
|
@ -129,7 +135,7 @@ QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString
|
|||
album.remove(Song::kAlbumRemoveDisc);
|
||||
|
||||
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;
|
||||
}
|
||||
else {
|
||||
|
@ -147,7 +153,10 @@ QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString
|
|||
}
|
||||
|
||||
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.remove(OrganizeFormat::kInvalidFatCharacters);
|
||||
if (cover_lowercase_) filename = filename.toLower();
|
||||
|
@ -157,7 +166,8 @@ QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString
|
|||
filename.append(extension);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if (filename.isEmpty()) {
|
||||
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) {
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
QMutexLocker l(&mutex_load_image_async_);
|
||||
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
|
||||
if (it->id == id) {
|
||||
tasks_.erase(it);
|
||||
|
@ -232,7 +242,7 @@ void AlbumCoverLoader::CancelTask(const quint64 id) {
|
|||
|
||||
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();) {
|
||||
if (ids.contains(it->id)) {
|
||||
it = tasks_.erase(it);
|
||||
|
@ -244,26 +254,58 @@ void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
|
|||
|
||||
}
|
||||
|
||||
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) {
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song) {
|
||||
|
||||
Task task;
|
||||
task.options = options;
|
||||
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;
|
||||
|
||||
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_);
|
||||
task.id = next_id_++;
|
||||
QMutexLocker l(&mutex_load_image_async_);
|
||||
task.id = load_image_async_id_++;
|
||||
tasks_.enqueue(task);
|
||||
}
|
||||
|
||||
|
@ -279,7 +321,7 @@ void AlbumCoverLoader::ProcessTasks() {
|
|||
// Get the next task
|
||||
Task task;
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
QMutexLocker l(&mutex_load_image_async_);
|
||||
if (tasks_.isEmpty()) return;
|
||||
task = tasks_.dequeue();
|
||||
}
|
||||
|
@ -298,8 +340,16 @@ void AlbumCoverLoader::ProcessTask(Task *task) {
|
|||
}
|
||||
|
||||
if (result.loaded_success) {
|
||||
QPair<QImage, QImage> images = ScaleAndPad(task->options, result.image);
|
||||
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(result.type, result.cover_url, result.image, images.first, images.second, task->art_updated));
|
||||
result.album_cover.mime_type = Utilities::MimeTypeFromData(result.album_cover.image_data);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -316,30 +366,33 @@ void AlbumCoverLoader::NextState(Task *task) {
|
|||
}
|
||||
else {
|
||||
// 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) {
|
||||
|
||||
// An image embedded in the song itself takes priority
|
||||
if (!task->embedded_image.isNull()) {
|
||||
QPair<QImage, QImage> images = ScaleAndPad(task->options, task->embedded_image);
|
||||
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, QUrl(), images.first);
|
||||
// Only scale and pad.
|
||||
if (task->album_cover.is_valid()) {
|
||||
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, task->album_cover);
|
||||
}
|
||||
|
||||
// Use cached album cover if possible.
|
||||
if (task->state == State_Manual &&
|
||||
!task->song.art_manual_is_valid() &&
|
||||
task->art_manual.isEmpty() &&
|
||||
task->song.source() != Song::Source_Collection &&
|
||||
!task->options.scale_output_image_ &&
|
||||
!task->options.pad_output_image_) {
|
||||
task->song.InitArtManual();
|
||||
if (task->song.art_manual_is_valid() && task->art_manual != task->song.art_manual()) {
|
||||
task->art_manual = task->song.art_manual();
|
||||
task->art_updated = true;
|
||||
// For local files and streams initialize art if found.
|
||||
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()) {
|
||||
switch (task->state) {
|
||||
case State_None:
|
||||
break;
|
||||
case State_Manual:
|
||||
task->song.InitArtManual();
|
||||
if (task->song.art_manual_is_valid()) task->art_updated = true;
|
||||
break;
|
||||
case State_Automatic:
|
||||
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_Automatic:
|
||||
type = AlbumCoverLoaderResult::Type_Automatic;
|
||||
cover_url = task->art_automatic;
|
||||
cover_url = task->song.art_automatic();
|
||||
break;
|
||||
case State_Manual:
|
||||
type = AlbumCoverLoaderResult::Type_Manual;
|
||||
cover_url = task->art_manual;
|
||||
cover_url = task->song.art_manual();
|
||||
break;
|
||||
}
|
||||
task->type = type;
|
||||
|
||||
if (!cover_url.isEmpty() && !cover_url.path().isEmpty()) {
|
||||
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()) {
|
||||
const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile());
|
||||
if (!taglib_image.isNull()) {
|
||||
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, cover_url, ScaleAndPad(task->options, taglib_image).first);
|
||||
else if (cover_url.path() == Song::kEmbeddedCover && task->song.url().isLocalFile()) {
|
||||
QByteArray image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song.url().toLocalFile());
|
||||
if (!image_data.isEmpty()) {
|
||||
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());
|
||||
return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : image);
|
||||
|
||||
if (cover_url.isLocalFile()) {
|
||||
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.
|
||||
QImage image(cover_url.path());
|
||||
return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : image);
|
||||
QFile file(cover_url.path());
|
||||
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
|
||||
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); });
|
||||
|
||||
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) {
|
||||
// Try to load the image
|
||||
QByteArray image_data = reply->readAll();
|
||||
QString mime_type = Utilities::MimeTypeFromData(image_data);
|
||||
QImage image;
|
||||
if (image.load(reply, nullptr)) {
|
||||
QPair<QImage, QImage> images = ScaleAndPad(task.options, image);
|
||||
emit AlbumCoverLoaded(task.id, AlbumCoverLoaderResult(task.type, cover_url, image, images.first, images.second, task.art_updated));
|
||||
return;
|
||||
if (task.options.get_image_data_) {
|
||||
if (image.loadFromData(image_data)) {
|
||||
QImage image_scaled;
|
||||
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 {
|
||||
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 {
|
||||
|
@ -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_) {
|
||||
image_scaled = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString song_filename, 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(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 {
|
||||
image_scaled = image;
|
||||
}
|
||||
|
||||
// Pad the image to height x height
|
||||
if (options.pad_output_image_) {
|
||||
QImage image_padded(options.desired_height_, options.desired_height_, QImage::Format_ARGB32);
|
||||
image_padded.fill(0);
|
||||
|
||||
QPainter p(&image_padded);
|
||||
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);
|
||||
QByteArray image_data;
|
||||
QBuffer buffer(&image_data);
|
||||
if (buffer.open(QIODevice::WriteOnly)) {
|
||||
if (image.save(&buffer, "JPEG")) {
|
||||
SaveEmbeddedCover(id, urls, image_data);
|
||||
buffer.close();
|
||||
return;
|
||||
}
|
||||
buffer.close();
|
||||
}
|
||||
}
|
||||
|
||||
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 (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()->LoadEmbeddedArtBlocking(url.toLocalFile()));
|
||||
}
|
||||
else if (art_automatic.isLocalFile()) {
|
||||
ret.load(art_automatic.toLocalFile());
|
||||
}
|
||||
else if (art_automatic.scheme().isEmpty()) {
|
||||
ret.load(art_automatic.path());
|
||||
}
|
||||
if (file.size() >= 209715200 || !file.open(QIODevice::ReadOnly)) { // Max 200 MB.
|
||||
emit SaveEmbeddedCoverAsyncFinished(id, false);
|
||||
return;
|
||||
}
|
||||
|
||||
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 <QSet>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
#include <QQueue>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
class QThread;
|
||||
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());
|
||||
|
||||
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 CancelTasks(const QSet<quint64> &ids);
|
||||
|
||||
static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QUrl &url = QUrl());
|
||||
static QPair<QImage, QImage> ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image);
|
||||
qint64 SaveEmbeddedCoverAsync(const QString song_filename, const QString &cover_filename);
|
||||
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:
|
||||
void ExitFinished();
|
||||
void AlbumCoverLoaded(quint64 id, AlbumCoverLoaderResult result);
|
||||
void SaveEmbeddedCoverAsyncFinished(quint64 id, bool success);
|
||||
|
||||
protected slots:
|
||||
void Exit();
|
||||
void ProcessTasks();
|
||||
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:
|
||||
|
||||
struct Task {
|
||||
|
@ -95,11 +115,8 @@ class AlbumCoverLoader : public QObject {
|
|||
AlbumCoverLoaderOptions options;
|
||||
|
||||
quint64 id;
|
||||
QUrl art_manual;
|
||||
QUrl art_automatic;
|
||||
QUrl song_url;
|
||||
Song song;
|
||||
QImage embedded_image;
|
||||
AlbumCoverImageResult album_cover;
|
||||
State state;
|
||||
AlbumCoverLoaderResult::Type type;
|
||||
bool art_updated;
|
||||
|
@ -107,33 +124,42 @@ class AlbumCoverLoader : public QObject {
|
|||
};
|
||||
|
||||
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 loaded_success;
|
||||
|
||||
AlbumCoverLoaderResult::Type type;
|
||||
QUrl cover_url;
|
||||
QImage image;
|
||||
AlbumCoverImageResult album_cover;
|
||||
};
|
||||
|
||||
quint64 EnqueueTask(Task &task);
|
||||
void ProcessTask(Task *task);
|
||||
void NextState(Task *task);
|
||||
TryLoadResult TryLoadImage(Task *task);
|
||||
|
||||
bool stop_requested_;
|
||||
|
||||
QMutex mutex_;
|
||||
QMutex mutex_load_image_async_;
|
||||
QMutex mutex_save_image_async_;
|
||||
QQueue<Task> tasks_;
|
||||
QMap<QNetworkReply*, Task> remote_tasks_;
|
||||
quint64 next_id_;
|
||||
quint64 load_image_async_id_;
|
||||
quint64 save_image_async_id_;
|
||||
|
||||
NetworkAccessManager *network_;
|
||||
|
||||
static const int kMaxRedirects = 3;
|
||||
|
||||
bool cover_album_dir_;
|
||||
CollectionSettingsPage::SaveCover cover_filename_;
|
||||
CollectionSettingsPage::SaveCoverType save_cover_type_;
|
||||
CollectionSettingsPage::SaveCoverFilename save_cover_filename_;
|
||||
QString cover_pattern_;
|
||||
bool cover_overwrite_;
|
||||
bool cover_lowercase_;
|
||||
|
@ -141,6 +167,8 @@ class AlbumCoverLoader : public QObject {
|
|||
|
||||
QThread *original_thread_;
|
||||
|
||||
QMultiMap<qint64, TagReaderReply*> tagreader_save_embedded_art_requests_;
|
||||
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERLOADER_H
|
||||
|
|
|
@ -29,19 +29,25 @@
|
|||
|
||||
struct AlbumCoverLoaderOptions {
|
||||
explicit AlbumCoverLoaderOptions()
|
||||
: desired_height_(120),
|
||||
: get_image_data_(true),
|
||||
get_image_(true),
|
||||
scale_output_image_(true),
|
||||
pad_output_image_(true),
|
||||
create_thumbnail_(false),
|
||||
pad_thumbnail_image_(false) {}
|
||||
pad_thumbnail_image_(false),
|
||||
desired_height_(120),
|
||||
thumbnail_size_(120, 120) {}
|
||||
|
||||
int desired_height_;
|
||||
QSize thumbnail_size_;
|
||||
bool get_image_data_;
|
||||
bool get_image_;
|
||||
bool scale_output_image_;
|
||||
bool pad_output_image_;
|
||||
bool create_thumbnail_;
|
||||
bool pad_thumbnail_image_;
|
||||
int desired_height_;
|
||||
QSize thumbnail_size_;
|
||||
QImage default_output_image_;
|
||||
QImage default_scaled_image_;
|
||||
QImage default_thumbnail_image_;
|
||||
};
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include <QImage>
|
||||
#include <QUrl>
|
||||
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
struct AlbumCoverLoaderResult {
|
||||
|
||||
enum Type {
|
||||
|
@ -36,11 +38,22 @@ struct AlbumCoverLoaderResult {
|
|||
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;
|
||||
QUrl cover_url;
|
||||
QImage image_original;
|
||||
AlbumCoverImageResult album_cover;
|
||||
QImage image_scaled;
|
||||
QImage image_thumbnail;
|
||||
bool updated;
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
#include <QObject>
|
||||
#include <QMainWindow>
|
||||
#include <QWidget>
|
||||
#include <QtConcurrent>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
#include <QItemSelectionModel>
|
||||
|
@ -64,12 +67,15 @@
|
|||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/imageutils.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "widgets/forcescrollperpixel.h"
|
||||
#include "widgets/qsearchfield.h"
|
||||
#include "collection/sqlrow.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "coverproviders.h"
|
||||
#include "albumcovermanager.h"
|
||||
#include "albumcoversearcher.h"
|
||||
|
@ -83,6 +89,7 @@
|
|||
#include "albumcovermanagerlist.h"
|
||||
#include "coversearchstatistics.h"
|
||||
#include "coversearchstatisticsdialog.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
#include "ui_albumcovermanager.h"
|
||||
|
||||
|
@ -93,6 +100,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
|||
ui_(new Ui_CoverManager),
|
||||
mainwindow_(mainwindow),
|
||||
app_(app),
|
||||
collection_backend_(collection_backend),
|
||||
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
||||
cover_fetcher_(new AlbumCoverFetcher(app_->cover_providers(), this)),
|
||||
cover_searcher_(nullptr),
|
||||
|
@ -100,14 +108,13 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
|||
cover_exporter_(new AlbumCoverExporter(this)),
|
||||
artist_icon_(IconLoader::Load("folder-sound")),
|
||||
all_artists_icon_(IconLoader::Load("library-music")),
|
||||
no_cover_icon_(":/pictures/cdcase.png"),
|
||||
no_cover_image_(GenerateNoCoverImage(no_cover_icon_)),
|
||||
no_cover_item_icon_(QPixmap::fromImage(no_cover_image_)),
|
||||
image_nocover_thumbnail_(ImageUtils::GenerateNoCoverImage(QSize(120, 120))),
|
||||
icon_nocover_item_(QPixmap::fromImage(image_nocover_thumbnail_)),
|
||||
context_menu_(new QMenu(this)),
|
||||
progress_bar_(new QProgressBar(this)),
|
||||
abort_progress_(new QPushButton(this)),
|
||||
jobs_(0),
|
||||
collection_backend_(collection_backend) {
|
||||
all_artists_(nullptr) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
ui_->albums->set_cover_manager(this);
|
||||
|
@ -122,7 +129,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
|||
|
||||
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);
|
||||
|
||||
// Set up the status bar
|
||||
|
@ -139,8 +146,15 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
|||
QShortcut *close = new QShortcut(QKeySequence::Close, this);
|
||||
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();
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverManager::~AlbumCoverManager() {
|
||||
|
@ -151,11 +165,10 @@ AlbumCoverManager::~AlbumCoverManager() {
|
|||
}
|
||||
|
||||
void AlbumCoverManager::ReloadSettings() {
|
||||
app_->album_cover_loader()->ReloadSettings();
|
||||
}
|
||||
|
||||
CollectionBackend *AlbumCoverManager::backend() const {
|
||||
return collection_backend_;
|
||||
app_->album_cover_loader()->ReloadSettings();
|
||||
album_cover_choice_controller_->ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
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_->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_->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(cover_exporter_, &AlbumCoverExporter::AlbumCoversExportUpdate, this, &AlbumCoverManager::UpdateExportStatus);
|
||||
|
@ -213,13 +228,19 @@ void AlbumCoverManager::Init() {
|
|||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
restoreGeometry(s.value("geometry").toByteArray());
|
||||
if (!ui_->splitter->restoreState(s.value("splitter_state").toByteArray())) {
|
||||
if (s.contains("geometry")) {
|
||||
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
|
||||
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::SaveEmbeddedCoverAsyncFinished, this, &AlbumCoverManager::SaveEmbeddedCoverAsyncFinished);
|
||||
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
|
@ -227,10 +248,12 @@ void AlbumCoverManager::Init() {
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::showEvent(QShowEvent *) {
|
||||
void AlbumCoverManager::showEvent(QShowEvent *e) {
|
||||
|
||||
LoadGeometry();
|
||||
Reset();
|
||||
if (!e->spontaneous()) {
|
||||
LoadGeometry();
|
||||
Reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -246,7 +269,7 @@ void AlbumCoverManager::closeEvent(QCloseEvent *e) {
|
|||
}
|
||||
}
|
||||
|
||||
SaveGeometry();
|
||||
SaveSettings();
|
||||
|
||||
// Cancel any outstanding requests
|
||||
CancelRequests();
|
||||
|
@ -287,12 +310,13 @@ void AlbumCoverManager::LoadGeometry() {
|
|||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::SaveGeometry() {
|
||||
void AlbumCoverManager::SaveSettings() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("geometry", saveGeometry());
|
||||
s.setValue("splitter_state", ui_->splitter->saveState());
|
||||
s.setValue("save_cover_type", album_cover_choice_controller_->get_save_album_cover_type());
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
@ -305,6 +329,8 @@ void AlbumCoverManager::CancelRequests() {
|
|||
app_->album_cover_loader()->CancelTasks(QSet<quint64>::fromList(cover_loading_tasks_.keys()));
|
||||
#endif
|
||||
cover_loading_tasks_.clear();
|
||||
cover_save_tasks_.clear();
|
||||
cover_save_tasks2_.clear();
|
||||
|
||||
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) {
|
||||
return CompareNocase(left.album_name, right.album_name);
|
||||
return CompareNocase(left.album, right.album);
|
||||
}
|
||||
|
||||
void AlbumCoverManager::Reset() {
|
||||
|
@ -330,8 +356,8 @@ void AlbumCoverManager::Reset() {
|
|||
EnableCoversButtons();
|
||||
|
||||
ui_->artists->clear();
|
||||
new QListWidgetItem(all_artists_icon_, tr("All artists"), ui_->artists, All_Artists);
|
||||
new QListWidgetItem(artist_icon_, tr("Various artists"), ui_->artists, Various_Artists);
|
||||
all_artists_ = new QListWidgetItem(all_artists_icon_, tr("All artists"), ui_->artists, All_Artists);
|
||||
new AlbumItem(artist_icon_, tr("Various artists"), ui_->artists, Various_Artists);
|
||||
|
||||
QStringList artists(collection_backend_->GetAllArtistsWithAlbums());
|
||||
std::stable_sort(artists.begin(), artists.end(), CompareNocase);
|
||||
|
@ -341,6 +367,10 @@ void AlbumCoverManager::Reset() {
|
|||
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() {
|
||||
|
@ -357,9 +387,6 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
|
|||
|
||||
if (!current) return;
|
||||
|
||||
QString artist;
|
||||
if (current->type() == Specific_Artist) artist = current->text();
|
||||
|
||||
ui_->albums->clear();
|
||||
context_menu_items_.clear();
|
||||
CancelRequests();
|
||||
|
@ -377,32 +404,42 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
|
|||
std::stable_sort(albums.begin(), albums.end(), CompareAlbumNameNocase);
|
||||
|
||||
for (const CollectionBackend::Album &info : albums) {
|
||||
|
||||
// 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);
|
||||
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 display_text;
|
||||
|
||||
QString effective_artist = EffectiveAlbumArtistName(*item);
|
||||
if (!artist.isEmpty()) {
|
||||
item->setToolTip(effective_artist + " - " + info.album_name);
|
||||
if (current->type() == Specific_Artist) {
|
||||
display_text = info.album;
|
||||
}
|
||||
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()) {
|
||||
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_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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
UpdateFilter();
|
||||
|
@ -413,11 +450,18 @@ void AlbumCoverManager::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoade
|
|||
|
||||
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();
|
||||
|
||||
}
|
||||
|
@ -440,14 +484,14 @@ void AlbumCoverManager::UpdateFilter() {
|
|||
qint32 without_cover = 0;
|
||||
|
||||
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);
|
||||
item->setHidden(should_hide);
|
||||
|
||||
if (!should_hide) {
|
||||
total_count++;
|
||||
++total_count;
|
||||
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);
|
||||
if (hide == Hide_WithCovers && has_cover) {
|
||||
|
@ -474,9 +518,8 @@ bool AlbumCoverManager::ShouldHide(const QListWidgetItem &item, const QString &f
|
|||
QStringList query = filter.split(' ');
|
||||
for (const QString &s : query) {
|
||||
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_AlbumArtistName).toString().contains(s, Qt::CaseInsensitive);
|
||||
if (!in_text && !in_artist && !in_albumartist) {
|
||||
bool in_albumartist = item.data(Role_AlbumArtist).toString().contains(s, Qt::CaseInsensitive);
|
||||
if (!in_text && !in_albumartist) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -488,11 +531,11 @@ bool AlbumCoverManager::ShouldHide(const QListWidgetItem &item, const QString &f
|
|||
void AlbumCoverManager::FetchAlbumCovers() {
|
||||
|
||||
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 (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;
|
||||
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))
|
||||
return;
|
||||
if (!cover_fetching_tasks_.contains(id)) return;
|
||||
|
||||
QListWidgetItem *item = cover_fetching_tasks_.take(id);
|
||||
if (!image.isNull()) {
|
||||
SaveAndSetCover(item, cover_url, image);
|
||||
AlbumItem *item = cover_fetching_tasks_.take(id);
|
||||
if (!result.image.isNull()) {
|
||||
SaveAndSetCover(item, result);
|
||||
}
|
||||
|
||||
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();
|
||||
if (context_menu_items_.isEmpty()) return false;
|
||||
|
||||
bool some_with_covers = false;
|
||||
|
||||
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_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_->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);
|
||||
context_menu_->popup(e->globalPos());
|
||||
QContextMenuEvent *context_menu_event = static_cast<QContextMenuEvent*>(e);
|
||||
context_menu_->popup(context_menu_event->globalPos());
|
||||
return true;
|
||||
}
|
||||
return QMainWindow::eventFilter(obj, event);
|
||||
return QMainWindow::eventFilter(obj, e);
|
||||
|
||||
}
|
||||
|
||||
Song AlbumCoverManager::GetSingleSelectionAsSong() {
|
||||
|
@ -587,12 +634,12 @@ Song AlbumCoverManager::GetFirstSelectedAsSong() {
|
|||
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);
|
||||
|
||||
QString title = item->data(Role_AlbumName).toString();
|
||||
QString artist_name = EffectiveAlbumArtistName(*item);
|
||||
QString title = item->data(Role_Album).toString();
|
||||
QString artist_name = item->data(Role_AlbumArtist).toString();
|
||||
if (!artist_name.isEmpty()) {
|
||||
result.set_title(artist_name + " - " + title);
|
||||
}
|
||||
|
@ -600,11 +647,13 @@ Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) {
|
|||
result.set_title(title);
|
||||
}
|
||||
|
||||
result.set_artist(item->data(Role_ArtistName).toString());
|
||||
result.set_albumartist(item->data(Role_AlbumArtistName).toString());
|
||||
result.set_album(item->data(Role_AlbumName).toString());
|
||||
result.set_artist(item->data(Role_AlbumArtist).toString());
|
||||
result.set_albumartist(item->data(Role_AlbumArtist).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_manual(item->data(Role_PathManual).toUrl());
|
||||
|
@ -628,8 +677,9 @@ void AlbumCoverManager::ShowCover() {
|
|||
void AlbumCoverManager::FetchSingleCover() {
|
||||
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), false);
|
||||
cover_fetching_tasks_[id] = item;
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(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_++;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
item->setData(Role_PathManual, cover_url);
|
||||
|
@ -653,12 +703,9 @@ void AlbumCoverManager::LoadCoverFromFile() {
|
|||
Song song = GetSingleSelectionAsSong();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromFile(&song);
|
||||
|
||||
if (!cover_url.isEmpty()) {
|
||||
UpdateCoverInList(item, cover_url);
|
||||
AlbumCoverImageResult result = album_cover_choice_controller_->LoadImageFromFile(&song);
|
||||
if (!result.image.isNull()) {
|
||||
SaveImageToAlbums(&song, result);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -666,33 +713,37 @@ void AlbumCoverManager::LoadCoverFromFile() {
|
|||
void AlbumCoverManager::SaveCoverToFile() {
|
||||
|
||||
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
|
||||
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 {
|
||||
if (!song.art_manual().isEmpty() && song.art_manual().isLocalFile() && QFile::exists(song.art_manual().toLocalFile())) {
|
||||
image = QImage(song.art_manual().toLocalFile());
|
||||
}
|
||||
else if (!song.art_manual().isEmpty() && !song.art_manual().path().isEmpty() && song.art_manual().scheme().isEmpty() && QFile::exists(song.art_manual().path())) {
|
||||
image = QImage(song.art_manual().path());
|
||||
}
|
||||
else if (!song.art_automatic().isEmpty() && song.art_automatic().isLocalFile() && QFile::exists(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())) {
|
||||
image = QImage(song.art_automatic().path());
|
||||
}
|
||||
else {
|
||||
image = no_cover_image_;
|
||||
}
|
||||
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 = Utilities::ReadDataFromFile(song.art_manual().path());
|
||||
}
|
||||
else if (song.has_embedded_cover()) {
|
||||
result.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().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());
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
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();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromURL(&song);
|
||||
|
||||
if (!cover_url.isEmpty()) {
|
||||
UpdateCoverInList(item, cover_url);
|
||||
AlbumCoverImageResult result = album_cover_choice_controller_->LoadImageFromURL();
|
||||
if (result.is_valid()) {
|
||||
SaveImageToAlbums(&song, result);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -716,20 +764,58 @@ void AlbumCoverManager::SearchForCover() {
|
|||
Song song = GetFirstSelectedAsSong();
|
||||
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
|
||||
for (QListWidgetItem *current : context_menu_items_) {
|
||||
// Don't save the first one twice
|
||||
if (current != item) {
|
||||
Song current_song = ItemAsSong(current);
|
||||
album_cover_choice_controller_->SaveCoverToSong(¤t_song, cover_url);
|
||||
QList<QUrl> urls;
|
||||
QList<AlbumItem*> album_items;
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||
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();
|
||||
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);
|
||||
|
||||
// Force the 'none' cover on all of the selected items
|
||||
for (QListWidgetItem *current : context_menu_items_) {
|
||||
current->setIcon(no_cover_item_icon_);
|
||||
current->setData(Role_PathManual, cover_url);
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||
album_item->setIcon(icon_nocover_item_);
|
||||
album_item->setData(Role_PathManual, cover_url);
|
||||
|
||||
// Don't save the first one twice
|
||||
if (current != item) {
|
||||
Song current_song = ItemAsSong(current);
|
||||
album_cover_choice_controller_->SaveCoverToSong(¤t_song, cover_url);
|
||||
if (album_item != first_album_item) {
|
||||
Song current_song = ItemAsSong(album_item);
|
||||
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;
|
||||
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");
|
||||
|
||||
QString artist = idx.data(Role_ArtistName).toString();
|
||||
QString albumartist = idx.data(Role_AlbumArtistName).toString();
|
||||
|
||||
QString albumartist = idx.data(Role_AlbumArtist).toString();
|
||||
if (!albumartist.isEmpty()) {
|
||||
q.AddWhere("albumartist", albumartist);
|
||||
}
|
||||
else if (!artist.isEmpty()) {
|
||||
q.AddWhere("artist", artist);
|
||||
q.AddWhere("effective_albumartist", albumartist);
|
||||
}
|
||||
|
||||
q.AddCompilationRequirement(artist.isEmpty() && albumartist.isEmpty());
|
||||
q.AddCompilationRequirement(albumartist.isEmpty());
|
||||
|
||||
if (!collection_backend_->ExecQuery(&q)) return ret;
|
||||
|
||||
|
@ -813,7 +943,7 @@ SongMimeData *AlbumCoverManager::GetMimeDataForAlbums(const QModelIndexList &ind
|
|||
|
||||
void AlbumCoverManager::AlbumDoubleClicked(const QModelIndex &idx) {
|
||||
|
||||
QListWidgetItem *item = static_cast<QListWidgetItem*>(idx.internalPointer());
|
||||
AlbumItem *item = static_cast<AlbumItem*>(idx.internalPointer());
|
||||
if (!item) return;
|
||||
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_AlbumArtistName).toString();
|
||||
const QString album = item->data(Role_AlbumName).toString();
|
||||
const QUrl url = item->data(Role_FirstUrl).toUrl();
|
||||
const QString albumartist = item->data(Role_AlbumArtist).toString();
|
||||
const QString album = item->data(Role_Album).toString();
|
||||
const QList<QUrl> &urls = item->urls;
|
||||
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 (new_cover_url.isEmpty()) return;
|
||||
if (album_cover_choice_controller_->get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType_Embedded && Song::save_embedded_cover_supported(filetype) && !has_cue) {
|
||||
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
|
||||
collection_backend_->UpdateManualAlbumArtAsync(artist, albumartist, album, new_cover_url);
|
||||
if (cover_url.isEmpty()) return;
|
||||
|
||||
// Update the icon in our list
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), new_cover_url);
|
||||
item->setData(Role_PathManual, new_cover_url);
|
||||
cover_loading_tasks_[id] = item;
|
||||
// Save the image in the database
|
||||
collection_backend_->UpdateManualAlbumArtAsync(albumartist, album, cover_url);
|
||||
|
||||
// Update the icon in our list
|
||||
UpdateCoverInList(item, cover_url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -866,7 +1008,7 @@ void AlbumCoverManager::ExportCovers() {
|
|||
cover_exporter_->SetDialogResult(result);
|
||||
|
||||
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
|
||||
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 {
|
||||
QString albumartist = item.data(Role_AlbumArtistName).toString();
|
||||
if (!albumartist.isEmpty()) {
|
||||
return albumartist;
|
||||
}
|
||||
return item.data(Role_ArtistName).toString();
|
||||
}
|
||||
QImage AlbumCoverManager::GenerateNoCoverImage(const QImage &image) const {
|
||||
|
||||
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 nocover = no_cover_icon.pixmap(no_cover_icon.availableSizes().last()).toImage();
|
||||
nocover = nocover.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
QImage square_nocover(120, 120, QImage::Format_ARGB32);
|
||||
square_nocover.fill(0);
|
||||
QPainter p(&square_nocover);
|
||||
QImage image_square(120, 120, QImage::Format_ARGB32);
|
||||
image_square.fill(0);
|
||||
QPainter p(&image_square);
|
||||
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();
|
||||
|
||||
return square_nocover;
|
||||
return image_square;
|
||||
|
||||
}
|
||||
|
||||
bool AlbumCoverManager::ItemHasCover(const QListWidgetItem &item) const {
|
||||
return item.icon().cacheKey() != no_cover_item_icon_.cacheKey();
|
||||
bool AlbumCoverManager::ItemHasCover(const AlbumItem &item) const {
|
||||
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 <QListWidgetItem>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QIcon>
|
||||
|
@ -38,7 +39,9 @@
|
|||
#include "core/song.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
#include "albumcoverchoicecontroller.h"
|
||||
#include "coversearchstatistics.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
|
||||
class QWidget;
|
||||
class QMimeData;
|
||||
|
@ -53,7 +56,6 @@ class QShowEvent;
|
|||
class Application;
|
||||
class CollectionBackend;
|
||||
class SongMimeData;
|
||||
class AlbumCoverChoiceController;
|
||||
class AlbumCoverExport;
|
||||
class AlbumCoverExporter;
|
||||
class AlbumCoverFetcher;
|
||||
|
@ -61,17 +63,21 @@ class AlbumCoverSearcher;
|
|||
|
||||
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 {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent = nullptr);
|
||||
~AlbumCoverManager() override;
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
CollectionBackend *backend() const;
|
||||
QIcon no_cover_icon() const { return no_cover_icon_; }
|
||||
|
||||
void Reset();
|
||||
void Init();
|
||||
void ReloadSettings();
|
||||
|
@ -80,15 +86,15 @@ class AlbumCoverManager : public QMainWindow {
|
|||
void DisableCoversButtons();
|
||||
|
||||
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:
|
||||
void showEvent(QShowEvent*) override;
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
void showEvent(QShowEvent *e) override;
|
||||
void closeEvent(QCloseEvent *e) override;
|
||||
|
||||
// For the album view context menu events
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
bool eventFilter(QObject *obj, QEvent *e) override;
|
||||
|
||||
private:
|
||||
enum ArtistItemType {
|
||||
|
@ -98,12 +104,14 @@ class AlbumCoverManager : public QMainWindow {
|
|||
};
|
||||
|
||||
enum Role {
|
||||
Role_ArtistName = Qt::UserRole + 1,
|
||||
Role_AlbumArtistName,
|
||||
Role_AlbumName,
|
||||
Role_AlbumArtist = Qt::UserRole + 1,
|
||||
Role_Album,
|
||||
Role_PathAutomatic,
|
||||
Role_PathManual,
|
||||
Role_FirstUrl
|
||||
Role_Filetype,
|
||||
Role_CuePath,
|
||||
Role_ImageData,
|
||||
Role_Image
|
||||
};
|
||||
|
||||
enum HideCovers {
|
||||
|
@ -113,21 +121,29 @@ class AlbumCoverManager : public QMainWindow {
|
|||
};
|
||||
|
||||
void LoadGeometry();
|
||||
void SaveGeometry();
|
||||
void SaveSettings();
|
||||
|
||||
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.
|
||||
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.
|
||||
Song GetFirstSelectedAsSong();
|
||||
|
||||
Song ItemAsSong(QListWidgetItem *item);
|
||||
Song ItemAsSong(QListWidgetItem *item) { return ItemAsSong(static_cast<AlbumItem*>(item)); }
|
||||
Song ItemAsSong(AlbumItem *item);
|
||||
|
||||
void UpdateStatusText();
|
||||
bool ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const;
|
||||
void SaveAndSetCover(QListWidgetItem *item, const QUrl &cover_url, const QImage &image);
|
||||
bool ShouldHide(const AlbumItem &item, const QString &filter, HideCovers hide) const;
|
||||
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:
|
||||
void AddToPlaylist(QMimeData *data);
|
||||
|
@ -138,7 +154,7 @@ class AlbumCoverManager : public QMainWindow {
|
|||
void UpdateFilter();
|
||||
void FetchAlbumCovers();
|
||||
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();
|
||||
|
||||
// On the context menu
|
||||
|
@ -149,6 +165,8 @@ class AlbumCoverManager : public QMainWindow {
|
|||
void LoadCoverFromURL();
|
||||
void SearchForCover();
|
||||
void UnsetCover();
|
||||
void ClearCover();
|
||||
void DeleteCover();
|
||||
void ShowCover();
|
||||
|
||||
// For adding albums to the playlist
|
||||
|
@ -156,14 +174,16 @@ class AlbumCoverManager : public QMainWindow {
|
|||
void AddSelectedToPlaylist();
|
||||
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 SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success);
|
||||
|
||||
private:
|
||||
Ui_CoverManager *ui_;
|
||||
QMainWindow *mainwindow_;
|
||||
Application *app_;
|
||||
|
||||
CollectionBackend *collection_backend_;
|
||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||
|
||||
QAction *filter_all_;
|
||||
|
@ -171,24 +191,20 @@ class AlbumCoverManager : public QMainWindow {
|
|||
QAction *filter_without_covers_;
|
||||
|
||||
AlbumCoverLoaderOptions cover_loader_options_;
|
||||
QMap<quint64, QListWidgetItem*> cover_loading_tasks_;
|
||||
QMap<quint64, AlbumItem*> cover_loading_tasks_;
|
||||
|
||||
AlbumCoverFetcher *cover_fetcher_;
|
||||
QMap<quint64, QListWidgetItem*> cover_fetching_tasks_;
|
||||
QMap<quint64, AlbumItem*> cover_fetching_tasks_;
|
||||
CoverSearchStatistics fetch_statistics_;
|
||||
|
||||
AlbumCoverSearcher *cover_searcher_;
|
||||
AlbumCoverExport *cover_export_;
|
||||
AlbumCoverExporter *cover_exporter_;
|
||||
|
||||
QImage GenerateNoCoverImage(const QIcon &no_cover_icon) const;
|
||||
bool ItemHasCover(const QListWidgetItem &item) const;
|
||||
|
||||
QIcon artist_icon_;
|
||||
QIcon all_artists_icon_;
|
||||
const QIcon no_cover_icon_;
|
||||
const QImage no_cover_image_;
|
||||
const QIcon no_cover_item_icon_;
|
||||
const QImage image_nocover_thumbnail_;
|
||||
const QIcon icon_nocover_item_;
|
||||
|
||||
QMenu *context_menu_;
|
||||
QList<QListWidgetItem*> context_menu_items_;
|
||||
|
@ -197,7 +213,10 @@ class AlbumCoverManager : public QMainWindow {
|
|||
QPushButton *abort_progress_;
|
||||
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->setUrls(urls);
|
||||
mime_data->setData(orig_data->formats()[0], orig_data->data(orig_data->formats()[0]));
|
||||
|
||||
return mime_data;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManagerList::dropEvent(QDropEvent *e) {
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <QStandardItem>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
|
@ -58,6 +59,7 @@
|
|||
#include "albumcoverloader.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
#include "ui_albumcoversearcher.h"
|
||||
|
||||
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->setModel(model_);
|
||||
|
||||
options_.get_image_data_ = true;
|
||||
options_.get_image_ = true;
|
||||
options_.scale_output_image_ = false;
|
||||
options_.pad_output_image_ = false;
|
||||
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
|
||||
ui_->artist->clear();
|
||||
|
@ -175,16 +179,18 @@ QImage AlbumCoverSearcher::Exec(const QString &artist, const QString &album) {
|
|||
Search();
|
||||
}
|
||||
|
||||
if (exec() == QDialog::Rejected) return QImage();
|
||||
if (exec() == QDialog::Rejected) return AlbumCoverImageResult();
|
||||
|
||||
QModelIndex selected = ui_->covers->currentIndex();
|
||||
if (!selected.isValid() || !selected.data(Role_ImageFetchFinished).toBool())
|
||||
return QImage();
|
||||
return AlbumCoverImageResult();
|
||||
|
||||
QIcon icon = selected.data(Qt::DecorationRole).value<QIcon>();
|
||||
if (icon.cacheKey() == no_cover_icon_.cacheKey()) return QImage();
|
||||
AlbumCoverImageResult result;
|
||||
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_)
|
||||
return;
|
||||
if (id != id_) return;
|
||||
|
||||
ui_->search->setEnabled(true);
|
||||
ui_->artist->setEnabled(true);
|
||||
|
@ -225,7 +230,8 @@ void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverSearchResul
|
|||
ui_->search->setText(tr("Search"));
|
||||
id_ = 0;
|
||||
|
||||
for (const CoverSearchResult &result : results) {
|
||||
for (const CoverProviderSearchResult &result : results) {
|
||||
|
||||
if (result.image_url.isEmpty()) continue;
|
||||
|
||||
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 (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());
|
||||
return;
|
||||
}
|
||||
|
||||
QIcon icon;
|
||||
icon.addPixmap(QPixmap::fromImage(result.image_original));
|
||||
icon.addPixmap(QPixmap::fromImage(result.image_thumbnail));
|
||||
QPixmap pixmap = QPixmap::fromImage(result.image_thumbnail);
|
||||
if (pixmap.isNull()) {
|
||||
model_->removeRow(item->row());
|
||||
return;
|
||||
}
|
||||
|
||||
QIcon icon(pixmap);
|
||||
|
||||
item->setData(true, Role_ImageFetchFinished);
|
||||
item->setData(result.image_original.width() * result.image_original.height(), Role_ImageDimensions);
|
||||
item->setData(result.image_original.size(), Role_ImageSize);
|
||||
item->setIcon(icon);
|
||||
item->setData(result.album_cover.image_data, Role_ImageData);
|
||||
item->setData(result.album_cover.image, Role_Image);
|
||||
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 <QStyleOptionViewItem>
|
||||
#include <QMap>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QIcon>
|
||||
|
@ -39,6 +40,7 @@
|
|||
#include "albumcoverfetcher.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
class QWidget;
|
||||
class QStandardItem;
|
||||
|
@ -77,20 +79,22 @@ class AlbumCoverSearcher : public QDialog {
|
|||
Role_ImageURL = Qt::UserRole + 1,
|
||||
Role_ImageRequestId,
|
||||
Role_ImageFetchFinished,
|
||||
Role_ImageDimensions, // width * height
|
||||
Role_ImageData,
|
||||
Role_Image,
|
||||
Role_ImageDimensions,
|
||||
Role_ImageSize,
|
||||
};
|
||||
|
||||
void Init(AlbumCoverFetcher *fetcher);
|
||||
|
||||
QImage Exec(const QString &artist, const QString &album);
|
||||
AlbumCoverImageResult Exec(const QString &artist, const QString &album);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent*) override;
|
||||
|
||||
private slots:
|
||||
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 CoverDoubleClicked(const QModelIndex &idx);
|
||||
|
|
|
@ -89,7 +89,7 @@ void CoverExportRunnable::ProcessAndExportCover() {
|
|||
|
||||
// load embedded cover if any
|
||||
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()) {
|
||||
EmitCoverSkipped();
|
||||
|
@ -179,7 +179,7 @@ void CoverExportRunnable::ExportCover() {
|
|||
|
||||
if (cover_path == Song::kEmbeddedCover) {
|
||||
// an embedded cover
|
||||
QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song_.url().toLocalFile());
|
||||
QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
|
||||
if (!embedded.save(new_file)) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
|
|
|
@ -31,8 +31,10 @@
|
|||
#include <QNetworkRequest>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/utilities.h"
|
||||
#include "core/networkaccessmanager.h"
|
||||
#include "widgets/busyindicator.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
#include "coverfromurldialog.h"
|
||||
#include "ui_coverfromurldialog.h"
|
||||
|
||||
|
@ -47,17 +49,17 @@ CoverFromURLDialog::~CoverFromURLDialog() {
|
|||
delete ui_;
|
||||
}
|
||||
|
||||
QImage CoverFromURLDialog::Exec() {
|
||||
AlbumCoverImageResult CoverFromURLDialog::Exec() {
|
||||
|
||||
// reset state
|
||||
ui_->url->setText("");;
|
||||
last_image_ = QImage();
|
||||
last_album_cover_ = AlbumCoverImageResult();
|
||||
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
ui_->url->setText(clipboard->text());
|
||||
|
||||
exec();
|
||||
return last_image_;
|
||||
return last_album_cover_;
|
||||
|
||||
}
|
||||
|
||||
|
@ -89,11 +91,13 @@ void CoverFromURLDialog::LoadCoverFromURLFinished() {
|
|||
return;
|
||||
}
|
||||
|
||||
QImage image;
|
||||
image.loadFromData(reply->readAll());
|
||||
AlbumCoverImageResult result;
|
||||
result.image_data = reply->readAll();
|
||||
result.image.loadFromData(result.image_data);
|
||||
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
|
||||
|
||||
if (!image.isNull()) {
|
||||
last_image_ = image;
|
||||
if (!result.image.isNull()) {
|
||||
last_album_cover_ = result;
|
||||
QDialog::accept();
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
#include <QString>
|
||||
#include <QImage>
|
||||
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
class QWidget;
|
||||
|
||||
class NetworkAccessManager;
|
||||
|
@ -42,7 +44,7 @@ class CoverFromURLDialog : public QDialog {
|
|||
~CoverFromURLDialog() override;
|
||||
|
||||
// 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:
|
||||
void accept() override;
|
||||
|
@ -52,7 +54,7 @@ class CoverFromURLDialog : public QDialog {
|
|||
Ui_CoverFromURLDialog *ui_;
|
||||
|
||||
NetworkAccessManager *network_;
|
||||
QImage last_image_;
|
||||
AlbumCoverImageResult last_album_cover_;
|
||||
};
|
||||
|
||||
#endif // COVERFROMURLDIALOG_H
|
||||
|
|
|
@ -27,4 +27,4 @@
|
|||
#include "core/application.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
|
||||
|
||||
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".
|
||||
QString name() const { return name_; }
|
||||
bool is_enabled() const { return enabled_; }
|
||||
int order() const { return order_; }
|
||||
bool quality() const { return quality_; }
|
||||
bool fetchall() const { return fetchall_; }
|
||||
bool batch() const { return batch_; }
|
||||
bool allow_missing_album() const { return allow_missing_album_; }
|
||||
|
||||
void set_enabled(const bool enabled) { enabled_ = enabled; }
|
||||
|
@ -70,8 +70,8 @@ class CoverProvider : public QObject {
|
|||
void AuthenticationComplete(bool, QStringList = QStringList());
|
||||
void AuthenticationSuccess();
|
||||
void AuthenticationFailure(QStringList);
|
||||
void SearchResults(int, CoverSearchResults);
|
||||
void SearchFinished(int, CoverSearchResults);
|
||||
void SearchResults(int, CoverProviderSearchResults);
|
||||
void SearchFinished(int, CoverProviderSearchResults);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
|
@ -80,7 +80,7 @@ class CoverProvider : public QObject {
|
|||
int order_;
|
||||
bool authentication_required_;
|
||||
float quality_;
|
||||
bool fetchall_;
|
||||
bool batch_;
|
||||
bool allow_missing_album_;
|
||||
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include <QTemporaryFile>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/song.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
|
@ -43,6 +44,8 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare
|
|||
id_(0)
|
||||
{
|
||||
|
||||
options_.get_image_data_ = true;
|
||||
options_.get_image_ = true;
|
||||
options_.scale_output_image_ = false;
|
||||
options_.pad_output_image_ = false;
|
||||
options_.create_thumbnail_ = true;
|
||||
|
@ -74,11 +77,11 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
|
|||
if (id != id_) return;
|
||||
id_ = 0;
|
||||
|
||||
if (!result.image_scaled.isNull()) {
|
||||
if (!result.album_cover.image.isNull()) {
|
||||
temp_cover_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_cover_->setAutoRemove(true);
|
||||
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());
|
||||
}
|
||||
else {
|
||||
|
@ -108,7 +111,7 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -205,23 +205,23 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
|
|||
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (data.isEmpty()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue value_data = ExtractData(data);
|
||||
if (!value_data.isArray()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray array_data = value_data.toArray();
|
||||
if (array_data.isEmpty()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
|
||||
QMap<QUrl, CoverSearchResult> results;
|
||||
QMap<QUrl, CoverProviderSearchResult> results;
|
||||
int i = 0;
|
||||
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::kAlbumRemoveMisc);
|
||||
|
||||
CoverSearchResult cover_result;
|
||||
CoverProviderSearchResult cover_result;
|
||||
cover_result.artist = artist;
|
||||
cover_result.album = album;
|
||||
|
||||
|
@ -309,11 +309,11 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
|
|||
}
|
||||
|
||||
if (results.isEmpty()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
}
|
||||
else {
|
||||
CoverSearchResults cover_results = results.values();
|
||||
std::stable_sort(cover_results.begin(), cover_results.end(), AlbumCoverFetcherSearch::CoverSearchResultCompareNumber);
|
||||
CoverProviderSearchResults cover_results = results.values();
|
||||
std::stable_sort(cover_results.begin(), cover_results.end(), AlbumCoverFetcherSearch::CoverProviderSearchResultCompareNumber);
|
||||
emit SearchFinished(id, cover_results);
|
||||
}
|
||||
|
||||
|
|
|
@ -441,7 +441,7 @@ void DiscogsCoverProvider::HandleReleaseReply(QNetworkReply *reply, const int se
|
|||
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);
|
||||
if (aspect_score < 0.85) continue;
|
||||
CoverSearchResult result;
|
||||
CoverProviderSearchResult result;
|
||||
result.artist = artist;
|
||||
result.album = album;
|
||||
result.image_url = QUrl(obj_image["resource_url"].toString());
|
||||
|
|
|
@ -73,7 +73,7 @@ class DiscogsCoverProvider : public JsonCoverProvider {
|
|||
QString album;
|
||||
DiscogsCoverType type;
|
||||
QMap<quint64, DiscogsCoverReleaseContext> requests_release_;
|
||||
CoverSearchResults results;
|
||||
CoverProviderSearchResults results;
|
||||
};
|
||||
|
||||
private:
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
#include "coverprovider.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) {
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ class JsonCoverProvider : public CoverProvider {
|
|||
Q_OBJECT
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -135,7 +135,7 @@ void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, const int id, cons
|
|||
QObject::disconnect(reply, nullptr, this, nullptr);
|
||||
reply->deleteLater();
|
||||
|
||||
CoverSearchResults results;
|
||||
CoverProviderSearchResults results;
|
||||
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (data.isEmpty()) {
|
||||
|
@ -275,7 +275,7 @@ void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, const int id, cons
|
|||
|
||||
if (!url.isValid()) continue;
|
||||
|
||||
CoverSearchResult cover_result;
|
||||
CoverProviderSearchResult cover_result;
|
||||
cover_result.artist = artist;
|
||||
cover_result.album = album;
|
||||
cover_result.image_url = url;
|
||||
|
|
|
@ -128,7 +128,7 @@ void MusicbrainzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
|
|||
QObject::disconnect(reply, nullptr, this, nullptr);
|
||||
reply->deleteLater();
|
||||
|
||||
CoverSearchResults results;
|
||||
CoverProviderSearchResults results;
|
||||
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (data.isEmpty()) {
|
||||
|
@ -217,7 +217,7 @@ void MusicbrainzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
|
|||
QString id = obj_release["id"].toString();
|
||||
QString album = obj_release["title"].toString();
|
||||
|
||||
CoverSearchResult cover_result;
|
||||
CoverProviderSearchResult cover_result;
|
||||
QUrl url(QString(kAlbumCoverUrl).arg(id));
|
||||
cover_result.artist = artist;
|
||||
cover_result.album = album;
|
||||
|
|
|
@ -105,7 +105,7 @@ void MusixmatchCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
|
|||
QObject::disconnect(reply, nullptr, this, nullptr);
|
||||
reply->deleteLater();
|
||||
|
||||
CoverSearchResults results;
|
||||
CoverProviderSearchResults results;
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
|
@ -195,7 +195,7 @@ void MusixmatchCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
|
|||
return;
|
||||
}
|
||||
|
||||
CoverSearchResult result;
|
||||
CoverProviderSearchResult result;
|
||||
result.artist = obj_album["artistName"].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);
|
||||
reply->deleteLater();
|
||||
|
||||
CoverSearchResults results;
|
||||
CoverProviderSearchResults results;
|
||||
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (data.isEmpty()) {
|
||||
|
@ -274,7 +274,7 @@ void QobuzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
|
|||
album = album.remove(Song::kAlbumRemoveDisc);
|
||||
album = album.remove(Song::kAlbumRemoveMisc);
|
||||
|
||||
CoverSearchResult cover_result;
|
||||
CoverProviderSearchResult cover_result;
|
||||
cover_result.artist = artist;
|
||||
cover_result.album = album;
|
||||
cover_result.image_url = cover_url;
|
||||
|
|
|
@ -466,36 +466,36 @@ void SpotifyCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id,
|
|||
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (data.isEmpty()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains(extract) || !json_obj[extract].isObject()) {
|
||||
Error(QString("Json object is missing %1 object.").arg(extract), json_obj);
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
json_obj = json_obj[extract].toObject();
|
||||
|
||||
if (!json_obj.contains("items") || !json_obj["items"].isArray()) {
|
||||
Error(QString("%1 object is missing items array.").arg(extract), json_obj);
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray array_items = json_obj["items"].toArray();
|
||||
if (array_items.isEmpty()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
|
||||
CoverSearchResults results;
|
||||
CoverProviderSearchResults results;
|
||||
for (const QJsonValue value_item : array_items) {
|
||||
|
||||
if (!value_item.isObject()) {
|
||||
|
@ -531,7 +531,7 @@ void SpotifyCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id,
|
|||
int height = obj_image["height"].toInt();
|
||||
if (width < 300 || height < 300) continue;
|
||||
QUrl url(obj_image["url"].toString());
|
||||
CoverSearchResult result;
|
||||
CoverProviderSearchResult result;
|
||||
result.album = album;
|
||||
result.image_url = url;
|
||||
result.image_size = QSize(width, height);
|
||||
|
|
|
@ -183,34 +183,34 @@ void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
|
|||
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (data.isEmpty()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("items")) {
|
||||
Error("Json object is missing items.", json_obj);
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
QJsonValue value_items = json_obj["items"];
|
||||
|
||||
if (!value_items.isArray()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
QJsonArray array_items = value_items.toArray();
|
||||
if (array_items.isEmpty()) {
|
||||
emit SearchFinished(id, CoverSearchResults());
|
||||
emit SearchFinished(id, CoverProviderSearchResults());
|
||||
return;
|
||||
}
|
||||
|
||||
CoverSearchResults results;
|
||||
CoverProviderSearchResults results;
|
||||
int i = 0;
|
||||
for (const QJsonValue value_item : array_items) {
|
||||
|
||||
|
@ -262,7 +262,7 @@ void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
|
|||
album = album.remove(Song::kAlbumRemoveMisc);
|
||||
cover = cover.replace("-", "/");
|
||||
|
||||
CoverSearchResult cover_result;
|
||||
CoverProviderSearchResult cover_result;
|
||||
cover_result.artist = artist;
|
||||
cover_result.album = album;
|
||||
cover_result.number = ++i;
|
||||
|
|
|
@ -41,8 +41,10 @@ AddStreamDialog::~AddStreamDialog() { delete ui_; }
|
|||
|
||||
void AddStreamDialog::showEvent(QShowEvent *e) {
|
||||
|
||||
ui_->url->setFocus();
|
||||
ui_->url->selectAll();
|
||||
if (!e->spontaneous()) {
|
||||
ui_->url->setFocus();
|
||||
ui_->url->selectAll();
|
||||
}
|
||||
|
||||
QDialog::showEvent(e);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -28,10 +28,11 @@
|
|||
#include <QObject>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QImage>
|
||||
|
||||
#include "core/song.h"
|
||||
|
@ -39,6 +40,7 @@
|
|||
#include "playlist/playlistitem.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "covermanager/albumcoverimageresult.h"
|
||||
|
||||
class QWidget;
|
||||
class QMenu;
|
||||
|
@ -51,9 +53,9 @@ class QHideEvent;
|
|||
|
||||
class Application;
|
||||
class AlbumCoverChoiceController;
|
||||
class TrackSelectionDialog;
|
||||
class Ui_EditTagDialog;
|
||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||
class TrackSelectionDialog;
|
||||
class TagFetcher;
|
||||
#endif
|
||||
|
||||
|
@ -64,8 +66,9 @@ class EditTagDialog : public QDialog {
|
|||
explicit EditTagDialog(Application *app, QWidget *parent = nullptr);
|
||||
~EditTagDialog() override;
|
||||
|
||||
static const char *kHintText;
|
||||
static const char *kSettingsGroup;
|
||||
static const char *kTagsDifferentHintText;
|
||||
static const char *kArtDifferentHintText;
|
||||
|
||||
void SetSongs(const SongList &songs, const PlaylistItemList &items = PlaylistItemList());
|
||||
|
||||
|
@ -78,25 +81,30 @@ class EditTagDialog : public QDialog {
|
|||
|
||||
protected:
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
void showEvent(QShowEvent*) override;
|
||||
void hideEvent(QHideEvent*) override;
|
||||
void showEvent(QShowEvent *e) override;
|
||||
void hideEvent(QHideEvent *e) override;
|
||||
|
||||
private:
|
||||
enum UpdateCoverAction {
|
||||
UpdateCoverAction_None = 0,
|
||||
UpdateCoverAction_Clear,
|
||||
UpdateCoverAction_Unset,
|
||||
UpdateCoverAction_Delete,
|
||||
UpdateCoverAction_New,
|
||||
};
|
||||
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);
|
||||
QVariant original_value(const QString &id) const {
|
||||
return value(original_, id);
|
||||
}
|
||||
QVariant current_value(const QString &id) const {
|
||||
return value(current_, id);
|
||||
}
|
||||
QVariant original_value(const QString &id) const { return value(original_, id); }
|
||||
QVariant current_value(const QString &id) const { return value(current_, id); }
|
||||
|
||||
void set_value(const QString &id, const QVariant &value);
|
||||
|
||||
Song original_;
|
||||
Song current_;
|
||||
UpdateCoverAction cover_action_;
|
||||
AlbumCoverImageResult cover_result_;
|
||||
};
|
||||
|
||||
private slots:
|
||||
|
@ -118,12 +126,15 @@ class EditTagDialog : public QDialog {
|
|||
void LoadCoverFromURL();
|
||||
void SearchForCover();
|
||||
void UnsetCover();
|
||||
void ClearCover();
|
||||
void DeleteCover();
|
||||
void ShowCover();
|
||||
|
||||
void PreviousSong();
|
||||
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:
|
||||
struct FieldData {
|
||||
|
@ -136,7 +147,7 @@ class EditTagDialog : public QDialog {
|
|||
};
|
||||
|
||||
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 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 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 UpdateUI(const QModelIndexList &sel);
|
||||
QString GetArtSummary(const Song &song, const UpdateCoverAction cover_action);
|
||||
|
||||
void UpdateUI(const QModelIndexList &indexes);
|
||||
|
||||
bool SetLoading(const QString &message);
|
||||
void SetSongListVisibility(bool visible);
|
||||
|
||||
// Called by QtConcurrentRun
|
||||
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:
|
||||
Ui_EditTagDialog *ui_;
|
||||
|
||||
Application *app_;
|
||||
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_;
|
||||
|
||||
|
@ -172,25 +194,20 @@ class EditTagDialog : public QDialog {
|
|||
|
||||
bool ignore_edits_;
|
||||
|
||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||
TagFetcher *tag_fetcher_;
|
||||
#endif
|
||||
|
||||
AlbumCoverLoaderOptions cover_options_;
|
||||
quint64 cover_art_id_;
|
||||
quint64 summary_cover_art_id_;
|
||||
quint64 tags_cover_art_id_;
|
||||
bool cover_art_is_set_;
|
||||
|
||||
// A copy of the original, unscaled album cover.
|
||||
QImage original_;
|
||||
|
||||
QMenu *cover_menu_;
|
||||
|
||||
QPushButton *previous_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
|
||||
|
|
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) {
|
||||
|
||||
if (!cover_loader_tasks_.contains(id)) {
|
||||
return;
|
||||
}
|
||||
if (!cover_loader_tasks_.contains(id)) return;
|
||||
|
||||
QPair<QModelIndex, QString> cover_loader_task = cover_loader_tasks_.take(id);
|
||||
QModelIndex idx = cover_loader_task.first;
|
||||
QString key = cover_loader_task.second;
|
||||
|
||||
QPixmap pixmap = QPixmap::fromImage(albumcover_result.image_scaled);
|
||||
if (!pixmap.isNull()) {
|
||||
pixmap_cache_.insert(key, pixmap);
|
||||
}
|
||||
|
||||
if (idx.isValid()) {
|
||||
QStandardItem *item = front_model_->itemFromIndex(idx);
|
||||
if (item) {
|
||||
item->setData(albumcover_result.image_scaled, Qt::DecorationRole);
|
||||
if (albumcover_result.success && !albumcover_result.image_scaled.isNull()) {
|
||||
QPixmap pixmap = QPixmap::fromImage(albumcover_result.image_scaled);
|
||||
if (!pixmap.isNull()) {
|
||||
pixmap_cache_.insert(key, pixmap);
|
||||
}
|
||||
if (idx.isValid()) {
|
||||
QStandardItem *item = front_model_->itemFromIndex(idx);
|
||||
if (item) {
|
||||
item->setData(albumcover_result.image_scaled, Qt::DecorationRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ void Organize::ProcessSomeFiles() {
|
|||
if (!song.is_valid()) continue;
|
||||
|
||||
// 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);
|
||||
|
||||
#ifdef HAVE_GSTREAMER
|
||||
|
@ -212,7 +212,7 @@ void Organize::ProcessSomeFiles() {
|
|||
job.remove_original_ = !copy_;
|
||||
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())) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
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())) {
|
||||
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);
|
||||
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;
|
||||
|
||||
|
@ -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())));
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
|
||||
// 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();
|
||||
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.";
|
||||
item->SetArtManual(result.cover_url);
|
||||
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.album_cover.cover_url);
|
||||
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_)
|
||||
glow_timer_.start(1500 / kGlowIntensitySteps, this);
|
||||
|
||||
MaybeAutoscroll(Playlist::AutoScroll_Maybe);
|
||||
|
||||
QTreeView::showEvent(e);
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -1087,6 +1092,8 @@ void PlaylistView::paintEvent(QPaintEvent *event) {
|
|||
p.setPen(line_pen);
|
||||
p.drawLine(QPoint(0, drop_pos), QPoint(width(), drop_pos));
|
||||
|
||||
QTreeView::paintEvent(event);
|
||||
|
||||
}
|
||||
|
||||
void PlaylistView::dragMoveEvent(QDragMoveEvent *event) {
|
||||
|
@ -1260,6 +1267,7 @@ void PlaylistView::StretchChanged(const bool stretch) {
|
|||
void PlaylistView::resizeEvent(QResizeEvent *e) {
|
||||
|
||||
QTreeView::resizeEvent(e);
|
||||
|
||||
if (dynamic_controls_->isVisible()) {
|
||||
RepositionDynamicControls();
|
||||
}
|
||||
|
@ -1379,9 +1387,9 @@ void PlaylistView::Stopped() {
|
|||
|
||||
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 (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) {
|
||||
set_background_image(QImage());
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#include "playlistitem.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) {}
|
||||
|
||||
bool SongPlaylistItem::InitFromQuery(const SqlRow &query) {
|
||||
|
|
|
@ -32,12 +32,12 @@
|
|||
|
||||
class SongPlaylistItem : public PlaylistItem {
|
||||
public:
|
||||
explicit SongPlaylistItem(const Song::Source &source);
|
||||
explicit SongPlaylistItem(const Song::Source source);
|
||||
explicit SongPlaylistItem(const Song &song);
|
||||
|
||||
// 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!
|
||||
bool InitFromQuery(const SqlRow& query) override;
|
||||
bool InitFromQuery(const SqlRow &query) override;
|
||||
void Reload() override;
|
||||
|
||||
Song Metadata() const override;
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
#include "core/song.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/application.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/imageutils.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "qobuzservice.h"
|
||||
#include "qobuzurlhandler.h"
|
||||
|
@ -1154,7 +1154,7 @@ void QobuzRequest::GetAlbumCovers() {
|
|||
|
||||
void QobuzRequest::AddAlbumCoverRequest(Song &song) {
|
||||
|
||||
QUrl cover_url(song.art_automatic());
|
||||
QUrl cover_url = song.art_automatic();
|
||||
if (!cover_url.isValid()) return;
|
||||
|
||||
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();
|
||||
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()));
|
||||
if (album_covers_requests_sent_.contains(cover_url)) album_covers_requests_sent_.remove(cover_url);
|
||||
AlbumCoverFinishCheck();
|
||||
|
@ -1248,7 +1248,7 @@ void QobuzRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl &cover_ur
|
|||
return;
|
||||
}
|
||||
|
||||
QList<QByteArray> format_list = Utilities::ImageFormatsForMimeType(mimetype.toUtf8());
|
||||
QList<QByteArray> format_list = ImageUtils::ImageFormatsForMimeType(mimetype.toUtf8());
|
||||
char *format = nullptr;
|
||||
if (!format_list.isEmpty()) {
|
||||
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_->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_pattern, &QRadioButton::toggled, this, &CollectionSettingsPage::CoverSaveInAlbumDirChanged);
|
||||
|
||||
|
@ -162,11 +162,17 @@ void CollectionSettingsPage::Load() {
|
|||
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
||||
ui_->cover_art_patterns->setText(filters.join(","));
|
||||
|
||||
ui_->checkbox_cover_album_dir->setChecked(s.value("cover_album_dir", false).toBool());
|
||||
SaveCover save_cover = SaveCover(s.value("cover_filename", SaveCover_Hash).toInt());
|
||||
switch (save_cover) {
|
||||
case SaveCover_Hash: ui_->radiobutton_cover_hash->setChecked(true); break;
|
||||
case SaveCover_Pattern: ui_->radiobutton_cover_pattern->setChecked(true); break;
|
||||
SaveCoverType save_cover_type = SaveCoverType(s.value("save_cover_type", SaveCoverType_Cache).toInt());
|
||||
switch (save_cover_type) {
|
||||
case SaveCoverType_Cache: ui_->radiobutton_save_albumcover_cache->setChecked(true); break;
|
||||
case SaveCoverType_Album: ui_->radiobutton_save_albumcover_albumdir->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();
|
||||
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_album_dir", ui_->checkbox_cover_album_dir->isChecked());
|
||||
SaveCover save_cover = SaveCover_Hash;
|
||||
if (ui_->radiobutton_cover_hash->isChecked()) save_cover = SaveCover_Hash;
|
||||
if (ui_->radiobutton_cover_pattern->isChecked()) save_cover = SaveCover_Pattern;
|
||||
s.setValue("cover_filename", int(save_cover));
|
||||
SaveCoverType save_cover_type = SaveCoverType_Cache;
|
||||
if (ui_->radiobutton_save_albumcover_cache->isChecked()) save_cover_type = SaveCoverType_Cache;
|
||||
else if (ui_->radiobutton_save_albumcover_albumdir->isChecked()) save_cover_type = SaveCoverType_Album;
|
||||
else if (ui_->radiobutton_save_albumcover_embedded->isChecked()) save_cover_type = SaveCoverType_Embedded;
|
||||
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_overwrite", ui_->checkbox_cover_overwrite->isChecked());
|
||||
s.setValue("cover_lowercase", ui_->checkbox_cover_lowercase->isChecked());
|
||||
|
@ -246,7 +258,7 @@ void CollectionSettingsPage::Save() {
|
|||
|
||||
void CollectionSettingsPage::CoverSaveInAlbumDirChanged() {
|
||||
|
||||
if (ui_->checkbox_cover_album_dir->isChecked()) {
|
||||
if (ui_->radiobutton_save_albumcover_albumdir->isChecked()) {
|
||||
if (!ui_->groupbox_cover_filename->isEnabled()) {
|
||||
ui_->groupbox_cover_filename->setEnabled(true);
|
||||
}
|
||||
|
|
|
@ -50,6 +50,17 @@ class CollectionSettingsPage : public SettingsPage {
|
|||
static const int kSettingsCacheSizeDefault;
|
||||
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 {
|
||||
CacheSizeUnit_KB,
|
||||
CacheSizeUnit_MB,
|
||||
|
@ -57,11 +68,6 @@ class CollectionSettingsPage : public SettingsPage {
|
|||
CacheSizeUnit_TB,
|
||||
};
|
||||
|
||||
enum SaveCover {
|
||||
SaveCover_Hash = 1,
|
||||
SaveCover_Pattern = 2
|
||||
};
|
||||
|
||||
void Load() 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>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkbox_cover_album_dir">
|
||||
<property name="text">
|
||||
<string>Save album covers in album directory</string>
|
||||
<widget class="QGroupBox" name="groupbox_save_options">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</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>
|
||||
</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>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_cover_hash">
|
||||
<widget class="QRadioButton" name="radiobutton_cover_pattern">
|
||||
<property name="text">
|
||||
<string>Use hash</string>
|
||||
<string>Pattern</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radiobutton_cover_pattern">
|
||||
<widget class="QRadioButton" name="radiobutton_cover_hash">
|
||||
<property name="text">
|
||||
<string>Use pattern</string>
|
||||
<string>Random</string>
|
||||
</property>
|
||||
</widget>
|
||||
</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>pretty_covers</tabstop>
|
||||
<tabstop>show_dividers</tabstop>
|
||||
<tabstop>checkbox_cover_album_dir</tabstop>
|
||||
<tabstop>radiobutton_cover_hash</tabstop>
|
||||
<tabstop>radiobutton_cover_pattern</tabstop>
|
||||
<tabstop>lineedit_cover_pattern</tabstop>
|
||||
<tabstop>checkbox_cover_overwrite</tabstop>
|
||||
<tabstop>checkbox_cover_lowercase</tabstop>
|
||||
|
|
|
@ -189,14 +189,15 @@ SettingsDialog::~SettingsDialog() {
|
|||
|
||||
void SettingsDialog::showEvent(QShowEvent *e) {
|
||||
|
||||
LoadGeometry();
|
||||
|
||||
// Load settings
|
||||
loading_settings_ = true;
|
||||
for (const PageData &page : pages_.values()) {
|
||||
page.page_->Load();
|
||||
if (!e->spontaneous()) {
|
||||
LoadGeometry();
|
||||
// Load settings
|
||||
loading_settings_ = true;
|
||||
for (const PageData &page : pages_.values()) {
|
||||
page.page_->Load();
|
||||
}
|
||||
loading_settings_ = false;
|
||||
}
|
||||
loading_settings_ = false;
|
||||
|
||||
QDialog::showEvent(e);
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include <QShowEvent>
|
||||
|
||||
#include "core/iconloader.h"
|
||||
#include "settingspage.h"
|
||||
#include "transcoder/transcoderoptionsflac.h"
|
||||
|
@ -49,7 +51,7 @@ TranscoderSettingsPage::~TranscoderSettingsPage() {
|
|||
|
||||
void TranscoderSettingsPage::showEvent(QShowEvent *e) {
|
||||
|
||||
set_changed();
|
||||
if (!e->spontaneous()) set_changed();
|
||||
|
||||
QWidget::showEvent(e);
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
#include "settingspage.h"
|
||||
|
||||
class QShowEvent;
|
||||
|
||||
class SettingsDialog;
|
||||
class Ui_TranscoderSettingsPage;
|
||||
|
||||
|
@ -35,7 +37,7 @@ class TranscoderSettingsPage : public SettingsPage {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TranscoderSettingsPage(SettingsDialog* dialog);
|
||||
explicit TranscoderSettingsPage(SettingsDialog *dialog);
|
||||
~TranscoderSettingsPage() override;
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
@ -47,7 +49,7 @@ class TranscoderSettingsPage : public SettingsPage {
|
|||
void showEvent(QShowEvent *e) override;
|
||||
|
||||
private:
|
||||
Ui_TranscoderSettingsPage* ui_;
|
||||
Ui_TranscoderSettingsPage *ui_;
|
||||
};
|
||||
|
||||
#endif // TRANSCODERSETTINGSPAGE_H
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
#include "core/logging.h"
|
||||
#include "core/song.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/imageutils.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "subsonicservice.h"
|
||||
#include "subsonicurlhandler.h"
|
||||
|
@ -788,7 +788,7 @@ void SubsonicRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl url, c
|
|||
}
|
||||
|
||||
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()));
|
||||
if (album_covers_requests_sent_.contains(url)) album_covers_requests_sent_.remove(url);
|
||||
AlbumCoverFinishCheck();
|
||||
|
@ -803,7 +803,7 @@ void SubsonicRequest::AlbumCoverReceived(QNetworkReply *reply, const QUrl url, c
|
|||
return;
|
||||
}
|
||||
|
||||
QList<QByteArray> format_list = Utilities::ImageFormatsForMimeType(mimetype.toUtf8());
|
||||
QList<QByteArray> format_list = ImageUtils::ImageFormatsForMimeType(mimetype.toUtf8());
|
||||
char *format = nullptr;
|
||||
if (!format_list.isEmpty()) {
|
||||
format = format_list.first().data();
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
#include "core/song.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/application.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/imageutils.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "tidalservice.h"
|
||||
#include "tidalurlhandler.h"
|
||||
|
@ -1116,7 +1116,7 @@ void TidalRequest::AddAlbumCoverRequest(Song &song) {
|
|||
|
||||
AlbumCoverRequest request;
|
||||
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);
|
||||
if (request.filename.isEmpty()) return;
|
||||
|
||||
|
@ -1187,7 +1187,7 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
|
|||
}
|
||||
|
||||
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()));
|
||||
if (album_covers_requests_sent_.contains(album_id)) album_covers_requests_sent_.remove(album_id);
|
||||
AlbumCoverFinishCheck();
|
||||
|
@ -1202,7 +1202,7 @@ void TidalRequest::AlbumCoverReceived(QNetworkReply *reply, const QString &album
|
|||
return;
|
||||
}
|
||||
|
||||
QList<QByteArray> format_list = Utilities::ImageFormatsForMimeType(mimetype.toUtf8());
|
||||
QList<QByteArray> format_list = ImageUtils::ImageFormatsForMimeType(mimetype.toUtf8());
|
||||
char *format = nullptr;
|
||||
if (!format_list.isEmpty()) {
|
||||
format = format_list.first().data();
|
||||
|
|
|
@ -154,16 +154,20 @@ TranscodeDialog::~TranscodeDialog() {
|
|||
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();
|
||||
|
||||
QDialog::closeEvent(e);
|
||||
|
||||
}
|
||||
|
||||
void TranscodeDialog::accept() {
|
||||
|
|
|
@ -54,8 +54,8 @@ class TranscodeDialog : public QDialog {
|
|||
void SetFilenames(const QStringList &filenames);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent*) override;
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
void showEvent(QShowEvent *e) override;
|
||||
void closeEvent(QCloseEvent *e) override;
|
||||
void timerEvent(QTimerEvent *e) override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -44,8 +44,8 @@
|
|||
#include <QtEvents>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/imageutils.h"
|
||||
#include "covermanager/albumcoverchoicecontroller.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "playingwidget.h"
|
||||
|
||||
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_->Init(app_);
|
||||
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_->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_->setCheckable(true);
|
||||
|
@ -333,7 +334,7 @@ void PlayingWidget::SetImage(const QImage &image) {
|
|||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -193,7 +193,7 @@ TEST_F(SingleSong, GetSongWithNoAlbum) {
|
|||
CollectionBackend::AlbumList albums = backend_->GetAllAlbums();
|
||||
//EXPECT_EQ(1, albums.size());
|
||||
//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();
|
||||
ASSERT_EQ(1, albums.size());
|
||||
EXPECT_EQ(song_.album(), albums[0].album_name);
|
||||
EXPECT_EQ(song_.artist(), albums[0].artist);
|
||||
EXPECT_EQ(song_.album(), albums[0].album);
|
||||
EXPECT_EQ(song_.artist(), albums[0].album_artist);
|
||||
|
||||
}
|
||||
|
||||
|
@ -224,8 +224,8 @@ TEST_F(SingleSong, GetAlbumsByArtist) {
|
|||
|
||||
CollectionBackend::AlbumList albums = backend_->GetAlbumsByArtist("Artist");
|
||||
ASSERT_EQ(1, albums.size());
|
||||
EXPECT_EQ(song_.album(), albums[0].album_name);
|
||||
EXPECT_EQ(song_.artist(), albums[0].artist);
|
||||
EXPECT_EQ(song_.album(), albums[0].album);
|
||||
EXPECT_EQ(song_.artist(), albums[0].album_artist);
|
||||
|
||||
}
|
||||
|
||||
|
@ -233,9 +233,9 @@ TEST_F(SingleSong, GetAlbumArt) {
|
|||
|
||||
AddDummySong(); if (HasFatalFailure()) return;
|
||||
|
||||
CollectionBackend::Album album = backend_->GetAlbumArt("Artist", "AlbumArtist", "Album");
|
||||
EXPECT_EQ(song_.album(), album.album_name);
|
||||
EXPECT_EQ(song_.artist(), album.artist);
|
||||
CollectionBackend::Album album = backend_->GetAlbumArt("Artist", "Album");
|
||||
EXPECT_EQ(song_.album(), album.album);
|
||||
EXPECT_EQ(song_.effective_albumartist(), album.album_artist);
|
||||
|
||||
}
|
||||
|
||||
|
@ -243,7 +243,7 @@ TEST_F(SingleSong, GetSongs) {
|
|||
|
||||
AddDummySong(); if (HasFatalFailure()) return;
|
||||
|
||||
SongList songs = backend_->GetSongs("Artist", "Album");
|
||||
SongList songs = backend_->GetAlbumSongs("Artist", "Album");
|
||||
ASSERT_EQ(1, songs.size());
|
||||
EXPECT_EQ(song_.album(), songs[0].album());
|
||||
EXPECT_EQ(song_.artist(), songs[0].artist());
|
||||
|
|
Loading…
Reference in New Issue