Improve album cover searching and cover manager, use HttpStatusCodeAttribute and QSslError for services
- Improve album cover manager - Change art_automatic and art_manual to QUrl - Refresh collection album covers when new album covers are fetched - Fix automatic album cover searching for local files outside of the collection - Make all Json services check HttpStatusCodeAttribute - Show detailed SSL errors for Subsonic, Tidal and Qobuz
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019, 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
|
||||
@@ -53,12 +54,14 @@
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "organise/organiseformat.h"
|
||||
#include "internet/internetservices.h"
|
||||
#include "internet/internetservice.h"
|
||||
#include "albumcoverchoicecontroller.h"
|
||||
#include "albumcoverfetcher.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "albumcoversearcher.h"
|
||||
#include "coverfromurldialog.h"
|
||||
#include "currentartloader.h"
|
||||
#include "currentalbumcoverloader.h"
|
||||
|
||||
const char *AlbumCoverChoiceController::kLoadImageFileFilter = QT_TR_NOOP("Images (*.png *.jpg *.jpeg *.bmp *.gif *.xpm *.pbm *.pgm *.ppm *.xbm)");
|
||||
const char *AlbumCoverChoiceController::kSaveImageFileFilter = QT_TR_NOOP("Images (*.png *.jpg *.jpeg *.bmp *.xpm *.pbm *.ppm *.xbm)");
|
||||
@@ -100,6 +103,18 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) :
|
||||
|
||||
AlbumCoverChoiceController::~AlbumCoverChoiceController() {}
|
||||
|
||||
void AlbumCoverChoiceController::Init(Application *app) {
|
||||
|
||||
app_ = app;
|
||||
|
||||
cover_fetcher_ = new AlbumCoverFetcher(app_->cover_providers(), this);
|
||||
cover_searcher_ = new AlbumCoverSearcher(QIcon(":/pictures/cdcase.png"), app, this);
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(const quint64, const QUrl&, const QImage&, CoverSearchStatistics)), this, SLOT(AlbumCoverFetched(const quint64, const QUrl&, const QImage&, CoverSearchStatistics)));
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
@@ -114,37 +129,28 @@ void AlbumCoverChoiceController::ReloadSettings() {
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SetApplication(Application *app) {
|
||||
|
||||
app_ = app;
|
||||
|
||||
cover_fetcher_ = new AlbumCoverFetcher(app_->cover_providers(), this);
|
||||
cover_searcher_ = new AlbumCoverSearcher(QIcon(":/pictures/cdcase.png"), app, this);
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)), this, SLOT(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)));
|
||||
|
||||
}
|
||||
|
||||
QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
|
||||
return QList<QAction*>() << cover_from_file_ << cover_to_file_ << separator_ << cover_from_url_ << search_for_cover_ << unset_cover_ << show_cover_;
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
|
||||
QUrl AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
|
||||
|
||||
QString cover = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
||||
QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
||||
|
||||
if (cover.isNull()) return QString();
|
||||
if (cover_file.isNull()) return QUrl();
|
||||
|
||||
// Can we load the image?
|
||||
QImage image(cover);
|
||||
QImage image(cover_file);
|
||||
|
||||
if (!image.isNull()) {
|
||||
SaveCover(song, cover);
|
||||
return cover;
|
||||
if (image.isNull()) {
|
||||
return QUrl();
|
||||
}
|
||||
else {
|
||||
return QString();
|
||||
QUrl cover_url;
|
||||
cover_url.setScheme("file");
|
||||
cover_url.setPath(cover_file);
|
||||
SaveCoverToSong(song, cover_url);
|
||||
return cover_url;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -178,38 +184,43 @@ QString AlbumCoverChoiceController::GetInitialPathForFileDialog(const Song &song
|
||||
|
||||
// Art automatic is first to show user which cover the album may be using now;
|
||||
// The song is using it if there's no manual path but we cannot use manual path here because it can contain cached paths
|
||||
if (!song.art_automatic().isEmpty() && !song.has_embedded_cover()) {
|
||||
return song.art_automatic();
|
||||
|
||||
if (!song.art_automatic().isEmpty() && !song.art_automatic().path().isEmpty() && !song.has_embedded_cover()) {
|
||||
if (song.art_automatic().scheme().isEmpty() && QFile::exists(QFileInfo(song.art_automatic().path()).path())) {
|
||||
return song.art_automatic().path();
|
||||
}
|
||||
else if (song.art_automatic().scheme() == "file" && QFile::exists(QFileInfo(song.art_automatic().toLocalFile()).path())) {
|
||||
return song.art_automatic().toLocalFile();
|
||||
}
|
||||
// If no automatic art, start in the song's folder
|
||||
}
|
||||
else if (!song.url().isEmpty() && song.url().toLocalFile().contains('/')) {
|
||||
return song.url().toLocalFile().section('/', 0, -2) + filename;
|
||||
// Fallback - start in home
|
||||
}
|
||||
else {
|
||||
return QDir::home().absolutePath() + filename;
|
||||
}
|
||||
|
||||
return QDir::home().absolutePath() + filename;
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
|
||||
QUrl AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
|
||||
|
||||
if (!cover_from_url_dialog_) { cover_from_url_dialog_ = new CoverFromURLDialog(this); }
|
||||
|
||||
QImage image = cover_from_url_dialog_->Exec();
|
||||
|
||||
if (!image.isNull()) {
|
||||
QString cover = SaveCoverToFileAutomatic(song, image);
|
||||
if (cover.isEmpty()) return QString();
|
||||
SaveCover(song, cover);
|
||||
return cover;
|
||||
if (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;
|
||||
}
|
||||
else { return QString(); }
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SearchForCover(Song *song) {
|
||||
QUrl AlbumCoverChoiceController::SearchForCover(Song *song) {
|
||||
|
||||
QString album = song->effective_album();
|
||||
album.remove(Song::kAlbumRemoveDisc);
|
||||
@@ -218,23 +229,26 @@ QString AlbumCoverChoiceController::SearchForCover(Song *song) {
|
||||
// Get something sensible to stick in the search box
|
||||
QImage image = cover_searcher_->Exec(song->effective_albumartist(), album);
|
||||
|
||||
if (!image.isNull()) {
|
||||
QString cover = SaveCoverToFileAutomatic(song, image);
|
||||
if (cover.isEmpty()) return QString();
|
||||
SaveCover(song, cover);
|
||||
|
||||
return cover;
|
||||
if (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;
|
||||
}
|
||||
else { return QString(); }
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::UnsetCover(Song *song) {
|
||||
QUrl AlbumCoverChoiceController::UnsetCover(Song *song) {
|
||||
|
||||
QString cover = Song::kManuallyUnsetCover;
|
||||
SaveCover(song, cover);
|
||||
QUrl cover_url;
|
||||
cover_url.setScheme("file");
|
||||
cover_url.setPath(Song::kManuallyUnsetCover);
|
||||
SaveCoverToSong(song, cover_url);
|
||||
|
||||
return cover;
|
||||
return cover_url;
|
||||
|
||||
}
|
||||
|
||||
@@ -307,7 +321,7 @@ void AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics) {
|
||||
void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
|
||||
|
||||
Song song;
|
||||
if (cover_fetching_tasks_.contains(id)) {
|
||||
@@ -315,93 +329,76 @@ void AlbumCoverChoiceController::AlbumCoverFetched(quint64 id, const QImage &ima
|
||||
}
|
||||
|
||||
if (!image.isNull()) {
|
||||
QString cover = SaveCoverToFileAutomatic(&song, image);
|
||||
if (cover.isEmpty()) return;
|
||||
SaveCover(&song, cover);
|
||||
QUrl new_cover_url = SaveCoverToFileAutomatic(&song, cover_url, image, false);
|
||||
if (!new_cover_url.isEmpty()) SaveCoverToSong(&song, new_cover_url);
|
||||
}
|
||||
|
||||
emit AutomaticCoverSearchDone();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SaveCover(Song *song, const QString &cover) {
|
||||
void AlbumCoverChoiceController::SaveCoverToSong(Song *song, const QUrl &cover_url) {
|
||||
|
||||
if (song->is_valid() && song->id() != -1) {
|
||||
song->set_art_manual(cover);
|
||||
app_->collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover);
|
||||
if (!song->is_valid()) return;
|
||||
|
||||
if (song->url() == app_->current_art_loader()->last_song().url()) {
|
||||
app_->current_art_loader()->LoadArt(*song);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (song->url() == app_->current_albumcover_loader()->last_song().url()) {
|
||||
app_->current_albumcover_loader()->LoadAlbumCover(*song);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const QImage &image) {
|
||||
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const QUrl &cover_url, const QImage &image, const bool overwrite) {
|
||||
|
||||
QString albumartist(song->effective_albumartist());
|
||||
QString artist(song->artist());
|
||||
QString album(song->effective_album());
|
||||
album.remove(Song::kAlbumRemoveDisc);
|
||||
|
||||
return SaveCoverToFileAutomatic(albumartist, artist, album, song->url().adjusted(QUrl::RemoveFilename).path(), image);
|
||||
return SaveCoverToFileAutomatic(song->source(), song->effective_albumartist(), song->effective_album(), song->album_id(), song->url().adjusted(QUrl::RemoveFilename).path(), cover_url, image, overwrite);
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SaveCoverToFileAutomatic(const QString &albumartist, const QString &artist, const QString &album, const QString &album_dir, const QImage &image) {
|
||||
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) {
|
||||
|
||||
QString album_new(album);
|
||||
album_new.remove(Song::kAlbumRemoveDisc);
|
||||
QString filepath = app_->album_cover_loader()->CoverFilePath(source, artist, album, album_id, album_dir, cover_url);
|
||||
if (filepath.isEmpty()) return QUrl();
|
||||
|
||||
QString path;
|
||||
QString filename;
|
||||
if (cover_album_dir_) {
|
||||
path = album_dir;
|
||||
}
|
||||
else {
|
||||
path = AlbumCoverLoader::ImageCacheDir();
|
||||
QUrl new_cover_url;
|
||||
new_cover_url.setScheme("file");
|
||||
new_cover_url.setPath(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;
|
||||
}
|
||||
|
||||
if (path.right(1) == QDir::separator()) {
|
||||
path.chop(1);
|
||||
}
|
||||
if (!image.save(filepath, "JPG") && !QFile::exists(filepath)) return QUrl();
|
||||
|
||||
QDir dir;
|
||||
if (!dir.mkpath(path)) {
|
||||
qLog(Error) << "Unable to create directory" << path;
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) {
|
||||
filename = CreateCoverFilename(albumartist, artist, album_new) + ".jpg";
|
||||
filename.remove(OrganiseFormat::kValidFatCharacters);
|
||||
if (cover_lowercase_) filename = filename.toLower();
|
||||
if (cover_replace_spaces_) filename.replace(QRegExp("\\s"), "-");
|
||||
}
|
||||
else {
|
||||
filename = Utilities::Sha1CoverHash(albumartist, album_new).toHex() + ".jpg";
|
||||
}
|
||||
|
||||
QString filepath(path + "/" + filename);
|
||||
|
||||
// Don't overwrite when saving in album dir if the filename is set to pattern unless the "cover_overwrite" is set.
|
||||
if (QFile::exists(filepath) && !cover_overwrite_ && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern) {
|
||||
return filepath;
|
||||
}
|
||||
|
||||
image.save(filepath, "JPG");
|
||||
|
||||
return filepath;
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::CreateCoverFilename(const QString &albumartist, const QString &artist, const QString &album) {
|
||||
|
||||
QString filename(cover_pattern_);
|
||||
filename.replace("%albumartist", albumartist);
|
||||
filename.replace("%artist", artist);
|
||||
filename.replace("%album", album);
|
||||
return filename;
|
||||
return new_cover_url;
|
||||
|
||||
}
|
||||
|
||||
@@ -426,7 +423,7 @@ bool AlbumCoverChoiceController::CanAcceptDrag(const QDragEnterEvent *e) {
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
|
||||
QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
|
||||
|
||||
for (const QUrl &url : e->mimeData()->urls()) {
|
||||
|
||||
@@ -434,21 +431,21 @@ QString AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
|
||||
const QString suffix = QFileInfo(filename).suffix().toLower();
|
||||
|
||||
if (IsKnownImageExtension(suffix)) {
|
||||
SaveCover(song, filename);
|
||||
return filename;
|
||||
SaveCoverToSong(song, url);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
if (e->mimeData()->hasImage()) {
|
||||
QImage image = qvariant_cast<QImage>(e->mimeData()->imageData());
|
||||
if (!image.isNull()) {
|
||||
QString cover_path = SaveCoverToFileAutomatic(song, image);
|
||||
if (cover_path.isEmpty()) return QString();
|
||||
SaveCover(song, cover_path);
|
||||
return cover_path;
|
||||
QUrl cover_url = SaveCoverToFileAutomatic(song, QUrl(), image, true);
|
||||
if (cover_url.isEmpty()) return QUrl();
|
||||
SaveCoverToSong(song, cover_url);
|
||||
return cover_url;
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
return QUrl();
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019, 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
|
||||
@@ -58,7 +59,7 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
AlbumCoverChoiceController(QWidget *parent = nullptr);
|
||||
~AlbumCoverChoiceController();
|
||||
|
||||
void SetApplication(Application *app);
|
||||
void Init(Application *app);
|
||||
void ReloadSettings();
|
||||
|
||||
// Getters for all QActions implemented by this controller.
|
||||
@@ -85,7 +86,7 @@ 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.
|
||||
QString LoadCoverFromFile(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.
|
||||
@@ -93,14 +94,14 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
|
||||
// Downloads the cover from an URL given by user.
|
||||
// This returns the downloaded image or null image if something went wrong for example when user cancelled the dialog.
|
||||
QString LoadCoverFromURL(Song *song);
|
||||
QUrl LoadCoverFromURL(Song *song);
|
||||
|
||||
// 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.
|
||||
QString SearchForCover(Song *song);
|
||||
QUrl SearchForCover(Song *song);
|
||||
|
||||
// Returns a path which indicates that the cover has been unset manually.
|
||||
QString UnsetCover(Song *song);
|
||||
QUrl UnsetCover(Song *song);
|
||||
|
||||
// Shows the cover of given song in it's original size.
|
||||
void ShowCover(const Song &song);
|
||||
@@ -111,15 +112,14 @@ class AlbumCoverChoiceController : public QWidget {
|
||||
void SearchCoverAutomatically(const Song &song);
|
||||
|
||||
// Saves the chosen cover as manual cover path of this song in collection.
|
||||
void SaveCover(Song *song, const QString &cover);
|
||||
void SaveCoverToSong(Song *song, const QUrl &cover_url);
|
||||
|
||||
// Saves the cover that the user picked through a drag and drop operation.
|
||||
QString SaveCover(Song *song, const QDropEvent *e);
|
||||
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.
|
||||
QString SaveCoverToFileAutomatic(const QString &albumartist, const QString &artist, const QString &album, const QString &album_dir, const QImage &image);
|
||||
QString SaveCoverToFileAutomatic(const Song *song, const QImage &image);
|
||||
QString CreateCoverFilename(const QString &albumartist, const QString &artist, const QString &album);
|
||||
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);
|
||||
|
||||
static bool CanAcceptDrag(const QDragEnterEvent *e);
|
||||
|
||||
@@ -127,7 +127,7 @@ signals:
|
||||
void AutomaticCoverSearchDone();
|
||||
|
||||
private slots:
|
||||
void AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics);
|
||||
void AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics);
|
||||
|
||||
private:
|
||||
QString GetInitialPathForFileDialog(const Song &song, const QString &filename);
|
||||
|
||||
@@ -114,15 +114,15 @@ void AlbumCoverFetcher::StartRequests() {
|
||||
AlbumCoverFetcherSearch *search = new AlbumCoverFetcherSearch(request, network_, this);
|
||||
active_requests_.insert(request.id, search);
|
||||
|
||||
connect(search, SIGNAL(SearchFinished(quint64, CoverSearchResults)), SLOT(SingleSearchFinished(quint64, CoverSearchResults)));
|
||||
connect(search, SIGNAL(AlbumCoverFetched(quint64, const QImage&)), SLOT(SingleCoverFetched(quint64, const QImage&)));
|
||||
connect(search, SIGNAL(SearchFinished(const quint64, const CoverSearchResults)), SLOT(SingleSearchFinished(const quint64, const CoverSearchResults)));
|
||||
connect(search, SIGNAL(AlbumCoverFetched(const quint64, const QUrl&, const QImage&)), SLOT(SingleCoverFetched(const quint64, const QUrl&, const QImage&)));
|
||||
|
||||
search->Start(cover_providers_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::SingleSearchFinished(quint64 request_id, CoverSearchResults results) {
|
||||
void AlbumCoverFetcher::SingleSearchFinished(const quint64 request_id, const CoverSearchResults results) {
|
||||
|
||||
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
||||
if (!search) return;
|
||||
@@ -132,13 +132,13 @@ void AlbumCoverFetcher::SingleSearchFinished(quint64 request_id, CoverSearchResu
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::SingleCoverFetched(quint64 request_id, const QImage &image) {
|
||||
void AlbumCoverFetcher::SingleCoverFetched(const quint64 request_id, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
||||
if (!search) return;
|
||||
|
||||
search->deleteLater();
|
||||
emit AlbumCoverFetched(request_id, image, search->statistics());
|
||||
emit AlbumCoverFetched(request_id, cover_url, image, search->statistics());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QVector>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
class CoverProviders;
|
||||
@@ -45,6 +44,8 @@ struct CoverSearchStatistics;
|
||||
|
||||
// This class represents a single search-for-cover request. It identifies and describes the request.
|
||||
struct CoverSearchRequest {
|
||||
CoverSearchRequest() : id(-1), search(false), fetchall(false) {}
|
||||
|
||||
// An unique (for one AlbumCoverFetcher) request identifier
|
||||
quint64 id;
|
||||
|
||||
@@ -61,6 +62,8 @@ struct CoverSearchRequest {
|
||||
|
||||
// This structure represents a single result of some album's cover search request.
|
||||
struct CoverSearchResult {
|
||||
CoverSearchResult() : score(0.0) {}
|
||||
|
||||
// Used for grouping in the user interface.
|
||||
QString provider;
|
||||
|
||||
@@ -92,17 +95,17 @@ class AlbumCoverFetcher : public QObject {
|
||||
static const int kMaxConcurrentRequests;
|
||||
|
||||
quint64 SearchForCovers(const QString &artist, const QString &album);
|
||||
quint64 FetchAlbumCover(const QString &artist, const QString &album, bool fetchall);
|
||||
quint64 FetchAlbumCover(const QString &artist, const QString &album, const bool fetchall);
|
||||
|
||||
void Clear();
|
||||
|
||||
signals:
|
||||
void AlbumCoverFetched(quint64, const QImage &cover, const CoverSearchStatistics &statistics);
|
||||
void SearchFinished(quint64, const CoverSearchResults &results, const CoverSearchStatistics &statistics);
|
||||
void AlbumCoverFetched(const quint64 request_id, const QUrl &cover_url, const QImage &cover, const CoverSearchStatistics &statistics);
|
||||
void SearchFinished(const quint64 request_id, const CoverSearchResults &results, const CoverSearchStatistics &statistics);
|
||||
|
||||
private slots:
|
||||
void SingleSearchFinished(quint64, CoverSearchResults results);
|
||||
void SingleCoverFetched(quint64, const QImage &cover);
|
||||
void SingleSearchFinished(const quint64, const CoverSearchResults results);
|
||||
void SingleCoverFetched(const quint64, const QUrl &cover_url, const QImage &cover);
|
||||
void StartRequests();
|
||||
|
||||
private:
|
||||
|
||||
@@ -68,7 +68,7 @@ AlbumCoverFetcherSearch::AlbumCoverFetcherSearch(
|
||||
|
||||
void AlbumCoverFetcherSearch::TerminateSearch() {
|
||||
|
||||
for (int id : pending_requests_.keys()) {
|
||||
for (quint64 id : pending_requests_.keys()) {
|
||||
pending_requests_.take(id)->CancelSearch(id);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
|
||||
continue;
|
||||
}
|
||||
|
||||
connect(provider, SIGNAL(SearchFinished(int, QList<CoverSearchResult>)), SLOT(ProviderSearchFinished(int, QList<CoverSearchResult>)));
|
||||
connect(provider, SIGNAL(SearchFinished(int, CoverSearchResults)), SLOT(ProviderSearchFinished(int, CoverSearchResults)));
|
||||
const int id = cover_providers->NextId();
|
||||
const bool success = provider->StartSearch(request_.artist, request_.album, id);
|
||||
|
||||
@@ -107,7 +107,7 @@ static bool CompareProviders(const CoverSearchResult &a, const CoverSearchResult
|
||||
return a.provider < b.provider;
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::ProviderSearchFinished(int id, const QList<CoverSearchResult> &results) {
|
||||
void AlbumCoverFetcherSearch::ProviderSearchFinished(const int id, const CoverSearchResults &results) {
|
||||
|
||||
if (!pending_requests_.contains(id)) return;
|
||||
CoverProvider *provider = pending_requests_.take(id);
|
||||
@@ -155,7 +155,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
|
||||
// No results?
|
||||
if (results_.isEmpty()) {
|
||||
statistics_.missing_images_++;
|
||||
emit AlbumCoverFetched(request_.id, QImage());
|
||||
emit AlbumCoverFetched(request_.id, QUrl(), QImage());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -265,10 +265,12 @@ float AlbumCoverFetcherSearch::ScoreImage(const QImage &image) const {
|
||||
|
||||
void AlbumCoverFetcherSearch::SendBestImage() {
|
||||
|
||||
QUrl cover_url;
|
||||
QImage image;
|
||||
|
||||
if (!candidate_images_.isEmpty()) {
|
||||
const CandidateImage best_image = candidate_images_.values().back();
|
||||
cover_url = best_image.first.image_url;
|
||||
image = best_image.second;
|
||||
|
||||
qLog(Info) << "Using " << best_image.first.image_url << "from" << best_image.first.provider << "with score" << best_image.first.score;
|
||||
@@ -282,7 +284,7 @@ void AlbumCoverFetcherSearch::SendBestImage() {
|
||||
statistics_.missing_images_++;
|
||||
}
|
||||
|
||||
emit AlbumCoverFetched(request_.id, image);
|
||||
emit AlbumCoverFetched(request_.id, cover_url, image);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -60,13 +60,13 @@ class AlbumCoverFetcherSearch : public QObject {
|
||||
|
||||
signals:
|
||||
// It's the end of search (when there was no fetch-me-a-cover request).
|
||||
void SearchFinished(quint64, const CoverSearchResults &results);
|
||||
void SearchFinished(const quint64, const CoverSearchResults &results);
|
||||
|
||||
// It's the end of search and we've fetched a cover.
|
||||
void AlbumCoverFetched(quint64, const QImage &cover);
|
||||
void AlbumCoverFetched(const quint64, const QUrl &cover_url, const QImage &cover);
|
||||
|
||||
private slots:
|
||||
void ProviderSearchFinished(int id, const QList<CoverSearchResult> &results);
|
||||
void ProviderSearchFinished(const int id, const CoverSearchResults &results);
|
||||
void ProviderCoverFetchFinished(RedirectFollower *reply);
|
||||
void TerminateSearch();
|
||||
|
||||
@@ -106,4 +106,3 @@ class AlbumCoverFetcherSearch : public QObject {
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERFETCHERSEARCH_H
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -22,6 +23,7 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QDir>
|
||||
#include <QQueue>
|
||||
#include <QMutex>
|
||||
#include <QStandardPaths>
|
||||
@@ -37,11 +39,15 @@
|
||||
#include <QPainter>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/utilities.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "organise/organiseformat.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
|
||||
@@ -49,13 +55,147 @@ AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
|
||||
: QObject(parent),
|
||||
stop_requested_(false),
|
||||
next_id_(1),
|
||||
network_(new NetworkAccessManager(this)){}
|
||||
network_(new NetworkAccessManager(this)),
|
||||
cover_album_dir_(false),
|
||||
cover_filename_(CollectionSettingsPage::SaveCover_Hash),
|
||||
cover_overwrite_(false),
|
||||
cover_lowercase_(true),
|
||||
cover_replace_spaces_(true)
|
||||
{
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
QString AlbumCoverLoader::ImageCacheDir() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/albumcovers";
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::CancelTask(quint64 id) {
|
||||
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());
|
||||
cover_pattern_ = s.value("cover_pattern", "%albumartist-%album").toString();
|
||||
cover_overwrite_ = s.value("cover_overwrite", false).toBool();
|
||||
cover_lowercase_ = s.value("cover_lowercase", false).toBool();
|
||||
cover_replace_spaces_ = s.value("cover_replace_spaces", false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverLoader::ImageCacheDir(const Song::Source source) {
|
||||
|
||||
switch (source) {
|
||||
case Song::Source_LocalFile:
|
||||
case Song::Source_Collection:
|
||||
case Song::Source_CDDA:
|
||||
case Song::Source_Device:
|
||||
case Song::Source_Stream:
|
||||
case Song::Source_Unknown:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/albumcovers";
|
||||
case Song::Source_Tidal:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/tidalalbumcovers";
|
||||
case Song::Source_Qobuz:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/qobuzalbumcovers";
|
||||
case Song::Source_Subsonic:
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/subsonicalbumcovers";
|
||||
}
|
||||
|
||||
return QString();
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url) {
|
||||
|
||||
album.remove(Song::kAlbumRemoveDisc);
|
||||
|
||||
QString path;
|
||||
if (source == Song::Source_Collection && cover_album_dir_ && !album_dir.isEmpty()) {
|
||||
path = album_dir;
|
||||
}
|
||||
else {
|
||||
path = AlbumCoverLoader::ImageCacheDir(source);
|
||||
}
|
||||
|
||||
if (path.right(1) == QDir::separator()) {
|
||||
path.chop(1);
|
||||
}
|
||||
|
||||
QDir dir;
|
||||
if (!dir.mkpath(path)) {
|
||||
qLog(Error) << "Unable to create directory" << path;
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString filename;
|
||||
if (source == Song::Source_Collection && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) {
|
||||
filename = CreateCoverFilename(artist, album) + ".jpg";
|
||||
filename.remove(OrganiseFormat::kValidFatCharacters);
|
||||
if (cover_lowercase_) filename = filename.toLower();
|
||||
if (cover_replace_spaces_) filename.replace(QRegExp("\\s"), "-");
|
||||
}
|
||||
else {
|
||||
switch (source) {
|
||||
case Song::Source_Collection:
|
||||
case Song::Source_LocalFile:
|
||||
case Song::Source_CDDA:
|
||||
case Song::Source_Device:
|
||||
case Song::Source_Stream:
|
||||
case Song::Source_Unknown:
|
||||
filename = Utilities::Sha1CoverHash(artist, album).toHex() + ".jpg";
|
||||
break;
|
||||
case Song::Source_Tidal:
|
||||
filename = album_id + "-" + cover_url.fileName();
|
||||
break;
|
||||
case Song::Source_Qobuz:
|
||||
case Song::Source_Subsonic:
|
||||
filename = AlbumCoverFileName(artist, album);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (filename.isEmpty()) return QString();
|
||||
|
||||
QString filepath(path + "/" + filename);
|
||||
|
||||
return filepath;
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverLoader::AlbumCoverFileName(QString artist, QString album) {
|
||||
|
||||
artist.remove('/');
|
||||
album.remove('/');
|
||||
|
||||
QString filename = artist + "-" + album + ".jpg";
|
||||
filename = filename.toLower();
|
||||
filename.replace(' ', '-');
|
||||
filename.replace("--", "-");
|
||||
filename.replace(230, "ae");
|
||||
filename.replace(198, "AE");
|
||||
filename.replace(246, 'o');
|
||||
filename.replace(248, 'o');
|
||||
filename.replace(214, 'O');
|
||||
filename.replace(216, 'O');
|
||||
filename.replace(228, 'a');
|
||||
filename.replace(229, 'a');
|
||||
filename.replace(196, 'A');
|
||||
filename.replace(197, 'A');
|
||||
filename.remove(OrganiseFormat::kValidFatCharacters);
|
||||
|
||||
return filename;
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverLoader::CreateCoverFilename(const QString &artist, const QString &album) {
|
||||
|
||||
QString filename(cover_pattern_);
|
||||
filename.replace("%albumartist", artist);
|
||||
filename.replace("%artist", artist);
|
||||
filename.replace("%album", album);
|
||||
return filename;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::CancelTask(const quint64 id) {
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
|
||||
@@ -83,7 +223,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions& options,
|
||||
return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url().toLocalFile(), song.image());
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QString &art_automatic, const QString &art_manual, const QString &song_filename, const QImage &embedded_image) {
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QString &song_filename, const QImage &embedded_image) {
|
||||
|
||||
Task task;
|
||||
task.options = options;
|
||||
@@ -102,6 +242,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
|
||||
metaObject()->invokeMethod(this, "ProcessTasks", Qt::QueuedConnection);
|
||||
|
||||
return task.id;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::ProcessTasks() {
|
||||
@@ -129,12 +270,13 @@ void AlbumCoverLoader::ProcessTask(Task *task) {
|
||||
|
||||
if (result.loaded_success) {
|
||||
QImage scaled = ScaleAndPad(task->options, result.image);
|
||||
emit ImageLoaded(task->id, scaled);
|
||||
emit ImageLoaded(task->id, scaled, result.image);
|
||||
emit ImageLoaded(task->id, result.cover_url, scaled);
|
||||
emit ImageLoaded(task->id, result.cover_url, scaled, result.image);
|
||||
return;
|
||||
}
|
||||
|
||||
NextState(task);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::NextState(Task *task) {
|
||||
@@ -146,8 +288,8 @@ void AlbumCoverLoader::NextState(Task *task) {
|
||||
}
|
||||
else {
|
||||
// Give up
|
||||
emit ImageLoaded(task->id, task->options.default_output_image_);
|
||||
emit ImageLoaded(task->id, task->options.default_output_image_, task->options.default_output_image_);
|
||||
emit ImageLoaded(task->id, QUrl(), task->options.default_output_image_);
|
||||
emit ImageLoaded(task->id, QUrl(), task->options.default_output_image_, task->options.default_output_image_);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -156,47 +298,56 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(const Task &task)
|
||||
|
||||
// An image embedded in the song itself takes priority
|
||||
if (!task.embedded_image.isNull())
|
||||
return TryLoadResult(false, true, ScaleAndPad(task.options, task.embedded_image));
|
||||
return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, task.embedded_image));
|
||||
|
||||
QUrl cover_url;
|
||||
|
||||
QString filename;
|
||||
switch (task.state) {
|
||||
case State_TryingAuto: filename = task.art_automatic; break;
|
||||
case State_TryingManual: filename = task.art_manual; break;
|
||||
case State_TryingAuto: cover_url = task.art_automatic; break;
|
||||
case State_TryingManual: cover_url = task.art_manual; break;
|
||||
}
|
||||
|
||||
if (filename == Song::kManuallyUnsetCover)
|
||||
return TryLoadResult(false, true, task.options.default_output_image_);
|
||||
if (cover_url.path() == Song::kManuallyUnsetCover)
|
||||
return TryLoadResult(false, true, QUrl(), task.options.default_output_image_);
|
||||
|
||||
if (filename == Song::kEmbeddedCover && !task.song_filename.isEmpty()) {
|
||||
else if (cover_url.path() == Song::kEmbeddedCover && !task.song_filename.isEmpty()) {
|
||||
const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task.song_filename);
|
||||
|
||||
if (!taglib_image.isNull())
|
||||
return TryLoadResult(false, true, ScaleAndPad(task.options, taglib_image));
|
||||
return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, taglib_image));
|
||||
}
|
||||
|
||||
if (filename.toLower().startsWith("http://") || filename.toLower().startsWith("https://")) {
|
||||
|
||||
QUrl url(filename);
|
||||
QNetworkReply *reply = network_->get(QNetworkRequest(url));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*)), reply);
|
||||
|
||||
remote_tasks_.insert(reply, task);
|
||||
return TryLoadResult(true, false, QImage());
|
||||
if (cover_url.path().isEmpty()) {
|
||||
return TryLoadResult(false, false, cover_url, task.options.default_output_image_);
|
||||
}
|
||||
else if (filename.isEmpty()) {
|
||||
// Avoid "QFSFileEngine::open: No file name specified" messages if we know that the filename is empty
|
||||
return TryLoadResult(false, false, task.options.default_output_image_);
|
||||
else {
|
||||
if (cover_url.scheme() == "file") {
|
||||
QImage image(cover_url.toLocalFile());
|
||||
return TryLoadResult(false, !image.isNull(), cover_url, image.isNull() ? task.options.default_output_image_ : image);
|
||||
}
|
||||
else if (cover_url.scheme().isEmpty()) { // Assume a local file with no scheme.
|
||||
QImage image(cover_url.path());
|
||||
return TryLoadResult(false, !image.isNull(), cover_url, image.isNull() ? task.options.default_output_image_ : image);
|
||||
}
|
||||
else if (!cover_url.scheme().isEmpty()) { // Assume remote URL
|
||||
|
||||
QNetworkReply *reply = network_->get(QNetworkRequest(cover_url));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, const QUrl&)), reply, cover_url);
|
||||
|
||||
remote_tasks_.insert(reply, task);
|
||||
return TryLoadResult(true, false, cover_url, QImage());
|
||||
}
|
||||
}
|
||||
|
||||
QImage image(filename);
|
||||
return TryLoadResult(false, !image.isNull(), image.isNull() ? task.options.default_output_image_ : image);
|
||||
return TryLoadResult(false, false, cover_url, task.options.default_output_image_);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) {
|
||||
void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (!remote_tasks_.contains(reply)) return;
|
||||
Task task = remote_tasks_.take(reply);
|
||||
|
||||
// Handle redirects.
|
||||
@@ -208,7 +359,7 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) {
|
||||
QNetworkRequest request = reply->request();
|
||||
request.setUrl(redirect.toUrl());
|
||||
QNetworkReply *redirected_reply = network_->get(request);
|
||||
NewClosure(redirected_reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*)), redirected_reply);
|
||||
NewClosure(redirected_reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, const QUrl&)), redirected_reply, redirect.toUrl());
|
||||
|
||||
remote_tasks_.insert(redirected_reply, task);
|
||||
return;
|
||||
@@ -219,8 +370,8 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) {
|
||||
QImage image;
|
||||
if (image.load(reply, 0)) {
|
||||
QImage scaled = ScaleAndPad(task.options, image);
|
||||
emit ImageLoaded(task.id, scaled);
|
||||
emit ImageLoaded(task.id, scaled, image);
|
||||
emit ImageLoaded(task.id, cover_url, scaled);
|
||||
emit ImageLoaded(task.id, cover_url, scaled, image);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -256,16 +407,30 @@ QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, con
|
||||
|
||||
}
|
||||
|
||||
QPixmap AlbumCoverLoader::TryLoadPixmap(const QString &automatic, const QString &manual, const QString &filename) {
|
||||
QPixmap AlbumCoverLoader::TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QString &filename) {
|
||||
|
||||
QPixmap ret;
|
||||
if (manual == Song::kManuallyUnsetCover) return ret;
|
||||
if (!manual.isEmpty()) ret.load(manual);
|
||||
if (manual.path() == Song::kManuallyUnsetCover) return ret;
|
||||
if (!manual.path().isEmpty()) {
|
||||
if (manual.scheme().isEmpty()) {
|
||||
ret.load(manual.path());
|
||||
}
|
||||
else if (manual.scheme() == "file") {
|
||||
ret.load(manual.toLocalFile());
|
||||
}
|
||||
}
|
||||
if (ret.isNull()) {
|
||||
if (automatic == Song::kEmbeddedCover && !filename.isNull())
|
||||
if (automatic.path() == Song::kEmbeddedCover && !filename.isEmpty()) {
|
||||
ret = QPixmap::fromImage(TagReaderClient::Instance()->LoadEmbeddedArtBlocking(filename));
|
||||
else if (!automatic.isEmpty())
|
||||
ret.load(automatic);
|
||||
}
|
||||
else if (!automatic.path().isEmpty()) {
|
||||
if (automatic.scheme().isEmpty()) {
|
||||
ret.load(automatic.path());
|
||||
}
|
||||
else if (manual.scheme() == "file") {
|
||||
ret.load(automatic.toLocalFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -37,6 +38,7 @@
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
|
||||
class Song;
|
||||
@@ -48,26 +50,31 @@ class AlbumCoverLoader : public QObject {
|
||||
public:
|
||||
explicit AlbumCoverLoader(QObject *parent = nullptr);
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
void Stop() { stop_requested_ = true; }
|
||||
|
||||
static QString ImageCacheDir();
|
||||
static QString ImageCacheDir(const Song::Source source);
|
||||
QString CreateCoverFilename(const QString &artist, const QString &album);
|
||||
QString CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url);
|
||||
QString AlbumCoverFileName(QString artist, QString album);
|
||||
|
||||
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song);
|
||||
virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QString &art_automatic, const QString &art_manual, const QString &song_filename = QString(), const QImage &embedded_image = QImage());
|
||||
virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QString &song_filename = QString(), const QImage &embedded_image = QImage());
|
||||
|
||||
void CancelTask(quint64 id);
|
||||
void CancelTask(const quint64 id);
|
||||
void CancelTasks(const QSet<quint64> &ids);
|
||||
|
||||
static QPixmap TryLoadPixmap(const QString &automatic, const QString &manual, const QString &filename = QString());
|
||||
static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QString &filename = QString());
|
||||
static QImage ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image);
|
||||
|
||||
signals:
|
||||
void ImageLoaded(quint64 id, const QImage &image);
|
||||
void ImageLoaded(quint64 id, const QImage &scaled, const QImage &original);
|
||||
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
||||
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original);
|
||||
|
||||
protected slots:
|
||||
void ProcessTasks();
|
||||
void RemoteFetchFinished(QNetworkReply *reply);
|
||||
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
|
||||
|
||||
protected:
|
||||
enum State {
|
||||
@@ -81,8 +88,8 @@ signals:
|
||||
AlbumCoverLoaderOptions options;
|
||||
|
||||
quint64 id;
|
||||
QString art_automatic;
|
||||
QString art_manual;
|
||||
QUrl art_automatic;
|
||||
QUrl art_manual;
|
||||
QString song_filename;
|
||||
QImage embedded_image;
|
||||
State state;
|
||||
@@ -90,10 +97,12 @@ signals:
|
||||
};
|
||||
|
||||
struct TryLoadResult {
|
||||
TryLoadResult(bool async, bool success, const QImage &i) : started_async(async), loaded_success(success), image(i) {}
|
||||
TryLoadResult(bool async, bool success, const QUrl &_cover_url, const QImage &_image) : started_async(async), loaded_success(success), cover_url(_cover_url), image(_image) {}
|
||||
|
||||
bool started_async;
|
||||
bool loaded_success;
|
||||
|
||||
QUrl cover_url;
|
||||
QImage image;
|
||||
};
|
||||
|
||||
@@ -111,6 +120,14 @@ signals:
|
||||
NetworkAccessManager *network_;
|
||||
|
||||
static const int kMaxRedirects = 3;
|
||||
|
||||
bool cover_album_dir_;
|
||||
CollectionSettingsPage::SaveCover cover_filename_;
|
||||
QString cover_pattern_;
|
||||
bool cover_overwrite_;
|
||||
bool cover_lowercase_;
|
||||
bool cover_replace_spaces_;
|
||||
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERLOADER_H
|
||||
|
||||
@@ -39,4 +39,3 @@ struct AlbumCoverLoaderOptions {
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERLOADEROPTIONS_H
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
|
||||
ui_->action_add_to_playlist->setIcon(IconLoader::Load("media-play" ));
|
||||
ui_->action_load->setIcon(IconLoader::Load("media-play" ));
|
||||
|
||||
album_cover_choice_controller_->SetApplication(app_);
|
||||
album_cover_choice_controller_->Init(app_);
|
||||
|
||||
cover_searcher_ = new AlbumCoverSearcher(no_cover_item_icon_, app_, this);
|
||||
cover_export_ = new AlbumCoverExport(this);
|
||||
@@ -146,7 +146,7 @@ AlbumCoverManager::~AlbumCoverManager() {
|
||||
}
|
||||
|
||||
void AlbumCoverManager::ReloadSettings() {
|
||||
album_cover_choice_controller_->ReloadSettings();
|
||||
app_->album_cover_loader()->ReloadSettings();
|
||||
}
|
||||
|
||||
CollectionBackend *AlbumCoverManager::backend() const {
|
||||
@@ -198,7 +198,7 @@ void AlbumCoverManager::Init() {
|
||||
connect(ui_->view, SIGNAL(clicked()), ui_->view, SLOT(showMenu()));
|
||||
connect(ui_->button_fetch, SIGNAL(clicked()), SLOT(FetchAlbumCovers()));
|
||||
connect(ui_->export_covers, SIGNAL(clicked()), SLOT(ExportCovers()));
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)), SLOT(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)));
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(const quint64, const QUrl&, const QImage&, const CoverSearchStatistics&)), SLOT(AlbumCoverFetched(const quint64, const QUrl&, const QImage&, const CoverSearchStatistics&)));
|
||||
connect(ui_->action_fetch, SIGNAL(triggered()), SLOT(FetchSingleCover()));
|
||||
connect(ui_->albums, SIGNAL(doubleClicked(QModelIndex)), SLOT(AlbumDoubleClicked(QModelIndex)));
|
||||
connect(ui_->action_add_to_playlist, SIGNAL(triggered()), SLOT(AddSelectedToPlaylist()));
|
||||
@@ -214,7 +214,7 @@ void AlbumCoverManager::Init() {
|
||||
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
|
||||
}
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(CoverImageLoaded(quint64, QImage)));
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(const quint64, const QUrl&, const QImage&)), SLOT(CoverImageLoaded(const quint64, const QUrl&, const QImage&)));
|
||||
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
@@ -357,7 +357,7 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::CoverImageLoaded(quint64 id, const QImage &image) {
|
||||
void AlbumCoverManager::CoverImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
if (!cover_loading_tasks_.contains(id)) return;
|
||||
|
||||
@@ -455,14 +455,14 @@ void AlbumCoverManager::FetchAlbumCovers() {
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics) {
|
||||
void AlbumCoverManager::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
|
||||
|
||||
if (!cover_fetching_tasks_.contains(id))
|
||||
return;
|
||||
|
||||
QListWidgetItem *item = cover_fetching_tasks_.take(id);
|
||||
if (!image.isNull()) {
|
||||
SaveAndSetCover(item, image);
|
||||
SaveAndSetCover(item, cover_url, image);
|
||||
}
|
||||
|
||||
if (cover_fetching_tasks_.isEmpty()) {
|
||||
@@ -537,7 +537,7 @@ Song AlbumCoverManager::GetFirstSelectedAsSong() {
|
||||
|
||||
Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) {
|
||||
|
||||
Song result;
|
||||
Song result(Song::Source_Collection);
|
||||
|
||||
QString title = item->data(Role_AlbumName).toString();
|
||||
QString artist_name = EffectiveAlbumArtistName(*item);
|
||||
@@ -554,8 +554,8 @@ Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) {
|
||||
|
||||
result.set_url(item->data(Role_FirstUrl).toUrl());
|
||||
|
||||
result.set_art_automatic(item->data(Role_PathAutomatic).toString());
|
||||
result.set_art_manual(item->data(Role_PathManual).toString());
|
||||
result.set_art_automatic(item->data(Role_PathAutomatic).toUrl());
|
||||
result.set_art_manual(item->data(Role_PathManual).toUrl());
|
||||
|
||||
// force validity
|
||||
result.set_valid(true);
|
||||
@@ -587,10 +587,10 @@ void AlbumCoverManager::FetchSingleCover() {
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::UpdateCoverInList(QListWidgetItem *item, const QString &cover) {
|
||||
void AlbumCoverManager::UpdateCoverInList(QListWidgetItem *item, const QUrl &cover_url) {
|
||||
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QString(), cover);
|
||||
item->setData(Role_PathManual, cover);
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), cover_url);
|
||||
item->setData(Role_PathManual, cover_url);
|
||||
cover_loading_tasks_[id] = item;
|
||||
|
||||
}
|
||||
@@ -602,10 +602,10 @@ void AlbumCoverManager::LoadCoverFromFile() {
|
||||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->LoadCoverFromFile(&song);
|
||||
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromFile(&song);
|
||||
|
||||
if (!cover.isEmpty()) {
|
||||
UpdateCoverInList(item, cover);
|
||||
if (!cover_url.isEmpty()) {
|
||||
UpdateCoverInList(item, cover_url);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -622,11 +622,23 @@ void AlbumCoverManager::SaveCoverToFile() {
|
||||
image = no_cover_image_;
|
||||
}
|
||||
else {
|
||||
if (!song.art_manual().isEmpty() && QFile::exists(song.art_manual())) {
|
||||
image = QImage(song.art_manual());
|
||||
if (!song.art_manual().isEmpty() && !song.art_manual().path().isEmpty() &&
|
||||
(
|
||||
(song.art_manual().scheme().isEmpty() && QFile::exists(song.art_manual().path()))
|
||||
||
|
||||
(song.art_manual().scheme() == "file" && QFile::exists(song.art_manual().toLocalFile()))
|
||||
)
|
||||
) {
|
||||
image = QImage(song.art_manual().toLocalFile());
|
||||
}
|
||||
else if(!song.art_automatic().isEmpty() && QFile::exists(song.art_automatic())) {
|
||||
image = QImage(song.art_automatic());
|
||||
if (!song.art_automatic().isEmpty() && !song.art_automatic().path().isEmpty() &&
|
||||
(
|
||||
(song.art_automatic().scheme().isEmpty() && QFile::exists(song.art_automatic().path()))
|
||||
||
|
||||
(song.art_automatic().scheme() == "file" && QFile::exists(song.art_automatic().toLocalFile()))
|
||||
)
|
||||
) {
|
||||
image = QImage(song.art_automatic().toLocalFile());
|
||||
}
|
||||
else {
|
||||
image = no_cover_image_;
|
||||
@@ -644,10 +656,10 @@ void AlbumCoverManager::LoadCoverFromURL() {
|
||||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->LoadCoverFromURL(&song);
|
||||
QUrl cover_url = album_cover_choice_controller_->LoadCoverFromURL(&song);
|
||||
|
||||
if (!cover.isEmpty()) {
|
||||
UpdateCoverInList(item, cover);
|
||||
if (!cover_url.isEmpty()) {
|
||||
UpdateCoverInList(item, cover_url);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -659,18 +671,18 @@ void AlbumCoverManager::SearchForCover() {
|
||||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->SearchForCover(&song);
|
||||
if (cover.isEmpty()) return;
|
||||
QUrl cover_url = album_cover_choice_controller_->SearchForCover(&song);
|
||||
if (cover_url.isEmpty()) return;
|
||||
|
||||
// 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_->SaveCover(¤t_song, cover);
|
||||
album_cover_choice_controller_->SaveCoverToSong(¤t_song, cover_url);
|
||||
}
|
||||
|
||||
UpdateCoverInList(current, cover);
|
||||
UpdateCoverInList(current, cover_url);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -682,17 +694,17 @@ void AlbumCoverManager::UnsetCover() {
|
||||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->UnsetCover(&song);
|
||||
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);
|
||||
current->setData(Role_PathManual, cover_url);
|
||||
|
||||
// Don't save the first one twice
|
||||
if (current != item) {
|
||||
Song current_song = ItemAsSong(current);
|
||||
album_cover_choice_controller_->SaveCover(¤t_song, cover);
|
||||
album_cover_choice_controller_->SaveCoverToSong(¤t_song, cover_url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,22 +788,22 @@ void AlbumCoverManager::LoadSelectedToPlaylist() {
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::SaveAndSetCover(QListWidgetItem *item, const QImage &image) {
|
||||
void AlbumCoverManager::SaveAndSetCover(QListWidgetItem *item, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
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();
|
||||
|
||||
QString path = album_cover_choice_controller_->SaveCoverToFileAutomatic((!albumartist.isEmpty() ? albumartist : artist), artist, album, url.adjusted(QUrl::RemoveFilename).path(), image);
|
||||
if (path.isEmpty()) return;
|
||||
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;
|
||||
|
||||
// Save the image in the database
|
||||
collection_backend_->UpdateManualAlbumArtAsync(artist, albumartist, album, path);
|
||||
collection_backend_->UpdateManualAlbumArtAsync(artist, albumartist, album, new_cover_url);
|
||||
|
||||
// Update the icon in our list
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QString(), path);
|
||||
item->setData(Role_PathManual, path);
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
@@ -93,11 +93,11 @@ class AlbumCoverManager : public QMainWindow {
|
||||
|
||||
private slots:
|
||||
void ArtistChanged(QListWidgetItem *current);
|
||||
void CoverImageLoaded(quint64 id, const QImage &image);
|
||||
void CoverImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
||||
void UpdateFilter();
|
||||
void FetchAlbumCovers();
|
||||
void ExportCovers();
|
||||
void AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics);
|
||||
void AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics);
|
||||
void CancelRequests();
|
||||
|
||||
// On the context menu
|
||||
@@ -115,7 +115,7 @@ class AlbumCoverManager : public QMainWindow {
|
||||
void AddSelectedToPlaylist();
|
||||
void LoadSelectedToPlaylist();
|
||||
|
||||
void UpdateCoverInList(QListWidgetItem *item, const QString &cover);
|
||||
void UpdateCoverInList(QListWidgetItem *item, const QUrl &cover);
|
||||
void UpdateExportStatus(int exported, int bad, int count);
|
||||
|
||||
private:
|
||||
@@ -152,7 +152,7 @@ class AlbumCoverManager : public QMainWindow {
|
||||
|
||||
void UpdateStatusText();
|
||||
bool ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const;
|
||||
void SaveAndSetCover(QListWidgetItem *item, const QImage &image);
|
||||
void SaveAndSetCover(QListWidgetItem *item, const QUrl &cover_url, const QImage &image);
|
||||
|
||||
private:
|
||||
Ui_CoverManager *ui_;
|
||||
|
||||
@@ -127,7 +127,7 @@ AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *
|
||||
options_.scale_output_image_ = false;
|
||||
options_.pad_output_image_ = false;
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(ImageLoaded(quint64, QImage)));
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(ImageLoaded(quint64, QUrl, QImage)));
|
||||
|
||||
connect(ui_->search, SIGNAL(clicked()), SLOT(Search()));
|
||||
connect(ui_->covers, SIGNAL(doubleClicked(QModelIndex)), SLOT(CoverDoubleClicked(QModelIndex)));
|
||||
@@ -145,7 +145,7 @@ AlbumCoverSearcher::~AlbumCoverSearcher() {
|
||||
void AlbumCoverSearcher::Init(AlbumCoverFetcher *fetcher) {
|
||||
|
||||
fetcher_ = fetcher;
|
||||
connect(fetcher_, SIGNAL(SearchFinished(quint64,CoverSearchResults,CoverSearchStatistics)), SLOT(SearchFinished(quint64, CoverSearchResults)));
|
||||
connect(fetcher_, SIGNAL(SearchFinished(quint64, CoverSearchResults, CoverSearchStatistics)), SLOT(SearchFinished(quint64, CoverSearchResults)));
|
||||
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ void AlbumCoverSearcher::Search() {
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults &results) {
|
||||
void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverSearchResults &results) {
|
||||
|
||||
if (id != id_)
|
||||
return;
|
||||
@@ -212,7 +212,7 @@ void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults &re
|
||||
for (const CoverSearchResult &result : results) {
|
||||
if (result.image_url.isEmpty()) continue;
|
||||
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url.toString(), QString());
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url, QUrl());
|
||||
|
||||
QStandardItem *item = new QStandardItem;
|
||||
item->setIcon(no_cover_icon_);
|
||||
@@ -232,7 +232,7 @@ void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults &re
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverSearcher::ImageLoaded(quint64 id, const QImage &image) {
|
||||
void AlbumCoverSearcher::ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
|
||||
|
||||
if (!cover_loading_tasks_.contains(id)) return;
|
||||
QStandardItem *item = cover_loading_tasks_.take(id);
|
||||
|
||||
@@ -87,8 +87,8 @@ protected:
|
||||
|
||||
private slots:
|
||||
void Search();
|
||||
void SearchFinished(quint64 id, const CoverSearchResults &results);
|
||||
void ImageLoaded(quint64 id, const QImage &image);
|
||||
void SearchFinished(const quint64 id, const CoverSearchResults &results);
|
||||
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
||||
|
||||
void CoverDoubleClicked(const QModelIndex &index);
|
||||
|
||||
|
||||
@@ -59,11 +59,11 @@ QString CoverExportRunnable::GetCoverPath() {
|
||||
// Export downloaded covers?
|
||||
}
|
||||
else if (!song_.art_manual().isEmpty() && dialog_result_.export_downloaded_) {
|
||||
return song_.art_manual();
|
||||
return song_.art_manual().toLocalFile();
|
||||
// Export embedded covers?
|
||||
}
|
||||
else if (!song_.art_automatic().isEmpty() && song_.art_automatic() == Song::kEmbeddedCover && dialog_result_.export_embedded_) {
|
||||
return song_.art_automatic();
|
||||
else if (!song_.art_automatic().isEmpty() && song_.art_automatic().path() == Song::kEmbeddedCover && dialog_result_.export_embedded_) {
|
||||
return song_.art_automatic().toLocalFile();
|
||||
}
|
||||
else {
|
||||
return QString();
|
||||
@@ -168,8 +168,7 @@ void CoverExportRunnable::ExportCover() {
|
||||
return;
|
||||
}
|
||||
|
||||
// we're handling overwrite as remove + copy so we need to delete the old file
|
||||
// first
|
||||
// We're handling overwrite as remove + copy so we need to delete the old file first
|
||||
if (dialog_result_.overwrite_ != AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) {
|
||||
if (!QFile::remove(new_file)) {
|
||||
EmitCoverSkipped();
|
||||
@@ -186,7 +185,7 @@ void CoverExportRunnable::ExportCover() {
|
||||
}
|
||||
}
|
||||
else {
|
||||
// automatic or manual cover, available in an image file
|
||||
// Automatic or manual cover, available in an image file
|
||||
if (!QFile::copy(cover_path, new_file)) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
|
||||
@@ -29,8 +29,9 @@
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include "albumcoverfetcher.h"
|
||||
|
||||
class Application;
|
||||
struct CoverSearchResult;
|
||||
|
||||
// Each implementation of this interface downloads covers from one online service.
|
||||
// There are no limitations on what this service might be - last.fm, Amazon, Google Images - you name it.
|
||||
@@ -53,7 +54,7 @@ class CoverProvider : public QObject {
|
||||
virtual void CancelSearch(int id) {}
|
||||
|
||||
signals:
|
||||
void SearchFinished(int id, const QList<CoverSearchResult>& results);
|
||||
void SearchFinished(int id, const CoverSearchResults& results);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
|
||||
@@ -28,63 +28,71 @@
|
||||
#include <QString>
|
||||
#include <QStringBuilder>
|
||||
#include <QImage>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "albumcoverloader.h"
|
||||
#include "currentartloader.h"
|
||||
#include "currentalbumcoverloader.h"
|
||||
|
||||
CurrentArtLoader::CurrentArtLoader(Application *app, QObject *parent)
|
||||
CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *parent)
|
||||
: QObject(parent),
|
||||
app_(app),
|
||||
temp_file_pattern_(QDir::tempPath() + "/strawberry-art-XXXXXX.jpg"),
|
||||
id_(0)
|
||||
temp_file_pattern_(QDir::tempPath() + "/strawberry-cover-XXXXXX.jpg"),
|
||||
id_(0)
|
||||
{
|
||||
|
||||
options_.scale_output_image_ = false;
|
||||
options_.pad_output_image_ = false;
|
||||
options_.default_output_image_ = QImage(":/pictures/cdcase.png");
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(TempArtLoaded(quint64, QImage)));
|
||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(LoadArt(Song)));
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(TempAlbumCoverLoaded(quint64, QUrl, QImage)));
|
||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(LoadAlbumCover(Song)));
|
||||
|
||||
}
|
||||
|
||||
CurrentArtLoader::~CurrentArtLoader() {}
|
||||
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {}
|
||||
|
||||
void CurrentArtLoader::LoadArt(const Song &song) {
|
||||
void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) {
|
||||
last_song_ = song;
|
||||
id_ = app_->album_cover_loader()->LoadImageAsync(options_, last_song_);
|
||||
}
|
||||
|
||||
void CurrentArtLoader::TempArtLoaded(quint64 id, const QImage &image) {
|
||||
void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image) {
|
||||
|
||||
if (id != id_) return;
|
||||
id_ = 0;
|
||||
|
||||
QString uri;
|
||||
QString thumbnail_uri;
|
||||
QUrl cover_url;
|
||||
QUrl thumbnail_url;
|
||||
QImage thumbnail;
|
||||
|
||||
if (!image.isNull()) {
|
||||
temp_art_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_art_->setAutoRemove(true);
|
||||
temp_art_->open();
|
||||
image.save(temp_art_->fileName(), "JPEG");
|
||||
|
||||
QString filename;
|
||||
|
||||
temp_cover_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_cover_->setAutoRemove(true);
|
||||
temp_cover_->open();
|
||||
|
||||
image.save(temp_cover_->fileName(), "JPEG");
|
||||
|
||||
// Scale the image down to make a thumbnail. It's a bit crap doing it here since it's the GUI thread, but the alternative is hard.
|
||||
temp_art_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_art_thumbnail_->open();
|
||||
temp_art_thumbnail_->setAutoRemove(true);
|
||||
temp_cover_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_cover_thumbnail_->open();
|
||||
temp_cover_thumbnail_->setAutoRemove(true);
|
||||
thumbnail = image.scaledToHeight(120, Qt::SmoothTransformation);
|
||||
thumbnail.save(temp_art_thumbnail_->fileName(), "JPEG");
|
||||
thumbnail.save(temp_cover_thumbnail_->fileName(), "JPEG");
|
||||
|
||||
uri = "file://" + temp_art_->fileName();
|
||||
thumbnail_uri = "file://" + temp_art_thumbnail_->fileName();
|
||||
cover_url.setScheme("file");
|
||||
cover_url.setPath(temp_cover_->fileName());
|
||||
|
||||
thumbnail_url.setScheme("file");
|
||||
thumbnail_url.setPath(temp_cover_thumbnail_->fileName());
|
||||
}
|
||||
|
||||
emit ArtLoaded(last_song_, uri, image);
|
||||
emit ThumbnailLoaded(last_song_, thumbnail_uri, thumbnail);
|
||||
emit AlbumCoverLoaded(last_song_, cover_url, image);
|
||||
emit ThumbnailLoaded(last_song_, thumbnail_url, thumbnail);
|
||||
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CURRENTARTLOADER_H
|
||||
#define CURRENTARTLOADER_H
|
||||
#ifndef CURRENTALBUMCOVERLOADER_H
|
||||
#define CURRENTALBUMCOVERLOADER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -36,25 +36,25 @@
|
||||
|
||||
class Application;
|
||||
|
||||
class CurrentArtLoader : public QObject {
|
||||
class CurrentAlbumCoverLoader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CurrentArtLoader(Application *app, QObject *parent = nullptr);
|
||||
~CurrentArtLoader();
|
||||
explicit CurrentAlbumCoverLoader(Application *app, QObject *parent = nullptr);
|
||||
~CurrentAlbumCoverLoader();
|
||||
|
||||
const AlbumCoverLoaderOptions &options() const { return options_; }
|
||||
const Song &last_song() const { return last_song_; }
|
||||
|
||||
public slots:
|
||||
void LoadArt(const Song &song);
|
||||
void LoadAlbumCover(const Song &song);
|
||||
|
||||
signals:
|
||||
void ArtLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||
void ThumbnailLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||
signals:
|
||||
void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
|
||||
void ThumbnailLoaded(const Song &song, const QUrl &thumbnail_uri, const QImage &image);
|
||||
|
||||
private slots:
|
||||
void TempArtLoaded(quint64 id, const QImage &image);
|
||||
void TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
@@ -62,11 +62,12 @@ signals:
|
||||
|
||||
QString temp_file_pattern_;
|
||||
|
||||
std::unique_ptr<QTemporaryFile> temp_art_;
|
||||
std::unique_ptr<QTemporaryFile> temp_art_thumbnail_;
|
||||
std::unique_ptr<QTemporaryFile> temp_cover_;
|
||||
std::unique_ptr<QTemporaryFile> temp_cover_thumbnail_;
|
||||
quint64 id_;
|
||||
|
||||
Song last_song_;
|
||||
|
||||
};
|
||||
|
||||
#endif // CURRENTARTLOADER_H
|
||||
#endif // CURRENTALBUMCOVERLOADER_H
|
||||
@@ -53,13 +53,13 @@ DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): Cov
|
||||
bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
|
||||
typedef QPair<QString, QString> Param;
|
||||
typedef QList<Param> Parameters;
|
||||
typedef QList<Param> Params;
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
Parameters params = Parameters() << Param("output", "json")
|
||||
<< Param("q", QString(artist + " " + album))
|
||||
<< Param("limit", QString::number(kLimit));
|
||||
const Params params = Params() << Param("output", "json")
|
||||
<< Param("q", QString(artist + " " + album))
|
||||
<< Param("limit", QString::number(kLimit));
|
||||
|
||||
QUrlQuery url_query;
|
||||
for (const Param ¶m : params) {
|
||||
@@ -88,18 +88,18 @@ QByteArray DeezerCoverProvider::GetReplyData(QNetworkReply *reply) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
QString error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(error);
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "error" - then use that instead.
|
||||
// See if there is Json data containing "error" object - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
QString error;
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error")) {
|
||||
QJsonValue json_value_error = json_obj["error"];
|
||||
@@ -108,12 +108,19 @@ QByteArray DeezerCoverProvider::GetReplyData(QNetworkReply *reply) {
|
||||
int code = json_error["code"].toInt();
|
||||
QString message = json_error["message"].toString();
|
||||
QString type = json_error["type"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
error = QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
@@ -184,30 +184,32 @@ QByteArray DiscogsCoverProvider::GetReplyData(QNetworkReply *reply) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
QString error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(error);
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "message" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QString error;
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("message")) {
|
||||
failure_reason = json_obj["message"].toString();
|
||||
error = json_obj["message"].toString();
|
||||
}
|
||||
}
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
Error(failure_reason);
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class Application;
|
||||
|
||||
// This struct represents a single search-for-cover request. It identifies and describes the request.
|
||||
struct DiscogsCoverSearchContext {
|
||||
DiscogsCoverSearchContext() : id(-1), r_count(0) {}
|
||||
|
||||
// The unique request identifier
|
||||
int id;
|
||||
@@ -55,6 +56,7 @@ Q_DECLARE_METATYPE(DiscogsCoverSearchContext)
|
||||
|
||||
// This struct represents a single release request. It identifies and describes the request.
|
||||
struct DiscogsCoverReleaseContext {
|
||||
DiscogsCoverReleaseContext() : id(-1) {}
|
||||
|
||||
int id; // The unique request identifier
|
||||
int s_id; // The search request identifier
|
||||
@@ -102,4 +104,3 @@ class DiscogsCoverProvider : public CoverProvider {
|
||||
};
|
||||
|
||||
#endif // DISCOGSCOVERPROVIDER_H
|
||||
|
||||
|
||||
@@ -204,32 +204,33 @@ QByteArray LastFmCoverProvider::GetReplyData(QNetworkReply *reply) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "error" and "message" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QString error;
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error") && json_obj.contains("message")) {
|
||||
int error = json_obj["error"].toInt();
|
||||
int code = json_obj["error"].toInt();
|
||||
QString message = json_obj["message"].toString();
|
||||
failure_reason = "Error: " + QString::number(error) + ": " + message;
|
||||
error = "Error: " + QString::number(code) + ": " + message;
|
||||
}
|
||||
}
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
Error(failure_reason);
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ QByteArray MusicbrainzCoverProvider::GetReplyData(QNetworkReply *reply) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
@@ -192,22 +192,24 @@ QByteArray MusicbrainzCoverProvider::GetReplyData(QNetworkReply *reply) {
|
||||
else {
|
||||
// See if there is Json data containing "error" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QString error;
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
if (json_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error")) {
|
||||
failure_reason = json_obj["error"].toString();
|
||||
error = json_obj["error"].toString();
|
||||
}
|
||||
}
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
Error(failure_reason);
|
||||
Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
@@ -57,14 +57,14 @@ TidalCoverProvider::TidalCoverProvider(Application *app, QObject *parent) :
|
||||
|
||||
}
|
||||
|
||||
bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) {
|
||||
|
||||
if (!service_ || !service_->authenticated()) return false;
|
||||
|
||||
QList<Param> parameters;
|
||||
parameters << Param("query", QString(artist + " " + album));
|
||||
parameters << Param("limit", QString::number(kLimit));
|
||||
QNetworkReply *reply = CreateRequest("search/albums", parameters);
|
||||
ParamList params = ParamList() << Param("query", QString(artist + " " + album))
|
||||
<< Param("limit", QString::number(kLimit));
|
||||
|
||||
QNetworkReply *reply = CreateRequest("search/albums", params);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, int)), reply, id);
|
||||
|
||||
return true;
|
||||
@@ -73,20 +73,13 @@ bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album
|
||||
|
||||
void TidalCoverProvider::CancelSearch(int id) {}
|
||||
|
||||
QNetworkReply *TidalCoverProvider::CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_supplied) {
|
||||
QNetworkReply *TidalCoverProvider::CreateRequest(const QString &ressource_name, const ParamList ¶ms_supplied) {
|
||||
|
||||
typedef QPair<QString, QString> Param;
|
||||
typedef QList<Param> ParamList;
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
ParamList parameters = ParamList()
|
||||
<< params_supplied
|
||||
<< Param("sessionId", service_->session_id())
|
||||
<< Param("countryCode", service_->country_code());
|
||||
const ParamList params = ParamList() << params_supplied
|
||||
<< Param("countryCode", service_->country_code());
|
||||
|
||||
QUrlQuery url_query;
|
||||
for (const Param& param : parameters) {
|
||||
for (const Param ¶m : params) {
|
||||
EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
|
||||
url_query.addQueryItem(encoded_param.first, encoded_param.second);
|
||||
}
|
||||
@@ -95,7 +88,8 @@ QNetworkReply *TidalCoverProvider::CreateRequest(const QString &ressource_name,
|
||||
url.setQuery(url_query);
|
||||
QNetworkRequest req(url);
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
req.setRawHeader("X-Tidal-SessionId", service_->session_id().toUtf8());
|
||||
if (!service_->access_token().isEmpty()) req.setRawHeader("authorization", "Bearer " + service_->access_token().toUtf8());
|
||||
if (!service_->session_id().isEmpty()) req.setRawHeader("X-Tidal-SessionId", service_->session_id().toUtf8());
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
|
||||
return reply;
|
||||
@@ -106,38 +100,42 @@ QByteArray TidalCoverProvider::GetReplyData(QNetworkReply *reply, QString &error
|
||||
|
||||
QByteArray data;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
if (reply->error() != QNetworkReply::NoError && reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "userMessage" - then use that instead.
|
||||
// See if there is Json data containing "status" and "userMessage" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
|
||||
int status = 0;
|
||||
int sub_status = 0;
|
||||
QString failure_reason;
|
||||
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("userMessage")) {
|
||||
status = json_obj["status"].toInt();
|
||||
sub_status = json_obj["subStatus"].toInt();
|
||||
QString user_message = json_obj["userMessage"].toString();
|
||||
failure_reason = QString("%1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
error = QString("%1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (error.isEmpty()) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
error = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
else {
|
||||
error = QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt());
|
||||
}
|
||||
}
|
||||
if (status == 401 && sub_status == 6001) { // User does not have a valid session
|
||||
service_->Logout();
|
||||
}
|
||||
error = Error(failure_reason);
|
||||
error = Error(error);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
@@ -195,7 +193,7 @@ QJsonValue TidalCoverProvider::ExtractItems(QJsonObject &json_obj, QString &erro
|
||||
|
||||
}
|
||||
|
||||
void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, int id) {
|
||||
void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
|
||||
@@ -43,20 +43,21 @@ class TidalCoverProvider : public CoverProvider {
|
||||
|
||||
public:
|
||||
explicit TidalCoverProvider(Application *app, QObject *parent = nullptr);
|
||||
bool StartSearch(const QString &artist, const QString &album, int id);
|
||||
bool StartSearch(const QString &artist, const QString &album, const int id);
|
||||
void CancelSearch(int id);
|
||||
|
||||
private slots:
|
||||
void HandleSearchReply(QNetworkReply *reply, int id);
|
||||
void HandleSearchReply(QNetworkReply *reply, const int id);
|
||||
|
||||
private:
|
||||
typedef QPair<QString, QString> Param;
|
||||
typedef QList<Param> ParamList;
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
static const char *kApiUrl;
|
||||
static const char *kResourcesUrl;
|
||||
static const char *kApiTokenB64;
|
||||
static const int kLimit;
|
||||
|
||||
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_supplied);
|
||||
QNetworkReply *CreateRequest(const QString &ressource_name, const ParamList ¶ms_supplied);
|
||||
QByteArray GetReplyData(QNetworkReply *reply, QString &error);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
|
||||
QJsonValue ExtractItems(QByteArray &data, QString &error);
|
||||
|
||||
Reference in New Issue
Block a user