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 2019-2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2019-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
@@ -62,10 +62,11 @@
#include "core/application.h"
#include "core/song.h"
#include "core/iconloader.h"
#include "core/tagreaderclient.h"
#include "collection/collectionfilteroptions.h"
#include "collection/collectionbackend.h"
#include "settings/collectionsettingspage.h"
#include "settings/coverssettingspage.h"
#include "internet/internetservices.h"
#include "internet/internetservice.h"
#include "albumcoverchoicecontroller.h"
@@ -135,22 +136,23 @@ void AlbumCoverChoiceController::Init(Application *app) {
cover_searcher_->Init(cover_fetcher_);
QObject::connect(cover_fetcher_, &AlbumCoverFetcher::AlbumCoverFetched, this, &AlbumCoverChoiceController::AlbumCoverFetched);
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::SaveEmbeddedCoverAsyncFinished, this, &AlbumCoverChoiceController::SaveEmbeddedCoverAsyncFinished);
}
void AlbumCoverChoiceController::ReloadSettings() {
QSettings s;
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
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.beginGroup(CoversSettingsPage::kSettingsGroup);
cover_options_.cover_type = static_cast<CoverOptions::CoverType>(s.value(CoversSettingsPage::kSaveType, static_cast<int>(CoverOptions::CoverType::Cache)).toInt());
cover_options_.cover_filename = static_cast<CoverOptions::CoverFilename>(s.value(CoversSettingsPage::kSaveFilename, static_cast<int>(CoverOptions::CoverFilename::Pattern)).toInt());
cover_options_.cover_pattern = s.value(CoversSettingsPage::kSavePattern, "%albumartist-%album").toString();
cover_options_.cover_overwrite = s.value(CoversSettingsPage::kSaveOverwrite, false).toBool();
cover_options_.cover_lowercase = s.value(CoversSettingsPage::kSaveLowercase, false).toBool();
cover_options_.cover_replace_spaces = s.value(CoversSettingsPage::kSaveReplaceSpaces, false).toBool();
s.endGroup();
cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
}
QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
@@ -170,30 +172,31 @@ QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromFile(Song *song) {
if (!song->url().isLocalFile()) return AlbumCoverImageResult();
if (!song->url().isValid() || !song->url().isLocalFile()) {
return AlbumCoverImageResult();
}
QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
if (cover_file.isEmpty()) return AlbumCoverImageResult();
AlbumCoverImageResult result;
QFile file(cover_file);
if (file.open(QIODevice::ReadOnly)) {
result.image_data = file.readAll();
file.close();
if (result.image_data.isEmpty()) {
qLog(Error) << "Cover file" << cover_file << "is empty.";
emit Error(tr("Cover file %1 is empty.").arg(cover_file));
}
else {
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
result.image.loadFromData(result.image_data);
result.cover_url = QUrl::fromLocalFile(cover_file);
}
}
else {
if (!file.open(QIODevice::ReadOnly)) {
qLog(Error) << "Failed to open cover file" << cover_file << "for reading:" << file.errorString();
emit Error(tr("Failed to open cover file %1 for reading: %2").arg(cover_file, file.errorString()));
return AlbumCoverImageResult();
}
result.image_data = file.readAll();
file.close();
if (result.image_data.isEmpty()) {
qLog(Error) << "Cover file" << cover_file << "is empty.";
emit Error(tr("Cover file %1 is empty.").arg(cover_file));
return AlbumCoverImageResult();
}
if (result.image.loadFromData(result.image_data)) {
result.cover_url = QUrl::fromLocalFile(cover_file);
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
}
return result;
@@ -202,24 +205,21 @@ AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromFile(Song *song)
QUrl AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
if (cover_file.isEmpty()) return QUrl();
if (QImage(cover_file).isNull()) return QUrl();
if (cover_file.isEmpty() || QImage(cover_file).isNull()) return QUrl();
switch (get_save_album_cover_type()) {
case CoverOptions::CoverType::Embedded:
if (song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, cover_file);
return QUrl::fromLocalFile(Song::kEmbeddedCover);
SaveCoverEmbeddedToCollectionSongs(*song, cover_file);
return QUrl();
}
[[fallthrough]];
case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:{
QUrl cover_url = QUrl::fromLocalFile(cover_file);
const QUrl cover_url = QUrl::fromLocalFile(cover_file);
SaveArtManualToSong(song, cover_url);
return cover_url;
}
@@ -258,17 +258,19 @@ void AlbumCoverChoiceController::SaveCoverToFileManual(const Song &song, const A
if (result.is_jpeg() && fileinfo.completeSuffix().compare("jpg", Qt::CaseInsensitive) == 0) {
QFile file(save_filename);
if (file.open(QIODevice::WriteOnly)) {
if (file.write(result.image_data) <= 0) {
qLog(Error) << "Failed writing cover to file" << save_filename << file.errorString();
emit Error(tr("Failed writing cover to file %1: %2").arg(save_filename, file.errorString()));
}
file.close();
}
else {
if (!file.open(QIODevice::WriteOnly)) {
qLog(Error) << "Failed to open cover file" << save_filename << "for writing:" << file.errorString();
emit Error(tr("Failed to open cover file %1 for writing: %2").arg(save_filename, file.errorString()));
file.close();
return;
}
if (file.write(result.image_data) <= 0) {
qLog(Error) << "Failed writing cover to file" << save_filename << file.errorString();
emit Error(tr("Failed writing cover to file %1: %2").arg(save_filename, file.errorString()));
file.close();
return;
}
file.close();
}
else {
if (!result.image.save(save_filename)) {
@@ -283,35 +285,26 @@ QString AlbumCoverChoiceController::GetInitialPathForFileDialog(const Song &song
// Art automatic is first to show user which cover the album may be using now;
// The song is using it if there's no manual path but we cannot use manual path here because it can contain cached paths
if (!song.art_automatic().isEmpty() && !song.art_automatic().path().isEmpty() && !song.has_embedded_cover()) {
if (song.art_automatic().scheme().isEmpty() && QFile::exists(QFileInfo(song.art_automatic().path()).path())) {
return song.art_automatic().path();
}
else if (song.art_automatic().isLocalFile() && QFile::exists(QFileInfo(song.art_automatic().toLocalFile()).path())) {
return song.art_automatic().toLocalFile();
}
// If no automatic art, start in the song's folder
if (song.art_automatic_is_valid()) {
return song.art_automatic().toLocalFile();
}
else if (!song.url().isEmpty() && song.url().toLocalFile().contains('/')) {
// If no automatic art, start in the song's folder
if (!song.url().isEmpty() && song.url().isValid() && song.url().isLocalFile() && song.url().toLocalFile().contains('/')) {
return song.url().toLocalFile().section('/', 0, -2) + filename;
// Fallback - start in home
}
return QDir::home().absolutePath() + filename;
}
QUrl AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
void AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
const AlbumCoverImageResult result = LoadImageFromURL();
if (result.image.isNull()) {
return QUrl();
}
else {
return SaveCoverAutomatic(song, result);
if (!result.image.isNull()) {
SaveCoverAutomatic(song, result);
}
}
@@ -324,24 +317,21 @@ AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromURL() {
}
QUrl AlbumCoverChoiceController::SearchForCover(Song *song) {
void AlbumCoverChoiceController::SearchForCover(Song *song) {
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
// Get something sensible to stick in the search box
AlbumCoverImageResult result = SearchForImage(song);
if (result.is_valid()) {
return SaveCoverAutomatic(song, result);
}
else {
return QUrl();
SaveCoverAutomatic(song, result);
}
}
AlbumCoverImageResult AlbumCoverChoiceController::SearchForImage(Song *song) {
if (!song->url().isLocalFile()) return AlbumCoverImageResult();
if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return AlbumCoverImageResult();
QString album = song->effective_album();
album = album.remove(Song::kAlbumRemoveDisc).remove(Song::kAlbumRemoveMisc);
@@ -351,52 +341,38 @@ AlbumCoverImageResult AlbumCoverChoiceController::SearchForImage(Song *song) {
}
QUrl AlbumCoverChoiceController::UnsetCover(Song *song, const bool clear_art_automatic) {
void AlbumCoverChoiceController::UnsetCover(Song *song) {
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
QUrl cover_url = QUrl::fromLocalFile(Song::kManuallyUnsetCover);
SaveArtManualToSong(song, cover_url, clear_art_automatic);
return cover_url;
UnsetAlbumCoverForSong(song);
}
void AlbumCoverChoiceController::ClearCover(Song *song, const bool clear_art_automatic) {
void AlbumCoverChoiceController::ClearCover(Song *song) {
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
song->clear_art_manual();
if (clear_art_automatic) song->clear_art_automatic();
SaveArtManualToSong(song, QUrl(), clear_art_automatic);
ClearAlbumCoverForSong(song);
}
bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_unset) {
bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool unset) {
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return false;
if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return false;
if (song->has_embedded_cover() && song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, AlbumCoverImageResult());
}
QString art_automatic;
QString art_manual;
if (song->art_automatic().isValid() && song->art_automatic().isLocalFile()) {
art_automatic = song->art_automatic().toLocalFile();
}
if (song->art_manual().isValid() && song->art_manual().isLocalFile()) {
art_manual = song->art_manual().toLocalFile();
if (song->art_embedded() && song->save_embedded_cover_supported()) {
SaveCoverEmbeddedToCollectionSongs(*song, AlbumCoverImageResult());
}
bool success = true;
if (!art_automatic.isEmpty()) {
if (song->art_automatic().isValid() && song->art_automatic().isLocalFile()) {
const QString art_automatic = song->art_automatic().toLocalFile();
QFile file(art_automatic);
if (file.exists()) {
if (file.remove()) {
song->clear_art_automatic();
if (art_automatic == art_manual) song->clear_art_manual();
}
else {
success = false;
@@ -408,12 +384,12 @@ bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_uns
}
else song->clear_art_automatic();
if (!art_manual.isEmpty()) {
if (song->art_manual().isValid() && song->art_manual().isLocalFile()) {
const QString art_manual = song->art_manual().toLocalFile();
QFile file(art_manual);
if (file.exists()) {
if (file.remove()) {
song->clear_art_manual();
if (art_automatic == art_manual) song->clear_art_automatic();
}
else {
success = false;
@@ -426,8 +402,8 @@ bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_uns
else song->clear_art_manual();
if (success) {
if (manually_unset) UnsetCover(song, true);
else ClearCover(song, true);
if (unset) UnsetCover(song);
else ClearCover(song);
}
return success;
@@ -436,23 +412,55 @@ bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_uns
void AlbumCoverChoiceController::ShowCover(const Song &song, const QImage &image) {
if (image.isNull()) {
if ((song.art_manual().isValid() && song.art_manual().isLocalFile() && QFile::exists(song.art_manual().toLocalFile())) ||
(song.art_automatic().isValid() && song.art_automatic().isLocalFile() && QFile::exists(song.art_automatic().toLocalFile())) ||
song.has_embedded_cover()
) {
QPixmap pixmap = ImageUtils::TryLoadPixmap(song.art_automatic(), song.art_manual(), song.url());
if (!pixmap.isNull()) {
pixmap.setDevicePixelRatio(devicePixelRatioF());
ShowCover(song, pixmap);
}
}
}
else {
if (!image.isNull()) {
QPixmap pixmap = QPixmap::fromImage(image);
if (!pixmap.isNull()) {
pixmap.setDevicePixelRatio(devicePixelRatioF());
ShowCover(song, pixmap);
pixmap.setDevicePixelRatio(devicePixelRatioF());
ShowCover(song, pixmap);
return;
}
}
for (const AlbumCoverLoaderOptions::Type type : cover_types_) {
switch (type) {
case AlbumCoverLoaderOptions::Type::Unset: {
if (song.art_unset()) {
return;
}
break;
}
case AlbumCoverLoaderOptions::Type::Manual:{
QPixmap pixmap;
if (song.art_manual_is_valid() && song.art_manual().isLocalFile() && pixmap.load(song.art_manual().toLocalFile())) {
pixmap.setDevicePixelRatio(devicePixelRatioF());
ShowCover(song, pixmap);
return;
}
break;
}
case AlbumCoverLoaderOptions::Type::Embedded:{
if (song.art_embedded() && !song.url().isEmpty() && song.url().isValid() && song.url().isLocalFile()) {
const QImage image_embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song.url().toLocalFile());
if (!image_embedded_cover.isNull()) {
QPixmap pixmap = QPixmap::fromImage(image_embedded_cover);
if (!pixmap.isNull()) {
pixmap.setDevicePixelRatio(devicePixelRatioF());
ShowCover(song, pixmap);
return;
}
}
}
break;
}
case AlbumCoverLoaderOptions::Type::Automatic:{
QPixmap pixmap;
if (song.art_automatic_is_valid() && song.art_automatic().isLocalFile() && pixmap.load(song.art_automatic().toLocalFile())) {
pixmap.setDevicePixelRatio(devicePixelRatioF());
ShowCover(song, pixmap);
return;
}
break;
}
}
}
@@ -507,7 +515,7 @@ quint64 AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
quint64 id = cover_fetcher_->FetchAlbumCover(song.effective_albumartist(), song.album(), song.title(), true);
cover_fetching_tasks_[id] = song;
cover_fetching_tasks_.insert(id, song);
return id;
@@ -530,17 +538,15 @@ void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const Album
}
void AlbumCoverChoiceController::SaveArtAutomaticToSong(Song *song, const QUrl &art_automatic) {
void AlbumCoverChoiceController::SaveArtEmbeddedToSong(Song *song, const bool art_embedded) {
if (!song->is_valid()) return;
song->set_art_automatic(art_automatic);
if (song->has_embedded_cover()) {
song->clear_art_manual();
}
song->set_art_embedded(art_embedded);
song->set_art_unset(false);
if (song->source() == Song::Source::Collection) {
app_->collection_backend()->UpdateAutomaticAlbumArtAsync(song->effective_albumartist(), song->album(), art_automatic, song->has_embedded_cover());
app_->collection_backend()->UpdateEmbeddedAlbumArtAsync(song->effective_albumartist(), song->album(), art_embedded);
}
if (*song == app_->current_albumcover_loader()->last_song()) {
@@ -549,17 +555,17 @@ void AlbumCoverChoiceController::SaveArtAutomaticToSong(Song *song, const QUrl &
}
void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art_manual, const bool clear_art_automatic) {
void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art_manual) {
if (!song->is_valid()) return;
song->set_art_manual(art_manual);
if (clear_art_automatic) song->clear_art_automatic();
song->set_art_unset(false);
// Update the backends.
switch (song->source()) {
case Song::Source::Collection:
app_->collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
app_->collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual);
break;
case Song::Source::LocalFile:
case Song::Source::CDDA:
@@ -575,13 +581,13 @@ void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art
InternetService *service = app_->internet_services()->ServiceBySource(song->source());
if (!service) break;
if (service->artists_collection_backend()) {
service->artists_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
service->artists_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual);
}
if (service->albums_collection_backend()) {
service->albums_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
service->albums_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual);
}
if (service->songs_collection_backend()) {
service->songs_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
service->songs_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual);
}
break;
}
@@ -592,6 +598,44 @@ void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art
}
void AlbumCoverChoiceController::ClearAlbumCoverForSong(Song *song) {
if (!song->is_valid()) return;
song->set_art_unset(false);
song->set_art_embedded(false);
song->clear_art_automatic();
song->clear_art_manual();
if (song->source() == Song::Source::Collection) {
app_->collection_backend()->ClearAlbumArtAsync(song->effective_albumartist(), song->album(), false);
}
if (*song == app_->current_albumcover_loader()->last_song()) {
app_->current_albumcover_loader()->LoadAlbumCover(*song);
}
}
void AlbumCoverChoiceController::UnsetAlbumCoverForSong(Song *song) {
if (!song->is_valid()) return;
song->set_art_unset(true);
song->set_art_embedded(false);
song->clear_art_manual();
song->clear_art_automatic();
if (song->source() == Song::Source::Collection) {
app_->collection_backend()->UnsetAlbumArtAsync(song->effective_albumartist(), song->album());
}
if (*song == app_->current_albumcover_loader()->last_song()) {
app_->current_albumcover_loader()->LoadAlbumCover(*song);
}
}
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const AlbumCoverImageResult &result, const bool force_overwrite) {
return SaveCoverToFileAutomatic(song->source(),
@@ -625,11 +669,10 @@ QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source sou
filepath = file.fileName();
}
QUrl cover_url;
if (result.is_jpeg()) {
if (!result.image_data.isEmpty() && result.is_jpeg()) {
if (file.open(QIODevice::WriteOnly)) {
if (file.write(result.image_data) > 0) {
cover_url = QUrl::fromLocalFile(filepath);
return QUrl::fromLocalFile(filepath);
}
else {
qLog(Error) << "Failed to write cover to file" << file.fileName() << file.errorString();
@@ -643,88 +686,58 @@ QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source sou
}
}
else {
if (result.image.save(filepath, "JPG")) cover_url = QUrl::fromLocalFile(filepath);
}
return cover_url;
}
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const AlbumCoverImageResult &result) {
if (song.source() == Song::Source::Collection) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QFuture<SongList> future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
#else
QFuture<SongList> future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
#endif
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [this, watcher, song, result]() {
SongList songs = watcher->result();
watcher->deleteLater();
QList<QUrl> urls;
urls.reserve(songs.count());
for (const Song &s : songs) urls << s.url();
if (result.is_jpeg()) {
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image_data);
QMutexLocker l(&mutex_cover_save_tasks_);
cover_save_tasks_.insert(id, song);
}
else {
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
QMutexLocker l(&mutex_cover_save_tasks_);
cover_save_tasks_.insert(id, song);
}
});
watcher->setFuture(future);
}
else {
if (result.is_jpeg()) {
app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), result.image_data);
}
else {
app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), result.image);
if (result.image.save(filepath, "JPG")) {
return QUrl::fromLocalFile(filepath);
}
}
}
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const QUrl &cover_url) {
SaveCoverEmbeddedAutomatic(song, cover_url.toLocalFile());
return QUrl();
}
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const QString &cover_filename) {
void AlbumCoverChoiceController::SaveCoverEmbeddedToCollectionSongs(const Song &song, const AlbumCoverImageResult &result) {
SaveCoverEmbeddedToCollectionSongs(song, QString(), result.image_data, result.mime_type);
}
void AlbumCoverChoiceController::SaveCoverEmbeddedToCollectionSongs(const Song &song, const QString &cover_filename, const QByteArray &image_data, const QString &mime_type) {
if (song.source() == Song::Source::Collection) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QFuture<SongList> future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
#else
QFuture<SongList> future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
#endif
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [this, watcher, song, cover_filename]() {
SongList songs = watcher->result();
watcher->deleteLater();
QList<QUrl> urls;
urls.reserve(songs.count());
for (const Song &s : songs) urls << s.url();
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, cover_filename);
QMutexLocker l(&mutex_cover_save_tasks_);
cover_save_tasks_.insert(id, song);
});
watcher->setFuture(future);
SaveCoverEmbeddedToCollectionSongs(song.effective_albumartist(), song.effective_album(), cover_filename, image_data, mime_type);
}
else {
app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), cover_filename);
SaveCoverEmbeddedToSong(song, cover_filename, image_data, mime_type);
}
}
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const QList<QUrl> &urls, const QImage &image) {
void AlbumCoverChoiceController::SaveCoverEmbeddedToCollectionSongs(const QString &effective_albumartist, const QString &effective_album, const QString &cover_filename, const QByteArray &image_data, const QString &mime_type) {
app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, image);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QFuture<SongList> future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), effective_albumartist, effective_album, CollectionFilterOptions());
#else
QFuture<SongList> future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, effective_albumartist, effective_album, CollectionFilterOptions());
#endif
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [this, watcher, cover_filename, image_data, mime_type]() {
const SongList collection_songs = watcher->result();
watcher->deleteLater();
for (const Song &collection_song : collection_songs) {
SaveCoverEmbeddedToSong(collection_song, cover_filename, image_data, mime_type);
}
});
watcher->setFuture(future);
}
void AlbumCoverChoiceController::SaveCoverEmbeddedToSong(const Song &song, const QString &cover_filename, const QByteArray &image_data, const QString &mime_type) {
QMutexLocker l(&mutex_cover_save_tasks_);
cover_save_tasks_.append(song);
const bool art_embedded = !image_data.isNull();
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(song.url().toLocalFile(), TagReaderClient::SaveCoverOptions(cover_filename, image_data, mime_type));
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, song, art_embedded]() { SaveEmbeddedCoverFinished(reply, song, art_embedded); });
}
@@ -749,7 +762,7 @@ bool AlbumCoverChoiceController::CanAcceptDrag(const QDragEnterEvent *e) {
}
QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
void AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
for (const QUrl &url : e->mimeData()->urls()) {
@@ -758,25 +771,22 @@ QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
if (IsKnownImageExtension(suffix)) {
if (get_save_album_cover_type() == CoverOptions::CoverType::Embedded && song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, filename);
return QUrl::fromLocalFile(Song::kEmbeddedCover);
SaveCoverEmbeddedToCollectionSongs(*song, filename);
}
else {
SaveArtManualToSong(song, url);
}
return url;
return;
}
}
if (e->mimeData()->hasImage()) {
QImage image = qvariant_cast<QImage>(e->mimeData()->imageData());
if (!image.isNull()) {
return SaveCoverAutomatic(song, AlbumCoverImageResult(image));
SaveCoverAutomatic(song, AlbumCoverImageResult(image));
}
}
return QUrl();
}
QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCoverImageResult &result) {
@@ -785,8 +795,7 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
switch(get_save_album_cover_type()) {
case CoverOptions::CoverType::Embedded:{
if (song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, result);
cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
SaveCoverEmbeddedToCollectionSongs(*song, result);
break;
}
}
@@ -803,14 +812,16 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
}
void AlbumCoverChoiceController::SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success, const bool cleared) {
void AlbumCoverChoiceController::SaveEmbeddedCoverFinished(TagReaderReply *reply, Song song, const bool art_embedded) {
if (!cover_save_tasks_.contains(id)) return;
if (!cover_save_tasks_.contains(song)) return;
cover_save_tasks_.removeAll(song);
Song song = cover_save_tasks_.take(id);
if (success) {
if (cleared) SaveArtAutomaticToSong(&song, QUrl());
else SaveArtAutomaticToSong(&song, QUrl::fromLocalFile(Song::kEmbeddedCover));
if (reply->is_successful()) {
SaveArtEmbeddedToSong(&song, art_embedded);
}
else {
emit Error(tr("Could not save cover to file %1.").arg(song.url().toLocalFile()));
}
}