Rewrite album cover loader

This commit is contained in:
Jonas Kvinge
2023-05-14 11:34:55 +02:00
parent 3c160c2f13
commit 331aa382f9
68 changed files with 2948 additions and 2565 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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