Save embedded cover in the same process as tags

Possible fix for #1158
This commit is contained in:
Jonas Kvinge
2023-03-18 20:03:07 +01:00
parent 394955a03f
commit e20cbe4170
42 changed files with 1205 additions and 723 deletions

View File

@@ -53,9 +53,12 @@
#include <QSettings>
#include <QtEvents>
#include "utilities/filenameconstants.h"
#include "utilities/strutils.h"
#include "utilities/mimeutils.h"
#include "utilities/imageutils.h"
#include "utilities/coveroptions.h"
#include "utilities/coverutils.h"
#include "core/application.h"
#include "core/song.h"
#include "core/iconloader.h"
@@ -63,7 +66,6 @@
#include "collection/collectionfilteroptions.h"
#include "collection/collectionbackend.h"
#include "settings/collectionsettingspage.h"
#include "organize/organizeformat.h"
#include "internet/internetservices.h"
#include "internet/internetservice.h"
#include "albumcoverchoicecontroller.h"
@@ -98,11 +100,6 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent)
separator2_(nullptr),
show_cover_(nullptr),
search_cover_auto_(nullptr),
save_cover_type_(CollectionSettingsPage::SaveCoverType::Cache),
save_cover_filename_(CollectionSettingsPage::SaveCoverFilename::Pattern),
cover_overwrite_(false),
cover_lowercase_(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);
@@ -146,12 +143,12 @@ void AlbumCoverChoiceController::ReloadSettings() {
QSettings s;
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
save_cover_type_ = static_cast<CollectionSettingsPage::SaveCoverType>(s.value("save_cover_type", static_cast<int>(CollectionSettingsPage::SaveCoverType::Cache)).toInt());
save_cover_filename_ = static_cast<CollectionSettingsPage::SaveCoverFilename>(s.value("save_cover_filename", static_cast<int>(CollectionSettingsPage::SaveCoverFilename::Pattern)).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();
cover_replace_spaces_ = s.value("cover_replace_spaces", false).toBool();
cover_options_.cover_type = static_cast<CoverOptions::CoverType>(s.value("save_cover_type", static_cast<int>(CoverOptions::CoverType::Cache)).toInt());
cover_options_.cover_filename = static_cast<CoverOptions::CoverFilename>(s.value("save_cover_filename", static_cast<int>(CoverOptions::CoverFilename::Pattern)).toInt());
cover_options_.cover_pattern = s.value("cover_pattern", "%albumartist-%album").toString();
cover_options_.cover_overwrite = s.value("cover_overwrite", false).toBool();
cover_options_.cover_lowercase = s.value("cover_lowercase", false).toBool();
cover_options_.cover_replace_spaces = s.value("cover_replace_spaces", false).toBool();
s.endGroup();
}
@@ -214,14 +211,14 @@ QUrl AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
if (QImage(cover_file).isNull()) return QUrl();
switch (get_save_album_cover_type()) {
case CollectionSettingsPage::SaveCoverType::Embedded:
case CoverOptions::CoverType::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:{
case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:{
QUrl cover_url = QUrl::fromLocalFile(cover_file);
SaveArtManualToSong(song, cover_url);
return cover_url;
@@ -242,7 +239,7 @@ void AlbumCoverChoiceController::SaveCoverToFileManual(const Song &song, const A
initial_file_name = initial_file_name + "-" + (song.effective_album().isEmpty() ? tr("unknown") : song.effective_album()) + ".jpg";
initial_file_name = initial_file_name.toLower();
initial_file_name.replace(QRegularExpression("\\s"), "-");
initial_file_name.remove(OrganizeFormat::kInvalidFatCharacters);
initial_file_name.remove(QRegularExpression(QString(kInvalidFatCharactersRegex), QRegularExpression::CaseInsensitiveOption));
QString save_filename = QFileDialog::getSaveFileName(this, tr("Save album cover"), GetInitialPathForFileDialog(song, initial_file_name), tr(kSaveImageFileFilter) + ";;" + tr(kAllFilesFilter));
@@ -613,12 +610,12 @@ QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source sou
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");
QString filepath = CoverUtils::CoverFilePath(cover_options_, source, artist, album, album_id, album_dir, result.cover_url, "jpg");
if (filepath.isEmpty()) return QUrl();
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()) {
if (source == Song::Source::Collection && !cover_options_.cover_overwrite && !force_overwrite && get_save_album_cover_type() == CoverOptions::CoverType::Album && cover_options_.cover_filename == CoverOptions::CoverFilename::Pattern && file.exists()) {
while (file.exists()) {
QFileInfo fileinfo(file.fileName());
file.setFileName(fileinfo.path() + "/0" + fileinfo.fileName());
@@ -758,7 +755,7 @@ QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
const QString suffix = QFileInfo(filename).suffix().toLower();
if (IsKnownImageExtension(suffix)) {
if (get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType::Embedded && song->save_embedded_cover_supported()) {
if (get_save_album_cover_type() == CoverOptions::CoverType::Embedded && song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, filename);
return QUrl::fromLocalFile(Song::kEmbeddedCover);
}
@@ -784,7 +781,7 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
QUrl cover_url;
switch(get_save_album_cover_type()) {
case CollectionSettingsPage::SaveCoverType::Embedded:{
case CoverOptions::CoverType::Embedded:{
if (song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, result);
cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
@@ -792,8 +789,8 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
}
}
[[fallthrough]];
case CollectionSettingsPage::SaveCoverType::Cache:
case CollectionSettingsPage::SaveCoverType::Album:{
case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:{
cover_url = SaveCoverToFileAutomatic(song, result);
if (!cover_url.isEmpty()) SaveArtManualToSong(song, cover_url);
break;

View File

@@ -38,6 +38,7 @@
#include <QMutex>
#include "core/song.h"
#include "utilities/coveroptions.h"
#include "settings/collectionsettingspage.h"
#include "albumcoverimageresult.h"
@@ -69,8 +70,8 @@ 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_); }
CollectionSettingsPage::SaveCoverType get_collection_save_album_cover_type() const { return save_cover_type_; }
CoverOptions::CoverType get_save_album_cover_type() const { return (save_embedded_cover_override_ ? CoverOptions::CoverType::Embedded : cover_options_.cover_type); }
CoverOptions::CoverType get_collection_save_album_cover_type() const { return cover_options_.cover_type; }
// Getters for all QActions implemented by this controller.
@@ -189,12 +190,7 @@ class AlbumCoverChoiceController : public QWidget {
QMap<quint64, Song> cover_save_tasks_;
QMutex mutex_cover_save_tasks_;
CollectionSettingsPage::SaveCoverType save_cover_type_;
CollectionSettingsPage::SaveCoverFilename save_cover_filename_;
QString cover_pattern_;
bool cover_overwrite_;
bool cover_lowercase_;
bool cover_replace_spaces_;
CoverOptions cover_options_;
bool save_embedded_cover_override_;
};

View File

@@ -34,24 +34,18 @@
#include <QVariant>
#include <QByteArray>
#include <QString>
#include <QRegularExpression>
#include <QUrl>
#include <QFile>
#include <QImage>
#include <QPainter>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSettings>
#include "core/networkaccessmanager.h"
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "utilities/transliterate.h"
#include "utilities/mimeutils.h"
#include "utilities/cryptutils.h"
#include "utilities/imageutils.h"
#include "settings/collectionsettingspage.h"
#include "organize/organizeformat.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
@@ -63,15 +57,9 @@ AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
load_image_async_id_(1),
save_image_async_id_(1),
network_(new NetworkAccessManager(this)),
save_cover_type_(CollectionSettingsPage::SaveCoverType::Cache),
save_cover_filename_(CollectionSettingsPage::SaveCoverFilename::Pattern),
cover_overwrite_(false),
cover_lowercase_(true),
cover_replace_spaces_(true),
original_thread_(nullptr) {
original_thread_ = thread();
ReloadSettings();
}
@@ -90,145 +78,6 @@ void AlbumCoverLoader::Exit() {
}
void AlbumCoverLoader::ReloadSettings() {
QSettings s;
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
save_cover_type_ = static_cast<CollectionSettingsPage::SaveCoverType>(s.value("save_cover_type", static_cast<int>(CollectionSettingsPage::SaveCoverType::Cache)).toInt());
save_cover_filename_ = static_cast<CollectionSettingsPage::SaveCoverFilename>(s.value("save_cover_filename", static_cast<int>(CollectionSettingsPage::SaveCoverFilename::Pattern)).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();
cover_replace_spaces_ = s.value("cover_replace_spaces", false).toBool();
s.endGroup();
}
QString AlbumCoverLoader::AlbumCoverFilename(QString artist, QString album, const QString &extension) {
artist.remove('/').remove('\\');
album.remove('/').remove('\\');
QString filename = artist + "-" + album;
filename = Utilities::Transliterate(filename.toLower());
filename = filename.replace(' ', '-')
.replace("--", "-")
.remove(OrganizeFormat::kInvalidFatCharacters)
.simplified();
if (!extension.isEmpty()) {
filename.append('.');
filename.append(extension);
}
return filename;
}
QString AlbumCoverLoader::CoverFilePath(const Song &song, const QString &album_dir, const QUrl &cover_url, const QString &extension) {
return CoverFilePath(song.source(), song.effective_albumartist(), song.album(), song.album_id(), album_dir, cover_url, extension);
}
QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QString &extension) {
QString path;
if (source == Song::Source::Collection && save_cover_type_ == CollectionSettingsPage::SaveCoverType::Album && !album_dir.isEmpty()) {
path = album_dir;
}
else {
path = Song::ImageCacheDir(source);
}
if (path.right(1) == QDir::separator() || path.right(1) == "/") {
path.chop(1);
}
QDir dir;
if (!dir.mkpath(path)) {
qLog(Error) << "Unable to create directory" << path;
path = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
}
QString filename;
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).remove('/').remove('\\');
if (cover_lowercase_) filename = filename.toLower();
if (cover_replace_spaces_) filename.replace(QRegularExpression("\\s"), "-");
if (!extension.isEmpty()) {
filename.append('.');
filename.append(extension);
}
}
if (filename.isEmpty()) {
filename = CoverFilenameFromSource(source, cover_url, artist, album, album_id, extension);
}
QString filepath(path + "/" + filename);
return filepath;
}
QString AlbumCoverLoader::CoverFilenameFromSource(const Song::Source source, const QUrl &cover_url, const QString &artist, const QString &album, const QString &album_id, const QString &extension) {
QString filename;
switch (source) {
case Song::Source::Tidal:
if (!album_id.isEmpty()) {
filename = album_id + "-" + cover_url.fileName();
break;
}
[[fallthrough]];
case Song::Source::Subsonic:
case Song::Source::Qobuz:
if (!album_id.isEmpty()) {
filename = album_id;
break;
}
[[fallthrough]];
case Song::Source::Collection:
case Song::Source::LocalFile:
case Song::Source::CDDA:
case Song::Source::Device:
case Song::Source::Stream:
case Song::Source::SomaFM:
case Song::Source::RadioParadise:
case Song::Source::Unknown:
filename = Utilities::Sha1CoverHash(artist, album).toHex();
break;
}
if (!extension.isEmpty()) {
filename.append('.');
filename.append(extension);
}
return filename;
}
QString AlbumCoverLoader::CoverFilenameFromVariable(const QString &artist, QString album, const QString &extension) {
album = album.remove(Song::kAlbumRemoveDisc);
QString filename(cover_pattern_);
filename.replace("%albumartist", artist);
filename.replace("%artist", artist);
filename.replace("%album", album);
if (!extension.isEmpty()) {
filename.append('.');
filename.append(extension);
}
return filename;
}
void AlbumCoverLoader::CancelTask(const quint64 id) {
QMutexLocker l(&mutex_load_image_async_);
@@ -260,7 +109,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
Task task;
task.options = options;
task.song = song;
task.state = State_Manual;
task.state = State::Manual;
return EnqueueTask(task);
@@ -276,7 +125,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
Task task;
task.options = options;
task.song = song;
task.state = State_Manual;
task.state = State::Manual;
return EnqueueTask(task);
@@ -360,9 +209,9 @@ void AlbumCoverLoader::ProcessTask(Task *task) {
void AlbumCoverLoader::NextState(Task *task) {
if (task->state == State_Manual) {
if (task->state == State::Manual) {
// Try the automatic one next
task->state = State_Automatic;
task->state = State::Automatic;
ProcessTask(task);
}
else {
@@ -382,13 +231,13 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
// For local files and streams initialize art if found.
if ((task->song.source() == Song::Source::LocalFile || task->song.is_radio()) && !task->song.art_manual_is_valid() && !task->song.art_automatic_is_valid()) {
switch (task->state) {
case State_None:
case State::None:
break;
case State_Manual:
case State::Manual:
task->song.InitArtManual();
if (task->song.art_manual_is_valid()) task->art_updated = true;
break;
case State_Automatic:
case State::Automatic:
if (task->song.url().isLocalFile()) {
task->song.InitArtAutomatic();
if (task->song.art_automatic_is_valid()) task->art_updated = true;
@@ -400,12 +249,12 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
AlbumCoverLoaderResult::Type type(AlbumCoverLoaderResult::Type_None);
QUrl cover_url;
switch (task->state) {
case State_None:
case State_Automatic:
case State::None:
case State::Automatic:
type = AlbumCoverLoaderResult::Type_Automatic;
cover_url = task->song.art_automatic();
break;
case State_Manual:
case State::Manual:
type = AlbumCoverLoaderResult::Type_Manual;
cover_url = task->song.art_manual();
break;
@@ -593,7 +442,7 @@ quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QByteArray &image_data) {
TagReaderReply *reply = TagReaderClient::Instance()->SaveEmbeddedArt(song_filename, image_data);
TagReaderReply *reply = TagReaderClient::Instance()->SaveEmbeddedArt(song_filename, TagReaderClient::SaveCoverOptions(image_data));
tagreader_save_embedded_art_requests_.insert(id, reply);
const bool clear = image_data.isEmpty();
QObject::connect(reply, &TagReaderReply::Finished, this, [this, id, reply, clear]() { SaveEmbeddedArtFinished(id, reply, clear); }, Qt::QueuedConnection);
@@ -702,6 +551,6 @@ void AlbumCoverLoader::SaveEmbeddedArtFinished(const quint64 id, TagReaderReply
emit SaveEmbeddedCoverAsyncFinished(id, reply->is_successful(), cleared);
}
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
reply->deleteLater();
}

View File

@@ -39,7 +39,6 @@
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "settings/collectionsettingspage.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "albumcoverimageresult.h"
@@ -54,25 +53,15 @@ class AlbumCoverLoader : public QObject {
public:
explicit AlbumCoverLoader(QObject *parent = nullptr);
enum State {
State_None,
State_Manual,
State_Automatic,
enum class State {
None,
Manual,
Automatic
};
void ReloadSettings();
void ExitAsync();
void Stop() { stop_requested_ = true; }
static QString AlbumCoverFilename(QString artist, QString album, const QString &extension);
static QString CoverFilenameFromSource(const Song::Source source, const QUrl &cover_url, const QString &artist, const QString &album, const QString &album_id, const QString &extension);
QString CoverFilenameFromVariable(const QString &artist, QString album, const QString &extension = QString());
QString CoverFilePath(const Song &song, const QString &album_dir, const QUrl &cover_url, const QString &extension = QString());
QString CoverFilePath(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QString &extension = QString());
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song);
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const 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);
@@ -110,7 +99,7 @@ class AlbumCoverLoader : public QObject {
protected:
struct Task {
explicit Task() : id(0), state(State_None), type(AlbumCoverLoaderResult::Type_None), art_updated(false), redirects(0) {}
explicit Task() : id(0), state(State::None), type(AlbumCoverLoaderResult::Type_None), art_updated(false), redirects(0) {}
AlbumCoverLoaderOptions options;
@@ -158,13 +147,6 @@ class AlbumCoverLoader : public QObject {
static const int kMaxRedirects = 3;
CollectionSettingsPage::SaveCoverType save_cover_type_;
CollectionSettingsPage::SaveCoverFilename save_cover_filename_;
QString cover_pattern_;
bool cover_overwrite_;
bool cover_lowercase_;
bool cover_replace_spaces_;
QThread *original_thread_;
QMultiMap<quint64, TagReaderReply*> tagreader_save_embedded_art_requests_;

View File

@@ -781,13 +781,13 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
QUrl cover_url = result.cover_url;
switch (album_cover_choice_controller_->get_save_album_cover_type()) {
case CollectionSettingsPage::SaveCoverType::Cache:
case CollectionSettingsPage::SaveCoverType::Album:
case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:
if (cover_url.isEmpty() || !cover_url.isValid() || !cover_url.isLocalFile()) {
cover_url = album_cover_choice_controller_->SaveCoverToFileAutomatic(song, result);
}
break;
case CollectionSettingsPage::SaveCoverType::Embedded:
case CoverOptions::CoverType::Embedded:
cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
break;
}
@@ -798,14 +798,14 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
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:{
case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:{
Song current_song = ItemAsSong(album_item);
album_cover_choice_controller_->SaveArtManualToSong(&current_song, cover_url);
UpdateCoverInList(album_item, cover_url);
break;
}
case CollectionSettingsPage::SaveCoverType::Embedded:{
case CoverOptions::CoverType::Embedded:{
urls << album_item->urls;
album_items << album_item;
break;
@@ -813,7 +813,7 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
}
}
if (album_cover_choice_controller_->get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType::Embedded && !urls.isEmpty()) {
if (album_cover_choice_controller_->get_save_album_cover_type() == CoverOptions::CoverType::Embedded && !urls.isEmpty()) {
quint64 id = -1;
if (result.is_jpeg()) {
id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image_data);
@@ -971,7 +971,7 @@ void AlbumCoverManager::SaveAndSetCover(AlbumItem *item, const AlbumCoverImageRe
const Song::FileType filetype = static_cast<Song::FileType>(item->data(Role_Filetype).toInt());
const bool has_cue = !item->data(Role_CuePath).toString().isEmpty();
if (album_cover_choice_controller_->get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType::Embedded && Song::save_embedded_cover_supported(filetype) && !has_cue) {
if (album_cover_choice_controller_->get_save_album_cover_type() == CoverOptions::CoverType::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);