Add support for saving embedded album covers

Fixes #286
This commit is contained in:
Jonas Kvinge
2021-02-26 21:03:51 +01:00
parent e4c89c1aed
commit 133f094d72
79 changed files with 3509 additions and 1804 deletions

View File

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

View File

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

View File

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

View File

@@ -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);
}

View File

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