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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -38,8 +38,10 @@
|
||||
#include <QMutex>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "utilities/coveroptions.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
class QFileDialog;
|
||||
@@ -108,22 +110,21 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
|
||||
// Downloads the cover from an URL given by user.
|
||||
// This returns the downloaded image or null image if something went wrong for example when user cancelled the dialog.
|
||||
QUrl LoadCoverFromURL(Song *song);
|
||||
void LoadCoverFromURL(Song *song);
|
||||
AlbumCoverImageResult LoadImageFromURL();
|
||||
|
||||
// Lets the user choose a cover among all that have been found on last.fm.
|
||||
// Returns the chosen cover or null cover if user didn't choose anything.
|
||||
QUrl SearchForCover(Song *song);
|
||||
void SearchForCover(Song *song);
|
||||
AlbumCoverImageResult SearchForImage(Song *song);
|
||||
|
||||
// Returns a path which indicates that the cover has been unset manually.
|
||||
QUrl UnsetCover(Song *song, const bool clear_art_automatic = false);
|
||||
void UnsetCover(Song *song);
|
||||
|
||||
// Clears any album cover art associated with the song.
|
||||
void ClearCover(Song *song, const bool clear_art_automatic = false);
|
||||
void ClearCover(Song *song);
|
||||
|
||||
// Physically deletes associated album covers from disk.
|
||||
bool DeleteCover(Song *song, const bool manually_unset = false);
|
||||
bool DeleteCover(Song *song, const bool unset = false);
|
||||
|
||||
// Shows the cover of given song in it's original size.
|
||||
void ShowCover(const Song &song, const QImage &image = QImage());
|
||||
@@ -133,20 +134,25 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
quint64 SearchCoverAutomatically(const Song &song);
|
||||
|
||||
// Saves the chosen cover as manual cover path of this song in collection.
|
||||
void SaveArtEmbeddedToSong(Song *song, const bool art_embedded);
|
||||
void SaveArtAutomaticToSong(Song *song, const QUrl &art_automatic);
|
||||
void SaveArtManualToSong(Song *song, const QUrl &art_manual, const bool clear_art_automatic = false);
|
||||
void SaveArtManualToSong(Song *song, const QUrl &art_manual);
|
||||
void ClearAlbumCoverForSong(Song *song);
|
||||
void UnsetAlbumCoverForSong(Song *song);
|
||||
|
||||
// Saves the cover that the user picked through a drag and drop operation.
|
||||
QUrl SaveCover(Song *song, const QDropEvent *e);
|
||||
void SaveCover(Song *song, const QDropEvent *e);
|
||||
|
||||
// Saves the given image in album directory or cache as a cover for 'album artist' - 'album'. The method returns path of the image.
|
||||
QUrl SaveCoverAutomatic(Song *song, const AlbumCoverImageResult &result);
|
||||
|
||||
QUrl SaveCoverToFileAutomatic(const Song *song, const AlbumCoverImageResult &result, const bool force_overwrite = false);
|
||||
QUrl SaveCoverToFileAutomatic(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const AlbumCoverImageResult &result, const bool force_overwrite = false);
|
||||
void SaveCoverEmbeddedAutomatic(const Song &song, const AlbumCoverImageResult &result);
|
||||
void SaveCoverEmbeddedAutomatic(const Song &song, const QUrl &cover_url);
|
||||
void SaveCoverEmbeddedAutomatic(const Song &song, const QString &cover_filename);
|
||||
void SaveCoverEmbeddedAutomatic(const QList<QUrl> &urls, const QImage &image);
|
||||
|
||||
void SaveCoverEmbeddedToCollectionSongs(const Song &song, const AlbumCoverImageResult &result);
|
||||
void SaveCoverEmbeddedToCollectionSongs(const Song &song, const QString &cover_filename, const QByteArray &image_data = QByteArray(), const QString &mime_type = QString());
|
||||
void SaveCoverEmbeddedToCollectionSongs(const QString &effective_albumartist, const QString &effective_album, const QString &cover_filename, const QByteArray &image_data = QByteArray(), const QString &mime_type = QString());
|
||||
void SaveCoverEmbeddedToSong(const Song &song, const QString &cover_filename, const QByteArray &image_data, const QString &mime_type = QString());
|
||||
|
||||
static bool CanAcceptDrag(const QDragEnterEvent *e);
|
||||
|
||||
@@ -155,7 +161,7 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
|
||||
private slots:
|
||||
void AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics);
|
||||
void SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success, const bool cleared);
|
||||
void SaveEmbeddedCoverFinished(TagReaderReply *reply, Song song, const bool art_embedded);
|
||||
|
||||
signals:
|
||||
void Error(const QString &error);
|
||||
@@ -187,12 +193,13 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
QAction *search_cover_auto_;
|
||||
|
||||
QMap<quint64, Song> cover_fetching_tasks_;
|
||||
QMap<quint64, Song> cover_save_tasks_;
|
||||
QList<Song> cover_save_tasks_;
|
||||
QMutex mutex_cover_save_tasks_;
|
||||
|
||||
CoverOptions cover_options_;
|
||||
bool save_embedded_cover_override_;
|
||||
|
||||
AlbumCoverLoaderOptions::Types cover_types_;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERCHOICECONTROLLER_H
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <QThreadPool>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverexport.h"
|
||||
#include "albumcoverexporter.h"
|
||||
#include "coverexportrunnable.h"
|
||||
@@ -43,9 +44,15 @@ void AlbumCoverExporter::SetDialogResult(const AlbumCoverExport::DialogResult &d
|
||||
dialog_result_ = dialog_result;
|
||||
}
|
||||
|
||||
void AlbumCoverExporter::SetCoverTypes(const AlbumCoverLoaderOptions::Types cover_types) {
|
||||
cover_types_ = cover_types;
|
||||
}
|
||||
|
||||
void AlbumCoverExporter::AddExportRequest(const Song &song) {
|
||||
requests_.append(new CoverExportRunnable(dialog_result_, song));
|
||||
|
||||
requests_.append(new CoverExportRunnable(dialog_result_, cover_types_, song));
|
||||
all_ = static_cast<int>(requests_.count());
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverExporter::Cancel() { requests_.clear(); }
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverexport.h"
|
||||
|
||||
class QThreadPool;
|
||||
@@ -42,6 +43,7 @@ class AlbumCoverExporter : public QObject {
|
||||
static const int kMaxConcurrentRequests;
|
||||
|
||||
void SetDialogResult(const AlbumCoverExport::DialogResult &dialog_result);
|
||||
void SetCoverTypes(const AlbumCoverLoaderOptions::Types cover_types);
|
||||
void AddExportRequest(const Song &song);
|
||||
void StartExporting();
|
||||
void Cancel();
|
||||
@@ -57,6 +59,8 @@ class AlbumCoverExporter : public QObject {
|
||||
|
||||
private:
|
||||
void AddJobsToPool();
|
||||
|
||||
AlbumCoverLoaderOptions::Types cover_types_;
|
||||
AlbumCoverExport::DialogResult dialog_result_;
|
||||
|
||||
QQueue<CoverExportRunnable*> requests_;
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
#ifndef ALBUMCOVERIMAGERESULT_H
|
||||
#define ALBUMCOVERIMAGERESULT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMetaType>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
@@ -30,10 +28,7 @@
|
||||
|
||||
class AlbumCoverImageResult {
|
||||
public:
|
||||
explicit AlbumCoverImageResult(const QUrl &_cover_url = QUrl(),
|
||||
const QString &_mime_type = QString(),
|
||||
const QByteArray &_image_data = QByteArray(),
|
||||
const QImage &_image = QImage())
|
||||
explicit AlbumCoverImageResult(const QUrl &_cover_url = QUrl(), const QString &_mime_type = QString(), const QByteArray &_image_data = QByteArray(), const QImage &_image = QImage())
|
||||
: cover_url(_cover_url),
|
||||
mime_type(_mime_type),
|
||||
image_data(_image_data),
|
||||
@@ -47,7 +42,6 @@ class AlbumCoverImageResult {
|
||||
|
||||
bool is_valid() const { return !image_data.isNull() || !image.isNull(); }
|
||||
bool is_jpeg() const { return mime_type == "image/jpeg" && !image_data.isEmpty(); }
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(AlbumCoverImageResult)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -19,19 +17,13 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
#include <QBuffer>
|
||||
#include <QSet>
|
||||
#include <QList>
|
||||
#include <QQueue>
|
||||
#include <QVariant>
|
||||
#include <QByteArray>
|
||||
@@ -39,7 +31,6 @@
|
||||
#include <QUrl>
|
||||
#include <QFile>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
@@ -53,12 +44,13 @@
|
||||
#include "albumcoverloaderresult.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
|
||||
using std::make_shared;
|
||||
|
||||
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
|
||||
: QObject(parent),
|
||||
network_(new NetworkAccessManager(this)),
|
||||
stop_requested_(false),
|
||||
load_image_async_id_(1),
|
||||
save_image_async_id_(1),
|
||||
original_thread_(nullptr) {
|
||||
|
||||
original_thread_ = thread();
|
||||
@@ -86,7 +78,7 @@ void AlbumCoverLoader::CancelTask(const quint64 id) {
|
||||
for (QQueue<TaskPtr>::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
|
||||
TaskPtr task = *it;
|
||||
if (task->id == id) {
|
||||
tasks_.erase(it); // clazy:exclude=strict-iterators
|
||||
tasks_.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -99,7 +91,7 @@ void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
|
||||
for (QQueue<TaskPtr>::iterator it = tasks_.begin(); it != tasks_.end();) {
|
||||
TaskPtr task = *it;
|
||||
if (ids.contains(task->id)) {
|
||||
it = tasks_.erase(it); // clazy:exclude=strict-iterators
|
||||
it = tasks_.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
@@ -112,24 +104,28 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
|
||||
|
||||
TaskPtr task = std::make_shared<Task>();
|
||||
task->options = options;
|
||||
task->art_embedded = song.art_embedded();
|
||||
task->art_automatic = song.art_automatic();
|
||||
task->art_manual = song.art_manual();
|
||||
task->art_unset = song.art_unset();
|
||||
task->song_source = song.source();
|
||||
task->song_url = song.url();
|
||||
task->song = song;
|
||||
task->state = State::Manual;
|
||||
|
||||
return EnqueueTask(task);
|
||||
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url, const Song::Source song_source) {
|
||||
|
||||
Song song(song_source);
|
||||
song.set_url(song_url);
|
||||
song.set_art_automatic(art_automatic);
|
||||
song.set_art_manual(art_manual);
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const bool art_embedded, const QUrl &art_automatic, const QUrl &art_manual, const bool art_unset, const QUrl &song_url, const Song::Source song_source) {
|
||||
|
||||
TaskPtr task = std::make_shared<Task>();
|
||||
task->options = options;
|
||||
task->song = song;
|
||||
task->state = State::Manual;
|
||||
task->art_embedded = art_embedded;
|
||||
task->art_automatic = art_automatic;
|
||||
task->art_manual = art_manual;
|
||||
task->art_unset = art_unset;
|
||||
task->song_source = song_source;
|
||||
task->song_url = song_url;
|
||||
|
||||
return EnqueueTask(task);
|
||||
|
||||
@@ -137,7 +133,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
|
||||
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover) {
|
||||
|
||||
TaskPtr task = std::make_shared<Task>();
|
||||
TaskPtr task = make_shared<Task>();
|
||||
task->options = options;
|
||||
task->album_cover = album_cover;
|
||||
|
||||
@@ -147,7 +143,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
|
||||
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image) {
|
||||
|
||||
TaskPtr task = std::make_shared<Task>();
|
||||
TaskPtr task = make_shared<Task>();
|
||||
task->options = options;
|
||||
task->album_cover.image = image;
|
||||
|
||||
@@ -171,390 +167,254 @@ quint64 AlbumCoverLoader::EnqueueTask(TaskPtr task) {
|
||||
|
||||
void AlbumCoverLoader::ProcessTasks() {
|
||||
|
||||
while (!stop_requested_) {
|
||||
// Get the next task
|
||||
TaskPtr task;
|
||||
{
|
||||
QMutexLocker l(&mutex_load_image_async_);
|
||||
if (tasks_.isEmpty()) return;
|
||||
task = tasks_.dequeue();
|
||||
}
|
||||
|
||||
ProcessTask(task);
|
||||
TaskPtr task;
|
||||
{
|
||||
QMutexLocker l(&mutex_load_image_async_);
|
||||
if (tasks_.isEmpty()) return;
|
||||
task = tasks_.dequeue();
|
||||
}
|
||||
|
||||
ProcessTask(task);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::ProcessTask(TaskPtr task) {
|
||||
|
||||
TryLoadResult result = TryLoadImage(task);
|
||||
if (result.started_async) {
|
||||
// The image is being loaded from a remote URL, we'll carry on later when it's done
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.loaded_success) {
|
||||
result.album_cover.mime_type = Utilities::MimeTypeFromData(result.album_cover.image_data);
|
||||
QImage image_scaled;
|
||||
QImage image_thumbnail;
|
||||
if (task->options.get_image_ && task->options.scale_output_image_) {
|
||||
image_scaled = ImageUtils::ScaleAndPad(result.album_cover.image, task->options.scale_output_image_, task->options.pad_output_image_, task->options.desired_height_);
|
||||
}
|
||||
if (task->options.get_image_ && task->options.create_thumbnail_) {
|
||||
image_thumbnail = ImageUtils::CreateThumbnail(result.album_cover.image, task->options.pad_thumbnail_image_, task->options.thumbnail_size_);
|
||||
}
|
||||
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(result.loaded_success, result.type, result.album_cover, image_scaled, image_thumbnail, task->art_updated));
|
||||
return;
|
||||
}
|
||||
|
||||
NextState(task);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::NextState(TaskPtr task) {
|
||||
|
||||
if (task->state == State::Manual) {
|
||||
// Try the automatic one next
|
||||
task->state = State::Automatic;
|
||||
ProcessTask(task);
|
||||
// If we have album cover already, only do scale and pad.
|
||||
if (task->album_cover.is_valid()) {
|
||||
task->success = true;
|
||||
}
|
||||
else {
|
||||
// Give up
|
||||
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(false, AlbumCoverLoaderResult::Type::None, AlbumCoverImageResult(task->options.default_output_image_), task->options.default_scaled_image_, task->options.default_thumbnail_image_, task->art_updated));
|
||||
InitArt(task);
|
||||
}
|
||||
|
||||
while (!task->success && !task->options.types.isEmpty()) {
|
||||
const AlbumCoverLoaderOptions::Type type = task->options.types.takeFirst();
|
||||
const LoadImageResult result = LoadImage(task, type);
|
||||
if (result.status == LoadImageResult::Status::Async) {
|
||||
// The image is being loaded from a remote URL, we'll carry on later when it's done.
|
||||
return;
|
||||
}
|
||||
if (result.status == LoadImageResult::Status::Success) {
|
||||
task->success = true;
|
||||
task->result_type = result.type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!task->success && !task->options.default_cover.isEmpty()) {
|
||||
LoadLocalFileImage(task, AlbumCoverLoaderResult::Type::None, task->options.default_cover);
|
||||
}
|
||||
|
||||
FinishTask(task, task->result_type);
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(TaskPtr task) {
|
||||
void AlbumCoverLoader::FinishTask(TaskPtr task, const AlbumCoverLoaderResult::Type result_type) {
|
||||
|
||||
// Only scale and pad.
|
||||
if (task->album_cover.is_valid()) {
|
||||
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type::Embedded, task->album_cover);
|
||||
QImage image_scaled;
|
||||
if (task->success) {
|
||||
task->result_type = result_type;
|
||||
task->album_cover.mime_type = Utilities::MimeTypeFromData(task->album_cover.image_data);
|
||||
if (task->scaled_image()) {
|
||||
image_scaled = ImageUtils::ScaleImage(task->album_cover.image, task->options.desired_scaled_size, task->options.device_pixel_ratio, task->pad_scaled_image());
|
||||
}
|
||||
if (!task->raw_image_data() && !task->album_cover.image_data.isNull()) {
|
||||
task->album_cover.image_data = QByteArray();
|
||||
}
|
||||
if (!task->original_image() && !task->album_cover.image.isNull()) {
|
||||
task->album_cover.image = QImage();
|
||||
}
|
||||
}
|
||||
|
||||
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(task->success, task->result_type, task->album_cover, image_scaled, task->art_updated));
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::InitArt(TaskPtr 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:
|
||||
break;
|
||||
case State::Manual:
|
||||
task->song.InitArtManual();
|
||||
if (task->song.art_manual_is_valid()) task->art_updated = true;
|
||||
break;
|
||||
case State::Automatic:
|
||||
if (task->song.url().isLocalFile()) {
|
||||
task->song.InitArtAutomatic();
|
||||
if (task->song.art_automatic_is_valid()) task->art_updated = true;
|
||||
}
|
||||
break;
|
||||
if (task->song.is_valid() && (task->song.source() == Song::Source::LocalFile || task->song.is_radio()) && !task->song.art_manual_is_valid() && !task->song.art_automatic_is_valid()) {
|
||||
task->song.InitArtManual();
|
||||
if (task->song.art_manual_is_valid()) {
|
||||
task->art_updated = true;
|
||||
task->art_manual = task->song.art_manual();
|
||||
}
|
||||
if (task->song.url().isLocalFile()) {
|
||||
task->song.InitArtAutomatic();
|
||||
if (task->song.art_automatic_is_valid()) {
|
||||
task->art_updated = true;
|
||||
task->art_automatic = task->song.art_automatic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AlbumCoverLoaderResult::Type type = AlbumCoverLoaderResult::Type::None;
|
||||
QUrl cover_url;
|
||||
switch (task->state) {
|
||||
case State::None:
|
||||
case State::Automatic:
|
||||
type = AlbumCoverLoaderResult::Type::Automatic;
|
||||
cover_url = task->song.art_automatic();
|
||||
break;
|
||||
case State::Manual:
|
||||
type = AlbumCoverLoaderResult::Type::Manual;
|
||||
cover_url = task->song.art_manual();
|
||||
break;
|
||||
}
|
||||
task->type = type;
|
||||
|
||||
if (!cover_url.isEmpty() && !cover_url.path().isEmpty()) {
|
||||
if (cover_url.path() == Song::kManuallyUnsetCover) {
|
||||
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type::ManuallyUnset, AlbumCoverImageResult(cover_url, QString(), QByteArray(), task->options.default_output_image_));
|
||||
}
|
||||
else if (cover_url.path() == Song::kEmbeddedCover && task->song.url().isLocalFile()) {
|
||||
QByteArray image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song.url().toLocalFile());
|
||||
if (!image_data.isEmpty()) {
|
||||
QImage image;
|
||||
if (!image_data.isEmpty() && task->options.get_image_ && image.loadFromData(image_data)) {
|
||||
return TryLoadResult(false, !image.isNull(), AlbumCoverLoaderResult::Type::Embedded, AlbumCoverImageResult(cover_url, QString(), image_data, image));
|
||||
}
|
||||
else {
|
||||
return TryLoadResult(false, !image_data.isEmpty(), AlbumCoverLoaderResult::Type::Embedded, AlbumCoverImageResult(cover_url, QString(), image_data, image));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cover_url.isLocalFile()) {
|
||||
QFile file(cover_url.toLocalFile());
|
||||
if (file.exists()) {
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QByteArray image_data = file.readAll();
|
||||
file.close();
|
||||
QImage image;
|
||||
if (!image_data.isEmpty() && task->options.get_image_ && image.loadFromData(image_data)) {
|
||||
return TryLoadResult(false, !image.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
|
||||
}
|
||||
else {
|
||||
return TryLoadResult(false, !image_data.isEmpty(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
|
||||
}
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Failed to open cover file" << cover_url << "for reading" << file.errorString();
|
||||
}
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Cover file" << cover_url << "does not exist";
|
||||
}
|
||||
}
|
||||
else if (cover_url.scheme().isEmpty()) { // Assume a local file with no scheme.
|
||||
QFile file(cover_url.path());
|
||||
if (file.exists()) {
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QByteArray image_data = file.readAll();
|
||||
file.close();
|
||||
QImage image;
|
||||
if (!image_data.isEmpty() && task->options.get_image_ && image.loadFromData(image_data)) {
|
||||
return TryLoadResult(false, !image.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
|
||||
}
|
||||
else {
|
||||
return TryLoadResult(false, !image_data.isEmpty(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
|
||||
}
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Failed to open cover file" << cover_url << "for reading" << file.errorString();
|
||||
}
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Cover file" << cover_url << "does not exist";
|
||||
}
|
||||
}
|
||||
else if (network_->supportedSchemes().contains(cover_url.scheme())) { // Remote URL
|
||||
qLog(Debug) << "Loading remote cover from" << cover_url;
|
||||
QNetworkRequest request(cover_url);
|
||||
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
QNetworkReply *reply = network_->get(request);
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, cover_url]() { RemoteFetchFinished(reply, cover_url); });
|
||||
|
||||
remote_tasks_.insert(reply, task);
|
||||
return TryLoadResult(true, false, type, AlbumCoverImageResult(cover_url));
|
||||
}
|
||||
}
|
||||
|
||||
return TryLoadResult(false, false, AlbumCoverLoaderResult::Type::None, AlbumCoverImageResult(cover_url, QString(), QByteArray(), task->options.default_output_image_));
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url) {
|
||||
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type &type) {
|
||||
|
||||
switch (type) {
|
||||
case AlbumCoverLoaderOptions::Type::Unset:{
|
||||
if (task->art_unset) {
|
||||
if (!task->options.default_cover.isEmpty()) {
|
||||
return LoadLocalFileImage(task, AlbumCoverLoaderResult::Type::Unset, task->options.default_cover);
|
||||
}
|
||||
return LoadImageResult(AlbumCoverLoaderResult::Type::Unset, LoadImageResult::Status::Success);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:{
|
||||
if (task->art_embedded && task->song_url.isValid() && task->song_url.isLocalFile()) {
|
||||
return LoadEmbeddedImage(task);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AlbumCoverLoaderOptions::Type::Automatic:{
|
||||
if (task->art_automatic.isValid()) {
|
||||
return LoadUrlImage(task, AlbumCoverLoaderResult::Type::Automatic, task->art_automatic);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AlbumCoverLoaderOptions::Type::Manual:{
|
||||
if (task->art_manual.isValid()) {
|
||||
return LoadUrlImage(task, AlbumCoverLoaderResult::Type::Manual, task->art_manual);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return LoadImageResult();
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadEmbeddedImage(TaskPtr task) {
|
||||
|
||||
if (task->art_embedded && task->song_url.isValid() && task->song_url.isLocalFile()) {
|
||||
task->album_cover.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile());
|
||||
if (!task->album_cover.image_data.isEmpty() && task->album_cover.image.loadFromData(task->album_cover.image_data)) {
|
||||
return LoadImageResult(AlbumCoverLoaderResult::Type::Embedded, LoadImageResult::Status::Success);
|
||||
}
|
||||
}
|
||||
|
||||
return LoadImageResult(AlbumCoverLoaderResult::Type::Embedded, LoadImageResult::Status::Failure);
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url) {
|
||||
|
||||
if (cover_url.isValid()) {
|
||||
if (cover_url.isLocalFile()) {
|
||||
return LoadLocalUrlImage(task, result_type, cover_url);
|
||||
}
|
||||
else if (network_->supportedSchemes().contains(cover_url.scheme())) {
|
||||
return LoadRemoteUrlImage(task, result_type, cover_url);
|
||||
}
|
||||
}
|
||||
|
||||
return LoadImageResult(result_type, LoadImageResult::Status::Failure);
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadLocalUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url) {
|
||||
|
||||
if (cover_url.isEmpty()) {
|
||||
return LoadImageResult(result_type, LoadImageResult::Status::Failure);
|
||||
}
|
||||
|
||||
if (!cover_url.isValid()) {
|
||||
return LoadImageResult(result_type, LoadImageResult::Status::Failure);
|
||||
}
|
||||
|
||||
if (!cover_url.isLocalFile()) {
|
||||
return LoadImageResult(result_type, LoadImageResult::Status::Failure);
|
||||
}
|
||||
|
||||
return LoadLocalFileImage(task, result_type, cover_url.toLocalFile());
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadLocalFileImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QString &cover_file) {
|
||||
|
||||
if (!QFileInfo::exists(cover_file)) {
|
||||
qLog(Error) << "Cover file" << cover_file << "does not exist.";
|
||||
return LoadImageResult(result_type, LoadImageResult::Status::Failure);
|
||||
}
|
||||
|
||||
QFile file(cover_file);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qLog(Error) << "Unable to open cover file" << cover_file << "for reading:" << file.errorString();
|
||||
return LoadImageResult(result_type, LoadImageResult::Status::Failure);
|
||||
}
|
||||
|
||||
task->album_cover.image_data = file.readAll();
|
||||
file.close();
|
||||
|
||||
if (task->album_cover.image_data.isEmpty()) {
|
||||
qLog(Error) << "Cover file" << cover_file << "is empty.";
|
||||
return LoadImageResult(result_type, LoadImageResult::Status::Failure);
|
||||
}
|
||||
|
||||
if (!task->album_cover.image.loadFromData(task->album_cover.image_data)) {
|
||||
qLog(Error) << "Failed to load image from cover file" << cover_file << ":" << file.errorString();
|
||||
return LoadImageResult(result_type, LoadImageResult::Status::Failure);
|
||||
}
|
||||
|
||||
return LoadImageResult(result_type, LoadImageResult::Status::Success);
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadRemoteUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url) {
|
||||
|
||||
qLog(Debug) << "Loading remote cover from URL" << cover_url;
|
||||
|
||||
QNetworkRequest request(cover_url);
|
||||
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
QNetworkReply *reply = network_->get(request);
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, task, result_type, cover_url]() { LoadRemoteImageFinished(reply, task, result_type, cover_url); });
|
||||
|
||||
return LoadImageResult(result_type, LoadImageResult::Status::Async);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::LoadRemoteImageFinished(QNetworkReply *reply, TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (!remote_tasks_.contains(reply)) return;
|
||||
TaskPtr task = remote_tasks_.take(reply);
|
||||
|
||||
// Handle redirects.
|
||||
QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
if (redirect.isValid()) {
|
||||
if (++task->redirects > kMaxRedirects) {
|
||||
return; // Give up.
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
if (redirect.isValid() && redirect.metaType().id() == QMetaType::QUrl) {
|
||||
#else
|
||||
if (redirect.isValid() && redirect.type() == QVariant::Url) {
|
||||
#endif
|
||||
if (task->redirects++ >= kMaxRedirects) {
|
||||
ProcessTask(task);
|
||||
return;
|
||||
}
|
||||
const QUrl redirect_url = redirect.toUrl();
|
||||
qLog(Debug) << "Loading remote cover from redirected URL" << redirect_url;
|
||||
QNetworkRequest request = reply->request();
|
||||
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
request.setUrl(redirect.toUrl());
|
||||
request.setUrl(redirect_url);
|
||||
QNetworkReply *redirected_reply = network_->get(request);
|
||||
QObject::connect(redirected_reply, &QNetworkReply::finished, this, [this, redirected_reply, redirect]() { RemoteFetchFinished(redirected_reply, redirect.toUrl()); });
|
||||
|
||||
remote_tasks_.insert(redirected_reply, task);
|
||||
QObject::connect(redirected_reply, &QNetworkReply::finished, this, [this, reply, task, result_type, redirect_url]() { LoadRemoteImageFinished(reply, task, result_type, redirect_url); });
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
// Try to load the image
|
||||
QByteArray image_data = reply->readAll();
|
||||
QString mime_type = Utilities::MimeTypeFromData(image_data);
|
||||
QImage image;
|
||||
if (image.loadFromData(image_data)) {
|
||||
QImage image_scaled;
|
||||
QImage image_thumbnail;
|
||||
if (task->options.scale_output_image_) image_scaled = ImageUtils::ScaleAndPad(image, task->options.scale_output_image_, task->options.pad_output_image_, task->options.desired_height_);
|
||||
if (task->options.create_thumbnail_) image_thumbnail = ImageUtils::CreateThumbnail(image, task->options.pad_thumbnail_image_, task->options.thumbnail_size_);
|
||||
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(true, task->type, AlbumCoverImageResult(cover_url, mime_type, (task->options.get_image_data_ ? image_data : QByteArray()), image), image_scaled, image_thumbnail, task->art_updated));
|
||||
task->album_cover.image_data = reply->readAll();
|
||||
if (!task->album_cover.image_data.isEmpty() && task->album_cover.image.loadFromData(task->album_cover.image_data)) {
|
||||
task->success = true;
|
||||
FinishTask(task, result_type);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Unable to load album cover image" << cover_url;
|
||||
qLog(Error) << "Unable to load album cover image from URL" << cover_url;
|
||||
}
|
||||
}
|
||||
else {
|
||||
qLog(Error) << "Unable to get album cover" << cover_url << reply->error() << reply->errorString();
|
||||
qLog(Error) << "Unable to get album cover from URL" << cover_url << reply->error() << reply->errorString();
|
||||
}
|
||||
|
||||
NextState(task);
|
||||
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString &song_filename, const QString &cover_filename) {
|
||||
|
||||
QMutexLocker l(&mutex_save_image_async_);
|
||||
quint64 id = ++save_image_async_id_;
|
||||
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QString, song_filename), Q_ARG(QString, cover_filename));
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString &song_filename, const QImage &image) {
|
||||
|
||||
QMutexLocker l(&mutex_save_image_async_);
|
||||
quint64 id = ++save_image_async_id_;
|
||||
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QString, song_filename), Q_ARG(QImage, image));
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString &song_filename, const QByteArray &image_data) {
|
||||
|
||||
QMutexLocker l(&mutex_save_image_async_);
|
||||
quint64 id = ++save_image_async_id_;
|
||||
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QString, song_filename), Q_ARG(QByteArray, image_data));
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QString &cover_filename) {
|
||||
|
||||
QMutexLocker l(&mutex_save_image_async_);
|
||||
quint64 id = ++save_image_async_id_;
|
||||
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QString, cover_filename));
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QImage &image) {
|
||||
|
||||
QMutexLocker l(&mutex_save_image_async_);
|
||||
quint64 id = ++save_image_async_id_;
|
||||
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QImage, image));
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QByteArray &image_data) {
|
||||
|
||||
QMutexLocker l(&mutex_save_image_async_);
|
||||
quint64 id = ++save_image_async_id_;
|
||||
QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QByteArray, image_data));
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QByteArray &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);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QImage &image) {
|
||||
|
||||
QByteArray image_data;
|
||||
|
||||
if (!image.isNull()) {
|
||||
QBuffer buffer(&image_data);
|
||||
if (buffer.open(QIODevice::WriteOnly)) {
|
||||
image.save(&buffer, "JPEG");
|
||||
buffer.close();
|
||||
}
|
||||
}
|
||||
|
||||
SaveEmbeddedCover(id, song_filename, image_data);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QString &cover_filename) {
|
||||
|
||||
QFile file(cover_filename);
|
||||
|
||||
if (file.size() >= 209715200) { // Max 200 MB.
|
||||
emit SaveEmbeddedCoverAsyncFinished(id, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qLog(Error) << "Failed to open cover file" << cover_filename << "for reading:" << file.errorString();
|
||||
emit SaveEmbeddedCoverAsyncFinished(id, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray image_data = file.readAll();
|
||||
file.close();
|
||||
|
||||
SaveEmbeddedCover(id, song_filename, image_data);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QImage &image) {
|
||||
|
||||
if (image.isNull()) {
|
||||
for (const QUrl &url : urls) {
|
||||
SaveEmbeddedCover(id, url.toLocalFile(), QByteArray());
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
QByteArray image_data;
|
||||
QBuffer buffer(&image_data);
|
||||
if (buffer.open(QIODevice::WriteOnly)) {
|
||||
if (image.save(&buffer, "JPEG")) {
|
||||
SaveEmbeddedCover(id, urls, image_data);
|
||||
buffer.close();
|
||||
return;
|
||||
}
|
||||
buffer.close();
|
||||
}
|
||||
}
|
||||
|
||||
emit SaveEmbeddedCoverAsyncFinished(id, false, image.isNull());
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QString &cover_filename) {
|
||||
|
||||
QFile file(cover_filename);
|
||||
|
||||
if (file.size() >= 209715200) { // Max 200 MB.
|
||||
emit SaveEmbeddedCoverAsyncFinished(id, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qLog(Error) << "Failed to open cover file" << cover_filename << "for reading:" << file.errorString();
|
||||
emit SaveEmbeddedCoverAsyncFinished(id, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray image_data = file.readAll();
|
||||
file.close();
|
||||
SaveEmbeddedCover(id, urls, image_data);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QByteArray &image_data) {
|
||||
|
||||
for (const QUrl &url : urls) {
|
||||
SaveEmbeddedCover(id, url.toLocalFile(), image_data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::SaveEmbeddedArtFinished(const quint64 id, TagReaderReply *reply, const bool cleared) {
|
||||
|
||||
if (tagreader_save_embedded_art_requests_.contains(id)) {
|
||||
tagreader_save_embedded_art_requests_.remove(id, reply);
|
||||
}
|
||||
|
||||
if (!tagreader_save_embedded_art_requests_.contains(id)) {
|
||||
emit SaveEmbeddedCoverAsyncFinished(id, reply->is_successful(), cleared);
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
ProcessTask(task);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -24,21 +22,19 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include <QPair>
|
||||
#include <QSet>
|
||||
#include <QHash>
|
||||
#include <QMultiMap>
|
||||
#include <QQueue>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
#include "albumcoverimageresult.h"
|
||||
@@ -47,111 +43,96 @@ class QThread;
|
||||
class QNetworkReply;
|
||||
class NetworkAccessManager;
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
class AlbumCoverLoader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AlbumCoverLoader(QObject *parent = nullptr);
|
||||
|
||||
enum class State {
|
||||
None,
|
||||
Manual,
|
||||
Automatic
|
||||
};
|
||||
|
||||
void ExitAsync();
|
||||
void Stop() { stop_requested_ = true; }
|
||||
|
||||
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 bool art_embedded, const QUrl &art_automatic, const QUrl &art_manual, const bool art_unset, const QUrl &song_url = QUrl(), const Song::Source song_source = Song::Source::Unknown);
|
||||
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover);
|
||||
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image);
|
||||
|
||||
void CancelTask(const quint64 id);
|
||||
void CancelTasks(const QSet<quint64> &ids);
|
||||
|
||||
quint64 SaveEmbeddedCoverAsync(const QString &song_filename, const QString &cover_filename);
|
||||
quint64 SaveEmbeddedCoverAsync(const QString &song_filename, const QImage &image);
|
||||
quint64 SaveEmbeddedCoverAsync(const QString &song_filename, const QByteArray &image_data);
|
||||
quint64 SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QString &cover_filename);
|
||||
quint64 SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QImage &image);
|
||||
quint64 SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QByteArray &image_data);
|
||||
|
||||
signals:
|
||||
void ExitFinished();
|
||||
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
||||
void SaveEmbeddedCoverAsyncFinished(const quint64 id, const bool success, const bool cleared);
|
||||
|
||||
protected slots:
|
||||
void Exit();
|
||||
void ProcessTasks();
|
||||
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
|
||||
private:
|
||||
class Task {
|
||||
public:
|
||||
explicit Task() : id(0), success(false), art_embedded(false), art_unset(false), result_type(AlbumCoverLoaderResult::Type::None), art_updated(false), redirects(0) {}
|
||||
|
||||
void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QString &cover_filename);
|
||||
void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QImage &image);
|
||||
void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QByteArray &image_data);
|
||||
void SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QImage &image);
|
||||
void SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QString &cover_filename);
|
||||
void SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QByteArray &image_data);
|
||||
|
||||
void SaveEmbeddedArtFinished(const quint64 id, TagReaderReply *reply, const bool cleared);
|
||||
|
||||
protected:
|
||||
|
||||
struct Task {
|
||||
explicit Task() : id(0), state(State::None), type(AlbumCoverLoaderResult::Type::None), art_updated(false), redirects(0) {}
|
||||
quint64 id;
|
||||
bool success;
|
||||
|
||||
AlbumCoverLoaderOptions options;
|
||||
|
||||
quint64 id;
|
||||
bool raw_image_data() const { return options.options & AlbumCoverLoaderOptions::Option::RawImageData; }
|
||||
bool original_image() const { return options.options & AlbumCoverLoaderOptions::Option::OriginalImage; }
|
||||
bool scaled_image() const { return options.options & AlbumCoverLoaderOptions::Option::ScaledImage; }
|
||||
bool pad_scaled_image() const { return options.options & AlbumCoverLoaderOptions::Option::PadScaledImage; }
|
||||
|
||||
bool art_embedded;
|
||||
QUrl art_automatic;
|
||||
QUrl art_manual;
|
||||
bool art_unset;
|
||||
QUrl song_url;
|
||||
Song::Source song_source;
|
||||
Song song;
|
||||
AlbumCoverImageResult album_cover;
|
||||
State state;
|
||||
AlbumCoverLoaderResult::Type type;
|
||||
AlbumCoverLoaderResult::Type result_type;
|
||||
bool art_updated;
|
||||
int redirects;
|
||||
};
|
||||
using TaskPtr = std::shared_ptr<Task>;
|
||||
|
||||
struct TryLoadResult {
|
||||
explicit TryLoadResult(const bool _started_async = false,
|
||||
const bool _loaded_success = false,
|
||||
const AlbumCoverLoaderResult::Type _type = AlbumCoverLoaderResult::Type::None,
|
||||
const AlbumCoverImageResult &_album_cover = AlbumCoverImageResult()) :
|
||||
started_async(_started_async),
|
||||
loaded_success(_loaded_success),
|
||||
type(_type),
|
||||
album_cover(_album_cover) {}
|
||||
|
||||
bool started_async;
|
||||
bool loaded_success;
|
||||
using TaskPtr = shared_ptr<Task>;
|
||||
|
||||
class LoadImageResult {
|
||||
public:
|
||||
enum class Status {
|
||||
Failure,
|
||||
Async,
|
||||
Success
|
||||
};
|
||||
explicit LoadImageResult(AlbumCoverLoaderResult::Type _type = AlbumCoverLoaderResult::Type::None, Status _status = Status::Failure) : type(_type), status(_status) {}
|
||||
AlbumCoverLoaderResult::Type type;
|
||||
AlbumCoverImageResult album_cover;
|
||||
Status status;
|
||||
};
|
||||
|
||||
private:
|
||||
quint64 EnqueueTask(TaskPtr task);
|
||||
void ProcessTask(TaskPtr task);
|
||||
void NextState(TaskPtr task);
|
||||
TryLoadResult TryLoadImage(TaskPtr task);
|
||||
void InitArt(TaskPtr task);
|
||||
LoadImageResult LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type &type);
|
||||
LoadImageResult LoadEmbeddedImage(TaskPtr task);
|
||||
LoadImageResult LoadUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
|
||||
LoadImageResult LoadLocalUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
|
||||
LoadImageResult LoadLocalFileImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QString &cover_file);
|
||||
LoadImageResult LoadRemoteUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
|
||||
void FinishTask(TaskPtr task, const AlbumCoverLoaderResult::Type result_type);
|
||||
|
||||
NetworkAccessManager *network_;
|
||||
|
||||
bool stop_requested_;
|
||||
|
||||
QMutex mutex_load_image_async_;
|
||||
QMutex mutex_save_image_async_;
|
||||
QQueue<TaskPtr> tasks_;
|
||||
QHash<QNetworkReply*, TaskPtr> remote_tasks_;
|
||||
quint64 load_image_async_id_;
|
||||
quint64 save_image_async_id_;
|
||||
private slots:
|
||||
void Exit();
|
||||
void ProcessTasks();
|
||||
void LoadRemoteImageFinished(QNetworkReply *reply, TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
|
||||
|
||||
private:
|
||||
static const int kMaxRedirects = 3;
|
||||
|
||||
NetworkAccessManager *network_;
|
||||
bool stop_requested_;
|
||||
QMutex mutex_load_image_async_;
|
||||
QQueue<TaskPtr> tasks_;
|
||||
quint64 load_image_async_id_;
|
||||
QThread *original_thread_;
|
||||
|
||||
QMultiMap<quint64, TagReaderReply*> tagreader_save_embedded_art_requests_;
|
||||
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERLOADER_H
|
||||
|
||||
59
src/covermanager/albumcoverloaderoptions.cpp
Normal file
59
src/covermanager/albumcoverloaderoptions.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "albumcoverloaderoptions.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
#include "settings/coverssettingspage.h"
|
||||
|
||||
AlbumCoverLoaderOptions::AlbumCoverLoaderOptions(const Options _options, const QSize _desired_scaled_size, const qreal _device_pixel_ratio, const Types _types)
|
||||
: options(_options),
|
||||
desired_scaled_size(_desired_scaled_size),
|
||||
device_pixel_ratio(_device_pixel_ratio),
|
||||
types(_types) {}
|
||||
|
||||
AlbumCoverLoaderOptions::Types AlbumCoverLoaderOptions::LoadTypes() {
|
||||
|
||||
Types cover_types;
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(CoversSettingsPage::kSettingsGroup);
|
||||
const QStringList all_cover_types = QStringList() << "art_unset" << "art_embedded" << "art_manual" << "art_automatic";
|
||||
const QStringList cover_types_strlist = s.value(CoversSettingsPage::kTypes, all_cover_types).toStringList();
|
||||
for (const QString &cover_type_str : cover_types_strlist) {
|
||||
if (cover_type_str == "art_unset") {
|
||||
cover_types << AlbumCoverLoaderOptions::Type::Unset;
|
||||
}
|
||||
else if (cover_type_str == "art_embedded") {
|
||||
cover_types << AlbumCoverLoaderOptions::Type::Embedded;
|
||||
}
|
||||
else if (cover_type_str == "art_manual") {
|
||||
cover_types << AlbumCoverLoaderOptions::Type::Manual;
|
||||
}
|
||||
else if (cover_type_str == "art_automatic") {
|
||||
cover_types << AlbumCoverLoaderOptions::Type::Automatic;
|
||||
}
|
||||
}
|
||||
|
||||
s.endGroup();
|
||||
|
||||
return cover_types;
|
||||
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, 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
|
||||
@@ -22,34 +20,40 @@
|
||||
#ifndef ALBUMCOVERLOADEROPTIONS_H
|
||||
#define ALBUMCOVERLOADEROPTIONS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QImage>
|
||||
#include <QSize>
|
||||
|
||||
class AlbumCoverLoaderOptions {
|
||||
public:
|
||||
explicit AlbumCoverLoaderOptions()
|
||||
: get_image_data_(true),
|
||||
get_image_(true),
|
||||
scale_output_image_(true),
|
||||
pad_output_image_(true),
|
||||
create_thumbnail_(false),
|
||||
pad_thumbnail_image_(false),
|
||||
desired_height_(120),
|
||||
thumbnail_size_(120, 120) {}
|
||||
enum class Option {
|
||||
NoOptions = 0x0,
|
||||
RawImageData = 0x2,
|
||||
OriginalImage = 0x4,
|
||||
ScaledImage = 0x8,
|
||||
PadScaledImage = 0x16
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
|
||||
bool get_image_data_;
|
||||
bool get_image_;
|
||||
bool scale_output_image_;
|
||||
bool pad_output_image_;
|
||||
bool create_thumbnail_;
|
||||
bool pad_thumbnail_image_;
|
||||
int desired_height_;
|
||||
QSize thumbnail_size_;
|
||||
QImage default_output_image_;
|
||||
QImage default_scaled_image_;
|
||||
QImage default_thumbnail_image_;
|
||||
enum class Type {
|
||||
Embedded,
|
||||
Automatic,
|
||||
Manual,
|
||||
Unset
|
||||
};
|
||||
using Types = QList<Type>;
|
||||
|
||||
explicit AlbumCoverLoaderOptions(const Options _options = AlbumCoverLoaderOptions::Option::ScaledImage, const QSize _desired_scaled_size = QSize(32, 32), const qreal device_pixel_ratio = 1.0F, const Types _types = QList<AlbumCoverLoaderOptions::Type>() << AlbumCoverLoaderOptions::Type::Embedded << AlbumCoverLoaderOptions::Type::Automatic << AlbumCoverLoaderOptions::Type::Manual);
|
||||
|
||||
Options options;
|
||||
QSize desired_scaled_size;
|
||||
qreal device_pixel_ratio;
|
||||
Types types;
|
||||
QString default_cover;
|
||||
|
||||
static Types LoadTypes();
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(AlbumCoverLoaderOptions::Options)
|
||||
|
||||
#endif // ALBUMCOVERLOADEROPTIONS_H
|
||||
|
||||
@@ -32,35 +32,33 @@ class AlbumCoverLoaderResult {
|
||||
|
||||
enum class Type {
|
||||
None,
|
||||
ManuallyUnset,
|
||||
Unset,
|
||||
Embedded,
|
||||
Automatic,
|
||||
Manual,
|
||||
Remote
|
||||
Manual
|
||||
};
|
||||
|
||||
explicit AlbumCoverLoaderResult(const bool _success = false,
|
||||
const Type _type = Type::None,
|
||||
AlbumCoverImageResult _album_cover = AlbumCoverImageResult(),
|
||||
const QImage &_image_scaled = QImage(),
|
||||
const QImage &_image_thumbnail = QImage(),
|
||||
const bool _remote_cover = false,
|
||||
const bool _updated = false) :
|
||||
success(_success),
|
||||
type(_type),
|
||||
album_cover(_album_cover),
|
||||
image_scaled(_image_scaled),
|
||||
image_thumbnail(_image_thumbnail),
|
||||
remote_cover(_remote_cover),
|
||||
updated(_updated) {}
|
||||
|
||||
bool success;
|
||||
Type type;
|
||||
AlbumCoverImageResult album_cover;
|
||||
QImage image_scaled;
|
||||
QImage image_thumbnail;
|
||||
bool remote_cover;
|
||||
bool updated;
|
||||
|
||||
QUrl temp_cover_url;
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(AlbumCoverLoaderResult)
|
||||
|
||||
@@ -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
|
||||
@@ -95,6 +95,7 @@
|
||||
#include "ui_albumcovermanager.h"
|
||||
|
||||
const char *AlbumCoverManager::kSettingsGroup = "CoverManager";
|
||||
constexpr int AlbumCoverManager::kThumbnailSize = 120;
|
||||
|
||||
AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent)
|
||||
: QMainWindow(parent),
|
||||
@@ -112,7 +113,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
||||
cover_exporter_(new AlbumCoverExporter(this)),
|
||||
artist_icon_(IconLoader::Load("folder-sound")),
|
||||
all_artists_icon_(IconLoader::Load("library-music")),
|
||||
image_nocover_thumbnail_(ImageUtils::GenerateNoCoverImage(QSize(120 * devicePixelRatio(), 120 * devicePixelRatio()))),
|
||||
image_nocover_thumbnail_(ImageUtils::GenerateNoCoverImage(QSize(120, 120), devicePixelRatio())),
|
||||
icon_nocover_item_(QPixmap::fromImage(image_nocover_thumbnail_)),
|
||||
context_menu_(new QMenu(this)),
|
||||
progress_bar_(new QProgressBar(this)),
|
||||
@@ -150,11 +151,6 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
||||
QShortcut *close = new QShortcut(QKeySequence::Close, this);
|
||||
QObject::connect(close, &QShortcut::activated, this, &AlbumCoverManager::close);
|
||||
|
||||
cover_loader_options_.scale_output_image_ = true;
|
||||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.desired_height_ = 120 * devicePixelRatio();
|
||||
cover_loader_options_.create_thumbnail_ = false;
|
||||
|
||||
EnableCoversButtons();
|
||||
|
||||
}
|
||||
@@ -236,7 +232,6 @@ void AlbumCoverManager::Init() {
|
||||
s.endGroup();
|
||||
|
||||
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &AlbumCoverManager::AlbumCoverLoaded);
|
||||
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::SaveEmbeddedCoverAsyncFinished, this, &AlbumCoverManager::SaveEmbeddedCoverAsyncFinished);
|
||||
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
@@ -248,6 +243,7 @@ void AlbumCoverManager::showEvent(QShowEvent *e) {
|
||||
|
||||
if (!e->spontaneous()) {
|
||||
LoadGeometry();
|
||||
cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
|
||||
album_cover_choice_controller_->ReloadSettings();
|
||||
Reset();
|
||||
}
|
||||
@@ -321,7 +317,6 @@ void AlbumCoverManager::CancelRequests() {
|
||||
#endif
|
||||
cover_loading_tasks_.clear();
|
||||
cover_save_tasks_.clear();
|
||||
cover_save_tasks2_.clear();
|
||||
|
||||
cover_exporter_->Cancel();
|
||||
|
||||
@@ -390,41 +385,43 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
|
||||
// Sort by album name. The list is already sorted by sqlite but it was done case sensitively.
|
||||
std::stable_sort(albums.begin(), albums.end(), CompareAlbumNameNocase);
|
||||
|
||||
for (const CollectionBackend::Album &info : albums) {
|
||||
for (const CollectionBackend::Album &album_info : albums) {
|
||||
|
||||
// Don't show songs without an album, obviously
|
||||
if (info.album.isEmpty()) continue;
|
||||
if (album_info.album.isEmpty()) continue;
|
||||
|
||||
QString display_text;
|
||||
|
||||
if (current->type() == Specific_Artist) {
|
||||
display_text = info.album;
|
||||
display_text = album_info.album;
|
||||
}
|
||||
else {
|
||||
display_text = info.album_artist + " - " + info.album;
|
||||
display_text = album_info.album_artist + " - " + album_info.album;
|
||||
}
|
||||
|
||||
AlbumItem *item = new AlbumItem(icon_nocover_item_, display_text, ui_->albums);
|
||||
item->setData(Role_AlbumArtist, info.album_artist);
|
||||
item->setData(Role_Album, info.album);
|
||||
item->setData(Role_Filetype, QVariant::fromValue(info.filetype));
|
||||
item->setData(Role_CuePath, info.cue_path);
|
||||
item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter));
|
||||
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
|
||||
item->urls = info.urls;
|
||||
AlbumItem *album_item = new AlbumItem(icon_nocover_item_, display_text, ui_->albums);
|
||||
album_item->setData(Role_AlbumArtist, album_info.album_artist);
|
||||
album_item->setData(Role_Album, album_info.album);
|
||||
album_item->setData(Role_Filetype, QVariant::fromValue(album_info.filetype));
|
||||
album_item->setData(Role_CuePath, album_info.cue_path);
|
||||
album_item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter));
|
||||
album_item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
|
||||
album_item->urls = album_info.urls;
|
||||
|
||||
if (info.album_artist.isEmpty()) {
|
||||
item->setToolTip(info.album);
|
||||
if (album_info.album_artist.isEmpty()) {
|
||||
album_item->setToolTip(album_info.album);
|
||||
}
|
||||
else {
|
||||
item->setToolTip(info.album_artist + " - " + info.album);
|
||||
album_item->setToolTip(album_info.album_artist + " - " + album_info.album);
|
||||
}
|
||||
|
||||
if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) {
|
||||
item->setData(Role_PathAutomatic, info.art_automatic);
|
||||
item->setData(Role_PathManual, info.art_manual);
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.urls.first());
|
||||
cover_loading_tasks_[id] = item;
|
||||
album_item->setData(Role_ArtEmbedded, album_info.art_embedded);
|
||||
album_item->setData(Role_ArtAutomatic, album_info.art_automatic);
|
||||
album_item->setData(Role_ArtManual, album_info.art_manual);
|
||||
album_item->setData(Role_ArtUnset, album_info.art_unset);
|
||||
|
||||
if (album_info.art_embedded || !album_info.art_automatic.isEmpty() || !album_info.art_manual.isEmpty()) {
|
||||
LoadAlbumCoverAsync(album_item);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -437,18 +434,15 @@ void AlbumCoverManager::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoade
|
||||
|
||||
if (!cover_loading_tasks_.contains(id)) return;
|
||||
|
||||
AlbumItem *item = cover_loading_tasks_.take(id);
|
||||
AlbumItem *album_item = cover_loading_tasks_.take(id);
|
||||
|
||||
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::ManuallyUnset) {
|
||||
item->setIcon(icon_nocover_item_);
|
||||
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::Unset) {
|
||||
album_item->setIcon(icon_nocover_item_);
|
||||
}
|
||||
else {
|
||||
item->setIcon(QPixmap::fromImage(result.image_scaled));
|
||||
album_item->setIcon(QPixmap::fromImage(result.image_scaled));
|
||||
}
|
||||
|
||||
//item->setData(Role_Image, result.image_original);
|
||||
//item->setData(Role_ImageData, result.image_data);
|
||||
|
||||
UpdateFilter();
|
||||
|
||||
}
|
||||
@@ -471,13 +465,13 @@ void AlbumCoverManager::UpdateFilter() {
|
||||
qint32 without_cover = 0;
|
||||
|
||||
for (int i = 0; i < ui_->albums->count(); ++i) {
|
||||
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i));
|
||||
bool should_hide = ShouldHide(*item, filter, hide_covers);
|
||||
item->setHidden(should_hide);
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(ui_->albums->item(i));
|
||||
bool should_hide = ShouldHide(*album_item, filter, hide_covers);
|
||||
album_item->setHidden(should_hide);
|
||||
|
||||
if (!should_hide) {
|
||||
++total_count;
|
||||
if (!ItemHasCover(*item)) {
|
||||
if (!ItemHasCover(*album_item)) {
|
||||
++without_cover;
|
||||
}
|
||||
}
|
||||
@@ -488,9 +482,9 @@ void AlbumCoverManager::UpdateFilter() {
|
||||
|
||||
}
|
||||
|
||||
bool AlbumCoverManager::ShouldHide(const AlbumItem &item, const QString &filter, const HideCovers hide_covers) const {
|
||||
bool AlbumCoverManager::ShouldHide(const AlbumItem &album_item, const QString &filter, const HideCovers hide_covers) const {
|
||||
|
||||
bool has_cover = ItemHasCover(item);
|
||||
bool has_cover = ItemHasCover(album_item);
|
||||
if (hide_covers == HideCovers::WithCovers && has_cover) {
|
||||
return true;
|
||||
}
|
||||
@@ -504,8 +498,8 @@ bool AlbumCoverManager::ShouldHide(const AlbumItem &item, const QString &filter,
|
||||
|
||||
QStringList query = filter.split(' ');
|
||||
for (const QString &s : query) {
|
||||
bool in_text = item.text().contains(s, Qt::CaseInsensitive);
|
||||
bool in_albumartist = item.data(Role_AlbumArtist).toString().contains(s, Qt::CaseInsensitive);
|
||||
bool in_text = album_item.text().contains(s, Qt::CaseInsensitive);
|
||||
bool in_albumartist = album_item.data(Role_AlbumArtist).toString().contains(s, Qt::CaseInsensitive);
|
||||
if (!in_text && !in_albumartist) {
|
||||
return true;
|
||||
}
|
||||
@@ -518,12 +512,12 @@ bool AlbumCoverManager::ShouldHide(const AlbumItem &item, const QString &filter,
|
||||
void AlbumCoverManager::FetchAlbumCovers() {
|
||||
|
||||
for (int i = 0; i < ui_->albums->count(); ++i) {
|
||||
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i));
|
||||
if (item->isHidden()) continue;
|
||||
if (ItemHasCover(*item)) continue;
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(ui_->albums->item(i));
|
||||
if (album_item->isHidden()) continue;
|
||||
if (ItemHasCover(*album_item)) continue;
|
||||
|
||||
quint64 id = cover_fetcher_->FetchAlbumCover(item->data(Role_AlbumArtist).toString(), item->data(Role_Album).toString(), QString(), true);
|
||||
cover_fetching_tasks_[id] = item;
|
||||
quint64 id = cover_fetcher_->FetchAlbumCover(album_item->data(Role_AlbumArtist).toString(), album_item->data(Role_Album).toString(), QString(), true);
|
||||
cover_fetching_tasks_[id] = album_item;
|
||||
jobs_++;
|
||||
}
|
||||
|
||||
@@ -541,9 +535,9 @@ void AlbumCoverManager::AlbumCoverFetched(const quint64 id, const AlbumCoverImag
|
||||
|
||||
if (!cover_fetching_tasks_.contains(id)) return;
|
||||
|
||||
AlbumItem *item = cover_fetching_tasks_.take(id);
|
||||
AlbumItem *album_item = cover_fetching_tasks_.take(id);
|
||||
if (!result.image.isNull()) {
|
||||
SaveAndSetCover(item, result);
|
||||
SaveAndSetCover(album_item, result);
|
||||
}
|
||||
|
||||
if (cover_fetching_tasks_.isEmpty()) {
|
||||
@@ -593,13 +587,13 @@ bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *e) {
|
||||
bool some_unset = false;
|
||||
bool some_clear = false;
|
||||
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||
for (QListWidgetItem *list_widget_item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
|
||||
if (ItemHasCover(*album_item)) some_with_covers = true;
|
||||
if (album_item->data(Role_PathManual).toUrl().path() == Song::kManuallyUnsetCover) {
|
||||
if (album_item->data(Role_ArtUnset).toBool()) {
|
||||
some_unset = true;
|
||||
}
|
||||
else if (album_item->data(Role_PathAutomatic).toUrl().isEmpty() && album_item->data(Role_PathManual).toUrl().isEmpty()) {
|
||||
else if (!album_item->data(Role_ArtEmbedded).toBool() && album_item->data(Role_ArtAutomatic).toUrl().isEmpty() && album_item->data(Role_ArtManual).toUrl().isEmpty()) {
|
||||
some_clear = true;
|
||||
}
|
||||
}
|
||||
@@ -623,19 +617,19 @@ bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *e) {
|
||||
}
|
||||
|
||||
Song AlbumCoverManager::GetSingleSelectionAsSong() {
|
||||
return context_menu_items_.size() != 1 ? Song() : ItemAsSong(context_menu_items_[0]);
|
||||
return context_menu_items_.size() != 1 ? Song() : AlbumItemAsSong(context_menu_items_[0]);
|
||||
}
|
||||
|
||||
Song AlbumCoverManager::GetFirstSelectedAsSong() {
|
||||
return context_menu_items_.isEmpty() ? Song() : ItemAsSong(context_menu_items_[0]);
|
||||
return context_menu_items_.isEmpty() ? Song() : AlbumItemAsSong(context_menu_items_[0]);
|
||||
}
|
||||
|
||||
Song AlbumCoverManager::ItemAsSong(AlbumItem *item) {
|
||||
Song AlbumCoverManager::AlbumItemAsSong(AlbumItem *album_item) {
|
||||
|
||||
Song result(Song::Source::Collection);
|
||||
|
||||
QString title = item->data(Role_Album).toString();
|
||||
QString artist_name = item->data(Role_AlbumArtist).toString();
|
||||
QString title = album_item->data(Role_Album).toString();
|
||||
QString artist_name = album_item->data(Role_AlbumArtist).toString();
|
||||
if (!artist_name.isEmpty()) {
|
||||
result.set_title(artist_name + " - " + title);
|
||||
}
|
||||
@@ -643,27 +637,30 @@ Song AlbumCoverManager::ItemAsSong(AlbumItem *item) {
|
||||
result.set_title(title);
|
||||
}
|
||||
|
||||
result.set_artist(item->data(Role_AlbumArtist).toString());
|
||||
result.set_albumartist(item->data(Role_AlbumArtist).toString());
|
||||
result.set_album(item->data(Role_Album).toString());
|
||||
result.set_artist(album_item->data(Role_AlbumArtist).toString());
|
||||
result.set_albumartist(album_item->data(Role_AlbumArtist).toString());
|
||||
result.set_album(album_item->data(Role_Album).toString());
|
||||
|
||||
result.set_filetype(static_cast<Song::FileType>(item->data(Role_Filetype).toInt()));
|
||||
result.set_url(item->urls.first());
|
||||
result.set_cue_path(item->data(Role_CuePath).toString());
|
||||
result.set_filetype(static_cast<Song::FileType>(album_item->data(Role_Filetype).toInt()));
|
||||
result.set_url(album_item->urls.first());
|
||||
result.set_cue_path(album_item->data(Role_CuePath).toString());
|
||||
|
||||
result.set_art_automatic(item->data(Role_PathAutomatic).toUrl());
|
||||
result.set_art_manual(item->data(Role_PathManual).toUrl());
|
||||
result.set_art_embedded(album_item->data(Role_ArtEmbedded).toBool());
|
||||
result.set_art_automatic(album_item->data(Role_ArtAutomatic).toUrl());
|
||||
result.set_art_manual(album_item->data(Role_ArtManual).toUrl());
|
||||
result.set_art_unset(album_item->data(Role_ArtUnset).toBool());
|
||||
|
||||
// force validity
|
||||
result.set_valid(true);
|
||||
result.set_id(0);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::ShowCover() {
|
||||
|
||||
Song song = GetSingleSelectionAsSong();
|
||||
const Song song = GetSingleSelectionAsSong();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
album_cover_choice_controller_->ShowCover(song);
|
||||
@@ -672,8 +669,8 @@ void AlbumCoverManager::ShowCover() {
|
||||
|
||||
void AlbumCoverManager::FetchSingleCover() {
|
||||
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||
for (QListWidgetItem *list_widget_item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
|
||||
quint64 id = cover_fetcher_->FetchAlbumCover(album_item->data(Role_AlbumArtist).toString(), album_item->data(Role_Album).toString(), QString(), false);
|
||||
cover_fetching_tasks_[id] = album_item;
|
||||
jobs_++;
|
||||
@@ -686,11 +683,11 @@ void AlbumCoverManager::FetchSingleCover() {
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::UpdateCoverInList(AlbumItem *item, const QUrl &cover_url) {
|
||||
void AlbumCoverManager::UpdateCoverInList(AlbumItem *album_item, const QUrl &cover_url) {
|
||||
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), cover_url);
|
||||
item->setData(Role_PathManual, cover_url);
|
||||
cover_loading_tasks_[id] = item;
|
||||
album_item->setData(Role_ArtManual, cover_url);
|
||||
album_item->setData(Role_ArtUnset, false);
|
||||
LoadAlbumCoverAsync(album_item);
|
||||
|
||||
}
|
||||
|
||||
@@ -709,26 +706,31 @@ void AlbumCoverManager::LoadCoverFromFile() {
|
||||
void AlbumCoverManager::SaveCoverToFile() {
|
||||
|
||||
Song song = GetSingleSelectionAsSong();
|
||||
if (!song.is_valid() || song.has_manually_unset_cover()) return;
|
||||
|
||||
AlbumCoverImageResult result;
|
||||
if (!song.is_valid() || song.art_unset()) return;
|
||||
|
||||
// Load the image from disk
|
||||
|
||||
if (!song.art_manual().isEmpty() && !song.has_manually_unset_cover() && song.art_manual().isLocalFile() && QFile::exists(song.art_manual().toLocalFile())) {
|
||||
result.image_data = Utilities::ReadDataFromFile(song.art_manual().toLocalFile());
|
||||
}
|
||||
else if (!song.art_manual().isEmpty() && !song.art_manual().path().isEmpty() && song.art_manual().scheme().isEmpty() && QFile::exists(song.art_manual().path())) {
|
||||
result.image_data = Utilities::ReadDataFromFile(song.art_manual().path());
|
||||
}
|
||||
else if (song.has_embedded_cover()) {
|
||||
result.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile());
|
||||
}
|
||||
else if (!song.art_automatic().isEmpty() && song.art_automatic().isLocalFile() && QFile::exists(song.art_automatic().toLocalFile())) {
|
||||
result.image_data = Utilities::ReadDataFromFile(song.art_automatic().toLocalFile());
|
||||
}
|
||||
else if (!song.art_automatic().isEmpty() && !song.art_automatic().path().isEmpty() && song.art_automatic().scheme().isEmpty() && QFile::exists(song.art_automatic().path())) {
|
||||
result.image_data = Utilities::ReadDataFromFile(song.art_automatic().path());
|
||||
AlbumCoverImageResult result;
|
||||
for (const AlbumCoverLoaderOptions::Type cover_type : cover_types_) {
|
||||
switch (cover_type) {
|
||||
case AlbumCoverLoaderOptions::Type::Unset:
|
||||
return;
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:
|
||||
if (song.art_embedded()) {
|
||||
result.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile());
|
||||
}
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Automatic:
|
||||
if (song.art_automatic_is_valid()) {
|
||||
result.image_data = Utilities::ReadDataFromFile(song.art_automatic().toLocalFile());
|
||||
}
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Manual:
|
||||
if (song.art_manual_is_valid()) {
|
||||
result.image_data = Utilities::ReadDataFromFile(song.art_manual().toLocalFile());
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (result.is_valid()) break;
|
||||
}
|
||||
|
||||
if (!result.is_valid()) return;
|
||||
@@ -778,24 +780,32 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
|
||||
}
|
||||
break;
|
||||
case CoverOptions::CoverType::Embedded:
|
||||
cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
|
||||
cover_url.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
// Force the found cover on all of the selected items
|
||||
QList<QUrl> urls;
|
||||
QList<AlbumItem*> album_items;
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||
for (QListWidgetItem *list_widget_item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
|
||||
switch (album_cover_choice_controller_->get_save_album_cover_type()) {
|
||||
case CoverOptions::CoverType::Cache:
|
||||
case CoverOptions::CoverType::Album:{
|
||||
Song current_song = ItemAsSong(album_item);
|
||||
Song current_song = AlbumItemAsSong(album_item);
|
||||
album_cover_choice_controller_->SaveArtManualToSong(¤t_song, cover_url);
|
||||
UpdateCoverInList(album_item, cover_url);
|
||||
break;
|
||||
}
|
||||
case CoverOptions::CoverType::Embedded:{
|
||||
for (const QUrl &url : album_item->urls) {
|
||||
const bool art_embedded = !result.image_data.isEmpty();
|
||||
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(url.toLocalFile(), TagReaderClient::SaveCoverOptions(result.image_data, result.mime_type));
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
|
||||
SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
|
||||
});
|
||||
cover_save_tasks_.insert(album_item, url);
|
||||
}
|
||||
urls << album_item->urls;
|
||||
album_items << album_item;
|
||||
break;
|
||||
@@ -803,78 +813,53 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
else {
|
||||
id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
|
||||
}
|
||||
for (AlbumItem *album_item : album_items) {
|
||||
cover_save_tasks_.insert(id, album_item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::UnsetCover() {
|
||||
|
||||
Song song = GetFirstSelectedAsSong();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
AlbumItem *first_album_item = static_cast<AlbumItem*>(context_menu_items_[0]);
|
||||
|
||||
QUrl cover_url = album_cover_choice_controller_->UnsetCover(&song);
|
||||
if (context_menu_items_.isEmpty()) return;
|
||||
|
||||
// Force the 'none' cover on all of the selected items
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||
for (QListWidgetItem *list_widget_item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
|
||||
album_item->setIcon(icon_nocover_item_);
|
||||
album_item->setData(Role_PathManual, cover_url);
|
||||
album_item->setData(Role_ArtManual, QUrl());
|
||||
album_item->setData(Role_ArtUnset, true);
|
||||
|
||||
// Don't save the first one twice
|
||||
if (album_item != first_album_item) {
|
||||
Song current_song = ItemAsSong(album_item);
|
||||
album_cover_choice_controller_->SaveArtManualToSong(¤t_song, cover_url);
|
||||
}
|
||||
Song current_song = AlbumItemAsSong(album_item);
|
||||
album_cover_choice_controller_->UnsetAlbumCoverForSong(¤t_song);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::ClearCover() {
|
||||
|
||||
Song song = GetFirstSelectedAsSong();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
AlbumItem *first_album_item = static_cast<AlbumItem*>(context_menu_items_[0]);
|
||||
|
||||
album_cover_choice_controller_->ClearCover(&song);
|
||||
if (context_menu_items_.isEmpty()) return;
|
||||
|
||||
// Force the 'none' cover on all of the selected items
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||
for (QListWidgetItem *list_widget_item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
|
||||
album_item->setIcon(icon_nocover_item_);
|
||||
album_item->setData(Role_PathManual, QUrl());
|
||||
album_item->setData(Role_ArtEmbedded, false);
|
||||
album_item->setData(Role_ArtAutomatic, QUrl());
|
||||
album_item->setData(Role_ArtManual, QUrl());
|
||||
|
||||
// Don't save the first one twice
|
||||
if (album_item != first_album_item) {
|
||||
Song current_song = ItemAsSong(album_item);
|
||||
album_cover_choice_controller_->SaveArtManualToSong(¤t_song, QUrl(), false);
|
||||
}
|
||||
Song current_song = AlbumItemAsSong(album_item);
|
||||
album_cover_choice_controller_->ClearAlbumCoverForSong(¤t_song);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::DeleteCover() {
|
||||
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(item);
|
||||
Song song = ItemAsSong(album_item);
|
||||
for (QListWidgetItem *list_widget_item : context_menu_items_) {
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
|
||||
Song song = AlbumItemAsSong(album_item);
|
||||
album_cover_choice_controller_->DeleteCover(&song);
|
||||
album_item->setIcon(icon_nocover_item_);
|
||||
album_item->setData(Role_PathManual, QUrl());
|
||||
album_item->setData(Role_PathAutomatic, QUrl());
|
||||
album_item->setData(Role_ArtEmbedded, QUrl());
|
||||
album_item->setData(Role_ArtManual, QUrl());
|
||||
album_item->setData(Role_ArtAutomatic, QUrl());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -933,9 +918,9 @@ SongMimeData *AlbumCoverManager::GetMimeDataForAlbums(const QModelIndexList &ind
|
||||
|
||||
void AlbumCoverManager::AlbumDoubleClicked(const QModelIndex &idx) {
|
||||
|
||||
AlbumItem *item = static_cast<AlbumItem*>(idx.internalPointer());
|
||||
if (!item) return;
|
||||
album_cover_choice_controller_->ShowCover(ItemAsSong(item));
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(idx.internalPointer());
|
||||
if (!album_item) return;
|
||||
album_cover_choice_controller_->ShowCover(AlbumItemAsSong(album_item));
|
||||
|
||||
}
|
||||
|
||||
@@ -953,29 +938,25 @@ void AlbumCoverManager::LoadSelectedToPlaylist() {
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::SaveAndSetCover(AlbumItem *item, const AlbumCoverImageResult &result) {
|
||||
void AlbumCoverManager::SaveAndSetCover(AlbumItem *album_item, const AlbumCoverImageResult &result) {
|
||||
|
||||
const QString albumartist = item->data(Role_AlbumArtist).toString();
|
||||
const QString album = item->data(Role_Album).toString();
|
||||
const QList<QUrl> &urls = item->urls;
|
||||
const Song::FileType filetype = static_cast<Song::FileType>(item->data(Role_Filetype).toInt());
|
||||
const bool has_cue = !item->data(Role_CuePath).toString().isEmpty();
|
||||
const QList<QUrl> &urls = album_item->urls;
|
||||
const Song::FileType filetype = static_cast<Song::FileType>(album_item->data(Role_Filetype).toInt());
|
||||
const bool has_cue = !album_item->data(Role_CuePath).toString().isEmpty();
|
||||
|
||||
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);
|
||||
}
|
||||
else if (!result.image.isNull()) {
|
||||
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
|
||||
cover_save_tasks_.insert(id, item);
|
||||
}
|
||||
else if (!result.cover_url.isEmpty() && result.cover_url.isLocalFile()) {
|
||||
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.cover_url.toLocalFile());
|
||||
cover_save_tasks_.insert(id, item);
|
||||
for (const QUrl &url : urls) {
|
||||
const bool art_embedded = !result.image_data.isEmpty();
|
||||
TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(url.toLocalFile(), TagReaderClient::SaveCoverOptions(result.cover_url.isValid() ? result.cover_url.toLocalFile() : QString(), result.image_data, result.mime_type));
|
||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
|
||||
SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
|
||||
});
|
||||
cover_save_tasks_.insert(album_item, url);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const QString albumartist = album_item->data(Role_AlbumArtist).toString();
|
||||
const QString album = album_item->data(Role_Album).toString();
|
||||
QUrl cover_url;
|
||||
if (!result.cover_url.isEmpty() && result.cover_url.isValid() && result.cover_url.isLocalFile()) {
|
||||
cover_url = result.cover_url;
|
||||
@@ -990,7 +971,7 @@ void AlbumCoverManager::SaveAndSetCover(AlbumItem *item, const AlbumCoverImageRe
|
||||
collection_backend_->UpdateManualAlbumArtAsync(albumartist, album, cover_url);
|
||||
|
||||
// Update the icon in our list
|
||||
UpdateCoverInList(item, cover_url);
|
||||
UpdateCoverInList(album_item, cover_url);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1006,16 +987,17 @@ void AlbumCoverManager::ExportCovers() {
|
||||
DisableCoversButtons();
|
||||
|
||||
cover_exporter_->SetDialogResult(result);
|
||||
cover_exporter_->SetCoverTypes(cover_types_);
|
||||
|
||||
for (int i = 0; i < ui_->albums->count(); ++i) {
|
||||
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i));
|
||||
AlbumItem *album_item = static_cast<AlbumItem*>(ui_->albums->item(i));
|
||||
|
||||
// skip hidden and coverless albums
|
||||
if (item->isHidden() || !ItemHasCover(*item)) {
|
||||
if (album_item->isHidden() || !ItemHasCover(*album_item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cover_exporter_->AddExportRequest(ItemAsSong(item));
|
||||
cover_exporter_->AddExportRequest(AlbumItemAsSong(album_item));
|
||||
}
|
||||
|
||||
if (cover_exporter_->request_count() > 0) {
|
||||
@@ -1060,20 +1042,40 @@ void AlbumCoverManager::UpdateExportStatus(const int exported, const int skipped
|
||||
|
||||
}
|
||||
|
||||
bool AlbumCoverManager::ItemHasCover(const AlbumItem &item) const {
|
||||
return item.icon().cacheKey() != icon_nocover_item_.cacheKey();
|
||||
bool AlbumCoverManager::ItemHasCover(const AlbumItem &album_item) const {
|
||||
return album_item.icon().cacheKey() != icon_nocover_item_.cacheKey();
|
||||
}
|
||||
|
||||
void AlbumCoverManager::SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success) {
|
||||
void AlbumCoverManager::SaveEmbeddedCoverFinished(TagReaderReply *reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded) {
|
||||
|
||||
while (cover_save_tasks_.contains(id)) {
|
||||
AlbumItem *album_item = cover_save_tasks_.take(id);
|
||||
if (!success) continue;
|
||||
album_item->setData(Role_PathAutomatic, QUrl::fromLocalFile(Song::kEmbeddedCover));
|
||||
Song song = ItemAsSong(album_item);
|
||||
album_cover_choice_controller_->SaveArtAutomaticToSong(&song, QUrl::fromLocalFile(Song::kEmbeddedCover));
|
||||
quint64 cover_load_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, album_item->data(Role_PathAutomatic).toUrl(), album_item->data(Role_PathManual).toUrl(), album_item->urls.first());
|
||||
cover_loading_tasks_[cover_load_id] = album_item;
|
||||
if (cover_save_tasks_.contains(album_item, url)) {
|
||||
cover_save_tasks_.remove(album_item, url);
|
||||
}
|
||||
|
||||
if (!reply->is_successful()) {
|
||||
emit Error(tr("Could not save cover to file %1.").arg(url.toLocalFile()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cover_save_tasks_.contains(album_item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
album_item->setData(Role_ArtEmbedded, true);
|
||||
album_item->setData(Role_ArtUnset, false);
|
||||
Song song = AlbumItemAsSong(album_item);
|
||||
album_cover_choice_controller_->SaveArtEmbeddedToSong(&song, art_embedded);
|
||||
LoadAlbumCoverAsync(album_item);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::LoadAlbumCoverAsync(AlbumItem *album_item) {
|
||||
|
||||
AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
|
||||
cover_options.types = cover_types_;
|
||||
cover_options.desired_scaled_size = QSize(kThumbnailSize, kThumbnailSize);
|
||||
cover_options.device_pixel_ratio = devicePixelRatioF();
|
||||
quint64 cover_load_id = app_->album_cover_loader()->LoadImageAsync(cover_options, album_item->data(Role_ArtEmbedded).toBool(), album_item->data(Role_ArtAutomatic).toUrl(), album_item->data(Role_ArtManual).toUrl(), album_item->data(Role_ArtUnset).toBool(), album_item->urls.first());
|
||||
cover_loading_tasks_.insert(cover_load_id, album_item);
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <QIcon>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverloaderresult.h"
|
||||
#include "albumcoverchoicecontroller.h"
|
||||
@@ -79,8 +80,6 @@ class AlbumCoverManager : public QMainWindow {
|
||||
explicit AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent = nullptr);
|
||||
~AlbumCoverManager() override;
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
void Reset();
|
||||
void Init();
|
||||
|
||||
@@ -108,8 +107,10 @@ class AlbumCoverManager : public QMainWindow {
|
||||
enum Role {
|
||||
Role_AlbumArtist = Qt::UserRole + 1,
|
||||
Role_Album,
|
||||
Role_PathAutomatic,
|
||||
Role_PathManual,
|
||||
Role_ArtEmbedded,
|
||||
Role_ArtAutomatic,
|
||||
Role_ArtManual,
|
||||
Role_ArtUnset,
|
||||
Role_Filetype,
|
||||
Role_CuePath,
|
||||
Role_ImageData,
|
||||
@@ -132,19 +133,21 @@ class AlbumCoverManager : public QMainWindow {
|
||||
// Returns the first of the selected elements in form of a Song ready to be used by AlbumCoverChoiceController or invalid song if there's nothing selected.
|
||||
Song GetFirstSelectedAsSong();
|
||||
|
||||
Song ItemAsSong(QListWidgetItem *item) { return ItemAsSong(static_cast<AlbumItem*>(item)); }
|
||||
static Song ItemAsSong(AlbumItem *item);
|
||||
Song AlbumItemAsSong(QListWidgetItem *list_widget_item) { return AlbumItemAsSong(static_cast<AlbumItem*>(list_widget_item)); }
|
||||
static Song AlbumItemAsSong(AlbumItem *album_item);
|
||||
|
||||
void UpdateStatusText();
|
||||
bool ShouldHide(const AlbumItem &item, const QString &filter, const HideCovers hide_covers) const;
|
||||
void SaveAndSetCover(AlbumItem *item, const AlbumCoverImageResult &result);
|
||||
bool ShouldHide(const AlbumItem &album_item, const QString &filter, const HideCovers hide_covers) const;
|
||||
void SaveAndSetCover(AlbumItem *album_item, const AlbumCoverImageResult &result);
|
||||
|
||||
void SaveImageToAlbums(Song *song, const AlbumCoverImageResult &result);
|
||||
|
||||
SongList GetSongsInAlbums(const QModelIndexList &indexes) const;
|
||||
SongMimeData *GetMimeDataForAlbums(const QModelIndexList &indexes) const;
|
||||
|
||||
bool ItemHasCover(const AlbumItem &item) const;
|
||||
bool ItemHasCover(const AlbumItem &album_item) const;
|
||||
|
||||
void LoadAlbumCoverAsync(AlbumItem *album_item);
|
||||
|
||||
signals:
|
||||
void Error(const QString &error);
|
||||
@@ -176,12 +179,15 @@ class AlbumCoverManager : public QMainWindow {
|
||||
void AddSelectedToPlaylist();
|
||||
void LoadSelectedToPlaylist();
|
||||
|
||||
void UpdateCoverInList(AlbumItem *item, const QUrl &cover);
|
||||
void UpdateCoverInList(AlbumItem *album_item, const QUrl &cover);
|
||||
void UpdateExportStatus(const int exported, const int skipped, const int max);
|
||||
|
||||
void SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success);
|
||||
void SaveEmbeddedCoverFinished(TagReaderReply *reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded);
|
||||
|
||||
private:
|
||||
static const char *kSettingsGroup;
|
||||
static const int kThumbnailSize;
|
||||
|
||||
Ui_CoverManager *ui_;
|
||||
QMainWindow *mainwindow_;
|
||||
Application *app_;
|
||||
@@ -192,7 +198,6 @@ class AlbumCoverManager : public QMainWindow {
|
||||
QAction *filter_with_covers_;
|
||||
QAction *filter_without_covers_;
|
||||
|
||||
AlbumCoverLoaderOptions cover_loader_options_;
|
||||
QMap<quint64, AlbumItem*> cover_loading_tasks_;
|
||||
|
||||
AlbumCoverFetcher *cover_fetcher_;
|
||||
@@ -215,11 +220,11 @@ class AlbumCoverManager : public QMainWindow {
|
||||
QPushButton *abort_progress_;
|
||||
int jobs_;
|
||||
|
||||
QMultiMap<quint64, AlbumItem*> cover_save_tasks_;
|
||||
QList<AlbumItem*> cover_save_tasks2_;
|
||||
QMultiMap<AlbumItem*, QUrl> cover_save_tasks_;
|
||||
|
||||
QListWidgetItem *all_artists_;
|
||||
|
||||
AlbumCoverLoaderOptions::Types cover_types_;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERMANAGER_H
|
||||
|
||||
@@ -128,14 +128,6 @@ AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *
|
||||
ui_->covers->setItemDelegate(new SizeOverlayDelegate(this));
|
||||
ui_->covers->setModel(model_);
|
||||
|
||||
options_.get_image_data_ = true;
|
||||
options_.get_image_ = true;
|
||||
options_.scale_output_image_ = false;
|
||||
options_.pad_output_image_ = false;
|
||||
options_.create_thumbnail_ = true;
|
||||
options_.pad_thumbnail_image_ = true;
|
||||
options_.thumbnail_size_ = ui_->covers->iconSize();
|
||||
|
||||
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &AlbumCoverSearcher::AlbumCoverLoaded);
|
||||
QObject::connect(ui_->search, &QPushButton::clicked, this, &AlbumCoverSearcher::Search);
|
||||
QObject::connect(ui_->covers, &GroupedIconView::doubleClicked, this, &AlbumCoverSearcher::CoverDoubleClicked);
|
||||
@@ -222,7 +214,9 @@ void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverProviderSea
|
||||
|
||||
if (result.image_url.isEmpty()) continue;
|
||||
|
||||
quint64 new_id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url, QUrl());
|
||||
AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::RawImageData | AlbumCoverLoaderOptions::Option::OriginalImage | AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
|
||||
cover_options.desired_scaled_size = ui_->covers->iconSize(), ui_->covers->iconSize();
|
||||
quint64 new_id = app_->album_cover_loader()->LoadImageAsync(cover_options, false, result.image_url, QUrl(), false);
|
||||
|
||||
QStandardItem *item = new QStandardItem;
|
||||
item->setIcon(no_cover_icon_);
|
||||
@@ -249,12 +243,12 @@ void AlbumCoverSearcher::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoad
|
||||
|
||||
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
|
||||
|
||||
if (!result.success || result.album_cover.image_data.isNull() || result.album_cover.image.isNull() || result.image_thumbnail.isNull()) {
|
||||
if (!result.success || result.album_cover.image_data.isNull() || result.album_cover.image.isNull() || result.image_scaled.isNull()) {
|
||||
model_->removeRow(item->row());
|
||||
return;
|
||||
}
|
||||
|
||||
const QPixmap pixmap = QPixmap::fromImage(result.image_thumbnail);
|
||||
const QPixmap pixmap = QPixmap::fromImage(result.image_scaled);
|
||||
if (pixmap.isNull()) {
|
||||
model_->removeRow(item->row());
|
||||
return;
|
||||
|
||||
@@ -107,7 +107,6 @@ class AlbumCoverSearcher : public QDialog {
|
||||
QStandardItemModel *model_;
|
||||
|
||||
QIcon no_cover_icon_;
|
||||
AlbumCoverLoaderOptions options_;
|
||||
AlbumCoverFetcher *fetcher_;
|
||||
|
||||
quint64 id_;
|
||||
|
||||
@@ -28,20 +28,19 @@
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverexport.h"
|
||||
#include "coverexportrunnable.h"
|
||||
|
||||
CoverExportRunnable::CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const Song &song, QObject *parent)
|
||||
CoverExportRunnable::CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const AlbumCoverLoaderOptions::Types cover_types, const Song &song, QObject *parent)
|
||||
: QObject(parent),
|
||||
dialog_result_(dialog_result),
|
||||
cover_types_(cover_types),
|
||||
song_(song) {}
|
||||
|
||||
void CoverExportRunnable::run() {
|
||||
|
||||
QString cover_path = GetCoverPath();
|
||||
|
||||
// Manually unset?
|
||||
if (cover_path.isEmpty()) {
|
||||
if (song_.art_unset() || (!song_.art_embedded() && !song_.art_automatic_is_valid() && !song_.art_manual_is_valid())) {
|
||||
EmitCoverSkipped();
|
||||
}
|
||||
else {
|
||||
@@ -55,25 +54,6 @@ void CoverExportRunnable::run() {
|
||||
|
||||
}
|
||||
|
||||
QString CoverExportRunnable::GetCoverPath() {
|
||||
|
||||
if (song_.has_manually_unset_cover()) {
|
||||
return QString();
|
||||
// Export downloaded covers?
|
||||
}
|
||||
else if (!song_.art_manual().isEmpty() && dialog_result_.export_downloaded_) {
|
||||
return song_.art_manual().toLocalFile();
|
||||
// Export embedded covers?
|
||||
}
|
||||
else if (!song_.art_automatic().isEmpty() && song_.art_automatic().path() == Song::kEmbeddedCover && dialog_result_.export_embedded_) {
|
||||
return song_.art_automatic().toLocalFile();
|
||||
}
|
||||
else {
|
||||
return QString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Exports a single album cover using a "save QImage to file" approach.
|
||||
// For performance reasons this method will be invoked only if loading and in memory processing of images is necessary for current settings which means that:
|
||||
// - either the force size flag is being used
|
||||
@@ -81,45 +61,57 @@ QString CoverExportRunnable::GetCoverPath() {
|
||||
// In all other cases, the faster ExportCover() method will be used.
|
||||
void CoverExportRunnable::ProcessAndExportCover() {
|
||||
|
||||
QString cover_path = GetCoverPath();
|
||||
QImage image;
|
||||
QString extension;
|
||||
|
||||
// either embedded or disk - the one we'll export for the current album
|
||||
QImage cover;
|
||||
|
||||
QImage embedded_cover;
|
||||
QImage disk_cover;
|
||||
|
||||
// load embedded cover if any
|
||||
if (song_.has_embedded_cover()) {
|
||||
embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
|
||||
|
||||
if (embedded_cover.isNull()) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
for (const AlbumCoverLoaderOptions::Type cover_type : cover_types_) {
|
||||
switch (cover_type) {
|
||||
case AlbumCoverLoaderOptions::Type::Unset:
|
||||
if (song_.art_unset()) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:
|
||||
if (song_.art_embedded() && dialog_result_.export_embedded_) {
|
||||
image = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
|
||||
if (!image.isNull()) {
|
||||
extension = "jpg";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Manual:
|
||||
if (dialog_result_.export_downloaded_ && song_.art_manual_is_valid()) {
|
||||
const QString cover_path = song_.art_manual().toLocalFile();
|
||||
if (image.load(cover_path)) {
|
||||
extension = cover_path.section('.', -1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Automatic:
|
||||
if (dialog_result_.export_downloaded_ && song_.art_automatic_is_valid()) {
|
||||
const QString cover_path = song_.art_automatic().toLocalFile();
|
||||
if (image.load(cover_path)) {
|
||||
extension = cover_path.section('.', -1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
cover = embedded_cover;
|
||||
if (!image.isNull() && !extension.isEmpty()) break;
|
||||
}
|
||||
|
||||
// load a file cover which iss mandatory if there's no embedded cover
|
||||
disk_cover.load(cover_path);
|
||||
|
||||
if (embedded_cover.isNull()) {
|
||||
if (disk_cover.isNull()) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
cover = disk_cover;
|
||||
if (image.isNull() || extension.isEmpty()) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
|
||||
// rescale if necessary
|
||||
// Rescale if necessary
|
||||
if (dialog_result_.IsSizeForced()) {
|
||||
cover = cover.scaled(QSize(dialog_result_.width_, dialog_result_.height_), Qt::IgnoreAspectRatio);
|
||||
image = image.scaled(QSize(dialog_result_.width_, dialog_result_.height_), Qt::IgnoreAspectRatio);
|
||||
}
|
||||
|
||||
QString dir = song_.url().toLocalFile().section('/', 0, -2);
|
||||
QString extension = cover_path.section('.', -1);
|
||||
|
||||
QString new_file = dir + '/' + dialog_result_.filename_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension);
|
||||
QString cover_dir = song_.url().toLocalFile().section('/', 0, -2);
|
||||
QString new_file = cover_dir + '/' + dialog_result_.filename_ + '.' + (song_.art_embedded() ? "jpg" : extension);
|
||||
|
||||
// If the file exists, do not override!
|
||||
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::None && QFile::exists(new_file)) {
|
||||
@@ -127,16 +119,15 @@ void CoverExportRunnable::ProcessAndExportCover() {
|
||||
return;
|
||||
}
|
||||
|
||||
// we're handling overwrite as remove + copy so we need to delete the old file first
|
||||
// We're handling overwrite as remove + copy so we need to delete the old file first
|
||||
if (QFile::exists(new_file) && dialog_result_.overwrite_ != AlbumCoverExport::OverwriteMode::None) {
|
||||
|
||||
// if the mode is "overwrite smaller" then skip the cover if a bigger one is already available in the folder
|
||||
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::Smaller) {
|
||||
QImage existing;
|
||||
existing.load(new_file);
|
||||
|
||||
if (existing.isNull() || existing.size().height() >= cover.size().height() || existing.size().width() >= cover.size().width()) {
|
||||
QImage image_existing;
|
||||
image_existing.load(new_file);
|
||||
|
||||
if (image_existing.isNull() || image_existing.size().height() >= image.size().height() || image_existing.size().width() >= image.size().width()) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
@@ -148,7 +139,7 @@ void CoverExportRunnable::ProcessAndExportCover() {
|
||||
}
|
||||
}
|
||||
|
||||
if (cover.save(new_file)) {
|
||||
if (image.save(new_file)) {
|
||||
EmitCoverExported();
|
||||
}
|
||||
else {
|
||||
@@ -160,12 +151,55 @@ void CoverExportRunnable::ProcessAndExportCover() {
|
||||
// Exports a single album cover using a "copy file" approach.
|
||||
void CoverExportRunnable::ExportCover() {
|
||||
|
||||
QString cover_path = GetCoverPath();
|
||||
QImage image;
|
||||
QString extension;
|
||||
QString cover_path;
|
||||
bool embedded_cover = false;
|
||||
|
||||
QString dir = song_.url().toLocalFile().section('/', 0, -2);
|
||||
QString extension = cover_path.section('.', -1);
|
||||
for (const AlbumCoverLoaderOptions::Type cover_type : cover_types_) {
|
||||
switch (cover_type) {
|
||||
case AlbumCoverLoaderOptions::Type::Unset:
|
||||
if (song_.art_unset()) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Embedded:
|
||||
if (song_.art_embedded() && dialog_result_.export_embedded_) {
|
||||
image = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
|
||||
if (!image.isNull()) {
|
||||
embedded_cover = true;
|
||||
extension = "jpg";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Manual:
|
||||
if (dialog_result_.export_downloaded_ && song_.art_manual_is_valid()) {
|
||||
cover_path = song_.art_manual().toLocalFile();
|
||||
if (image.load(cover_path)) {
|
||||
extension = cover_path.section('.', -1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AlbumCoverLoaderOptions::Type::Automatic:
|
||||
if (dialog_result_.export_downloaded_ && song_.art_automatic_is_valid()) {
|
||||
cover_path = song_.art_automatic().toLocalFile();
|
||||
if (image.load(cover_path)) {
|
||||
extension = cover_path.section('.', -1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!image.isNull() && !extension.isEmpty() && (embedded_cover || !cover_path.isEmpty())) break;
|
||||
}
|
||||
|
||||
QString new_file = dir + '/' + dialog_result_.filename_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension);
|
||||
if (image.isNull() || extension.isEmpty()) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
|
||||
QString cover_dir = song_.url().toLocalFile().section('/', 0, -2);
|
||||
QString new_file = cover_dir + '/' + dialog_result_.filename_ + '.' + extension;
|
||||
|
||||
// If the file exists, do not override!
|
||||
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::None && QFile::exists(new_file)) {
|
||||
@@ -181,10 +215,8 @@ void CoverExportRunnable::ExportCover() {
|
||||
}
|
||||
}
|
||||
|
||||
if (cover_path == Song::kEmbeddedCover) {
|
||||
// an embedded cover
|
||||
QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
|
||||
if (!embedded.save(new_file)) {
|
||||
if (embedded_cover) {
|
||||
if (!image.save(new_file)) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
@@ -196,6 +228,7 @@ void CoverExportRunnable::ExportCover() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EmitCoverExported();
|
||||
|
||||
}
|
||||
|
||||
@@ -29,13 +29,14 @@
|
||||
#include <QString>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "albumcoverexport.h"
|
||||
|
||||
class CoverExportRunnable : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const Song &song, QObject *parent = nullptr);
|
||||
explicit CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const AlbumCoverLoaderOptions::Types cover_types, const Song &song, QObject *parent = nullptr);
|
||||
|
||||
void run() override;
|
||||
|
||||
@@ -49,11 +50,10 @@ class CoverExportRunnable : public QObject, public QRunnable {
|
||||
|
||||
void ProcessAndExportCover();
|
||||
void ExportCover();
|
||||
QString GetCoverPath();
|
||||
|
||||
AlbumCoverExport::DialogResult dialog_result_;
|
||||
AlbumCoverLoaderOptions::Types cover_types_;
|
||||
Song song_;
|
||||
|
||||
};
|
||||
|
||||
#endif // COVEREXPORTRUNNABLE_H
|
||||
|
||||
@@ -58,7 +58,7 @@ void CoverProviders::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(CoversSettingsPage::kSettingsGroup);
|
||||
QStringList providers_enabled = s.value("providers", QStringList() << all_providers.values()).toStringList();
|
||||
QStringList providers_enabled = s.value(CoversSettingsPage::kProviders, QStringList() << all_providers.values()).toStringList();
|
||||
s.endGroup();
|
||||
|
||||
int i = 0;
|
||||
|
||||
@@ -42,18 +42,15 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare
|
||||
temp_file_pattern_(QDir::tempPath() + "/strawberry-cover-XXXXXX.jpg"),
|
||||
id_(0) {
|
||||
|
||||
options_.get_image_data_ = true;
|
||||
options_.get_image_ = true;
|
||||
options_.scale_output_image_ = false;
|
||||
options_.pad_output_image_ = false;
|
||||
options_.create_thumbnail_ = true;
|
||||
options_.thumbnail_size_ = QSize(120, 120);
|
||||
options_.default_output_image_ = QImage(":/pictures/cdcase.png");
|
||||
options_.default_thumbnail_image_ = options_.default_output_image_.scaledToHeight(120, Qt::SmoothTransformation);
|
||||
options_.options = AlbumCoverLoaderOptions::Option::RawImageData | AlbumCoverLoaderOptions::Option::OriginalImage | AlbumCoverLoaderOptions::Option::ScaledImage;
|
||||
options_.desired_scaled_size = QSize(120, 120);
|
||||
options_.default_cover = ":/pictures/cdcase.png";
|
||||
|
||||
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CurrentAlbumCoverLoader::TempAlbumCoverLoaded);
|
||||
QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &CurrentAlbumCoverLoader::LoadAlbumCover);
|
||||
|
||||
ReloadSettingsAsync();
|
||||
|
||||
}
|
||||
|
||||
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {
|
||||
@@ -63,6 +60,18 @@ CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {
|
||||
|
||||
}
|
||||
|
||||
void CurrentAlbumCoverLoader::ReloadSettingsAsync() {
|
||||
|
||||
QMetaObject::invokeMethod(this, &CurrentAlbumCoverLoader::ReloadSettings);
|
||||
|
||||
}
|
||||
|
||||
void CurrentAlbumCoverLoader::ReloadSettings() {
|
||||
|
||||
options_.types = AlbumCoverLoaderOptions::LoadTypes();
|
||||
|
||||
}
|
||||
|
||||
void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) {
|
||||
|
||||
last_song_ = song;
|
||||
@@ -92,11 +101,11 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
|
||||
}
|
||||
|
||||
QUrl thumbnail_url;
|
||||
if (!result.image_thumbnail.isNull()) {
|
||||
if (!result.image_scaled.isNull()) {
|
||||
temp_cover_thumbnail_ = std::make_unique<QTemporaryFile>(temp_file_pattern_);
|
||||
temp_cover_thumbnail_->setAutoRemove(true);
|
||||
if (temp_cover_thumbnail_->open()) {
|
||||
if (result.image_thumbnail.save(temp_cover_thumbnail_->fileName(), "JPEG")) {
|
||||
if (result.image_scaled.save(temp_cover_thumbnail_->fileName(), "JPEG")) {
|
||||
thumbnail_url = QUrl::fromLocalFile(temp_cover_thumbnail_->fileName());
|
||||
}
|
||||
else {
|
||||
@@ -113,6 +122,6 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
|
||||
}
|
||||
|
||||
emit AlbumCoverLoaded(last_song_, result);
|
||||
emit ThumbnailLoaded(last_song_, thumbnail_url, result.image_thumbnail);
|
||||
emit ThumbnailLoaded(last_song_, thumbnail_url, result.image_scaled);
|
||||
|
||||
}
|
||||
|
||||
@@ -48,7 +48,10 @@ class CurrentAlbumCoverLoader : public QObject {
|
||||
const AlbumCoverLoaderOptions &options() const { return options_; }
|
||||
const Song &last_song() const { return last_song_; }
|
||||
|
||||
void ReloadSettingsAsync();
|
||||
|
||||
public slots:
|
||||
void ReloadSettings();
|
||||
void LoadAlbumCover(const Song &song);
|
||||
|
||||
signals:
|
||||
|
||||
Reference in New Issue
Block a user