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 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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user