Add support for saving embedded album covers

Fixes #286
This commit is contained in:
Jonas Kvinge
2021-02-26 21:03:51 +01:00
parent e4c89c1aed
commit 133f094d72
79 changed files with 3509 additions and 1804 deletions

View File

@@ -23,6 +23,9 @@
#include <QtGlobal>
#include <QGuiApplication>
#include <QtConcurrentRun>
#include <QFuture>
#include <QFutureWatcher>
#include <QScreen>
#include <QWindow>
#include <QWidget>
@@ -45,12 +48,16 @@
#include <QFileDialog>
#include <QLabel>
#include <QAction>
#include <QActionGroup>
#include <QMenu>
#include <QSettings>
#include <QtEvents>
#include "core/utilities.h"
#include "core/imageutils.h"
#include "core/application.h"
#include "core/song.h"
#include "core/iconloader.h"
#include "core/application.h"
#include "collection/collectionbackend.h"
#include "settings/collectionsettingspage.h"
@@ -61,6 +68,7 @@
#include "albumcoverfetcher.h"
#include "albumcoverloader.h"
#include "albumcoversearcher.h"
#include "albumcoverimageresult.h"
#include "coverfromurldialog.h"
#include "currentalbumcoverloader.h"
@@ -77,11 +85,23 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) :
cover_fetcher_(nullptr),
save_file_dialog_(nullptr),
cover_from_url_dialog_(nullptr),
cover_album_dir_(false),
cover_filename_(CollectionSettingsPage::SaveCover_Hash),
cover_from_file_(nullptr),
cover_to_file_(nullptr),
cover_from_url_(nullptr),
search_for_cover_(nullptr),
separator1_(nullptr),
unset_cover_(nullptr),
delete_cover_(nullptr),
clear_cover_(nullptr),
separator2_(nullptr),
show_cover_(nullptr),
search_cover_auto_(nullptr),
save_cover_type_(CollectionSettingsPage::SaveCoverType_Cache),
save_cover_filename_(CollectionSettingsPage::SaveCoverFilename_Hash),
cover_overwrite_(false),
cover_lowercase_(true),
cover_replace_spaces_(true)
cover_replace_spaces_(true),
save_embedded_cover_override_(false)
{
cover_from_file_ = new QAction(IconLoader::Load("document-open"), tr("Load cover from disk..."), this);
@@ -89,14 +109,18 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) :
cover_from_url_ = new QAction(IconLoader::Load("download"), tr("Load cover from URL..."), this);
search_for_cover_ = new QAction(IconLoader::Load("search"), tr("Search for album covers..."), this);
unset_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Unset cover"), this);
delete_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Delete cover"), this);
clear_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Clear cover"), this);
separator1_ = new QAction(this);
separator1_->setSeparator(true);
show_cover_ = new QAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this);
search_cover_auto_ = new QAction(tr("Search automatically"), this);
search_cover_auto_->setCheckable(true);
search_cover_auto_->setChecked(false);
separator_ = new QAction(this);
separator_->setSeparator(true);
separator2_ = new QAction(this);
separator2_->setSeparator(true);
ReloadSettings();
@@ -113,6 +137,7 @@ 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);
}
@@ -120,8 +145,8 @@ void AlbumCoverChoiceController::ReloadSettings() {
QSettings s;
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
cover_album_dir_ = s.value("cover_album_dir", false).toBool();
cover_filename_ = CollectionSettingsPage::SaveCover(s.value("cover_filename", CollectionSettingsPage::SaveCover_Hash).toInt());
save_cover_type_ = CollectionSettingsPage::SaveCoverType(s.value("save_cover_type", CollectionSettingsPage::SaveCoverType_Cache).toInt());
save_cover_filename_ = CollectionSettingsPage::SaveCoverFilename(s.value("save_cover_filename", CollectionSettingsPage::SaveCoverFilename_Hash).toInt());
cover_pattern_ = s.value("cover_pattern", "%albumartist-%album").toString();
cover_overwrite_ = s.value("cover_overwrite", false).toBool();
cover_lowercase_ = s.value("cover_lowercase", false).toBool();
@@ -131,30 +156,74 @@ void AlbumCoverChoiceController::ReloadSettings() {
}
QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
return QList<QAction*>() << cover_from_file_ << cover_to_file_ << separator_ << cover_from_url_ << search_for_cover_ << unset_cover_ << separator_ << show_cover_;
return QList<QAction*>() << show_cover_
<< cover_to_file_
<< separator1_
<< cover_from_file_
<< cover_from_url_
<< search_for_cover_
<< separator2_
<< unset_cover_
<< clear_cover_
<< delete_cover_;
}
AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromFile(Song *song) {
if (!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()) {
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
result.image.loadFromData(result.image_data);
}
}
return result;
}
QUrl AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
if (!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.isNull()) return QUrl();
if (cover_file.isEmpty()) return QUrl();
// Can we load the image?
QImage image(cover_file);
if (QImage(cover_file).isNull()) return QUrl();
if (image.isNull()) {
return QUrl();
}
else {
QUrl cover_url(QUrl::fromLocalFile(cover_file));
SaveCoverToSong(song, cover_url);
return cover_url;
switch(get_save_album_cover_type()) {
case CollectionSettingsPage::SaveCoverType_Embedded:
if (song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, cover_file);
return QUrl::fromLocalFile(Song::kEmbeddedCover);
}
// fallthrough
case CollectionSettingsPage::SaveCoverType_Cache:
case CollectionSettingsPage::SaveCoverType_Album:{
QUrl cover_url = QUrl::fromLocalFile(cover_file);
SaveArtManualToSong(song, cover_url);
return cover_url;
break;
}
}
return QUrl();
}
void AlbumCoverChoiceController::SaveCoverToFileManual(const Song &song, const QImage &image) {
void AlbumCoverChoiceController::SaveCoverToFileManual(const Song &song, const AlbumCoverImageResult &result) {
QString initial_file_name = "/";
@@ -168,14 +237,29 @@ void AlbumCoverChoiceController::SaveCoverToFileManual(const Song &song, const Q
QString save_filename = QFileDialog::getSaveFileName(this, tr("Save album cover"), GetInitialPathForFileDialog(song, initial_file_name), tr(kSaveImageFileFilter) + ";;" + tr(kAllFilesFilter));
if (save_filename.isNull()) return;
if (save_filename.isEmpty()) return;
QString extension = save_filename.right(4);
if (!extension.startsWith('.') || !QImageWriter::supportedImageFormats().contains(extension.right(3).toUtf8())) {
QFileInfo fileinfo(save_filename);
if (fileinfo.suffix().isEmpty()) {
save_filename.append(".jpg");
fileinfo.setFile(save_filename);
}
image.save(save_filename);
if (!QImageWriter::supportedImageFormats().contains(fileinfo.completeSuffix())) {
save_filename = Utilities::PathWithoutFilenameExtension(save_filename) + ".jpg";
fileinfo.setFile(save_filename);
}
if (result.is_jpeg() && fileinfo.completeSuffix().toLower() == "jpg") {
QFile file(save_filename);
if (file.open(QIODevice::WriteOnly)) {
file.write(result.image_data);
file.close();
}
}
else {
result.image.save(save_filename);
}
}
@@ -203,67 +287,138 @@ QString AlbumCoverChoiceController::GetInitialPathForFileDialog(const Song &song
QUrl AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
if (!cover_from_url_dialog_) { cover_from_url_dialog_ = new CoverFromURLDialog(this); }
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
QImage image = cover_from_url_dialog_->Exec();
AlbumCoverImageResult result = LoadImageFromURL();
if (image.isNull()) {
if (result.image.isNull()) {
return QUrl();
}
else {
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
if (cover_url.isEmpty()) return QUrl();
SaveCoverToSong(song, cover_url);
return cover_url;
return SaveCoverAutomatic(song, result);
}
}
AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromURL() {
if (!cover_from_url_dialog_) { cover_from_url_dialog_ = new CoverFromURLDialog(this); }
return cover_from_url_dialog_->Exec();
}
QUrl AlbumCoverChoiceController::SearchForCover(Song *song) {
QString album = song->effective_album();
album.remove(Song::kAlbumRemoveDisc);
album.remove(Song::kAlbumRemoveMisc);
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
// Get something sensible to stick in the search box
QImage image = cover_searcher_->Exec(song->effective_albumartist(), album);
if (image.isNull()) {
return QUrl();
AlbumCoverImageResult result = SearchForImage(song);
if (result.is_valid()) {
return SaveCoverAutomatic(song, result);
}
else {
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
if (cover_url.isEmpty()) return QUrl();
SaveCoverToSong(song, cover_url);
return cover_url;
return QUrl();
}
}
AlbumCoverImageResult AlbumCoverChoiceController::SearchForImage(Song *song) {
if (!song->url().isLocalFile()) return AlbumCoverImageResult();
QString album = song->effective_album();
album = album.remove(Song::kAlbumRemoveDisc).remove(Song::kAlbumRemoveMisc);
// Get something sensible to stick in the search box
return cover_searcher_->Exec(song->effective_albumartist(), album);
}
QUrl AlbumCoverChoiceController::UnsetCover(Song *song) {
QUrl cover_url(QUrl::fromLocalFile(Song::kManuallyUnsetCover));
SaveCoverToSong(song, cover_url);
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
QUrl cover_url = QUrl::fromLocalFile(Song::kManuallyUnsetCover);
SaveArtManualToSong(song, cover_url);
return cover_url;
}
void AlbumCoverChoiceController::ShowCover(const Song &song) {
void AlbumCoverChoiceController::ClearCover(Song *song) {
QPixmap pixmap = AlbumCoverLoader::TryLoadPixmap(song.art_automatic(), song.art_manual(), song.url());
if (pixmap.isNull()) return;
ShowCover(song, pixmap);
if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
song->clear_art_manual();
SaveArtManualToSong(song, QUrl());
}
bool AlbumCoverChoiceController::DeleteCover(Song *song) {
if (!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();
}
bool success = true;
if (!art_automatic.isEmpty()) {
if (QFile::exists(art_automatic)) {
if (QFile::remove(art_automatic)) {
song->clear_art_automatic();
if (art_automatic == art_manual) song->clear_art_manual();
}
else success = false;
}
else song->clear_art_automatic();
}
else song->clear_art_automatic();
if (!art_manual.isEmpty()) {
if (QFile::exists(art_manual)) {
if (QFile::remove(art_manual)) {
song->clear_art_manual();
if (art_automatic == art_manual) song->clear_art_automatic();
}
else success = false;
}
else song->clear_art_manual();
}
else song->clear_art_manual();
if (success) UnsetCover(song);
return success;
}
void AlbumCoverChoiceController::ShowCover(const Song &song, const QImage &image) {
if (song.art_manual().isLocalFile() || song.art_automatic().isLocalFile()) {
QPixmap pixmap = AlbumCoverLoader::TryLoadPixmap(song.art_automatic(), song.art_manual(), song.url());
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()) ShowCover(song, pixmap);
}
}
else {
QPixmap pixmap = QPixmap::fromImage(image);
if (!pixmap.isNull()) ShowCover(song, pixmap);
}
else if (!image.isNull()) ShowCover(song, QPixmap::fromImage(image));
}
@@ -326,7 +481,7 @@ qint64 AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
}
void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics) {
Q_UNUSED(statistics);
@@ -335,46 +490,22 @@ void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl
song = cover_fetching_tasks_.take(id);
}
if (!image.isNull()) {
QUrl new_cover_url = SaveCoverToFileAutomatic(&song, cover_url, image, false);
if (!new_cover_url.isEmpty()) SaveCoverToSong(&song, new_cover_url);
if (result.is_valid()) {
SaveCoverAutomatic(&song, result);
}
emit AutomaticCoverSearchDone();
}
void AlbumCoverChoiceController::SaveCoverToSong(Song *song, const QUrl &cover_url) {
void AlbumCoverChoiceController::SaveArtAutomaticToSong(Song *song, const QUrl &art_automatic) {
if (!song->is_valid()) return;
song->set_art_manual(cover_url);
if (song->id() != -1) { // Update the backends.
switch (song->source()) {
case Song::Source_Collection:
app_->collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
break;
case Song::Source_LocalFile:
case Song::Source_CDDA:
case Song::Source_Device:
case Song::Source_Stream:
case Song::Source_Unknown:
break;
case Song::Source_Tidal:
case Song::Source_Qobuz:
case Song::Source_Subsonic:
InternetService *service = app_->internet_services()->ServiceBySource(song->source());
if (!service) break;
if (service->artists_collection_backend())
service->artists_collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
if (service->albums_collection_backend())
service->albums_collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
if (service->songs_collection_backend())
service->songs_collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover_url);
break;
}
song->set_art_automatic(art_automatic);
if (song->source() == Song::Source_Collection) {
app_->collection_backend()->UpdateAutomaticAlbumArtAsync(song->effective_albumartist(), song->album(), art_automatic);
}
if (*song == app_->current_albumcover_loader()->last_song()) {
@@ -383,27 +514,168 @@ void AlbumCoverChoiceController::SaveCoverToSong(Song *song, const QUrl &cover_u
}
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const QUrl &cover_url, const QImage &image, const bool overwrite) {
void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art_manual, const bool clear_art_automatic) {
return SaveCoverToFileAutomatic(song->source(), song->effective_albumartist(), song->effective_album(), song->album_id(), song->url().adjusted(QUrl::RemoveFilename).path(), cover_url, image, overwrite);
if (!song->is_valid()) return;
song->set_art_manual(art_manual);
if (clear_art_automatic) song->clear_art_automatic();
// Update the backends.
switch (song->source()) {
case Song::Source_Collection:
app_->collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
break;
case Song::Source_LocalFile:
case Song::Source_CDDA:
case Song::Source_Device:
case Song::Source_Stream:
case Song::Source_Unknown:
break;
case Song::Source_Tidal:
case Song::Source_Qobuz:
case Song::Source_Subsonic:
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);
}
if (service->albums_collection_backend()) {
service->albums_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
}
if (service->songs_collection_backend()) {
service->songs_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
}
break;
}
if (*song == app_->current_albumcover_loader()->last_song()) {
app_->current_albumcover_loader()->LoadAlbumCover(*song);
}
}
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QImage &image, const bool overwrite) {
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const AlbumCoverImageResult &result, const bool force_overwrite) {
QString filepath = app_->album_cover_loader()->CoverFilePath(source, artist, album, album_id, album_dir, cover_url, "jpg");
return SaveCoverToFileAutomatic(song->source(),
song->effective_albumartist(),
song->effective_album(),
song->album_id(),
song->url().adjusted(QUrl::RemoveFilename).path(),
result,
force_overwrite);
}
QUrl AlbumCoverChoiceController::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) {
QString filepath = app_->album_cover_loader()->CoverFilePath(source, artist, album, album_id, album_dir, result.cover_url, "jpg");
if (filepath.isEmpty()) return QUrl();
QUrl new_cover_url(QUrl::fromLocalFile(filepath));
// Don't overwrite when saving in album dir if the filename is set to pattern unless the "overwrite" is set.
if (source == Song::Source_Collection && QFile::exists(filepath) && !cover_overwrite_ && !overwrite && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern) {
return new_cover_url;
QFile file(filepath);
// Don't overwrite when saving in album dir if the filename is set to pattern unless "force_overwrite" is set.
if (source == Song::Source_Collection && !cover_overwrite_ && !force_overwrite && get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType_Album && save_cover_filename_ == CollectionSettingsPage::SaveCoverFilename_Pattern && file.exists()) {
while (file.exists()) {
QFileInfo fileinfo(file.fileName());
file.setFileName(fileinfo.path() + "/0" + fileinfo.fileName());
}
filepath = file.fileName();
}
if (!image.save(filepath, "JPG") && !QFile::exists(filepath)) return QUrl();
QUrl cover_url;
if (result.is_jpeg()) {
if (file.open(QIODevice::WriteOnly)) {
if (file.write(result.image_data)) cover_url = QUrl::fromLocalFile(filepath);
file.close();
}
}
else {
if (result.image.save(filepath, "JPG")) cover_url = QUrl::fromLocalFile(filepath);
}
return new_cover_url;
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(), QueryOptions());
#else
QFuture<SongList> future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, song.effective_albumartist(), song.effective_album(), QueryOptions());
#endif
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
watcher->setFuture(future);
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [=]() {
SongList songs = watcher->result();
watcher->deleteLater();
QList<QUrl> urls;
for (const Song &s : songs) urls << s.url();
if (result.is_jpeg()) {
qint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image_data);
QMutexLocker l(&mutex_cover_save_tasks_);
cover_save_tasks_.insert(id, song);
}
else {
qint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
QMutexLocker l(&mutex_cover_save_tasks_);
cover_save_tasks_.insert(id, song);
}
});
}
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);
}
}
}
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const QUrl &cover_url) {
SaveCoverEmbeddedAutomatic(song, cover_url.toLocalFile());
}
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const QString &cover_filename) {
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(), QueryOptions());
#else
QFuture<SongList> future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, song->effective_albumartist(), song->effective_album(), QueryOptions());
#endif
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
watcher->setFuture(future);
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [=]() {
SongList songs = watcher->result();
watcher->deleteLater();
QList<QUrl> urls;
for (const Song &s : songs) urls << s.url();
qint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, cover_filename);
QMutexLocker l(&mutex_cover_save_tasks_);
cover_save_tasks_.insert(id, song);
});
}
else {
app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), cover_filename);
}
}
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const QList<QUrl> urls, const QImage &image) {
app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, image);
}
@@ -436,7 +708,13 @@ QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
const QString suffix = QFileInfo(filename).suffix().toLower();
if (IsKnownImageExtension(suffix)) {
SaveCoverToSong(song, url);
if (get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType_Embedded && song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, filename);
return QUrl::fromLocalFile(Song::kEmbeddedCover);
}
else {
SaveArtManualToSong(song, url);
}
return url;
}
}
@@ -444,13 +722,43 @@ QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
if (e->mimeData()->hasImage()) {
QImage image = qvariant_cast<QImage>(e->mimeData()->imageData());
if (!image.isNull()) {
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
if (cover_url.isEmpty()) return QUrl();
SaveCoverToSong(song, cover_url);
return cover_url;
return SaveCoverAutomatic(song, AlbumCoverImageResult(image));
}
}
return QUrl();
}
QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCoverImageResult &result) {
QUrl cover_url;
switch(get_save_album_cover_type()) {
case CollectionSettingsPage::SaveCoverType_Embedded:{
if (song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, result);
cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
break;
}
}
// fallthrough
case CollectionSettingsPage::SaveCoverType_Cache:
case CollectionSettingsPage::SaveCoverType_Album:{
cover_url = SaveCoverToFileAutomatic(song, result);
if (!cover_url.isEmpty()) SaveArtManualToSong(song, cover_url);
break;
}
}
return cover_url;
}
void AlbumCoverChoiceController::SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success) {
if (!cover_save_tasks_.contains(id)) return;
Song song = cover_save_tasks_.take(id);
if (success) SaveArtAutomaticToSong(&song, QUrl::fromLocalFile(Song::kEmbeddedCover));
}

View File

@@ -27,18 +27,24 @@
#include <QtGlobal>
#include <QObject>
#include <QWidget>
#include <QPair>
#include <QSet>
#include <QList>
#include <QMap>
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <QImage>
#include <QMutex>
#include "core/song.h"
#include "settings/collectionsettingspage.h"
#include "albumcoverimageresult.h"
class QFileDialog;
class QAction;
class QActionGroup;
class QMenu;
class QDragEnterEvent;
class QDropEvent;
@@ -63,6 +69,9 @@ class AlbumCoverChoiceController : public QWidget {
void Init(Application *app);
void ReloadSettings();
CollectionSettingsPage::SaveCoverType get_save_album_cover_type() const { return (save_embedded_cover_override_ ? CollectionSettingsPage::SaveCoverType_Embedded : save_cover_type_); }
void set_save_embedded_cover_override(const bool value) { save_embedded_cover_override_ = value; }
// Getters for all QActions implemented by this controller.
QAction *cover_from_file_action() const { return cover_from_file_; }
@@ -70,6 +79,8 @@ class AlbumCoverChoiceController : public QWidget {
QAction *cover_from_url_action() const { return cover_from_url_; }
QAction *search_for_cover_action() const { return search_for_cover_; }
QAction *unset_cover_action() const { return unset_cover_; }
QAction *delete_cover_action() const { return delete_cover_; }
QAction *clear_cover_action() const { return clear_cover_; }
QAction *show_cover_action() const { return show_cover_; }
QAction *search_cover_auto_action() const { return search_cover_auto_; }
@@ -87,40 +98,54 @@ class AlbumCoverChoiceController : public QWidget {
// Lets the user choose a cover from disk. If no cover will be chosen or the chosen cover will not be a proper image, this returns an empty string.
// Otherwise, the path to the chosen cover will be returned.
AlbumCoverImageResult LoadImageFromFile(Song *song);
QUrl LoadCoverFromFile(Song *song);
// Shows a dialog that allows user to save the given image on disk.
// The image is supposed to be the cover of the given song's album.
void SaveCoverToFileManual(const Song &song, const QImage &image);
void SaveCoverToFileManual(const Song &song, const AlbumCoverImageResult &result);
// 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);
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);
AlbumCoverImageResult SearchForImage(Song *song);
// Returns a path which indicates that the cover has been unset manually.
QUrl UnsetCover(Song *song);
// Clears any album cover art associated with the song.
void ClearCover(Song *song);
// Physically deletes associated album covers from disk.
bool DeleteCover(Song *song);
// Shows the cover of given song in it's original size.
void ShowCover(const Song &song);
void ShowCover(const Song &song, const QImage &image);
void ShowCover(const Song &song, const QImage &image = QImage());
void ShowCover(const Song &song, const QPixmap &pixmap);
// Search for covers automatically
qint64 SearchCoverAutomatically(const Song &song);
// Saves the chosen cover as manual cover path of this song in collection.
void SaveCoverToSong(Song *song, const QUrl &cover_url);
void SaveArtAutomaticToSong(Song *song, const QUrl &art_automatic);
void SaveArtManualToSong(Song *song, const QUrl &art_manual, const bool clear_art_automatic = false);
// Saves the cover that the user picked through a drag and drop operation.
QUrl 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 SaveCoverToFileAutomatic(const Song *song, const QUrl &cover_url, const QImage &image, const bool overwrite = false);
QUrl SaveCoverToFileAutomatic(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QImage &image, const bool overwrite = false);
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);
static bool CanAcceptDrag(const QDragEnterEvent *e);
@@ -128,9 +153,11 @@ class AlbumCoverChoiceController : public QWidget {
void AutomaticCoverSearchDone();
private slots:
void AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics);
void AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics);
void SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success);
private:
QString GetInitialPathForFileDialog(const Song &song, const QString &filename);
static bool IsKnownImageExtension(const QString &suffix);
@@ -145,21 +172,27 @@ class AlbumCoverChoiceController : public QWidget {
QAction *cover_from_file_;
QAction *cover_to_file_;
QAction *separator_;
QAction *cover_from_url_;
QAction *search_for_cover_;
QAction *separator1_;
QAction *unset_cover_;
QAction *delete_cover_;
QAction *clear_cover_;
QAction *separator2_;
QAction *show_cover_;
QAction *search_cover_auto_;
QMap<quint64, Song> cover_fetching_tasks_;
QMap<qint64, Song> cover_save_tasks_;
QMutex mutex_cover_save_tasks_;
bool cover_album_dir_;
CollectionSettingsPage::SaveCover cover_filename_;
CollectionSettingsPage::SaveCoverType save_cover_type_;
CollectionSettingsPage::SaveCoverFilename save_cover_filename_;
QString cover_pattern_;
bool cover_overwrite_;
bool cover_lowercase_;
bool cover_replace_spaces_;
bool save_embedded_cover_override_;
};

View File

@@ -56,7 +56,7 @@ AlbumCoverFetcher::~AlbumCoverFetcher() {
}
quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, const QString &title, bool fetchall) {
quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, const QString &title, const bool batch) {
CoverSearchRequest request;
request.id = next_id_++;
@@ -66,7 +66,7 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString
request.album = request.album.remove(Song::kAlbumRemoveMisc);
request.title = title;
request.search = false;
request.fetchall = fetchall;
request.batch = batch;
AddRequest(request);
return request.id;
@@ -83,7 +83,7 @@ quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString
request.album = request.album.remove(Song::kAlbumRemoveMisc);
request.title = title;
request.search = true;
request.fetchall = false;
request.batch = false;
AddRequest(request);
return request.id;
@@ -135,7 +135,7 @@ void AlbumCoverFetcher::StartRequests() {
}
void AlbumCoverFetcher::SingleSearchFinished(const quint64 request_id, const CoverSearchResults results) {
void AlbumCoverFetcher::SingleSearchFinished(const quint64 request_id, const CoverProviderSearchResults &results) {
if (!active_requests_.contains(request_id)) return;
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
@@ -145,12 +145,12 @@ void AlbumCoverFetcher::SingleSearchFinished(const quint64 request_id, const Cov
}
void AlbumCoverFetcher::SingleCoverFetched(const quint64 request_id, const QUrl &cover_url, const QImage &image) {
void AlbumCoverFetcher::SingleCoverFetched(const quint64 request_id, const AlbumCoverImageResult &result) {
if (!active_requests_.contains(request_id)) return;
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
search->deleteLater();
emit AlbumCoverFetched(request_id, cover_url, image, search->statistics());
emit AlbumCoverFetched(request_id, result, search->statistics());
}

View File

@@ -31,11 +31,13 @@
#include <QList>
#include <QHash>
#include <QQueue>
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <QImage>
#include "coversearchstatistics.h"
#include "albumcoverimageresult.h"
class QTimer;
class NetworkAccessManager;
@@ -44,7 +46,7 @@ class AlbumCoverFetcherSearch;
// This class represents a single search-for-cover request. It identifies and describes the request.
struct CoverSearchRequest {
explicit CoverSearchRequest() : id(-1), search(false), fetchall(false) {}
explicit CoverSearchRequest() : id(-1), search(false), batch(false) {}
// An unique (for one AlbumCoverFetcher) request identifier
quint64 id;
@@ -57,13 +59,13 @@ struct CoverSearchRequest {
// Is this only a search request or should we also fetch the first cover that's found?
bool search;
// Is the request part of fetchall (fetching all missing covers)
bool fetchall;
// Is the request part of a batch (fetching all missing covers)
bool batch;
};
// This structure represents a single result of some album's cover search request.
struct CoverSearchResult {
explicit CoverSearchResult() : score_provider(0.0), score_match(0.0), score_quality(0.0), number(0) {}
struct CoverProviderSearchResult {
explicit CoverProviderSearchResult() : score_provider(0.0), score_match(0.0), score_quality(0.0), number(0) {}
// Used for grouping in the user interface.
QString provider;
@@ -94,11 +96,11 @@ struct CoverSearchResult {
float score() const { return score_provider + score_match + score_quality; }
};
Q_DECLARE_METATYPE(CoverSearchResult)
Q_DECLARE_METATYPE(CoverProviderSearchResult)
// This is a complete result of a single search request (a list of results, each describing one image, actually).
typedef QList<CoverSearchResult> CoverSearchResults;
Q_DECLARE_METATYPE(QList<CoverSearchResult>)
typedef QList<CoverProviderSearchResult> CoverProviderSearchResults;
Q_DECLARE_METATYPE(QList<CoverProviderSearchResult>)
// This class searches for album covers for a given query or artist/album and returns URLs. It's NOT thread-safe.
class AlbumCoverFetcher : public QObject {
@@ -111,17 +113,17 @@ class AlbumCoverFetcher : public QObject {
static const int kMaxConcurrentRequests;
quint64 SearchForCovers(const QString &artist, const QString &album, const QString &title = QString());
quint64 FetchAlbumCover(const QString &artist, const QString &album, const QString &title, const bool fetchall);
quint64 FetchAlbumCover(const QString &artist, const QString &album, const QString &title, const bool batch);
void Clear();
signals:
void AlbumCoverFetched(quint64 request_id, QUrl cover_url, QImage cover, CoverSearchStatistics statistics);
void SearchFinished(quint64 request_id, CoverSearchResults results, CoverSearchStatistics statistics);
void AlbumCoverFetched(quint64 request_id, AlbumCoverImageResult result, CoverSearchStatistics statistics);
void SearchFinished(quint64 request_id, CoverProviderSearchResults results, CoverSearchStatistics statistics);
private slots:
void SingleSearchFinished(const quint64, const CoverSearchResults results);
void SingleCoverFetched(const quint64, const QUrl &cover_url, const QImage &image);
void SingleSearchFinished(const quint64, const CoverProviderSearchResults &results);
void SingleCoverFetched(const quint64, const AlbumCoverImageResult &result);
void StartRequests();
private:

View File

@@ -40,12 +40,14 @@
#include "core/logging.h"
#include "core/utilities.h"
#include "core/imageutils.h"
#include "core/networkaccessmanager.h"
#include "core/networktimeouts.h"
#include "albumcoverfetcher.h"
#include "albumcoverfetchersearch.h"
#include "coverprovider.h"
#include "coverproviders.h"
#include "albumcoverimageresult.h"
const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 20000;
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 6000;
@@ -99,8 +101,8 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
continue;
}
// Skip provider if it does not have fetchall set and we are doing fetchall - "Fetch Missing Covers".
if (!provider->fetchall() && request_.fetchall) {
// Skip provider if it does not have batch set and we are doing a batch - "Fetch Missing Covers".
if (!provider->batch() && request_.batch) {
continue;
}
@@ -109,7 +111,7 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
continue;
}
QObject::connect(provider, &CoverProvider::SearchResults, this, QOverload<const int, const CoverSearchResults&>::of(&AlbumCoverFetcherSearch::ProviderSearchResults));
QObject::connect(provider, &CoverProvider::SearchResults, this, QOverload<const int, const CoverProviderSearchResults&>::of(&AlbumCoverFetcherSearch::ProviderSearchResults));
QObject::connect(provider, &CoverProvider::SearchFinished, this, &AlbumCoverFetcherSearch::ProviderSearchFinished);
const int id = cover_providers->NextId();
const bool success = provider->StartSearch(request_.artist, request_.album, request_.title, id);
@@ -127,7 +129,7 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
}
void AlbumCoverFetcherSearch::ProviderSearchResults(const int id, const CoverSearchResults &results) {
void AlbumCoverFetcherSearch::ProviderSearchResults(const int id, const CoverProviderSearchResults &results) {
if (!pending_requests_.contains(id)) return;
CoverProvider *provider = pending_requests_[id];
@@ -135,9 +137,9 @@ void AlbumCoverFetcherSearch::ProviderSearchResults(const int id, const CoverSea
}
void AlbumCoverFetcherSearch::ProviderSearchResults(CoverProvider *provider, const CoverSearchResults &results) {
void AlbumCoverFetcherSearch::ProviderSearchResults(CoverProvider *provider, const CoverProviderSearchResults &results) {
CoverSearchResults results_copy(results);
CoverProviderSearchResults results_copy(results);
for (int i = 0 ; i < results_copy.count() ; ++i) {
results_copy[i].provider = provider->name();
@@ -225,7 +227,7 @@ void AlbumCoverFetcherSearch::ProviderSearchResults(CoverProvider *provider, con
}
void AlbumCoverFetcherSearch::ProviderSearchFinished(const int id, const CoverSearchResults &results) {
void AlbumCoverFetcherSearch::ProviderSearchFinished(const int id, const CoverProviderSearchResults &results) {
if (!pending_requests_.contains(id)) return;
@@ -256,7 +258,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
// No results?
if (results_.isEmpty()) {
statistics_.missing_images_++;
emit AlbumCoverFetched(request_.id, QUrl(), QImage());
emit AlbumCoverFetched(request_.id, AlbumCoverImageResult());
return;
}
@@ -264,7 +266,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
// We'll sort the list of results by current score, then load the first 3 images from each category and use some heuristics for additional score.
// If no images are good enough we'll keep loading more images until we find one that is or we run out of results.
std::stable_sort(results_.begin(), results_.end(), CoverSearchResultCompareScore);
std::stable_sort(results_.begin(), results_.end(), CoverProviderSearchResultCompareScore);
FetchMoreImages();
@@ -275,7 +277,7 @@ void AlbumCoverFetcherSearch::FetchMoreImages() {
int i = 0;
while (!results_.isEmpty()) {
++i;
CoverSearchResult result = results_.takeFirst();
CoverProviderSearchResult result = results_.takeFirst();
qLog(Debug) << "Loading" << result.artist << result.album << result.image_url << "from" << result.provider << "with current score" << result.score();
@@ -309,13 +311,11 @@ void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(QNetworkReply *reply) {
reply->deleteLater();
if (!pending_image_loads_.contains(reply)) return;
CoverSearchResult result = pending_image_loads_.take(reply);
CoverProviderSearchResult result = pending_image_loads_.take(reply);
statistics_.bytes_transferred_ += reply->bytesAvailable();
if (cancel_requested_) {
return;
}
if (cancel_requested_) return;
if (reply->error() != QNetworkReply::NoError) {
qLog(Error) << "Error requesting" << reply->url() << reply->errorString();
@@ -325,15 +325,17 @@ void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(QNetworkReply *reply) {
}
else {
QString mimetype = reply->header(QNetworkRequest::ContentTypeHeader).toString();
if (Utilities::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) || Utilities::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
if (ImageUtils::SupportedImageMimeTypes().contains(mimetype, Qt::CaseInsensitive) || ImageUtils::SupportedImageFormats().contains(mimetype, Qt::CaseInsensitive)) {
QByteArray image_data = reply->readAll();
QString mime_type = Utilities::MimeTypeFromData(image_data);
QImage image;
if (image.loadFromData(reply->readAll())) {
if (result.image_size != QSize(0,0) && result.image_size != image.size()) {
if (image.loadFromData(image_data)) {
if (result.image_size != QSize(0, 0) && result.image_size != image.size()) {
qLog(Debug) << "API size for image" << result.image_size << "for" << reply->url() << "from" << result.provider << "did not match retrieved size" << image.size();
}
result.image_size = image.size();
result.score_quality = ScoreImage(image.size());
candidate_images_.insert(result.score(), CandidateImage(result, image));
candidate_images_.insert(result.score(), CandidateImage(result, AlbumCoverImageResult(result.image_url, mime_type, image_data, image)));
qLog(Debug) << reply->url() << "from" << result.provider << "scored" << result.score();
}
else {
@@ -380,26 +382,24 @@ float AlbumCoverFetcherSearch::ScoreImage(const QSize size) const {
void AlbumCoverFetcherSearch::SendBestImage() {
QUrl cover_url;
QImage image;
AlbumCoverImageResult result;
if (!candidate_images_.isEmpty()) {
const CandidateImage best_image = candidate_images_.values().back();
cover_url = best_image.first.image_url;
image = best_image.second;
result = best_image.album_cover;
qLog(Info) << "Using" << best_image.first.image_url << "from" << best_image.first.provider << "with score" << best_image.first.score();
qLog(Info) << "Using" << best_image.result.image_url << "from" << best_image.result.provider << "with score" << best_image.result.score();
statistics_.chosen_images_by_provider_[best_image.first.provider]++;
statistics_.chosen_images_by_provider_[best_image.result.provider]++;
statistics_.chosen_images_++;
statistics_.chosen_width_ += image.width();
statistics_.chosen_height_ += image.height();
statistics_.chosen_width_ += result.image.width();
statistics_.chosen_height_ += result.image.height();
}
else {
statistics_.missing_images_++;
}
emit AlbumCoverFetched(request_.id, cover_url, image);
emit AlbumCoverFetched(request_.id, result);
}
@@ -425,10 +425,10 @@ bool AlbumCoverFetcherSearch::ProviderCompareOrder(CoverProvider *a, CoverProvid
return a->order() < b->order();
}
bool AlbumCoverFetcherSearch::CoverSearchResultCompareScore(const CoverSearchResult &a, const CoverSearchResult &b) {
bool AlbumCoverFetcherSearch::CoverProviderSearchResultCompareScore(const CoverProviderSearchResult &a, const CoverProviderSearchResult &b) {
return a.score() > b.score();
}
bool AlbumCoverFetcherSearch::CoverSearchResultCompareNumber(const CoverSearchResult &a, const CoverSearchResult &b) {
bool AlbumCoverFetcherSearch::CoverProviderSearchResultCompareNumber(const CoverProviderSearchResult &a, const CoverProviderSearchResult &b) {
return a.number < b.number;
}

View File

@@ -29,12 +29,14 @@
#include <QPair>
#include <QMap>
#include <QMultiMap>
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <QImage>
#include "albumcoverfetcher.h"
#include "coversearchstatistics.h"
#include "albumcoverimageresult.h"
class QNetworkReply;
class CoverProvider;
@@ -59,23 +61,23 @@ class AlbumCoverFetcherSearch : public QObject {
CoverSearchStatistics statistics() const { return statistics_; }
static bool CoverSearchResultCompareNumber(const CoverSearchResult &a, const CoverSearchResult &b);
static bool CoverProviderSearchResultCompareNumber(const CoverProviderSearchResult &a, const CoverProviderSearchResult &b);
signals:
// It's the end of search (when there was no fetch-me-a-cover request).
void SearchFinished(quint64, CoverSearchResults results);
void SearchFinished(quint64, CoverProviderSearchResults results);
// It's the end of search and we've fetched a cover.
void AlbumCoverFetched(const quint64, const QUrl &cover_url, const QImage &cover);
void AlbumCoverFetched(const quint64, AlbumCoverImageResult result);
private slots:
void ProviderSearchResults(const int id, const CoverSearchResults &results);
void ProviderSearchFinished(const int id, const CoverSearchResults &results);
void ProviderSearchResults(const int id, const CoverProviderSearchResults &results);
void ProviderSearchFinished(const int id, const CoverProviderSearchResults &results);
void ProviderCoverFetchFinished(QNetworkReply *reply);
void TerminateSearch();
private:
void ProviderSearchResults(CoverProvider *provider, const CoverSearchResults &results);
void ProviderSearchResults(CoverProvider *provider, const CoverProviderSearchResults &results);
void AllProvidersFinished();
void FetchMoreImages();
@@ -83,7 +85,7 @@ class AlbumCoverFetcherSearch : public QObject {
void SendBestImage();
static bool ProviderCompareOrder(CoverProvider *a, CoverProvider *b);
static bool CoverSearchResultCompareScore(const CoverSearchResult &a, const CoverSearchResult &b);
static bool CoverProviderSearchResultCompareScore(const CoverProviderSearchResult &a, const CoverProviderSearchResult &b);
private:
static const int kSearchTimeoutMs;
@@ -97,14 +99,18 @@ class AlbumCoverFetcherSearch : public QObject {
CoverSearchRequest request_;
// Complete results (from all of the available providers).
CoverSearchResults results_;
CoverProviderSearchResults results_;
QMap<int, CoverProvider*> pending_requests_;
QMap<QNetworkReply*, CoverSearchResult> pending_image_loads_;
QMap<QNetworkReply*, CoverProviderSearchResult> pending_image_loads_;
NetworkTimeouts* image_load_timeout_;
// QMap is sorted by key (score). Values are (result, image)
typedef QPair<CoverSearchResult, QImage> CandidateImage;
// QMap is sorted by key (score).
struct CandidateImage {
CandidateImage(const CoverProviderSearchResult &_result, const AlbumCoverImageResult &_album_cover) : result(_result), album_cover(_album_cover) {}
CoverProviderSearchResult result;
AlbumCoverImageResult album_cover;
};
QMultiMap<float, CandidateImage> candidate_images_;
NetworkAccessManager *network_;

View File

@@ -0,0 +1,53 @@
/*
* Strawberry Music Player
* Copyright 2021, 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/>.
*
*/
#ifndef ALBUMCOVERIMAGERESULT_H
#define ALBUMCOVERIMAGERESULT_H
#include "config.h"
#include <QMetaType>
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <QImage>
struct AlbumCoverImageResult {
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), image(_image) {}
explicit AlbumCoverImageResult(const QImage &_image) : image(_image) {}
QUrl cover_url;
QString mime_type;
QByteArray image_data;
QImage image;
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)
#endif // ALBUMCOVERIMAGERESULT_H

View File

@@ -27,10 +27,12 @@
#include <QDir>
#include <QThread>
#include <QMutex>
#include <QBuffer>
#include <QSet>
#include <QList>
#include <QQueue>
#include <QVariant>
#include <QByteArray>
#include <QString>
#include <QRegularExpression>
#include <QUrl>
@@ -40,25 +42,29 @@
#include <QSize>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QMimeDatabase>
#include <QSettings>
#include "core/networkaccessmanager.h"
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "core/utilities.h"
#include "core/imageutils.h"
#include "settings/collectionsettingspage.h"
#include "organize/organizeformat.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "albumcoverimageresult.h"
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
: QObject(parent),
stop_requested_(false),
next_id_(1),
load_image_async_id_(1),
save_image_async_id_(1),
network_(new NetworkAccessManager(this)),
cover_album_dir_(false),
cover_filename_(CollectionSettingsPage::SaveCover_Hash),
save_cover_type_(CollectionSettingsPage::SaveCoverType_Cache),
save_cover_filename_(CollectionSettingsPage::SaveCoverFilename_Hash),
cover_overwrite_(false),
cover_lowercase_(true),
cover_replace_spaces_(true),
@@ -89,8 +95,8 @@ void AlbumCoverLoader::ReloadSettings() {
QSettings s;
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
cover_album_dir_ = s.value("cover_album_dir", false).toBool();
cover_filename_ = CollectionSettingsPage::SaveCover(s.value("cover_filename", CollectionSettingsPage::SaveCover_Hash).toInt());
save_cover_type_ = CollectionSettingsPage::SaveCoverType(s.value("save_cover_type", CollectionSettingsPage::SaveCoverType_Cache).toInt());
save_cover_filename_ = CollectionSettingsPage::SaveCoverFilename(s.value("save_cover_filename", CollectionSettingsPage::SaveCoverFilename_Hash).toInt());
cover_pattern_ = s.value("cover_pattern", "%albumartist-%album").toString();
cover_overwrite_ = s.value("cover_overwrite", false).toBool();
cover_lowercase_ = s.value("cover_lowercase", false).toBool();
@@ -106,10 +112,10 @@ QString AlbumCoverLoader::AlbumCoverFilename(QString artist, QString album, cons
QString filename = artist + "-" + album;
filename = Utilities::UnicodeToAscii(filename.toLower());
filename = filename.replace(' ', '-');
filename = filename.replace("--", "-");
filename = filename.remove(OrganizeFormat::kInvalidFatCharacters);
filename = filename.simplified();
filename = filename.replace(' ', '-')
.replace("--", "-")
.remove(OrganizeFormat::kInvalidFatCharacters)
.simplified();
if (!extension.isEmpty()) {
filename.append('.');
@@ -129,7 +135,7 @@ QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString
album.remove(Song::kAlbumRemoveDisc);
QString path;
if (source == Song::Source_Collection && cover_album_dir_ && !album_dir.isEmpty()) {
if (source == Song::Source_Collection && save_cover_type_ == CollectionSettingsPage::SaveCoverType_Album && !album_dir.isEmpty()) {
path = album_dir;
}
else {
@@ -147,7 +153,10 @@ QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString
}
QString filename;
if (source == Song::Source_Collection && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) {
if (source == Song::Source_Collection &&
save_cover_type_ == CollectionSettingsPage::SaveCoverType_Album &&
save_cover_filename_ == CollectionSettingsPage::SaveCoverFilename_Pattern &&
!cover_pattern_.isEmpty()) {
filename = CoverFilenameFromVariable(artist, album);
filename.remove(OrganizeFormat::kInvalidFatCharacters);
if (cover_lowercase_) filename = filename.toLower();
@@ -157,7 +166,8 @@ QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString
filename.append(extension);
}
}
else {
if (filename.isEmpty()) {
filename = CoverFilenameFromSource(source, cover_url, artist, album, album_id, extension);
}
@@ -220,7 +230,7 @@ QString AlbumCoverLoader::CoverFilenameFromVariable(const QString &artist, const
void AlbumCoverLoader::CancelTask(const quint64 id) {
QMutexLocker l(&mutex_);
QMutexLocker l(&mutex_load_image_async_);
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
if (it->id == id) {
tasks_.erase(it);
@@ -232,7 +242,7 @@ void AlbumCoverLoader::CancelTask(const quint64 id) {
void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
QMutexLocker l(&mutex_);
QMutexLocker l(&mutex_load_image_async_);
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end();) {
if (ids.contains(it->id)) {
it = tasks_.erase(it);
@@ -244,26 +254,58 @@ void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
}
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions& options, const Song &song) {
return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url(), song, song.image());
}
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url, const Song song, const QImage &embedded_image) {
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song) {
Task task;
task.options = options;
task.song = song;
task.song_url = song_url;
task.art_manual = art_manual;
task.art_automatic = art_automatic;
task.art_updated = false;
task.embedded_image = embedded_image;
task.type = AlbumCoverLoaderResult::Type_None;
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);
Task task;
task.options = options;
task.song = song;
task.state = State_Manual;
return EnqueueTask(task);
}
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover) {
Task task;
task.options = options;
task.album_cover = album_cover;
return EnqueueTask(task);
}
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image) {
Task task;
task.options = options;
task.album_cover.image = image;
return EnqueueTask(task);
}
quint64 AlbumCoverLoader::EnqueueTask(Task &task) {
{
QMutexLocker l(&mutex_);
task.id = next_id_++;
QMutexLocker l(&mutex_load_image_async_);
task.id = load_image_async_id_++;
tasks_.enqueue(task);
}
@@ -279,7 +321,7 @@ void AlbumCoverLoader::ProcessTasks() {
// Get the next task
Task task;
{
QMutexLocker l(&mutex_);
QMutexLocker l(&mutex_load_image_async_);
if (tasks_.isEmpty()) return;
task = tasks_.dequeue();
}
@@ -298,8 +340,16 @@ void AlbumCoverLoader::ProcessTask(Task *task) {
}
if (result.loaded_success) {
QPair<QImage, QImage> images = ScaleAndPad(task->options, result.image);
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(result.type, result.cover_url, result.image, images.first, images.second, task->art_updated));
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;
}
@@ -316,30 +366,33 @@ void AlbumCoverLoader::NextState(Task *task) {
}
else {
// Give up
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(AlbumCoverLoaderResult::Type_None, QUrl(), task->options.default_output_image_, task->options.default_output_image_, task->options.default_thumbnail_image_, task->art_updated));
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));
}
}
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
// An image embedded in the song itself takes priority
if (!task->embedded_image.isNull()) {
QPair<QImage, QImage> images = ScaleAndPad(task->options, task->embedded_image);
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, QUrl(), images.first);
// Only scale and pad.
if (task->album_cover.is_valid()) {
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, task->album_cover);
}
// Use cached album cover if possible.
if (task->state == State_Manual &&
!task->song.art_manual_is_valid() &&
task->art_manual.isEmpty() &&
task->song.source() != Song::Source_Collection &&
!task->options.scale_output_image_ &&
!task->options.pad_output_image_) {
task->song.InitArtManual();
if (task->song.art_manual_is_valid() && task->art_manual != task->song.art_manual()) {
task->art_manual = task->song.art_manual();
task->art_updated = true;
// For local files and streams initialize art if found.
if ((task->song.source() == Song::Source_LocalFile || task->song.source() == Song::Source_Stream) && !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;
}
}
@@ -349,32 +402,64 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
case State_None:
case State_Automatic:
type = AlbumCoverLoaderResult::Type_Automatic;
cover_url = task->art_automatic;
cover_url = task->song.art_automatic();
break;
case State_Manual:
type = AlbumCoverLoaderResult::Type_Manual;
cover_url = task->art_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, cover_url, task->options.default_output_image_);
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()) {
const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile());
if (!taglib_image.isNull()) {
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, cover_url, ScaleAndPad(task->options, taglib_image).first);
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 (task->options.get_image_ && image.loadFromData(image_data)) {
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, AlbumCoverImageResult(cover_url, QString(), image_data, image));
}
else {
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, AlbumCoverImageResult(cover_url, QString(), image_data, image));
}
}
}
else if (cover_url.isLocalFile()) {
QImage image(cover_url.toLocalFile());
return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : 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.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
}
}
else {
qLog(Error) << "Failed to open album cover file" << cover_url;
}
}
}
else if (cover_url.scheme().isEmpty()) { // Assume a local file with no scheme.
QImage image(cover_url.path());
return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : image);
QFile file(cover_url.path());
if (file.exists() && 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.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
}
}
}
else if (network_->supportedSchemes().contains(cover_url.scheme())) { // Remote URL
qLog(Debug) << "Loading remote cover from" << cover_url;
@@ -388,11 +473,11 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
QObject::connect(reply, &QNetworkReply::finished, [this, reply, cover_url]() { RemoteFetchFinished(reply, cover_url); });
remote_tasks_.insert(reply, *task);
return TryLoadResult(true, false, type, cover_url, QImage());
return TryLoadResult(true, false, type, AlbumCoverImageResult(cover_url));
}
}
return TryLoadResult(false, false, AlbumCoverLoaderResult::Type_None, cover_url, task->options.default_output_image_);
return TryLoadResult(false, false, AlbumCoverLoaderResult::Type_None, AlbumCoverImageResult(cover_url, QString(), QByteArray(), task->options.default_output_image_));
}
@@ -425,14 +510,24 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cov
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.load(reply, nullptr)) {
QPair<QImage, QImage> images = ScaleAndPad(task.options, image);
emit AlbumCoverLoaded(task.id, AlbumCoverLoaderResult(task.type, cover_url, image, images.first, images.second, task.art_updated));
return;
if (task.options.get_image_data_) {
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));
return;
}
else {
qLog(Error) << "Unable to load album cover image" << cover_url;
}
}
else {
qLog(Error) << "Unable to load album cover image" << cover_url;
emit AlbumCoverLoaded(task.id, AlbumCoverLoaderResult(true, task.type, AlbumCoverImageResult(cover_url, mime_type, image_data, QImage()), QImage(), QImage(), task.art_updated));
}
}
else {
@@ -443,79 +538,158 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cov
}
QPair<QImage, QImage> AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString song_filename, const QString &cover_filename) {
if (image.isNull()) return qMakePair(image, image);
QMutexLocker l(&mutex_save_image_async_);
qint64 id = ++save_image_async_id_;
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QString, song_filename), Q_ARG(QString, cover_filename));
return id;
// Scale the image down
QImage image_scaled;
if (options.scale_output_image_) {
image_scaled = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString song_filename, const QImage &image) {
QMutexLocker l(&mutex_save_image_async_);
qint64 id = ++save_image_async_id_;
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QString, song_filename), Q_ARG(QImage, image));
return id;
}
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString song_filename, const QByteArray &image_data) {
QMutexLocker l(&mutex_save_image_async_);
qint64 id = ++save_image_async_id_;
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QString, song_filename), Q_ARG(QByteArray, image_data));
return id;
}
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QString &cover_filename) {
QMutexLocker l(&mutex_save_image_async_);
qint64 id = ++save_image_async_id_;
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QString, cover_filename));
return id;
}
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QImage &image) {
QMutexLocker l(&mutex_save_image_async_);
qint64 id = ++save_image_async_id_;
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QImage, image));
return id;
}
qint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QByteArray &image_data) {
QMutexLocker l(&mutex_save_image_async_);
qint64 id = ++save_image_async_id_;
metaObject()->invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(qint64, id), Q_ARG(QList<QUrl>, urls), Q_ARG(QByteArray, image_data));
return id;
}
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QString song_filename, const QByteArray &image_data) {
TagReaderReply *reply = TagReaderClient::Instance()->SaveEmbeddedArt(song_filename, image_data);
tagreader_save_embedded_art_requests_.insert(id, reply);
QObject::connect(reply, &TagReaderReply::Finished, this, [this, id, reply]() { SaveEmbeddedArtFinished(id, reply); }, Qt::QueuedConnection);
}
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 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 qint64 id, const QString song_filename, const QString &cover_filename) {
QFile file(cover_filename);
if (file.size() >= 209715200 || !file.open(QIODevice::ReadOnly)) { // Max 200 MB.
emit SaveEmbeddedCoverAsyncFinished(id, false);
return;
}
QByteArray image_data = file.readAll();
file.close();
SaveEmbeddedCover(id, song_filename, image_data);
}
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QImage &image) {
if (image.isNull()) {
for (const QUrl &url : urls) {
SaveEmbeddedCover(id, url.toLocalFile(), QByteArray());
}
return;
}
else {
image_scaled = image;
}
// Pad the image to height x height
if (options.pad_output_image_) {
QImage image_padded(options.desired_height_, options.desired_height_, QImage::Format_ARGB32);
image_padded.fill(0);
QPainter p(&image_padded);
p.drawImage((options.desired_height_ - image_scaled.width()) / 2, (options.desired_height_ - image_scaled.height()) / 2, image_scaled);
p.end();
image_scaled = image_padded;
}
// Create thumbnail
QImage image_thumbnail;
if (options.create_thumbnail_) {
if (options.pad_thumbnail_image_) {
image_thumbnail = image.scaled(options.thumbnail_size_, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QImage image_padded(options.thumbnail_size_, QImage::Format_ARGB32_Premultiplied);
image_padded.fill(0);
QPainter p(&image_padded);
p.drawImage((image_padded.width() - image_thumbnail.width()) / 2, (image_padded.height() - image_thumbnail.height()) / 2, image_thumbnail);
p.end();
image_thumbnail = image_padded;
}
else {
image_thumbnail = image.scaledToHeight(options.thumbnail_size_.height(), Qt::SmoothTransformation);
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();
}
}
return qMakePair(image_scaled, image_thumbnail);
emit SaveEmbeddedCoverAsyncFinished(id, false);
}
QPixmap AlbumCoverLoader::TryLoadPixmap(const QUrl &art_automatic, const QUrl &art_manual, const QUrl &url) {
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QString &cover_filename) {
QPixmap ret;
QFile file(cover_filename);
if (!art_manual.path().isEmpty()) {
if (art_manual.path() == Song::kManuallyUnsetCover) return ret;
else if (art_manual.isLocalFile()) {
ret.load(art_manual.toLocalFile());
}
else if (art_manual.scheme().isEmpty()) {
ret.load(art_manual.path());
}
}
if (ret.isNull() && !art_automatic.path().isEmpty()) {
if (art_automatic.path() == Song::kEmbeddedCover && !url.isEmpty() && url.isLocalFile()) {
ret = QPixmap::fromImage(TagReaderClient::Instance()->LoadEmbeddedArtBlocking(url.toLocalFile()));
}
else if (art_automatic.isLocalFile()) {
ret.load(art_automatic.toLocalFile());
}
else if (art_automatic.scheme().isEmpty()) {
ret.load(art_automatic.path());
}
if (file.size() >= 209715200 || !file.open(QIODevice::ReadOnly)) { // Max 200 MB.
emit SaveEmbeddedCoverAsyncFinished(id, false);
return;
}
return ret;
QByteArray image_data = file.readAll();
file.close();
SaveEmbeddedCover(id, urls, image_data);
}
void AlbumCoverLoader::SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QByteArray &image_data) {
for (const QUrl &url : urls) {
SaveEmbeddedCover(id, url.toLocalFile(), image_data);
}
}
void AlbumCoverLoader::SaveEmbeddedArtFinished(const qint64 id, TagReaderReply *reply) {
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());
}
metaObject()->invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
}

View File

@@ -30,15 +30,19 @@
#include <QPair>
#include <QSet>
#include <QMap>
#include <QMultiMap>
#include <QQueue>
#include <QByteArray>
#include <QString>
#include <QImage>
#include <QPixmap>
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "settings/collectionsettingspage.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "albumcoverimageresult.h"
class QThread;
class QNetworkReply;
@@ -70,23 +74,39 @@ class AlbumCoverLoader : public QObject {
QString CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QString &extension = QString());
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song);
virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url = QUrl(), const Song song = Song(), const QImage &embedded_image = QImage());
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url = QUrl(), const Song::Source song_source = Song::Source_Unknown);
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover);
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image);
void CancelTask(const quint64 id);
void CancelTasks(const QSet<quint64> &ids);
static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QUrl &url = QUrl());
static QPair<QImage, QImage> ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image);
qint64 SaveEmbeddedCoverAsync(const QString song_filename, const QString &cover_filename);
qint64 SaveEmbeddedCoverAsync(const QString song_filename, const QImage &image);
qint64 SaveEmbeddedCoverAsync(const QString song_filename, const QByteArray &image_data);
qint64 SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QString &cover_filename);
qint64 SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QImage &image);
qint64 SaveEmbeddedCoverAsync(const QList<QUrl> urls, const QByteArray &image_data);
signals:
void ExitFinished();
void AlbumCoverLoaded(quint64 id, AlbumCoverLoaderResult result);
void SaveEmbeddedCoverAsyncFinished(quint64 id, bool success);
protected slots:
void Exit();
void ProcessTasks();
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
void SaveEmbeddedCover(const qint64 id, const QString song_filename, const QString &cover_filename);
void SaveEmbeddedCover(const qint64 id, const QString song_filename, const QImage &image);
void SaveEmbeddedCover(const qint64 id, const QString song_filename, const QByteArray &image_data);
void SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QImage &image);
void SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QString &cover_filename);
void SaveEmbeddedCover(const qint64 id, const QList<QUrl> urls, const QByteArray &image_data);
void SaveEmbeddedArtFinished(const qint64 id, TagReaderReply *reply);
protected:
struct Task {
@@ -95,11 +115,8 @@ class AlbumCoverLoader : public QObject {
AlbumCoverLoaderOptions options;
quint64 id;
QUrl art_manual;
QUrl art_automatic;
QUrl song_url;
Song song;
QImage embedded_image;
AlbumCoverImageResult album_cover;
State state;
AlbumCoverLoaderResult::Type type;
bool art_updated;
@@ -107,33 +124,42 @@ class AlbumCoverLoader : public QObject {
};
struct TryLoadResult {
explicit TryLoadResult(const bool _started_async = false, const bool _loaded_success = false, const AlbumCoverLoaderResult::Type _type = AlbumCoverLoaderResult::Type_None, const QUrl &_cover_url = QUrl(), const QImage &_image = QImage()) : started_async(_started_async), loaded_success(_loaded_success), type(_type), cover_url(_cover_url), image(_image) {}
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;
AlbumCoverLoaderResult::Type type;
QUrl cover_url;
QImage image;
AlbumCoverImageResult album_cover;
};
quint64 EnqueueTask(Task &task);
void ProcessTask(Task *task);
void NextState(Task *task);
TryLoadResult TryLoadImage(Task *task);
bool stop_requested_;
QMutex mutex_;
QMutex mutex_load_image_async_;
QMutex mutex_save_image_async_;
QQueue<Task> tasks_;
QMap<QNetworkReply*, Task> remote_tasks_;
quint64 next_id_;
quint64 load_image_async_id_;
quint64 save_image_async_id_;
NetworkAccessManager *network_;
static const int kMaxRedirects = 3;
bool cover_album_dir_;
CollectionSettingsPage::SaveCover cover_filename_;
CollectionSettingsPage::SaveCoverType save_cover_type_;
CollectionSettingsPage::SaveCoverFilename save_cover_filename_;
QString cover_pattern_;
bool cover_overwrite_;
bool cover_lowercase_;
@@ -141,6 +167,8 @@ class AlbumCoverLoader : public QObject {
QThread *original_thread_;
QMultiMap<qint64, TagReaderReply*> tagreader_save_embedded_art_requests_;
};
#endif // ALBUMCOVERLOADER_H

View File

@@ -29,19 +29,25 @@
struct AlbumCoverLoaderOptions {
explicit AlbumCoverLoaderOptions()
: desired_height_(120),
: get_image_data_(true),
get_image_(true),
scale_output_image_(true),
pad_output_image_(true),
create_thumbnail_(false),
pad_thumbnail_image_(false) {}
pad_thumbnail_image_(false),
desired_height_(120),
thumbnail_size_(120, 120) {}
int desired_height_;
QSize thumbnail_size_;
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_;
};

View File

@@ -25,6 +25,8 @@
#include <QImage>
#include <QUrl>
#include "albumcoverimageresult.h"
struct AlbumCoverLoaderResult {
enum Type {
@@ -36,11 +38,22 @@ struct AlbumCoverLoaderResult {
Type_Remote,
};
explicit AlbumCoverLoaderResult(const Type _type = Type_None, const QUrl &_cover_url = QUrl(), const QImage &_image_original = QImage(), const QImage &_image_scaled = QImage(), const QImage &_image_thumbnail = QImage(), const bool _updated = false) : type(_type), cover_url(_cover_url), image_original(_image_original), image_scaled(_image_scaled), image_thumbnail(_image_thumbnail), updated(_updated) {}
explicit AlbumCoverLoaderResult(const bool _success = false,
const Type _type = Type_None,
const AlbumCoverImageResult &_album_cover = AlbumCoverImageResult(),
const QImage &_image_scaled = QImage(),
const QImage &_image_thumbnail = QImage(),
const bool _updated = false) :
success(_success),
type(_type),
album_cover(_album_cover),
image_scaled(_image_scaled),
image_thumbnail(_image_thumbnail),
updated(_updated) {}
bool success;
Type type;
QUrl cover_url;
QImage image_original;
AlbumCoverImageResult album_cover;
QImage image_scaled;
QImage image_thumbnail;
bool updated;

View File

@@ -28,6 +28,9 @@
#include <QObject>
#include <QMainWindow>
#include <QWidget>
#include <QtConcurrent>
#include <QFuture>
#include <QFutureWatcher>
#include <QScreen>
#include <QWindow>
#include <QItemSelectionModel>
@@ -64,12 +67,15 @@
#include "core/application.h"
#include "core/iconloader.h"
#include "core/utilities.h"
#include "core/imageutils.h"
#include "core/tagreaderclient.h"
#include "widgets/forcescrollperpixel.h"
#include "widgets/qsearchfield.h"
#include "collection/sqlrow.h"
#include "collection/collectionbackend.h"
#include "collection/collectionquery.h"
#include "playlist/songmimedata.h"
#include "settings/collectionsettingspage.h"
#include "coverproviders.h"
#include "albumcovermanager.h"
#include "albumcoversearcher.h"
@@ -83,6 +89,7 @@
#include "albumcovermanagerlist.h"
#include "coversearchstatistics.h"
#include "coversearchstatisticsdialog.h"
#include "albumcoverimageresult.h"
#include "ui_albumcovermanager.h"
@@ -93,6 +100,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
ui_(new Ui_CoverManager),
mainwindow_(mainwindow),
app_(app),
collection_backend_(collection_backend),
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
cover_fetcher_(new AlbumCoverFetcher(app_->cover_providers(), this)),
cover_searcher_(nullptr),
@@ -100,14 +108,13 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
cover_exporter_(new AlbumCoverExporter(this)),
artist_icon_(IconLoader::Load("folder-sound")),
all_artists_icon_(IconLoader::Load("library-music")),
no_cover_icon_(":/pictures/cdcase.png"),
no_cover_image_(GenerateNoCoverImage(no_cover_icon_)),
no_cover_item_icon_(QPixmap::fromImage(no_cover_image_)),
image_nocover_thumbnail_(ImageUtils::GenerateNoCoverImage(QSize(120, 120))),
icon_nocover_item_(QPixmap::fromImage(image_nocover_thumbnail_)),
context_menu_(new QMenu(this)),
progress_bar_(new QProgressBar(this)),
abort_progress_(new QPushButton(this)),
jobs_(0),
collection_backend_(collection_backend) {
all_artists_(nullptr) {
ui_->setupUi(this);
ui_->albums->set_cover_manager(this);
@@ -122,7 +129,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
album_cover_choice_controller_->Init(app_);
cover_searcher_ = new AlbumCoverSearcher(no_cover_item_icon_, app_, this);
cover_searcher_ = new AlbumCoverSearcher(icon_nocover_item_, app_, this);
cover_export_ = new AlbumCoverExport(this);
// Set up the status bar
@@ -139,8 +146,15 @@ 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;
cover_loader_options_.create_thumbnail_ = false;
EnableCoversButtons();
ReloadSettings();
}
AlbumCoverManager::~AlbumCoverManager() {
@@ -151,11 +165,10 @@ AlbumCoverManager::~AlbumCoverManager() {
}
void AlbumCoverManager::ReloadSettings() {
app_->album_cover_loader()->ReloadSettings();
}
CollectionBackend *AlbumCoverManager::backend() const {
return collection_backend_;
app_->album_cover_loader()->ReloadSettings();
album_cover_choice_controller_->ReloadSettings();
}
void AlbumCoverManager::Init() {
@@ -185,6 +198,8 @@ void AlbumCoverManager::Init() {
QObject::connect(album_cover_choice_controller_->cover_from_url_action(), &QAction::triggered, this, &AlbumCoverManager::LoadCoverFromURL);
QObject::connect(album_cover_choice_controller_->search_for_cover_action(), &QAction::triggered, this, &AlbumCoverManager::SearchForCover);
QObject::connect(album_cover_choice_controller_->unset_cover_action(), &QAction::triggered, this, &AlbumCoverManager::UnsetCover);
QObject::connect(album_cover_choice_controller_->clear_cover_action(), &QAction::triggered, this, &AlbumCoverManager::ClearCover);
QObject::connect(album_cover_choice_controller_->delete_cover_action(), &QAction::triggered, this, &AlbumCoverManager::DeleteCover);
QObject::connect(album_cover_choice_controller_->show_cover_action(), &QAction::triggered, this, &AlbumCoverManager::ShowCover);
QObject::connect(cover_exporter_, &AlbumCoverExporter::AlbumCoversExportUpdate, this, &AlbumCoverManager::UpdateExportStatus);
@@ -213,13 +228,19 @@ void AlbumCoverManager::Init() {
QSettings s;
s.beginGroup(kSettingsGroup);
restoreGeometry(s.value("geometry").toByteArray());
if (!ui_->splitter->restoreState(s.value("splitter_state").toByteArray())) {
if (s.contains("geometry")) {
restoreGeometry(s.value("geometry").toByteArray());
}
if (!s.contains("splitter_state") || (s.contains("splitter_state") && !ui_->splitter->restoreState(s.value("splitter_state").toByteArray()))) {
// Sensible default size for the artists view
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
}
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_);
@@ -227,10 +248,12 @@ void AlbumCoverManager::Init() {
}
void AlbumCoverManager::showEvent(QShowEvent *) {
void AlbumCoverManager::showEvent(QShowEvent *e) {
LoadGeometry();
Reset();
if (!e->spontaneous()) {
LoadGeometry();
Reset();
}
}
@@ -246,7 +269,7 @@ void AlbumCoverManager::closeEvent(QCloseEvent *e) {
}
}
SaveGeometry();
SaveSettings();
// Cancel any outstanding requests
CancelRequests();
@@ -287,12 +310,13 @@ void AlbumCoverManager::LoadGeometry() {
}
void AlbumCoverManager::SaveGeometry() {
void AlbumCoverManager::SaveSettings() {
QSettings s;
s.beginGroup(kSettingsGroup);
s.setValue("geometry", saveGeometry());
s.setValue("splitter_state", ui_->splitter->saveState());
s.setValue("save_cover_type", album_cover_choice_controller_->get_save_album_cover_type());
s.endGroup();
}
@@ -305,6 +329,8 @@ void AlbumCoverManager::CancelRequests() {
app_->album_cover_loader()->CancelTasks(QSet<quint64>::fromList(cover_loading_tasks_.keys()));
#endif
cover_loading_tasks_.clear();
cover_save_tasks_.clear();
cover_save_tasks2_.clear();
cover_exporter_->Cancel();
@@ -322,7 +348,7 @@ static bool CompareNocase(const QString &left, const QString &right) {
}
static bool CompareAlbumNameNocase(const CollectionBackend::Album &left, const CollectionBackend::Album &right) {
return CompareNocase(left.album_name, right.album_name);
return CompareNocase(left.album, right.album);
}
void AlbumCoverManager::Reset() {
@@ -330,8 +356,8 @@ void AlbumCoverManager::Reset() {
EnableCoversButtons();
ui_->artists->clear();
new QListWidgetItem(all_artists_icon_, tr("All artists"), ui_->artists, All_Artists);
new QListWidgetItem(artist_icon_, tr("Various artists"), ui_->artists, Various_Artists);
all_artists_ = new QListWidgetItem(all_artists_icon_, tr("All artists"), ui_->artists, All_Artists);
new AlbumItem(artist_icon_, tr("Various artists"), ui_->artists, Various_Artists);
QStringList artists(collection_backend_->GetAllArtistsWithAlbums());
std::stable_sort(artists.begin(), artists.end(), CompareNocase);
@@ -341,6 +367,10 @@ void AlbumCoverManager::Reset() {
new QListWidgetItem(artist_icon_, artist, ui_->artists, Specific_Artist);
}
if (ui_->artists->selectedItems().isEmpty()) {
ui_->artists->selectionModel()->setCurrentIndex(ui_->artists->indexFromItem(all_artists_), QItemSelectionModel::Clear);
}
}
void AlbumCoverManager::EnableCoversButtons() {
@@ -357,9 +387,6 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
if (!current) return;
QString artist;
if (current->type() == Specific_Artist) artist = current->text();
ui_->albums->clear();
context_menu_items_.clear();
CancelRequests();
@@ -377,32 +404,42 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
std::stable_sort(albums.begin(), albums.end(), CompareAlbumNameNocase);
for (const CollectionBackend::Album &info : albums) {
// Don't show songs without an album, obviously
if (info.album_name.isEmpty()) continue;
if (info.album.isEmpty()) continue;
QListWidgetItem *item = new QListWidgetItem(no_cover_item_icon_, info.album_name, ui_->albums);
item->setData(Role_ArtistName, info.artist);
item->setData(Role_AlbumArtistName, info.album_artist);
item->setData(Role_AlbumName, info.album_name);
item->setData(Role_FirstUrl, info.first_url);
item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter));
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
item->setToolTip(info.artist + " - " + info.album_name);
QString display_text;
QString effective_artist = EffectiveAlbumArtistName(*item);
if (!artist.isEmpty()) {
item->setToolTip(effective_artist + " - " + info.album_name);
if (current->type() == Specific_Artist) {
display_text = info.album;
}
else {
item->setToolTip(info.album_name);
display_text = info.album_artist + " - " + 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, 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;
if (info.album_artist.isEmpty()) {
item->setToolTip(info.album);
}
else {
item->setToolTip(info.album_artist + " - " + info.album);
}
if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) {
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.first_url);
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;
}
}
UpdateFilter();
@@ -413,11 +450,18 @@ void AlbumCoverManager::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoade
if (!cover_loading_tasks_.contains(id)) return;
QListWidgetItem *item = cover_loading_tasks_.take(id);
AlbumItem *item = cover_loading_tasks_.take(id);
if (result.image_scaled.isNull()) return;
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type_ManuallyUnset) {
item->setIcon(icon_nocover_item_);
}
else {
item->setIcon(QPixmap::fromImage(result.image_scaled));
}
//item->setData(Role_Image, result.image_original);
//item->setData(Role_ImageData, result.image_data);
item->setIcon(QPixmap::fromImage(result.image_scaled));
UpdateFilter();
}
@@ -440,14 +484,14 @@ void AlbumCoverManager::UpdateFilter() {
qint32 without_cover = 0;
for (int i = 0; i < ui_->albums->count(); ++i) {
QListWidgetItem *item = ui_->albums->item(i);
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i));
bool should_hide = ShouldHide(*item, filter, hide);
item->setHidden(should_hide);
if (!should_hide) {
total_count++;
++total_count;
if (!ItemHasCover(*item)) {
without_cover++;
++without_cover;
}
}
}
@@ -457,7 +501,7 @@ void AlbumCoverManager::UpdateFilter() {
}
bool AlbumCoverManager::ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const {
bool AlbumCoverManager::ShouldHide(const AlbumItem &item, const QString &filter, HideCovers hide) const {
bool has_cover = ItemHasCover(item);
if (hide == Hide_WithCovers && has_cover) {
@@ -474,9 +518,8 @@ bool AlbumCoverManager::ShouldHide(const QListWidgetItem &item, const QString &f
QStringList query = filter.split(' ');
for (const QString &s : query) {
bool in_text = item.text().contains(s, Qt::CaseInsensitive);
bool in_artist = item.data(Role_ArtistName).toString().contains(s, Qt::CaseInsensitive);
bool in_albumartist = item.data(Role_AlbumArtistName).toString().contains(s, Qt::CaseInsensitive);
if (!in_text && !in_artist && !in_albumartist) {
bool in_albumartist = item.data(Role_AlbumArtist).toString().contains(s, Qt::CaseInsensitive);
if (!in_text && !in_albumartist) {
return true;
}
}
@@ -488,11 +531,11 @@ bool AlbumCoverManager::ShouldHide(const QListWidgetItem &item, const QString &f
void AlbumCoverManager::FetchAlbumCovers() {
for (int i = 0; i < ui_->albums->count(); ++i) {
QListWidgetItem *item = ui_->albums->item(i);
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i));
if (item->isHidden()) continue;
if (ItemHasCover(*item)) continue;
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), true);
quint64 id = cover_fetcher_->FetchAlbumCover(item->data(Role_AlbumArtist).toString(), item->data(Role_Album).toString(), QString(), true);
cover_fetching_tasks_[id] = item;
jobs_++;
}
@@ -507,14 +550,13 @@ void AlbumCoverManager::FetchAlbumCovers() {
}
void AlbumCoverManager::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
void AlbumCoverManager::AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics) {
if (!cover_fetching_tasks_.contains(id))
return;
if (!cover_fetching_tasks_.contains(id)) return;
QListWidgetItem *item = cover_fetching_tasks_.take(id);
if (!image.isNull()) {
SaveAndSetCover(item, cover_url, image);
AlbumItem *item = cover_fetching_tasks_.take(id);
if (!result.image.isNull()) {
SaveAndSetCover(item, result);
}
if (cover_fetching_tasks_.isEmpty()) {
@@ -554,29 +596,34 @@ void AlbumCoverManager::UpdateStatusText() {
}
bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *event) {
bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *e) {
if (obj == ui_->albums && event->type() == QEvent::ContextMenu) {
if (obj == ui_->albums && e->type() == QEvent::ContextMenu) {
context_menu_items_ = ui_->albums->selectedItems();
if (context_menu_items_.isEmpty()) return false;
bool some_with_covers = false;
for (QListWidgetItem *item : context_menu_items_) {
if (ItemHasCover(*item)) some_with_covers = true;
AlbumItem *album_item = static_cast<AlbumItem*>(item);
if (ItemHasCover(*album_item)) some_with_covers = true;
}
album_cover_choice_controller_->show_cover_action()->setEnabled(some_with_covers && context_menu_items_.size() == 1);
album_cover_choice_controller_->cover_to_file_action()->setEnabled(some_with_covers);
album_cover_choice_controller_->cover_from_file_action()->setEnabled(context_menu_items_.size() == 1);
album_cover_choice_controller_->cover_from_url_action()->setEnabled(context_menu_items_.size() == 1);
album_cover_choice_controller_->show_cover_action()->setEnabled(some_with_covers && context_menu_items_.size() == 1);
album_cover_choice_controller_->unset_cover_action()->setEnabled(some_with_covers);
album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders());
album_cover_choice_controller_->unset_cover_action()->setEnabled(some_with_covers);
album_cover_choice_controller_->clear_cover_action()->setEnabled(some_with_covers);
album_cover_choice_controller_->delete_cover_action()->setEnabled(some_with_covers);
QContextMenuEvent *e = static_cast<QContextMenuEvent*>(event);
context_menu_->popup(e->globalPos());
QContextMenuEvent *context_menu_event = static_cast<QContextMenuEvent*>(e);
context_menu_->popup(context_menu_event->globalPos());
return true;
}
return QMainWindow::eventFilter(obj, event);
return QMainWindow::eventFilter(obj, e);
}
Song AlbumCoverManager::GetSingleSelectionAsSong() {
@@ -587,12 +634,12 @@ Song AlbumCoverManager::GetFirstSelectedAsSong() {
return context_menu_items_.isEmpty() ? Song() : ItemAsSong(context_menu_items_[0]);
}
Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) {
Song AlbumCoverManager::ItemAsSong(AlbumItem *item) {
Song result(Song::Source_Collection);
QString title = item->data(Role_AlbumName).toString();
QString artist_name = EffectiveAlbumArtistName(*item);
QString title = item->data(Role_Album).toString();
QString artist_name = item->data(Role_AlbumArtist).toString();
if (!artist_name.isEmpty()) {
result.set_title(artist_name + " - " + title);
}
@@ -600,11 +647,13 @@ Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) {
result.set_title(title);
}
result.set_artist(item->data(Role_ArtistName).toString());
result.set_albumartist(item->data(Role_AlbumArtistName).toString());
result.set_album(item->data(Role_AlbumName).toString());
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_url(item->data(Role_FirstUrl).toUrl());
result.set_filetype(Song::FileType(item->data(Role_Filetype).toInt()));
result.set_url(item->urls.first());
result.set_cue_path(item->data(Role_CuePath).toString());
result.set_art_automatic(item->data(Role_PathAutomatic).toUrl());
result.set_art_manual(item->data(Role_PathManual).toUrl());
@@ -628,8 +677,9 @@ void AlbumCoverManager::ShowCover() {
void AlbumCoverManager::FetchSingleCover() {
for (QListWidgetItem *item : context_menu_items_) {
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), false);
cover_fetching_tasks_[id] = item;
AlbumItem *album_item = static_cast<AlbumItem*>(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_++;
}
@@ -640,7 +690,7 @@ void AlbumCoverManager::FetchSingleCover() {
}
void AlbumCoverManager::UpdateCoverInList(QListWidgetItem *item, const QUrl &cover_url) {
void AlbumCoverManager::UpdateCoverInList(AlbumItem *item, const QUrl &cover_url) {
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), cover_url);
item->setData(Role_PathManual, cover_url);
@@ -653,12 +703,9 @@ void AlbumCoverManager::LoadCoverFromFile() {
Song song = GetSingleSelectionAsSong();
if (!song.is_valid()) return;
QListWidgetItem *item = context_menu_items_[0];
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromFile(&song);
if (!cover_url.isEmpty()) {
UpdateCoverInList(item, cover_url);
AlbumCoverImageResult result = album_cover_choice_controller_->LoadImageFromFile(&song);
if (!result.image.isNull()) {
SaveImageToAlbums(&song, result);
}
}
@@ -666,33 +713,37 @@ void AlbumCoverManager::LoadCoverFromFile() {
void AlbumCoverManager::SaveCoverToFile() {
Song song = GetSingleSelectionAsSong();
if (!song.is_valid()) return;
if (!song.is_valid() || song.has_manually_unset_cover()) return;
QImage image;
AlbumCoverImageResult result;
// Load the image from disk
if (song.has_manually_unset_cover()) {
image = no_cover_image_;
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().isLocalFile() && QFile::exists(song.art_manual().toLocalFile())) {
image = QImage(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())) {
image = QImage(song.art_manual().path());
}
else if (!song.art_automatic().isEmpty() && song.art_automatic().isLocalFile() && QFile::exists(song.art_automatic().toLocalFile())) {
image = QImage(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())) {
image = QImage(song.art_automatic().path());
}
else {
image = no_cover_image_;
}
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());
}
album_cover_choice_controller_->SaveCoverToFileManual(song, image);
if (!result.is_valid()) return;
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
if (!result.image_data.isEmpty()) {
result.image.loadFromData(result.image_data);
}
album_cover_choice_controller_->SaveCoverToFileManual(song, result);
}
@@ -701,12 +752,9 @@ void AlbumCoverManager::LoadCoverFromURL() {
Song song = GetSingleSelectionAsSong();
if (!song.is_valid()) return;
QListWidgetItem *item = context_menu_items_[0];
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromURL(&song);
if (!cover_url.isEmpty()) {
UpdateCoverInList(item, cover_url);
AlbumCoverImageResult result = album_cover_choice_controller_->LoadImageFromURL();
if (result.is_valid()) {
SaveImageToAlbums(&song, result);
}
}
@@ -716,20 +764,58 @@ void AlbumCoverManager::SearchForCover() {
Song song = GetFirstSelectedAsSong();
if (!song.is_valid()) return;
QListWidgetItem *item = context_menu_items_[0];
AlbumCoverImageResult result = album_cover_choice_controller_->SearchForImage(&song);
if (result.is_valid()) {
SaveImageToAlbums(&song, result);
}
QUrl cover_url = album_cover_choice_controller_->SearchForCover(&song);
if (cover_url.isEmpty()) return;
}
void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResult &result) {
QUrl cover_url;
switch (album_cover_choice_controller_->get_save_album_cover_type()) {
case CollectionSettingsPage::SaveCoverType_Cache:
case CollectionSettingsPage::SaveCoverType_Album:
cover_url = album_cover_choice_controller_->SaveCoverToFileAutomatic(song, result);
break;
case CollectionSettingsPage::SaveCoverType_Embedded:
cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
break;
}
// Force the found cover on all of the selected items
for (QListWidgetItem *current : context_menu_items_) {
// Don't save the first one twice
if (current != item) {
Song current_song = ItemAsSong(current);
album_cover_choice_controller_->SaveCoverToSong(&current_song, cover_url);
QList<QUrl> urls;
QList<AlbumItem*> album_items;
for (QListWidgetItem *item : context_menu_items_) {
AlbumItem *album_item = static_cast<AlbumItem*>(item);
switch (album_cover_choice_controller_->get_save_album_cover_type()) {
case CollectionSettingsPage::SaveCoverType_Cache:
case CollectionSettingsPage::SaveCoverType_Album:{
Song current_song = ItemAsSong(album_item);
album_cover_choice_controller_->SaveArtManualToSong(&current_song, cover_url);
UpdateCoverInList(album_item, cover_url);
break;
}
case CollectionSettingsPage::SaveCoverType_Embedded:{
urls << album_item->urls;
album_items << album_item;
break;
}
}
}
UpdateCoverInList(current, cover_url);
if (album_cover_choice_controller_->get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType_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);
}
}
}
@@ -739,19 +825,68 @@ void AlbumCoverManager::UnsetCover() {
Song song = GetFirstSelectedAsSong();
if (!song.is_valid()) return;
QListWidgetItem *item = context_menu_items_[0];
AlbumItem *first_album_item = static_cast<AlbumItem*>(context_menu_items_[0]);
QUrl cover_url = album_cover_choice_controller_->UnsetCover(&song);
// Force the 'none' cover on all of the selected items
for (QListWidgetItem *current : context_menu_items_) {
current->setIcon(no_cover_item_icon_);
current->setData(Role_PathManual, cover_url);
for (QListWidgetItem *item : context_menu_items_) {
AlbumItem *album_item = static_cast<AlbumItem*>(item);
album_item->setIcon(icon_nocover_item_);
album_item->setData(Role_PathManual, cover_url);
// Don't save the first one twice
if (current != item) {
Song current_song = ItemAsSong(current);
album_cover_choice_controller_->SaveCoverToSong(&current_song, cover_url);
if (album_item != first_album_item) {
Song current_song = ItemAsSong(album_item);
album_cover_choice_controller_->SaveArtManualToSong(&current_song, cover_url);
}
}
}
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);
// Force the 'none' cover on all of the selected items
for (QListWidgetItem *item : context_menu_items_) {
AlbumItem *album_item = static_cast<AlbumItem*>(item);
album_item->setIcon(icon_nocover_item_);
album_item->setData(Role_PathManual, 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(&current_song, QUrl(), false);
}
}
}
void AlbumCoverManager::DeleteCover() {
Song song = GetFirstSelectedAsSong();
if (!song.is_valid()) return;
AlbumItem *first_album_item = static_cast<AlbumItem*>(context_menu_items_[0]);
album_cover_choice_controller_->DeleteCover(&song);
// Force the 'none' cover on all of the selected items
for (QListWidgetItem *item : context_menu_items_) {
AlbumItem *album_item = static_cast<AlbumItem*>(item);
album_item->setIcon(icon_nocover_item_);
album_item->setData(Role_PathManual, 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(&current_song, QUrl(), true);
}
}
@@ -763,20 +898,15 @@ SongList AlbumCoverManager::GetSongsInAlbum(const QModelIndex &idx) const {
CollectionQuery q;
q.SetColumnSpec("ROWID," + Song::kColumnSpec);
q.AddWhere("album", idx.data(Role_AlbumName).toString());
q.AddWhere("album", idx.data(Role_Album).toString());
q.SetOrderBy("disc, track, title");
QString artist = idx.data(Role_ArtistName).toString();
QString albumartist = idx.data(Role_AlbumArtistName).toString();
QString albumartist = idx.data(Role_AlbumArtist).toString();
if (!albumartist.isEmpty()) {
q.AddWhere("albumartist", albumartist);
}
else if (!artist.isEmpty()) {
q.AddWhere("artist", artist);
q.AddWhere("effective_albumartist", albumartist);
}
q.AddCompilationRequirement(artist.isEmpty() && albumartist.isEmpty());
q.AddCompilationRequirement(albumartist.isEmpty());
if (!collection_backend_->ExecQuery(&q)) return ret;
@@ -813,7 +943,7 @@ SongMimeData *AlbumCoverManager::GetMimeDataForAlbums(const QModelIndexList &ind
void AlbumCoverManager::AlbumDoubleClicked(const QModelIndex &idx) {
QListWidgetItem *item = static_cast<QListWidgetItem*>(idx.internalPointer());
AlbumItem *item = static_cast<AlbumItem*>(idx.internalPointer());
if (!item) return;
album_cover_choice_controller_->ShowCover(ItemAsSong(item));
@@ -833,23 +963,35 @@ void AlbumCoverManager::LoadSelectedToPlaylist() {
}
void AlbumCoverManager::SaveAndSetCover(QListWidgetItem *item, const QUrl &cover_url, const QImage &image) {
void AlbumCoverManager::SaveAndSetCover(AlbumItem *item, const AlbumCoverImageResult &result) {
const QString artist = item->data(Role_ArtistName).toString();
const QString albumartist = item->data(Role_AlbumArtistName).toString();
const QString album = item->data(Role_AlbumName).toString();
const QUrl url = item->data(Role_FirstUrl).toUrl();
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 = Song::FileType(item->data(Role_Filetype).toInt());
const bool has_cue = !item->data(Role_CuePath).toString().isEmpty();
QUrl new_cover_url = album_cover_choice_controller_->SaveCoverToFileAutomatic(Song::Source_Collection, (!albumartist.isEmpty() ? albumartist : artist), album, QString(), url.adjusted(QUrl::RemoveFilename).path(), cover_url, image, false);
if (new_cover_url.isEmpty()) return;
if (album_cover_choice_controller_->get_save_album_cover_type() == CollectionSettingsPage::SaveCoverType_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 {
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
cover_save_tasks_.insert(id, item);
}
}
else {
QUrl cover_url = album_cover_choice_controller_->SaveCoverToFileAutomatic(Song::Source_Collection, albumartist, album, QString(), urls.first().adjusted(QUrl::RemoveFilename).path(), result, false);
// Save the image in the database
collection_backend_->UpdateManualAlbumArtAsync(artist, albumartist, album, new_cover_url);
if (cover_url.isEmpty()) return;
// Update the icon in our list
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), new_cover_url);
item->setData(Role_PathManual, new_cover_url);
cover_loading_tasks_[id] = item;
// Save the image in the database
collection_backend_->UpdateManualAlbumArtAsync(albumartist, album, cover_url);
// Update the icon in our list
UpdateCoverInList(item, cover_url);
}
}
@@ -866,7 +1008,7 @@ void AlbumCoverManager::ExportCovers() {
cover_exporter_->SetDialogResult(result);
for (int i = 0; i < ui_->albums->count(); ++i) {
QListWidgetItem *item = ui_->albums->item(i);
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i));
// skip hidden and coverless albums
if (item->isHidden() || !ItemHasCover(*item)) {
@@ -918,31 +1060,36 @@ void AlbumCoverManager::UpdateExportStatus(const int exported, const int skipped
}
QString AlbumCoverManager::EffectiveAlbumArtistName(const QListWidgetItem &item) const {
QString albumartist = item.data(Role_AlbumArtistName).toString();
if (!albumartist.isEmpty()) {
return albumartist;
}
return item.data(Role_ArtistName).toString();
}
QImage AlbumCoverManager::GenerateNoCoverImage(const QImage &image) const {
QImage AlbumCoverManager::GenerateNoCoverImage(const QIcon &no_cover_icon) const {
// Get a square version of the nocover image with some transparency:
QImage image_scaled = image.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation);
// Get a square version of cdcase.png with some transparency:
QImage nocover = no_cover_icon.pixmap(no_cover_icon.availableSizes().last()).toImage();
nocover = nocover.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QImage square_nocover(120, 120, QImage::Format_ARGB32);
square_nocover.fill(0);
QPainter p(&square_nocover);
QImage image_square(120, 120, QImage::Format_ARGB32);
image_square.fill(0);
QPainter p(&image_square);
p.setOpacity(0.4);
p.drawImage((120 - nocover.width()) / 2, (120 - nocover.height()) / 2, nocover);
p.drawImage((120 - image_scaled.width()) / 2, (120 - image_scaled.height()) / 2, image_scaled);
p.end();
return square_nocover;
return image_square;
}
bool AlbumCoverManager::ItemHasCover(const QListWidgetItem &item) const {
return item.icon().cacheKey() != no_cover_item_icon_.cacheKey();
bool AlbumCoverManager::ItemHasCover(const AlbumItem &item) const {
return item.icon().cacheKey() != icon_nocover_item_.cacheKey();
}
void AlbumCoverManager::SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success) {
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;
}
}

View File

@@ -31,6 +31,7 @@
#include <QList>
#include <QListWidgetItem>
#include <QMap>
#include <QMultiMap>
#include <QString>
#include <QImage>
#include <QIcon>
@@ -38,7 +39,9 @@
#include "core/song.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "albumcoverchoicecontroller.h"
#include "coversearchstatistics.h"
#include "settings/collectionsettingspage.h"
class QWidget;
class QMimeData;
@@ -53,7 +56,6 @@ class QShowEvent;
class Application;
class CollectionBackend;
class SongMimeData;
class AlbumCoverChoiceController;
class AlbumCoverExport;
class AlbumCoverExporter;
class AlbumCoverFetcher;
@@ -61,17 +63,21 @@ class AlbumCoverSearcher;
class Ui_CoverManager;
class AlbumItem : public QListWidgetItem {
public:
AlbumItem(const QIcon &icon, const QString &text, QListWidget *parent = nullptr, int type = Type) : QListWidgetItem(icon, text, parent, type) {};
QList<QUrl> urls;
};
class AlbumCoverManager : public QMainWindow {
Q_OBJECT
public:
explicit AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent = nullptr);
~AlbumCoverManager() override;
static const char *kSettingsGroup;
CollectionBackend *backend() const;
QIcon no_cover_icon() const { return no_cover_icon_; }
void Reset();
void Init();
void ReloadSettings();
@@ -80,15 +86,15 @@ class AlbumCoverManager : public QMainWindow {
void DisableCoversButtons();
SongList GetSongsInAlbum(const QModelIndex &idx) const;
SongList GetSongsInAlbums(const QModelIndexList &indexes) const;
SongMimeData *GetMimeDataForAlbums(const QModelIndexList &indexes) const;
CollectionBackend *backend() const { return collection_backend_; }
protected:
void showEvent(QShowEvent*) override;
void closeEvent(QCloseEvent*) override;
void showEvent(QShowEvent *e) override;
void closeEvent(QCloseEvent *e) override;
// For the album view context menu events
bool eventFilter(QObject *obj, QEvent *event) override;
bool eventFilter(QObject *obj, QEvent *e) override;
private:
enum ArtistItemType {
@@ -98,12 +104,14 @@ class AlbumCoverManager : public QMainWindow {
};
enum Role {
Role_ArtistName = Qt::UserRole + 1,
Role_AlbumArtistName,
Role_AlbumName,
Role_AlbumArtist = Qt::UserRole + 1,
Role_Album,
Role_PathAutomatic,
Role_PathManual,
Role_FirstUrl
Role_Filetype,
Role_CuePath,
Role_ImageData,
Role_Image
};
enum HideCovers {
@@ -113,21 +121,29 @@ class AlbumCoverManager : public QMainWindow {
};
void LoadGeometry();
void SaveGeometry();
void SaveSettings();
QString InitialPathForOpenCoverDialog(const QString &path_automatic, const QString &first_file_name) const;
QString EffectiveAlbumArtistName(const QListWidgetItem &item) const;
// Returns the selected element in form of a Song ready to be used by AlbumCoverChoiceController or invalid song if there's nothing or multiple elements selected.
Song GetSingleSelectionAsSong();
// 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);
Song ItemAsSong(QListWidgetItem *item) { return ItemAsSong(static_cast<AlbumItem*>(item)); }
Song ItemAsSong(AlbumItem *item);
void UpdateStatusText();
bool ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const;
void SaveAndSetCover(QListWidgetItem *item, const QUrl &cover_url, const QImage &image);
bool ShouldHide(const AlbumItem &item, const QString &filter, HideCovers hide) const;
void SaveAndSetCover(AlbumItem *item, const AlbumCoverImageResult &result);
void SaveImageToAlbums(Song *song, const AlbumCoverImageResult &result);
SongList GetSongsInAlbums(const QModelIndexList &indexes) const;
SongMimeData *GetMimeDataForAlbums(const QModelIndexList &indexes) const;
QImage GenerateNoCoverImage(const QImage &image) const;
bool ItemHasCover(const AlbumItem &item) const;
signals:
void AddToPlaylist(QMimeData *data);
@@ -138,7 +154,7 @@ class AlbumCoverManager : public QMainWindow {
void UpdateFilter();
void FetchAlbumCovers();
void ExportCovers();
void AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics);
void AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics);
void CancelRequests();
// On the context menu
@@ -149,6 +165,8 @@ class AlbumCoverManager : public QMainWindow {
void LoadCoverFromURL();
void SearchForCover();
void UnsetCover();
void ClearCover();
void DeleteCover();
void ShowCover();
// For adding albums to the playlist
@@ -156,14 +174,16 @@ class AlbumCoverManager : public QMainWindow {
void AddSelectedToPlaylist();
void LoadSelectedToPlaylist();
void UpdateCoverInList(QListWidgetItem *item, const QUrl &cover);
void UpdateCoverInList(AlbumItem *item, const QUrl &cover);
void UpdateExportStatus(const int exported, const int skipped, const int max);
void SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success);
private:
Ui_CoverManager *ui_;
QMainWindow *mainwindow_;
Application *app_;
CollectionBackend *collection_backend_;
AlbumCoverChoiceController *album_cover_choice_controller_;
QAction *filter_all_;
@@ -171,24 +191,20 @@ class AlbumCoverManager : public QMainWindow {
QAction *filter_without_covers_;
AlbumCoverLoaderOptions cover_loader_options_;
QMap<quint64, QListWidgetItem*> cover_loading_tasks_;
QMap<quint64, AlbumItem*> cover_loading_tasks_;
AlbumCoverFetcher *cover_fetcher_;
QMap<quint64, QListWidgetItem*> cover_fetching_tasks_;
QMap<quint64, AlbumItem*> cover_fetching_tasks_;
CoverSearchStatistics fetch_statistics_;
AlbumCoverSearcher *cover_searcher_;
AlbumCoverExport *cover_export_;
AlbumCoverExporter *cover_exporter_;
QImage GenerateNoCoverImage(const QIcon &no_cover_icon) const;
bool ItemHasCover(const QListWidgetItem &item) const;
QIcon artist_icon_;
QIcon all_artists_icon_;
const QIcon no_cover_icon_;
const QImage no_cover_image_;
const QIcon no_cover_item_icon_;
const QImage image_nocover_thumbnail_;
const QIcon icon_nocover_item_;
QMenu *context_menu_;
QList<QListWidgetItem*> context_menu_items_;
@@ -197,7 +213,10 @@ class AlbumCoverManager : public QMainWindow {
QPushButton *abort_progress_;
int jobs_;
CollectionBackend *collection_backend_;
QMultiMap<qint64, AlbumItem*> cover_save_tasks_;
QList<AlbumItem*> cover_save_tasks2_;
QListWidgetItem *all_artists_;
};

View File

@@ -69,7 +69,9 @@ QMimeData *AlbumCoverManagerList::mimeData(const QList<QListWidgetItem*> items)
mime_data->songs = songs;
mime_data->setUrls(urls);
mime_data->setData(orig_data->formats()[0], orig_data->data(orig_data->formats()[0]));
return mime_data;
}
void AlbumCoverManagerList::dropEvent(QDropEvent *e) {

View File

@@ -30,6 +30,7 @@
#include <QStandardItem>
#include <QList>
#include <QVariant>
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <QImage>
@@ -58,6 +59,7 @@
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "albumcoverimageresult.h"
#include "ui_albumcoversearcher.h"
const int SizeOverlayDelegate::kMargin = 4;
@@ -130,6 +132,8 @@ 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;
@@ -157,7 +161,7 @@ void AlbumCoverSearcher::Init(AlbumCoverFetcher *fetcher) {
}
QImage AlbumCoverSearcher::Exec(const QString &artist, const QString &album) {
AlbumCoverImageResult AlbumCoverSearcher::Exec(const QString &artist, const QString &album) {
#ifdef Q_OS_MACOS
ui_->artist->clear();
@@ -175,16 +179,18 @@ QImage AlbumCoverSearcher::Exec(const QString &artist, const QString &album) {
Search();
}
if (exec() == QDialog::Rejected) return QImage();
if (exec() == QDialog::Rejected) return AlbumCoverImageResult();
QModelIndex selected = ui_->covers->currentIndex();
if (!selected.isValid() || !selected.data(Role_ImageFetchFinished).toBool())
return QImage();
return AlbumCoverImageResult();
QIcon icon = selected.data(Qt::DecorationRole).value<QIcon>();
if (icon.cacheKey() == no_cover_icon_.cacheKey()) return QImage();
AlbumCoverImageResult result;
result.image_data = selected.data(Role_ImageData).value<QByteArray>();
result.image = selected.data(Role_Image).value<QImage>();
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
return icon.pixmap(icon.availableSizes()[0]).toImage();
return result;
}
@@ -213,10 +219,9 @@ void AlbumCoverSearcher::Search() {
}
void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverSearchResults &results) {
void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverProviderSearchResults &results) {
if (id != id_)
return;
if (id != id_) return;
ui_->search->setEnabled(true);
ui_->artist->setEnabled(true);
@@ -225,7 +230,8 @@ void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverSearchResul
ui_->search->setText(tr("Search"));
id_ = 0;
for (const CoverSearchResult &result : results) {
for (const CoverProviderSearchResult &result : results) {
if (result.image_url.isEmpty()) continue;
quint64 new_id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url, QUrl());
@@ -255,19 +261,25 @@ void AlbumCoverSearcher::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoad
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
if (result.image_original.isNull()) {
if (!result.success || result.album_cover.image_data.isNull() || result.album_cover.image.isNull() || result.image_thumbnail.isNull()) {
model_->removeRow(item->row());
return;
}
QIcon icon;
icon.addPixmap(QPixmap::fromImage(result.image_original));
icon.addPixmap(QPixmap::fromImage(result.image_thumbnail));
QPixmap pixmap = QPixmap::fromImage(result.image_thumbnail);
if (pixmap.isNull()) {
model_->removeRow(item->row());
return;
}
QIcon icon(pixmap);
item->setData(true, Role_ImageFetchFinished);
item->setData(result.image_original.width() * result.image_original.height(), Role_ImageDimensions);
item->setData(result.image_original.size(), Role_ImageSize);
item->setIcon(icon);
item->setData(result.album_cover.image_data, Role_ImageData);
item->setData(result.album_cover.image, Role_Image);
item->setData(result.album_cover.image.width() * result.album_cover.image.height(), Role_ImageDimensions);
item->setData(result.album_cover.image.size(), Role_ImageSize);
if (!icon.isNull()) item->setIcon(icon);
}

View File

@@ -32,6 +32,7 @@
#include <QStyledItemDelegate>
#include <QStyleOptionViewItem>
#include <QMap>
#include <QByteArray>
#include <QString>
#include <QImage>
#include <QIcon>
@@ -39,6 +40,7 @@
#include "albumcoverfetcher.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "albumcoverimageresult.h"
class QWidget;
class QStandardItem;
@@ -77,20 +79,22 @@ class AlbumCoverSearcher : public QDialog {
Role_ImageURL = Qt::UserRole + 1,
Role_ImageRequestId,
Role_ImageFetchFinished,
Role_ImageDimensions, // width * height
Role_ImageData,
Role_Image,
Role_ImageDimensions,
Role_ImageSize,
};
void Init(AlbumCoverFetcher *fetcher);
QImage Exec(const QString &artist, const QString &album);
AlbumCoverImageResult Exec(const QString &artist, const QString &album);
protected:
void keyPressEvent(QKeyEvent*) override;
private slots:
void Search();
void SearchFinished(const quint64 id, const CoverSearchResults &results);
void SearchFinished(const quint64 id, const CoverProviderSearchResults &results);
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
void CoverDoubleClicked(const QModelIndex &idx);

View File

@@ -89,7 +89,7 @@ void CoverExportRunnable::ProcessAndExportCover() {
// load embedded cover if any
if (song_.has_embedded_cover()) {
embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song_.url().toLocalFile());
embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
if (embedded_cover.isNull()) {
EmitCoverSkipped();
@@ -179,7 +179,7 @@ void CoverExportRunnable::ExportCover() {
if (cover_path == Song::kEmbeddedCover) {
// an embedded cover
QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song_.url().toLocalFile());
QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
if (!embedded.save(new_file)) {
EmitCoverSkipped();
return;

View File

@@ -31,8 +31,10 @@
#include <QNetworkRequest>
#include <QUrl>
#include "core/utilities.h"
#include "core/networkaccessmanager.h"
#include "widgets/busyindicator.h"
#include "albumcoverimageresult.h"
#include "coverfromurldialog.h"
#include "ui_coverfromurldialog.h"
@@ -47,17 +49,17 @@ CoverFromURLDialog::~CoverFromURLDialog() {
delete ui_;
}
QImage CoverFromURLDialog::Exec() {
AlbumCoverImageResult CoverFromURLDialog::Exec() {
// reset state
ui_->url->setText("");;
last_image_ = QImage();
last_album_cover_ = AlbumCoverImageResult();
QClipboard *clipboard = QApplication::clipboard();
ui_->url->setText(clipboard->text());
exec();
return last_image_;
return last_album_cover_;
}
@@ -89,11 +91,13 @@ void CoverFromURLDialog::LoadCoverFromURLFinished() {
return;
}
QImage image;
image.loadFromData(reply->readAll());
AlbumCoverImageResult result;
result.image_data = reply->readAll();
result.image.loadFromData(result.image_data);
result.mime_type = Utilities::MimeTypeFromData(result.image_data);
if (!image.isNull()) {
last_image_ = image;
if (!result.image.isNull()) {
last_album_cover_ = result;
QDialog::accept();
}
else {

View File

@@ -28,6 +28,8 @@
#include <QString>
#include <QImage>
#include "albumcoverimageresult.h"
class QWidget;
class NetworkAccessManager;
@@ -42,7 +44,7 @@ class CoverFromURLDialog : public QDialog {
~CoverFromURLDialog() override;
// Opens the dialog. This returns an image found at the URL chosen by user or null image if the dialog got rejected.
QImage Exec();
AlbumCoverImageResult Exec();
private slots:
void accept() override;
@@ -52,7 +54,7 @@ class CoverFromURLDialog : public QDialog {
Ui_CoverFromURLDialog *ui_;
NetworkAccessManager *network_;
QImage last_image_;
AlbumCoverImageResult last_album_cover_;
};
#endif // COVERFROMURLDIALOG_H

View File

@@ -27,4 +27,4 @@
#include "core/application.h"
#include "coverprovider.h"
CoverProvider::CoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent) : QObject(parent), app_(app), name_(name), enabled_(enabled), order_(0), authentication_required_(authentication_required), quality_(quality), fetchall_(fetchall), allow_missing_album_(allow_missing_album) {}
CoverProvider::CoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool batch, const bool allow_missing_album, Application *app, QObject *parent) : QObject(parent), app_(app), name_(name), enabled_(enabled), order_(0), authentication_required_(authentication_required), quality_(quality), batch_(batch), allow_missing_album_(allow_missing_album) {}

View File

@@ -40,14 +40,14 @@ class CoverProvider : public QObject {
Q_OBJECT
public:
explicit CoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent);
explicit CoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool batch, const bool allow_missing_album, Application *app, QObject *parent);
// A name (very short description) of this provider, like "last.fm".
QString name() const { return name_; }
bool is_enabled() const { return enabled_; }
int order() const { return order_; }
bool quality() const { return quality_; }
bool fetchall() const { return fetchall_; }
bool batch() const { return batch_; }
bool allow_missing_album() const { return allow_missing_album_; }
void set_enabled(const bool enabled) { enabled_ = enabled; }
@@ -70,8 +70,8 @@ class CoverProvider : public QObject {
void AuthenticationComplete(bool, QStringList = QStringList());
void AuthenticationSuccess();
void AuthenticationFailure(QStringList);
void SearchResults(int, CoverSearchResults);
void SearchFinished(int, CoverSearchResults);
void SearchResults(int, CoverProviderSearchResults);
void SearchFinished(int, CoverProviderSearchResults);
private:
Application *app_;
@@ -80,7 +80,7 @@ class CoverProvider : public QObject {
int order_;
bool authentication_required_;
float quality_;
bool fetchall_;
bool batch_;
bool allow_missing_album_;
};

View File

@@ -30,6 +30,7 @@
#include <QTemporaryFile>
#include "core/application.h"
#include "core/song.h"
#include "playlist/playlistmanager.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
@@ -43,6 +44,8 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare
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;
@@ -74,11 +77,11 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
if (id != id_) return;
id_ = 0;
if (!result.image_scaled.isNull()) {
if (!result.album_cover.image.isNull()) {
temp_cover_.reset(new QTemporaryFile(temp_file_pattern_));
temp_cover_->setAutoRemove(true);
if (temp_cover_->open()) {
if (result.image_scaled.save(temp_cover_->fileName(), "JPEG")) {
if (result.album_cover.image.save(temp_cover_->fileName(), "JPEG")) {
result.temp_cover_url = QUrl::fromLocalFile(temp_cover_->fileName());
}
else {
@@ -108,7 +111,7 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
}
if (result.updated) {
last_song_.set_art_manual(result.cover_url);
last_song_.set_art_manual(result.album_cover.cover_url);
}
emit AlbumCoverLoaded(last_song_, result);

View File

@@ -205,23 +205,23 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
QByteArray data = GetReplyData(reply);
if (data.isEmpty()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
QJsonValue value_data = ExtractData(data);
if (!value_data.isArray()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
QJsonArray array_data = value_data.toArray();
if (array_data.isEmpty()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
QMap<QUrl, CoverSearchResult> results;
QMap<QUrl, CoverProviderSearchResult> results;
int i = 0;
for (const QJsonValue json_value : array_data) {
@@ -279,7 +279,7 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
album = album.remove(Song::kAlbumRemoveDisc);
album = album.remove(Song::kAlbumRemoveMisc);
CoverSearchResult cover_result;
CoverProviderSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
@@ -309,11 +309,11 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
}
if (results.isEmpty()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
}
else {
CoverSearchResults cover_results = results.values();
std::stable_sort(cover_results.begin(), cover_results.end(), AlbumCoverFetcherSearch::CoverSearchResultCompareNumber);
CoverProviderSearchResults cover_results = results.values();
std::stable_sort(cover_results.begin(), cover_results.end(), AlbumCoverFetcherSearch::CoverProviderSearchResultCompareNumber);
emit SearchFinished(id, cover_results);
}

View File

@@ -441,7 +441,7 @@ void DiscogsCoverProvider::HandleReleaseReply(QNetworkReply *reply, const int se
if (width < 300 || height < 300) continue;
const float aspect_score = 1.0 - float(std::max(width, height) - std::min(width, height)) / std::max(height, width);
if (aspect_score < 0.85) continue;
CoverSearchResult result;
CoverProviderSearchResult result;
result.artist = artist;
result.album = album;
result.image_url = QUrl(obj_image["resource_url"].toString());

View File

@@ -73,7 +73,7 @@ class DiscogsCoverProvider : public JsonCoverProvider {
QString album;
DiscogsCoverType type;
QMap<quint64, DiscogsCoverReleaseContext> requests_release_;
CoverSearchResults results;
CoverProviderSearchResults results;
};
private:

View File

@@ -31,7 +31,7 @@
#include "coverprovider.h"
#include "jsoncoverprovider.h"
JsonCoverProvider::JsonCoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent) : CoverProvider(name, enabled, authentication_required, quality, fetchall, allow_missing_album, app, parent) {}
JsonCoverProvider::JsonCoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool batch, const bool allow_missing_album, Application *app, QObject *parent) : CoverProvider(name, enabled, authentication_required, quality, batch, allow_missing_album, app, parent) {}
QJsonObject JsonCoverProvider::ExtractJsonObj(const QByteArray &data) {

View File

@@ -35,7 +35,7 @@ class JsonCoverProvider : public CoverProvider {
Q_OBJECT
public:
explicit JsonCoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent);
explicit JsonCoverProvider(const QString &name, const bool enabled, const bool authentication_required, const float quality, const bool batch, const bool allow_missing_album, Application *app, QObject *parent);
QJsonObject ExtractJsonObj(const QByteArray &data);

View File

@@ -135,7 +135,7 @@ void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, const int id, cons
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
CoverSearchResults results;
CoverProviderSearchResults results;
QByteArray data = GetReplyData(reply);
if (data.isEmpty()) {
@@ -275,7 +275,7 @@ void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, const int id, cons
if (!url.isValid()) continue;
CoverSearchResult cover_result;
CoverProviderSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
cover_result.image_url = url;

View File

@@ -128,7 +128,7 @@ void MusicbrainzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
CoverSearchResults results;
CoverProviderSearchResults results;
QByteArray data = GetReplyData(reply);
if (data.isEmpty()) {
@@ -217,7 +217,7 @@ void MusicbrainzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
QString id = obj_release["id"].toString();
QString album = obj_release["title"].toString();
CoverSearchResult cover_result;
CoverProviderSearchResult cover_result;
QUrl url(QString(kAlbumCoverUrl).arg(id));
cover_result.artist = artist;
cover_result.album = album;

View File

@@ -105,7 +105,7 @@ void MusixmatchCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
CoverSearchResults results;
CoverProviderSearchResults results;
if (reply->error() != QNetworkReply::NoError) {
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
@@ -195,7 +195,7 @@ void MusixmatchCoverProvider::HandleSearchReply(QNetworkReply *reply, const int
return;
}
CoverSearchResult result;
CoverProviderSearchResult result;
result.artist = obj_album["artistName"].toString();
result.album = obj_album["name"].toString();

View File

@@ -170,7 +170,7 @@ void QobuzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
QObject::disconnect(reply, nullptr, this, nullptr);
reply->deleteLater();
CoverSearchResults results;
CoverProviderSearchResults results;
QByteArray data = GetReplyData(reply);
if (data.isEmpty()) {
@@ -274,7 +274,7 @@ void QobuzCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
album = album.remove(Song::kAlbumRemoveDisc);
album = album.remove(Song::kAlbumRemoveMisc);
CoverSearchResult cover_result;
CoverProviderSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
cover_result.image_url = cover_url;

View File

@@ -466,36 +466,36 @@ void SpotifyCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id,
QByteArray data = GetReplyData(reply);
if (data.isEmpty()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
if (!json_obj.contains(extract) || !json_obj[extract].isObject()) {
Error(QString("Json object is missing %1 object.").arg(extract), json_obj);
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
json_obj = json_obj[extract].toObject();
if (!json_obj.contains("items") || !json_obj["items"].isArray()) {
Error(QString("%1 object is missing items array.").arg(extract), json_obj);
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
QJsonArray array_items = json_obj["items"].toArray();
if (array_items.isEmpty()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
CoverSearchResults results;
CoverProviderSearchResults results;
for (const QJsonValue value_item : array_items) {
if (!value_item.isObject()) {
@@ -531,7 +531,7 @@ void SpotifyCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id,
int height = obj_image["height"].toInt();
if (width < 300 || height < 300) continue;
QUrl url(obj_image["url"].toString());
CoverSearchResult result;
CoverProviderSearchResult result;
result.album = album;
result.image_url = url;
result.image_size = QSize(width, height);

View File

@@ -183,34 +183,34 @@ void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
QByteArray data = GetReplyData(reply);
if (data.isEmpty()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
if (!json_obj.contains("items")) {
Error("Json object is missing items.", json_obj);
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
QJsonValue value_items = json_obj["items"];
if (!value_items.isArray()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
QJsonArray array_items = value_items.toArray();
if (array_items.isEmpty()) {
emit SearchFinished(id, CoverSearchResults());
emit SearchFinished(id, CoverProviderSearchResults());
return;
}
CoverSearchResults results;
CoverProviderSearchResults results;
int i = 0;
for (const QJsonValue value_item : array_items) {
@@ -262,7 +262,7 @@ void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
album = album.remove(Song::kAlbumRemoveMisc);
cover = cover.replace("-", "/");
CoverSearchResult cover_result;
CoverProviderSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
cover_result.number = ++i;