Rewrite album cover loader
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2018-2023, 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
|
||||
@@ -1435,7 +1435,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||
query.SetColumnSpec("url, effective_albumartist, album, compilation_effective, art_automatic, art_manual, filetype, cue_path");
|
||||
query.SetColumnSpec("url, filetype, cue_path, effective_albumartist, album, compilation_effective, art_embedded, art_automatic, art_manual, art_unset");
|
||||
query.SetOrderBy("effective_albumartist, album, url");
|
||||
|
||||
if (compilation_required) {
|
||||
@@ -1453,42 +1453,48 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
|
||||
QMap<QString, Album> albums;
|
||||
while (query.Next()) {
|
||||
bool is_compilation = query.Value(3).toBool();
|
||||
|
||||
Album info;
|
||||
Album album_info;
|
||||
QUrl url = QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||
|
||||
album_info.filetype = static_cast<Song::FileType>(query.Value(1).toInt());
|
||||
const QString filetype = Song::TextForFiletype(album_info.filetype);
|
||||
album_info.cue_path = query.Value(2).toString();
|
||||
|
||||
const bool is_compilation = query.Value(5).toBool();
|
||||
if (!is_compilation) {
|
||||
info.album_artist = query.Value(1).toString();
|
||||
album_info.album_artist = query.Value(3).toString();
|
||||
}
|
||||
info.album = query.Value(2).toString();
|
||||
|
||||
QString art_automatic = query.Value(4).toString();
|
||||
album_info.album = query.Value(4).toString();
|
||||
|
||||
album_info.art_embedded = query.Value(6).toBool();
|
||||
|
||||
const QString art_automatic = query.Value(7).toString();
|
||||
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
|
||||
info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
|
||||
album_info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
|
||||
}
|
||||
else {
|
||||
info.art_automatic = QUrl::fromLocalFile(art_automatic);
|
||||
album_info.art_automatic = QUrl::fromLocalFile(art_automatic);
|
||||
}
|
||||
|
||||
QString art_manual = query.Value(5).toString();
|
||||
const QString art_manual = query.Value(8).toString();
|
||||
if (art_manual.contains(QRegularExpression("..+:.*"))) {
|
||||
info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
|
||||
album_info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
|
||||
}
|
||||
else {
|
||||
info.art_manual = QUrl::fromLocalFile(art_manual);
|
||||
album_info.art_manual = QUrl::fromLocalFile(art_manual);
|
||||
}
|
||||
|
||||
info.filetype = static_cast<Song::FileType>(query.Value(6).toInt());
|
||||
QString filetype = Song::TextForFiletype(info.filetype);
|
||||
info.cue_path = query.Value(7).toString();
|
||||
album_info.art_unset = query.Value(9).toBool();
|
||||
|
||||
QString key;
|
||||
if (!info.album_artist.isEmpty()) {
|
||||
key.append(info.album_artist);
|
||||
if (!album_info.album_artist.isEmpty()) {
|
||||
key.append(album_info.album_artist);
|
||||
}
|
||||
if (!info.album.isEmpty()) {
|
||||
if (!album_info.album.isEmpty()) {
|
||||
if (!key.isEmpty()) key.append("-");
|
||||
key.append(info.album);
|
||||
key.append(album_info.album);
|
||||
}
|
||||
if (!filetype.isEmpty()) {
|
||||
key.append(filetype);
|
||||
@@ -1500,8 +1506,8 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
albums[key].urls.append(url);
|
||||
}
|
||||
else {
|
||||
info.urls << url;
|
||||
albums.insert(key, info);
|
||||
album_info.urls << url;
|
||||
albums.insert(key, album_info);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1520,7 +1526,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
||||
ret.album_artist = effective_albumartist;
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
query.SetColumnSpec("art_automatic, art_manual, url");
|
||||
query.SetColumnSpec("url, art_embedded, art_automatic, art_manual, art_unset");
|
||||
if (!effective_albumartist.isEmpty()) {
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
}
|
||||
@@ -1532,22 +1538,24 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
||||
}
|
||||
|
||||
if (query.Next()) {
|
||||
ret.art_automatic = QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||
ret.art_manual = QUrl::fromEncoded(query.Value(1).toByteArray());
|
||||
ret.urls << QUrl::fromEncoded(query.Value(2).toByteArray());
|
||||
ret.urls << QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||
ret.art_embedded = query.Value(1).toInt() == 1;
|
||||
ret.art_automatic = QUrl::fromEncoded(query.Value(2).toByteArray());
|
||||
ret.art_manual = QUrl::fromEncoded(query.Value(3).toByteArray());
|
||||
ret.art_unset = query.Value(4).toInt() == 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic) {
|
||||
void CollectionBackend::UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) {
|
||||
|
||||
QMetaObject::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));
|
||||
QMetaObject::invokeMethod(this, "UpdateEmbeddedAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(bool, art_embedded));
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic) {
|
||||
void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_embedded) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
@@ -1571,15 +1579,11 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
||||
}
|
||||
|
||||
// Update the songs
|
||||
QString sql = QString("UPDATE %1 SET art_manual = :cover").arg(songs_table_);
|
||||
if (clear_art_automatic) {
|
||||
sql += ", art_automatic = ''";
|
||||
}
|
||||
sql += " WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
|
||||
QString sql = QString("UPDATE %1 SET art_embedded = :art_embedded, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_);
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(sql);
|
||||
q.BindValue(":cover", cover_url.isValid() ? cover_url.toString(QUrl::FullyEncoded) : "");
|
||||
q.BindValue(":art_embedded", art_embedded ? 1 : 0);
|
||||
q.BindValue(":effective_albumartist", effective_albumartist);
|
||||
q.BindValue(":album", album);
|
||||
|
||||
@@ -1608,18 +1612,17 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual) {
|
||||
void CollectionBackend::UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) {
|
||||
|
||||
QMetaObject::invokeMethod(this, "UpdateAutomaticAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url), Q_ARG(bool, clear_art_manual));
|
||||
QMetaObject::invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, art_manual));
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual) {
|
||||
void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
// Get the songs before they're updated
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
@@ -1637,16 +1640,124 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
|
||||
deleted_songs << song;
|
||||
}
|
||||
|
||||
// Update the songs
|
||||
QString sql = QString("UPDATE %1 SET art_automatic = :cover").arg(songs_table_);
|
||||
if (clear_art_manual) {
|
||||
sql += ", art_manual = ''";
|
||||
}
|
||||
sql += " WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(sql);
|
||||
q.BindValue(":cover", cover_url.isValid() ? cover_url.toString(QUrl::FullyEncoded) : "");
|
||||
q.prepare(QString("UPDATE %1 SET art_manual = :art_manual, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(":art_manual", art_manual.isValid() ? art_manual.toString(QUrl::FullyEncoded) : "");
|
||||
q.BindValue(":effective_albumartist", effective_albumartist);
|
||||
q.BindValue(":album", album);
|
||||
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
}
|
||||
|
||||
SongList added_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
added_songs << song;
|
||||
}
|
||||
|
||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||
emit SongsDeleted(deleted_songs);
|
||||
emit SongsDiscovered(added_songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UnsetAlbumArtAsync(const QString &effective_albumartist, const QString &album) {
|
||||
|
||||
QMetaObject::invokeMethod(this, "UnsetAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album));
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, const QString &album) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
query.AddWhere("album", album);
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
}
|
||||
|
||||
SongList deleted_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
deleted_songs << song;
|
||||
}
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET art_unset = 1, art_manual = '', art_automatic = '', art_embedded = '' WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(":effective_albumartist", effective_albumartist);
|
||||
q.BindValue(":album", album);
|
||||
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
}
|
||||
|
||||
SongList added_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
added_songs << song;
|
||||
}
|
||||
|
||||
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||
emit SongsDeleted(deleted_songs);
|
||||
emit SongsDiscovered(added_songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::ClearAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool unset) {
|
||||
|
||||
QMetaObject::invokeMethod(this, "ClearAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(bool, unset));
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_unset) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
query.AddWhere("album", album);
|
||||
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
}
|
||||
|
||||
SongList deleted_songs;
|
||||
while (query.Next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(query, true);
|
||||
deleted_songs << song;
|
||||
}
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET art_embedded = 0, art_automatic = '', art_manual = '', art_unset = :art_unset WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
q.BindValue(":art_unset", art_unset ? 1 : 0);
|
||||
q.BindValue(":effective_albumartist", effective_albumartist);
|
||||
q.BindValue(":album", album);
|
||||
|
||||
@@ -1655,7 +1766,6 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
|
||||
return;
|
||||
}
|
||||
|
||||
// Now get the updated songs
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
return;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2018-2023, 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
|
||||
@@ -54,12 +54,14 @@ class CollectionBackendInterface : public QObject {
|
||||
explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {}
|
||||
|
||||
struct Album {
|
||||
Album() : filetype(Song::FileType::Unknown) {}
|
||||
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() : art_embedded(false), art_unset(false), filetype(Song::FileType::Unknown) {}
|
||||
Album(const QString &_album_artist, const QString &_album, const bool _art_embedded, const QUrl &_art_automatic, const QUrl &_art_manual, const bool _art_unset, const QList<QUrl> &_urls, const Song::FileType _filetype, const QString &_cue_path)
|
||||
: album_artist(_album_artist),
|
||||
album(_album),
|
||||
art_embedded(_art_embedded),
|
||||
art_automatic(_art_automatic),
|
||||
art_manual(_art_manual),
|
||||
art_unset(_art_unset),
|
||||
urls(_urls),
|
||||
filetype(_filetype),
|
||||
cue_path(_cue_path) {}
|
||||
@@ -67,8 +69,10 @@ class CollectionBackendInterface : public QObject {
|
||||
QString album_artist;
|
||||
QString album;
|
||||
|
||||
bool art_embedded;
|
||||
QUrl art_automatic;
|
||||
QUrl art_manual;
|
||||
bool art_unset;
|
||||
QList<QUrl> urls;
|
||||
Song::FileType filetype;
|
||||
QString cue_path;
|
||||
@@ -109,8 +113,10 @@ class CollectionBackendInterface : public QObject {
|
||||
virtual AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||
virtual AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 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, const bool clear_art_manual = false) = 0;
|
||||
virtual void UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) = 0;
|
||||
virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) = 0;
|
||||
virtual void UnsetAlbumArtAsync(const QString &effective_albumartist, const QString &album) = 0;
|
||||
virtual void ClearAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_unset) = 0;
|
||||
|
||||
virtual Album GetAlbumArt(const QString &effective_albumartist, const QString &album) = 0;
|
||||
|
||||
@@ -179,8 +185,10 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||
AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||
|
||||
void UpdateManualAlbumArtAsync(const QString &effective_albumartist, 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, const bool clear_art_manual = false) override;
|
||||
void UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) override;
|
||||
void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) override;
|
||||
void UnsetAlbumArtAsync(const QString &effective_albumartist, const QString &album) override;
|
||||
void ClearAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_unset) override;
|
||||
|
||||
Album GetAlbumArt(const QString &effective_albumartist, const QString &album) override;
|
||||
|
||||
@@ -232,8 +240,10 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||
void AddOrUpdateSubdirs(const CollectionSubdirectoryList &subdirs);
|
||||
void CompilationsNeedUpdating();
|
||||
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, const bool clear_art_manual = false);
|
||||
void UpdateEmbeddedAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_embedded);
|
||||
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &art_manual);
|
||||
void UnsetAlbumArt(const QString &effective_albumartist, const QString &album);
|
||||
void ClearAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_unset);
|
||||
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);
|
||||
@@ -303,7 +313,6 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
QString subdirs_table_;
|
||||
QString fts_table_;
|
||||
QThread *original_thread_;
|
||||
|
||||
};
|
||||
|
||||
#endif // COLLECTIONBACKEND_H
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2018-2023, 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
|
||||
@@ -71,6 +71,7 @@
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "settings/coverssettingspage.h"
|
||||
|
||||
const int CollectionModel::kPrettyCoverSize = 32;
|
||||
const char *CollectionModel::kPixmapDiskCacheDir = "pixmapcache";
|
||||
@@ -101,12 +102,6 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
|
||||
group_by_[1] = GroupBy::AlbumDisc;
|
||||
group_by_[2] = GroupBy::None;
|
||||
|
||||
cover_loader_options_.get_image_data_ = false;
|
||||
cover_loader_options_.get_image_ = true;
|
||||
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);
|
||||
}
|
||||
@@ -150,6 +145,7 @@ void CollectionModel::set_pretty_covers(const bool use_pretty_covers) {
|
||||
use_pretty_covers_ = use_pretty_covers;
|
||||
Reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::set_show_dividers(const bool show_dividers) {
|
||||
@@ -177,6 +173,8 @@ void CollectionModel::ReloadSettings() {
|
||||
|
||||
s.endGroup();
|
||||
|
||||
cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
|
||||
|
||||
if (!use_disk_cache_) {
|
||||
ClearDiskCache();
|
||||
}
|
||||
@@ -652,7 +650,10 @@ QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
|
||||
// No art is cached and we're not loading it already. Load art for the first song in the album.
|
||||
SongList songs = GetChildSongs(idx);
|
||||
if (!songs.isEmpty()) {
|
||||
const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, songs.first());
|
||||
AlbumCoverLoaderOptions cover_loader_options(AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
|
||||
cover_loader_options.desired_scaled_size = QSize(kPrettyCoverSize, kPrettyCoverSize);
|
||||
cover_loader_options.types = cover_types_;
|
||||
const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options, songs.first());
|
||||
pending_art_[id] = ItemAndCacheKey(item, cache_key);
|
||||
pending_cache_keys_.insert(cache_key);
|
||||
}
|
||||
@@ -674,7 +675,7 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderR
|
||||
pending_cache_keys_.remove(cache_key);
|
||||
|
||||
// Insert this image in the cache.
|
||||
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::ManuallyUnset) {
|
||||
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::Unset) {
|
||||
// Set the no_cover image so we don't continually try to load art.
|
||||
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2018-2023, 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
|
||||
@@ -49,11 +49,11 @@
|
||||
#include "core/song.h"
|
||||
#include "core/sqlrow.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
#include "collectionfilteroptions.h"
|
||||
#include "collectionquery.h"
|
||||
#include "collectionqueryoptions.h"
|
||||
#include "collectionitem.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
|
||||
class QSettings;
|
||||
|
||||
@@ -310,7 +310,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
bool use_disk_cache_;
|
||||
bool use_lazy_loading_;
|
||||
|
||||
AlbumCoverLoaderOptions cover_loader_options_;
|
||||
AlbumCoverLoaderOptions::Types cover_types_;
|
||||
|
||||
using ItemAndCacheKey = QPair<CollectionItem*, QString>;
|
||||
QMap<quint64, ItemAndCacheKey> pending_art_;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2018-2023, 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
|
||||
@@ -156,10 +156,10 @@ void CollectionWatcher::ReloadSettings() {
|
||||
overwrite_rating_ = s.value("overwrite_rating", false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
best_image_filters_.clear();
|
||||
best_art_filters_.clear();
|
||||
for (const QString &filter : filters) {
|
||||
QString str = filter.trimmed();
|
||||
if (!str.isEmpty()) best_image_filters_ << str;
|
||||
if (!str.isEmpty()) best_art_filters_ << str;
|
||||
}
|
||||
|
||||
if (!monitor_ && was_monitoring_before) {
|
||||
@@ -539,8 +539,8 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
bool changed = (matching_song.mtime() != qMax(fileinfo.lastModified().toSecsSinceEpoch(), matching_song_cue_mtime)) || cue_deleted || cue_added || cue_changed;
|
||||
|
||||
// Also want to look to see whether the album art has changed
|
||||
QUrl image = ImageForSong(file, album_art);
|
||||
if ((matching_song.art_automatic().isEmpty() && !image.isEmpty()) || (!matching_song.art_automatic().isEmpty() && !matching_song.has_embedded_cover() && !QFile::exists(matching_song.art_automatic().toLocalFile()))) {
|
||||
const QUrl art_automatic = ArtForSong(file, album_art);
|
||||
if (matching_song.art_automatic() != art_automatic || (!matching_song.art_automatic().isEmpty() && !matching_song.art_automatic_is_valid())) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -573,16 +573,16 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
#endif
|
||||
|
||||
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
|
||||
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, cue_deleted, t);
|
||||
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, art_automatic, cue_deleted, t);
|
||||
}
|
||||
else { // If CUE associated.
|
||||
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
|
||||
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, art_automatic, matching_songs, t);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Nothing has changed - mark the song available without re-scanning
|
||||
else if (matching_song.is_unavailable()) {
|
||||
else if (matching_song.unavailable()) {
|
||||
t->readded_songs << matching_songs;
|
||||
}
|
||||
|
||||
@@ -633,13 +633,13 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
}
|
||||
|
||||
// Get new album art
|
||||
QUrl image = ImageForSong(file, album_art);
|
||||
const QUrl art_automatic = ArtForSong(file, album_art);
|
||||
|
||||
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
|
||||
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, matching_songs_has_cue && new_cue_mtime == 0, t);
|
||||
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, art_automatic, matching_songs_has_cue && new_cue_mtime == 0, t);
|
||||
}
|
||||
else { // If CUE associated.
|
||||
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
|
||||
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, art_automatic, matching_songs, t);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -653,12 +653,12 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
|
||||
qLog(Debug) << file << "is new.";
|
||||
|
||||
// Choose an image for the song(s)
|
||||
QUrl image = ImageForSong(file, album_art);
|
||||
// Choose art for the song(s)
|
||||
const QUrl art_automatic = ArtForSong(file, album_art);
|
||||
|
||||
for (Song song : songs) {
|
||||
song.set_directory_id(t->dir());
|
||||
if (song.art_automatic().isEmpty()) song.set_art_automatic(image);
|
||||
if (song.art_automatic().isEmpty()) song.set_art_automatic(art_automatic);
|
||||
t->new_songs << song;
|
||||
}
|
||||
}
|
||||
@@ -669,7 +669,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
// Look for deleted songs
|
||||
for (const Song &song : songs_in_db) {
|
||||
QString file = song.url().toLocalFile();
|
||||
if (!song.is_unavailable() && !files_on_disk.contains(file) && !t->files_changed_path_.contains(file)) {
|
||||
if (!song.unavailable() && !files_on_disk.contains(file) && !t->files_changed_path_.contains(file)) {
|
||||
qLog(Debug) << "Song deleted from disk:" << file;
|
||||
t->deleted_songs << song;
|
||||
}
|
||||
@@ -704,7 +704,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
||||
const QString &path,
|
||||
const QString &fingerprint,
|
||||
const QString &matching_cue,
|
||||
const QUrl &image,
|
||||
const QUrl &art_automatic,
|
||||
const SongList &old_cue_songs,
|
||||
ScanTransaction *t) {
|
||||
|
||||
@@ -733,7 +733,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
||||
if (sections_map.contains(new_cue_song.beginning_nanosec())) { // Changed section
|
||||
const Song matching_cue_song = sections_map[new_cue_song.beginning_nanosec()];
|
||||
new_cue_song.set_id(matching_cue_song.id());
|
||||
if (!new_cue_song.has_embedded_cover()) new_cue_song.set_art_automatic(image);
|
||||
new_cue_song.set_art_automatic(art_automatic);
|
||||
new_cue_song.MergeUserSetData(matching_cue_song, true, true);
|
||||
AddChangedSong(file, matching_cue_song, new_cue_song, t);
|
||||
used_ids.insert(matching_cue_song.id());
|
||||
@@ -755,7 +755,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
||||
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
||||
const QString &fingerprint,
|
||||
const SongList &matching_songs,
|
||||
const QUrl &image,
|
||||
const QUrl &art_automatic,
|
||||
const bool cue_deleted,
|
||||
ScanTransaction *t) {
|
||||
|
||||
@@ -776,7 +776,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
||||
song_on_disk.set_directory_id(t->dir());
|
||||
song_on_disk.set_id(matching_song.id());
|
||||
song_on_disk.set_fingerprint(fingerprint);
|
||||
if (!song_on_disk.has_embedded_cover()) song_on_disk.set_art_automatic(image);
|
||||
song_on_disk.set_art_automatic(art_automatic);
|
||||
song_on_disk.MergeUserSetData(matching_song, !overwrite_playcount_, !overwrite_rating_);
|
||||
AddChangedSong(file, matching_song, song_on_disk, t);
|
||||
}
|
||||
@@ -838,7 +838,7 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
||||
bool notify_new = false;
|
||||
QStringList changes;
|
||||
|
||||
if (matching_song.is_unavailable()) {
|
||||
if (matching_song.unavailable()) {
|
||||
qLog(Debug) << "unavailable song" << file << "restored.";
|
||||
notify_new = true;
|
||||
}
|
||||
@@ -1039,20 +1039,20 @@ void CollectionWatcher::RescanPathsNow() {
|
||||
|
||||
}
|
||||
|
||||
QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
||||
QString CollectionWatcher::PickBestArt(const QStringList &art_automatic_list) {
|
||||
|
||||
// This is used when there is more than one image in a directory.
|
||||
// Pick the biggest image that matches the most important filter
|
||||
|
||||
QStringList filtered;
|
||||
|
||||
for (const QString &filter_text : best_image_filters_) {
|
||||
for (const QString &filter_text : best_art_filters_) {
|
||||
// The images in the images list are represented by a full path, so we need to isolate just the filename
|
||||
for (const QString &image : images) {
|
||||
QFileInfo fileinfo(image);
|
||||
for (const QString &art_automatic : art_automatic_list) {
|
||||
QFileInfo fileinfo(art_automatic);
|
||||
QString filename(fileinfo.fileName());
|
||||
if (filename.contains(filter_text, Qt::CaseInsensitive))
|
||||
filtered << image;
|
||||
filtered << art_automatic;
|
||||
}
|
||||
|
||||
// We assume the filters are give in the order best to worst, so if we've got a result, we go with it.
|
||||
@@ -1062,7 +1062,7 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
||||
|
||||
if (filtered.isEmpty()) {
|
||||
// The filter was too restrictive, just use the original list
|
||||
filtered = images;
|
||||
filtered = art_automatic_list;
|
||||
}
|
||||
|
||||
int biggest_size = 0;
|
||||
@@ -1085,20 +1085,21 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
||||
|
||||
}
|
||||
|
||||
QUrl CollectionWatcher::ImageForSong(const QString &path, QMap<QString, QStringList> &album_art) {
|
||||
QUrl CollectionWatcher::ArtForSong(const QString &path, QMap<QString, QStringList> &art_automatic_list) {
|
||||
|
||||
QString dir(DirectoryPart(path));
|
||||
|
||||
if (album_art.contains(dir)) {
|
||||
if (album_art[dir].count() == 1) {
|
||||
return QUrl::fromLocalFile(album_art[dir][0]);
|
||||
if (art_automatic_list.contains(dir)) {
|
||||
if (art_automatic_list[dir].count() == 1) {
|
||||
return QUrl::fromLocalFile(art_automatic_list[dir][0]);
|
||||
}
|
||||
else {
|
||||
QString best_image = PickBestImage(album_art[dir]);
|
||||
album_art[dir] = QStringList() << best_image;
|
||||
return QUrl::fromLocalFile(best_image);
|
||||
const QString best_art = PickBestArt(art_automatic_list[dir]);
|
||||
art_automatic_list[dir] = QStringList() << best_art;
|
||||
return QUrl::fromLocalFile(best_art);
|
||||
}
|
||||
}
|
||||
|
||||
return QUrl();
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
* Copyright 2018-2023, 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
|
||||
@@ -178,17 +178,17 @@ class CollectionWatcher : public QObject {
|
||||
inline static QString NoExtensionPart(const QString &fileName);
|
||||
inline static QString ExtensionPart(const QString &fileName);
|
||||
inline static QString DirectoryPart(const QString &fileName);
|
||||
QString PickBestImage(const QStringList &images);
|
||||
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
||||
QString PickBestArt(const QStringList &art_automatic_list);
|
||||
QUrl ArtForSong(const QString &path, QMap<QString, QStringList> &art_automatic_list);
|
||||
void AddWatch(const CollectionDirectory &dir, const QString &path);
|
||||
void RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir);
|
||||
static quint64 GetMtimeForCue(const QString &cue_path);
|
||||
void PerformScan(const bool incremental, const bool ignore_mtimes);
|
||||
|
||||
// Updates the sections of a cue associated and altered (according to mtime) media file during a scan.
|
||||
void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, const QUrl &image, const SongList &old_cue_songs, ScanTransaction *t);
|
||||
void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, const QUrl &art_automatic, const SongList &old_cue_songs, ScanTransaction *t);
|
||||
// Updates a single non-cue associated and altered (according to mtime) song during a scan.
|
||||
void UpdateNonCueAssociatedSong(const QString &file, const QString &fingerprint, const SongList &matching_songs, const QUrl &image, const bool cue_deleted, ScanTransaction *t);
|
||||
void UpdateNonCueAssociatedSong(const QString &file, const QString &fingerprint, const SongList &matching_songs, const QUrl &art_automatic, const bool cue_deleted, ScanTransaction *t);
|
||||
// Scans a single media file that's present on the disk but not yet in the collection.
|
||||
// It may result in a multiple files added to the collection when the media file has many sections (like a CUE related media file).
|
||||
SongList ScanNewFile(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, QSet<QString> *cues_processed);
|
||||
@@ -210,9 +210,9 @@ class CollectionWatcher : public QObject {
|
||||
QThread *original_thread_;
|
||||
QHash<QString, CollectionDirectory> subdir_mapping_;
|
||||
|
||||
// A list of words use to try to identify the (likely) best image found in an directory to use as cover artwork.
|
||||
// A list of words use to try to identify the (likely) best album cover art found in an directory to use as cover artwork.
|
||||
// e.g. using ["front", "cover"] would identify front.jpg and exclude back.jpg.
|
||||
QStringList best_image_filters_;
|
||||
QStringList best_art_filters_;
|
||||
|
||||
bool scan_on_startup_;
|
||||
bool monitor_;
|
||||
|
||||
Reference in New Issue
Block a user