Improve album cover loader, lyrics search and streaming support

- Improve album cover loader
- Add album cover loader result struct
- Move album cover thumbnail scaling to album cover loader
- Make init art manual look for album cover images in song directory
- Make album cover search work for songs outside of collection and
  streams
- Make album cover search work based on artist + title if album is not
  present
- Update art manual in playlist for local files, devices and CDDA
- Make lyrics search work for streams
- Add stream dialog to menu
- Remove dead code in InternetSearchModel
- Simplify code in InternetSearchView
This commit is contained in:
Jonas Kvinge
2020-04-20 18:03:18 +02:00
parent ab2ffd9ac1
commit a2c0e4d4b1
77 changed files with 1057 additions and 584 deletions

View File

@@ -312,12 +312,14 @@ void AlbumCoverChoiceController::ShowCover(const Song &song, const QPixmap &pixm
}
void AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
qint64 AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
qint64 id = cover_fetcher_->FetchAlbumCover(song.effective_albumartist(), song.effective_album(), true);
qint64 id = cover_fetcher_->FetchAlbumCover(song.effective_albumartist(), song.album(), song.title(), true);
cover_fetching_tasks_[id] = song;
return id;
}
void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
@@ -371,7 +373,7 @@ void AlbumCoverChoiceController::SaveCoverToSong(Song *song, const QUrl &cover_u
}
if (song->url() == app_->current_albumcover_loader()->last_song().url()) {
if (*song == app_->current_albumcover_loader()->last_song()) {
app_->current_albumcover_loader()->LoadAlbumCover(*song);
}

View File

@@ -110,7 +110,7 @@ class AlbumCoverChoiceController : public QWidget {
void ShowCover(const Song &song, const QPixmap &pixmap);
// Search for covers automatically
void SearchCoverAutomatically(const Song &song);
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);
@@ -124,7 +124,7 @@ class AlbumCoverChoiceController : public QWidget {
static bool CanAcceptDrag(const QDragEnterEvent *e);
signals:
signals:
void AutomaticCoverSearchDone();
private slots:

View File

@@ -44,14 +44,15 @@ AlbumCoverFetcher::AlbumCoverFetcher(CoverProviders *cover_providers, QObject *p
connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests()));
}
quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, bool fetchall) {
quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, const QString &title, bool fetchall) {
CoverSearchRequest request;
request.id = next_id_++;
request.artist = artist;
request.album = album;
request.album.remove(Song::kAlbumRemoveDisc);
request.album.remove(Song::kAlbumRemoveMisc);
request.album = request.album.remove(Song::kAlbumRemoveDisc);
request.album = request.album.remove(Song::kAlbumRemoveMisc);
request.title = title;
request.search = false;
request.fetchall = fetchall;
@@ -60,14 +61,15 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString
}
quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString &album) {
quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString &album, const QString &title) {
CoverSearchRequest request;
request.id = next_id_++;
request.artist = artist;
request.album = album;
request.album.remove(Song::kAlbumRemoveDisc);
request.album.remove(Song::kAlbumRemoveMisc);
request.album = request.album.remove(Song::kAlbumRemoveDisc);
request.album = request.album.remove(Song::kAlbumRemoveMisc);
request.title = title;
request.search = true;
request.fetchall = false;

View File

@@ -50,6 +50,7 @@ struct CoverSearchRequest {
// A search query
QString artist;
QString album;
QString title;
// Is this only a search request or should we also fetch the first cover that's found?
bool search;
@@ -92,12 +93,12 @@ class AlbumCoverFetcher : public QObject {
static const int kMaxConcurrentRequests;
quint64 SearchForCovers(const QString &artist, const QString &album);
quint64 FetchAlbumCover(const QString &artist, const QString &album, const bool fetchall);
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);
void Clear();
signals:
signals:
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);

View File

@@ -42,11 +42,6 @@
#include "coverprovider.h"
#include "coverproviders.h"
using std::min;
using std::max;
using std::stable_sort;
using std::sqrt;
const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 25000;
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 3000;
const int AlbumCoverFetcherSearch::kTargetSize = 500;
@@ -81,13 +76,16 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
// Skip provider if it does not have fetchall set, and we are doing fetchall - "Fetch Missing Covers".
if (!provider->fetchall() && request_.fetchall) {
//qLog(Debug) << "Skipping provider" << provider->name();
continue;
}
// If album is missing, check if we can still use this provider by searching using artist + title.
if (!provider->allow_missing_album() && request_.album.isEmpty()) {
continue;
}
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);
const bool success = provider->StartSearch(request_.artist, request_.album, request_.title, id);
if (success) {
pending_requests_[id] = provider;
@@ -112,7 +110,7 @@ void AlbumCoverFetcherSearch::ProviderSearchFinished(const int id, const CoverSe
CoverProvider *provider = pending_requests_.take(id);
CoverSearchResults results_copy(results);
for (int i = 0; i < results_copy.count(); ++i) {
for (int i = 0 ; i < results_copy.count() ; ++i) {
results_copy[i].provider = provider->name();
results_copy[i].score = provider->quality();
if (results_copy[i].artist.toLower() == request_.artist.toLower()) {
@@ -170,7 +168,7 @@ void AlbumCoverFetcherSearch::FetchMoreImages() {
// Try the first one in each category.
QString last_provider;
for (int i = 0; i < results_.count(); ++i) {
for (int i = 0 ; i < results_.count() ; ++i) {
if (results_[i].provider == last_provider) {
continue;
}

View File

@@ -50,6 +50,7 @@
#include "organise/organiseformat.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
: QObject(parent),
@@ -98,89 +99,7 @@ void AlbumCoverLoader::ReloadSettings() {
}
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::kInvalidFatCharacters);
if (cover_lowercase_) filename = filename.toLower();
if (cover_replace_spaces_) filename.replace(QRegExp("\\s"), "-");
}
else {
switch (source) {
case Song::Source_Tidal:
filename = album_id + "-" + cover_url.fileName();
break;
case Song::Source_Subsonic:
case Song::Source_Qobuz:
filename = AlbumCoverFileName(artist, album);
if (filename.length() > 8 && (filename.length() - 5) >= (artist.length() + album.length() - 2)) {
break;
}
// fallthrough
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;
}
}
if (filename.isEmpty()) return QString();
QString filepath(path + "/" + filename);
return filepath;
}
QString AlbumCoverLoader::AlbumCoverFileName(QString artist, QString album) {
QString AlbumCoverLoader::AlbumCoverFilename(QString artist, QString album) {
artist.remove('/');
album.remove('/');
@@ -196,7 +115,79 @@ QString AlbumCoverLoader::AlbumCoverFileName(QString artist, QString album) {
}
QString AlbumCoverLoader::CreateCoverFilename(const QString &artist, const QString &album) {
QString AlbumCoverLoader::CoverFilePath(const Song &song, const QString &album_dir, const QUrl &cover_url) {
return CoverFilePath(song.source(), song.effective_albumartist(), song.album(), song.album_id(), album_dir, cover_url);
}
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 = Song::ImageCacheDir(source);
}
if (path.right(1) == QDir::separator()) {
path.chop(1);
}
QDir dir;
if (!dir.mkpath(path)) {
qLog(Error) << "Unable to create directory" << path;
path = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
}
QString filename;
if (source == Song::Source_Collection && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) {
filename = CoverFilenameFromVariable(artist, album) + ".jpg";
filename.remove(OrganiseFormat::kInvalidFatCharacters);
if (cover_lowercase_) filename = filename.toLower();
if (cover_replace_spaces_) filename.replace(QRegExp("\\s"), "-");
}
else {
filename = CoverFilenameFromSource(source, cover_url, artist, album, album_id);
}
QString filepath(path + "/" + filename);
return filepath;
}
QString AlbumCoverLoader::CoverFilenameFromSource(const Song::Source source, const QUrl &cover_url, const QString &artist, const QString &album, const QString &album_id) {
QString filename;
switch (source) {
case Song::Source_Tidal:
filename = album_id + "-" + cover_url.fileName();
break;
case Song::Source_Subsonic:
case Song::Source_Qobuz:
filename = AlbumCoverFilename(artist, album);
if (filename.length() > 8 && (filename.length() - 5) >= (artist.length() + album.length() - 2)) {
break;
}
// fallthrough
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;
}
return filename;
}
QString AlbumCoverLoader::CoverFilenameFromVariable(const QString &artist, const QString &album) {
QString filename(cover_pattern_);
filename.replace("%albumartist", artist);
@@ -215,6 +206,7 @@ void AlbumCoverLoader::CancelTask(const quint64 id) {
break;
}
}
}
void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
@@ -228,21 +220,25 @@ void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
++it;
}
}
}
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions& options, const Song &song) {
return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url().toLocalFile(), song.image());
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 QString &song_filename, const QImage &embedded_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) {
Task task;
task.options = options;
task.art_automatic = art_automatic;
task.song = song;
task.song_url = song_url;
task.art_manual = art_manual;
task.song_filename = song_filename;
task.art_automatic = art_automatic;
task.art_updated = false;
task.embedded_image = embedded_image;
task.state = State_TryingManual;
task.type = AlbumCoverLoaderResult::Type_None;
task.state = State_Manual;
{
QMutexLocker l(&mutex_);
@@ -269,20 +265,20 @@ void AlbumCoverLoader::ProcessTasks() {
ProcessTask(&task);
}
}
void AlbumCoverLoader::ProcessTask(Task *task) {
TryLoadResult result = TryLoadImage(*task);
TryLoadResult result = TryLoadImage(task);
if (result.started_async) {
// The image is being loaded from a remote URL, we'll carry on later when it's done
return;
}
if (result.loaded_success) {
QImage scaled = ScaleAndPad(task->options, result.image);
emit ImageLoaded(task->id, result.cover_url, scaled);
emit ImageLoaded(task->id, result.cover_url, scaled, result.image);
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));
return;
}
@@ -292,64 +288,85 @@ void AlbumCoverLoader::ProcessTask(Task *task) {
void AlbumCoverLoader::NextState(Task *task) {
if (task->state == State_TryingManual) {
if (task->state == State_Manual) {
// Try the automatic one next
task->state = State_TryingAuto;
task->state = State_Automatic;
ProcessTask(task);
}
else {
// Give up
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_);
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));
}
}
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(const Task &task) {
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
// An image embedded in the song itself takes priority
if (!task.embedded_image.isNull())
return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, task.embedded_image));
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);
}
// 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->art_manual != task->song.art_manual()) {
task->art_manual = task->song.art_manual();
task->art_updated = true;
}
}
AlbumCoverLoaderResult::Type type(AlbumCoverLoaderResult::Type_None);
QUrl cover_url;
switch (task.state) {
case State_TryingAuto: cover_url = task.art_automatic; break;
case State_TryingManual: cover_url = task.art_manual; break;
switch (task->state) {
case State_None:
case State_Automatic:
type = AlbumCoverLoaderResult::Type_Automatic;
cover_url = task->art_automatic;
break;
case State_Manual:
type = AlbumCoverLoaderResult::Type_Manual;
cover_url = task->art_manual;
break;
}
task->type = type;
if (cover_url.path() == Song::kManuallyUnsetCover)
return TryLoadResult(false, true, QUrl(), task.options.default_output_image_);
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, QUrl(), ScaleAndPad(task.options, taglib_image));
}
if (cover_url.path().isEmpty()) {
return TryLoadResult(false, false, cover_url, task.options.default_output_image_);
}
else {
if (cover_url.isLocalFile()) {
if (!cover_url.isEmpty() && !cover_url.path().isEmpty()) {
if (cover_url.path() == Song::kManuallyUnsetCover) {
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_ManuallyUnset, QUrl(), 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, QUrl(), ScaleAndPad(task->options, taglib_image).first);
}
}
else if (cover_url.isLocalFile()) {
QImage image(cover_url.toLocalFile());
return TryLoadResult(false, !image.isNull(), cover_url, image.isNull() ? task.options.default_output_image_ : image);
return TryLoadResult(false, !image.isNull(), type, 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);
return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : image);
}
else if (network_->supportedSchemes().contains(cover_url.scheme())) { // Remote URL
QNetworkReply *reply = network_->get(QNetworkRequest(cover_url));
QNetworkRequest request(cover_url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
QNetworkReply *reply = network_->get(request);
NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, QUrl)), reply, cover_url);
remote_tasks_.insert(reply, task);
return TryLoadResult(true, false, cover_url, QImage());
remote_tasks_.insert(reply, *task);
return TryLoadResult(true, false, type, cover_url, QImage());
}
}
return TryLoadResult(false, false, cover_url, task.options.default_output_image_);
return TryLoadResult(false, false, AlbumCoverLoaderResult::Type_None, cover_url, task->options.default_output_image_);
}
@@ -367,6 +384,7 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cov
return; // Give up.
}
QNetworkRequest request = reply->request();
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
request.setUrl(redirect.toUrl());
QNetworkReply *redirected_reply = network_->get(request);
NewClosure(redirected_reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, QUrl)), redirected_reply, redirect.toUrl());
@@ -379,41 +397,67 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cov
// Try to load the image
QImage image;
if (image.load(reply, 0)) {
QImage scaled = ScaleAndPad(task.options, image);
emit ImageLoaded(task.id, cover_url, scaled);
emit ImageLoaded(task.id, cover_url, scaled, image);
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;
}
else {
qLog(Error) << "Unable to load album cover image" << cover_url;
}
}
else {
qLog(Error) << "Unable to get album cover" << cover_url << reply->error() << reply->errorString();
}
NextState(&task);
}
QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {
QPair<QImage, QImage> AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {
if (image.isNull()) return image;
if (image.isNull()) return qMakePair(image, image);
// Scale the image down
QImage copy;
QImage image_scaled;
if (options.scale_output_image_) {
copy = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation);
image_scaled = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
else {
copy = image;
image_scaled = image;
}
if (!options.pad_output_image_) return copy;
// 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);
// Pad the image to height_ x height_
QImage padded_image(options.desired_height_, options.desired_height_, QImage::Format_ARGB32);
padded_image.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();
QPainter p(&padded_image);
p.drawImage((options.desired_height_ - copy.width()) / 2, (options.desired_height_ - copy.height()) / 2, copy);
p.end();
image_scaled = image_padded;
}
return padded_image;
// 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);
}
}
return qMakePair(image_scaled, image_thumbnail);
}

View File

@@ -27,6 +27,7 @@
#include <QtGlobal>
#include <QObject>
#include <QMutex>
#include <QPair>
#include <QSet>
#include <QMap>
#include <QQueue>
@@ -37,6 +38,7 @@
#include "core/song.h"
#include "settings/collectionsettingspage.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
class QThread;
class QNetworkReply;
@@ -48,29 +50,36 @@ class AlbumCoverLoader : public QObject {
public:
explicit AlbumCoverLoader(QObject *parent = nullptr);
enum State {
State_None,
State_Manual,
State_Automatic,
};
void ReloadSettings();
void ExitAsync();
void Stop() { stop_requested_ = true; }
static QString ImageCacheDir(const Song::Source source);
QString CreateCoverFilename(const QString &artist, const QString &album);
static QString AlbumCoverFilename(QString artist, QString album);
QString CoverFilenameFromSource(const Song::Source source, const QUrl &cover_url, const QString &artist, const QString &album, const QString &album_id);
QString CoverFilenameFromVariable(const QString &artist, const QString &album);
QString CoverFilePath(const Song &song, const QString &album_dir, const QUrl &cover_url);
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 QUrl &art_automatic, const QUrl &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 QUrl &song_url = QUrl(), const Song song = Song(), const QImage &embedded_image = QImage());
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 QImage ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image);
static QPair<QImage, QImage> ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image);
signals:
void ExitFinished();
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);
void AlbumCoverLoaded(quint64 id, AlbumCoverLoaderResult result);
protected slots:
void Exit();
@@ -78,38 +87,38 @@ class AlbumCoverLoader : public QObject {
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
protected:
enum State {
State_TryingManual,
State_TryingAuto,
};
struct Task {
explicit Task() : redirects(0) {}
explicit Task() : id(0), state(State_None), type(AlbumCoverLoaderResult::Type_None), art_updated(false), redirects(0) {}
AlbumCoverLoaderOptions options;
quint64 id;
QUrl art_automatic;
QUrl art_manual;
QString song_filename;
QUrl art_automatic;
QUrl song_url;
Song song;
QImage embedded_image;
State state;
AlbumCoverLoaderResult::Type type;
bool art_updated;
int redirects;
};
struct TryLoadResult {
explicit TryLoadResult(bool async, bool success, const QUrl &_cover_url, const QImage &_image) : started_async(async), loaded_success(success), 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 QUrl &_cover_url = QUrl(), const QImage &_image = QImage()) : started_async(_started_async), loaded_success(_loaded_success), type(_type), cover_url(_cover_url), image(_image) {}
bool started_async;
bool loaded_success;
AlbumCoverLoaderResult::Type type;
QUrl cover_url;
QImage image;
};
void ProcessTask(Task *task);
void NextState(Task *task);
TryLoadResult TryLoadImage(const Task &task);
TryLoadResult TryLoadImage(Task *task);
bool stop_requested_;

View File

@@ -24,17 +24,24 @@
#include "config.h"
#include <QImage>
#include <QSize>
struct AlbumCoverLoaderOptions {
explicit AlbumCoverLoaderOptions()
: desired_height_(120),
scale_output_image_(true),
pad_output_image_(true) {}
pad_output_image_(true),
create_thumbnail_(false),
pad_thumbnail_image_(false) {}
int desired_height_;
QSize thumbnail_size_;
bool scale_output_image_;
bool pad_output_image_;
bool create_thumbnail_;
bool pad_thumbnail_image_;
QImage default_output_image_;
QImage default_thumbnail_image_;
};
#endif // ALBUMCOVERLOADEROPTIONS_H

View File

@@ -0,0 +1,52 @@
/*
* Strawberry Music Player
* Copyright 2020, 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 ALBUMCOVERLOADERRESULT_H
#define ALBUMCOVERLOADERRESULT_H
#include "config.h"
#include <QImage>
#include <QUrl>
struct AlbumCoverLoaderResult {
enum Type {
Type_None,
Type_ManuallyUnset,
Type_Embedded,
Type_Automatic,
Type_Manual,
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) {}
Type type;
QUrl cover_url;
QImage image_original;
QImage image_scaled;
QImage image_thumbnail;
bool updated;
QUrl temp_cover_url;
};
#endif // ALBUMCOVERLOADERRESULT_H

View File

@@ -77,6 +77,8 @@
#include "albumcoverexporter.h"
#include "albumcoverfetcher.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "albumcovermanagerlist.h"
#include "coversearchstatistics.h"
#include "coversearchstatisticsdialog.h"
@@ -216,7 +218,7 @@ void AlbumCoverManager::Init() {
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
}
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(CoverImageLoaded(quint64, QUrl, QImage)));
connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
cover_searcher_->Init(cover_fetcher_);
@@ -392,7 +394,7 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
}
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.toLocalFile());
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);
cover_loading_tasks_[id] = item;
@@ -403,17 +405,15 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
}
void AlbumCoverManager::CoverImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
Q_UNUSED(cover_url);
void AlbumCoverManager::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
if (!cover_loading_tasks_.contains(id)) return;
QListWidgetItem *item = cover_loading_tasks_.take(id);
if (image.isNull()) return;
if (result.image_scaled.isNull()) return;
item->setIcon(QPixmap::fromImage(image));
item->setIcon(QPixmap::fromImage(result.image_scaled));
UpdateFilter();
}
@@ -488,7 +488,7 @@ void AlbumCoverManager::FetchAlbumCovers() {
if (item->isHidden()) continue;
if (ItemHasCover(*item)) continue;
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), true);
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), true);
cover_fetching_tasks_[id] = item;
jobs_++;
}
@@ -623,7 +623,7 @@ void AlbumCoverManager::ShowCover() {
void AlbumCoverManager::FetchSingleCover() {
for (QListWidgetItem *item : context_menu_items_) {
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), false);
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), false);
cover_fetching_tasks_[id] = item;
jobs_++;
}

View File

@@ -36,6 +36,7 @@
#include "core/song.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "coversearchstatistics.h"
class QWidget;
@@ -132,7 +133,7 @@ class AlbumCoverManager : public QMainWindow {
private slots:
void ArtistChanged(QListWidgetItem *current);
void CoverImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
void UpdateFilter();
void FetchAlbumCovers();
void ExportCovers();

View File

@@ -47,6 +47,7 @@
#include "core/application.h"
#include "core/utilities.h"
#include "core/logging.h"
#include "widgets/busyindicator.h"
#include "widgets/forcescrollperpixel.h"
#include "widgets/groupediconview.h"
@@ -55,6 +56,7 @@
#include "albumcoverfetcher.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "ui_albumcoversearcher.h"
const int SizeOverlayDelegate::kMargin = 4;
@@ -129,8 +131,11 @@ AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *
options_.scale_output_image_ = false;
options_.pad_output_image_ = false;
options_.create_thumbnail_ = true;
options_.pad_thumbnail_image_ = true;
options_.thumbnail_size_ = ui_->covers->iconSize();
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(ImageLoaded(quint64, QUrl, QImage)));
connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
connect(ui_->search, SIGNAL(clicked()), SLOT(Search()));
connect(ui_->covers, SIGNAL(doubleClicked(QModelIndex)), SLOT(CoverDoubleClicked(QModelIndex)));
@@ -235,37 +240,25 @@ void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverSearchResul
}
void AlbumCoverSearcher::ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
Q_UNUSED(cover_url);
void AlbumCoverSearcher::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
if (!cover_loading_tasks_.contains(id)) return;
QStandardItem *item = cover_loading_tasks_.take(id);
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
if (image.isNull()) {
if (result.image_original.isNull()) {
model_->removeRow(item->row());
return;
}
QIcon icon(QPixmap::fromImage(image));
// Create a pixmap that's padded and exactly the right size for the icon.
QImage scaled_image(image.scaled(ui_->covers->iconSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
QImage padded_image(ui_->covers->iconSize(), QImage::Format_ARGB32_Premultiplied);
padded_image.fill(0);
QPainter p(&padded_image);
p.drawImage((padded_image.width() - scaled_image.width()) / 2, (padded_image.height() - scaled_image.height()) / 2, scaled_image);
p.end();
icon.addPixmap(QPixmap::fromImage(padded_image));
QIcon icon;
icon.addPixmap(QPixmap::fromImage(result.image_original));
icon.addPixmap(QPixmap::fromImage(result.image_thumbnail));
item->setData(true, Role_ImageFetchFinished);
item->setData(image.width() * image.height(), Role_ImageDimensions);
item->setData(image.size(), Role_ImageSize);
item->setData(result.image_original.width() * result.image_original.height(), Role_ImageDimensions);
item->setData(result.image_original.size(), Role_ImageSize);
item->setIcon(icon);
}

View File

@@ -36,6 +36,7 @@
#include "albumcoverfetcher.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
class QWidget;
class QStandardItem;
@@ -88,7 +89,7 @@ class AlbumCoverSearcher : public QDialog {
private slots:
void Search();
void SearchFinished(const quint64 id, const CoverSearchResults &results);
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
void CoverDoubleClicked(const QModelIndex &index);

View File

@@ -26,5 +26,5 @@
#include "core/application.h"
#include "coverprovider.h"
CoverProvider::CoverProvider(const QString &name, const float &quality, const bool &fetchall, Application *app, QObject *parent)
: QObject(parent), app_(app), name_(name), quality_(quality), fetchall_(fetchall) {}
CoverProvider::CoverProvider(const QString &name, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent)
: QObject(parent), app_(app), name_(name), quality_(quality), fetchall_(fetchall), allow_missing_album_(allow_missing_album) {}

View File

@@ -37,17 +37,18 @@ class CoverProvider : public QObject {
Q_OBJECT
public:
explicit CoverProvider(const QString &name, const float &quality, const bool &fetchall, Application *app, QObject *parent);
explicit CoverProvider(const QString &name, const float quality, const bool fetchall, 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 quality() const { return quality_; }
bool fetchall() const { return fetchall_; }
bool allow_missing_album() const { return allow_missing_album_; }
// Starts searching for covers matching the given query text.
// Returns true if the query has been started, or false if an error occurred.
// The provider should remember the ID and emit it along with the result when it finishes.
virtual bool StartSearch(const QString &artist, const QString &album, int id) = 0;
virtual bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id) = 0;
virtual void CancelSearch(int id) { Q_UNUSED(id); }
@@ -59,6 +60,7 @@ class CoverProvider : public QObject {
QString name_;
float quality_;
bool fetchall_;
bool allow_missing_album_;
};

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2020, 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
@@ -32,6 +33,7 @@
#include "playlist/playlistmanager.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "currentalbumcoverloader.h"
CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *parent)
@@ -43,56 +45,73 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare
options_.scale_output_image_ = false;
options_.pad_output_image_ = false;
options_.create_thumbnail_ = true;
options_.thumbnail_size_ = QSize(120, 120);
options_.default_output_image_ = QImage(":/pictures/cdcase.png");
options_.default_thumbnail_image_ = options_.default_output_image_.scaledToHeight(120, Qt::SmoothTransformation);
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(TempAlbumCoverLoaded(quint64, QUrl, QImage)));
connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(TempAlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(LoadAlbumCover(Song)));
}
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {
if (temp_cover_) temp_cover_->remove();
if (temp_cover_thumbnail_) temp_cover_thumbnail_->remove();
}
void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) {
last_song_ = song;
id_ = app_->album_cover_loader()->LoadImageAsync(options_, last_song_);
}
void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image) {
Q_UNUSED(remote_url);
void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverLoaderResult result) {
if (id != id_) return;
id_ = 0;
QUrl cover_url;
QUrl thumbnail_url;
QImage thumbnail;
if (!image.isNull()) {
QString filename;
if (!result.image_scaled.isNull()) {
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_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_cover_thumbnail_->fileName(), "JPEG");
cover_url = QUrl::fromLocalFile(temp_cover_->fileName());
thumbnail_url = QUrl::fromLocalFile(temp_cover_thumbnail_->fileName());
if (temp_cover_->open()) {
if (result.image_scaled.save(temp_cover_->fileName(), "JPEG")) {
result.temp_cover_url = QUrl::fromLocalFile(temp_cover_->fileName());
}
else {
qLog(Error) << "Unable to save cover image to" << temp_cover_->fileName();
}
}
else {
qLog(Error) << "Unable to open" << temp_cover_->fileName();
}
}
emit AlbumCoverLoaded(last_song_, cover_url, image);
emit ThumbnailLoaded(last_song_, thumbnail_url, thumbnail);
QUrl thumbnail_url;
if (!result.image_thumbnail.isNull()) {
temp_cover_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_));
temp_cover_thumbnail_->setAutoRemove(true);
if (temp_cover_thumbnail_->open()) {
if (result.image_thumbnail.save(temp_cover_thumbnail_->fileName(), "JPEG")) {
thumbnail_url = QUrl::fromLocalFile(temp_cover_thumbnail_->fileName());
}
else {
qLog(Error) << "Unable to save cover thumbnail image to" << temp_cover_thumbnail_->fileName();
}
}
else {
qLog(Error) << "Unable to open" << temp_cover_thumbnail_->fileName();
}
}
if (result.updated) {
last_song_.set_art_manual(result.cover_url);
}
emit AlbumCoverLoaded(last_song_, result);
emit ThumbnailLoaded(last_song_, thumbnail_url, result.image_thumbnail);
}

View File

@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2020, 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
@@ -33,6 +34,7 @@
#include "core/song.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
class Application;
@@ -50,11 +52,11 @@ class CurrentAlbumCoverLoader : public QObject {
void LoadAlbumCover(const Song &song);
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);
void AlbumCoverLoaded(Song song, AlbumCoverLoaderResult result);
void ThumbnailLoaded(Song song, QUrl thumbnail_uri, QImage image);
private slots:
void TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image);
void TempAlbumCoverLoaded(const quint64 id, AlbumCoverLoaderResult result);
private:
Application *app_;

View File

@@ -50,16 +50,27 @@
const char *DeezerCoverProvider::kApiUrl = "https://api.deezer.com";
const int DeezerCoverProvider::kLimit = 10;
DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): CoverProvider("Deezer", 2.0, true, app, parent), network_(new NetworkAccessManager(this)) {}
DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): CoverProvider("Deezer", 2.0, true, true, app, parent), network_(new NetworkAccessManager(this)) {}
bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) {
bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) {
typedef QPair<QString, QString> Param;
typedef QList<Param> Params;
typedef QPair<QByteArray, QByteArray> EncodedParam;
QUrl url(kApiUrl);
QString search;
if (album.isEmpty()) {
url.setPath("/search/track");
search = artist + " " + title;
}
else {
url.setPath("/search/album");
search = artist + " " + album;
}
const Params params = Params() << Param("output", "json")
<< Param("q", QString(artist + " " + album))
<< Param("q", search)
<< Param("limit", QString::number(kLimit));
QUrlQuery url_query;
@@ -68,7 +79,6 @@ bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &albu
url_query.addQueryItem(encoded_param.first, encoded_param.second);
}
QUrl url(kApiUrl + QString("/search/album"));
url.setQuery(url_query);
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
@@ -220,19 +230,27 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
for (const QJsonValue &value : json_data) {
if (!value.isObject()) {
Error("Invalid Json reply, data is not an object.", value);
Error("Invalid Json reply, data in array is not a object.", value);
continue;
}
QJsonObject json_obj = value.toObject();
QJsonObject json_album = json_obj;
if (json_obj.contains("album") && json_obj["album"].isObject()) { // Song search, so extract the album.
json_album = json_obj["album"].toObject();
}
if (!json_obj.contains("id") || !json_obj.contains("type")) {
Error("Invalid Json reply, item is missing ID or type.", json_obj);
if (!json_obj.contains("id") || !json_album.contains("id")) {
Error("Invalid Json reply, object is missing ID.", json_obj);
continue;
}
QString type = json_obj["type"].toString();
if (!json_album.contains("type")) {
Error("Invalid Json reply, album object is missing type.", json_album);
continue;
}
QString type = json_album["type"].toString();
if (type != "album") {
Error("Invalid Json reply, incorrect type returned", json_obj);
Error("Invalid Json reply, incorrect type returned", json_album);
continue;
}
@@ -242,7 +260,7 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
}
QJsonValue json_value_artist = json_obj["artist"];
if (!json_value_artist.isObject()) {
Error("Invalid Json reply, item artist is not a object.", json_value_artist);
Error("Invalid Json reply, artist is not a object.", json_value_artist);
continue;
}
QJsonObject json_artist = json_value_artist.toObject();
@@ -253,27 +271,27 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
}
QString artist = json_artist["name"].toString();
if (!json_obj.contains("title")) {
Error("Invalid Json reply, data missing title.", json_obj);
if (!json_album.contains("title")) {
Error("Invalid Json reply, data missing title.", json_album);
continue;
}
QString album = json_obj["title"].toString();
QString album = json_album["title"].toString();
QString cover;
if (json_obj.contains("cover_xl")) {
cover = json_obj["cover_xl"].toString();
if (json_album.contains("cover_xl")) {
cover = json_album["cover_xl"].toString();
}
else if (json_obj.contains("cover_big")) {
cover = json_obj["cover_big"].toString();
else if (json_album.contains("cover_big")) {
cover = json_album["cover_big"].toString();
}
else if (json_obj.contains("cover_medium")) {
cover = json_obj["cover_medium"].toString();
else if (json_album.contains("cover_medium")) {
cover = json_album["cover_medium"].toString();
}
else if (json_obj.contains("cover_small")) {
cover = json_obj["cover_small"].toString();
else if (json_album.contains("cover_small")) {
cover = json_album["cover_small"].toString();
}
else {
Error("Invalid Json reply, data missing cover.", json_obj);
Error("Invalid Json reply, album missing cover.", json_album);
continue;
}
QUrl url(cover);

View File

@@ -40,7 +40,7 @@ class DeezerCoverProvider : public CoverProvider {
public:
explicit DeezerCoverProvider(Application *app, QObject *parent = nullptr);
bool StartSearch(const QString &artist, const QString &album, const int id);
bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id);
void CancelSearch(const int id);
private slots:

View File

@@ -59,9 +59,11 @@ const char *DiscogsCoverProvider::kUrlReleases = "https://api.discogs.com/releas
const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
const char *DiscogsCoverProvider::kSecretKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI=";
DiscogsCoverProvider::DiscogsCoverProvider(Application *app, QObject *parent) : CoverProvider("Discogs", 0.0, false, app, parent), network_(new NetworkAccessManager(this)) {}
DiscogsCoverProvider::DiscogsCoverProvider(Application *app, QObject *parent) : CoverProvider("Discogs", 0.0, false, false, app, parent), network_(new NetworkAccessManager(this)) {}
bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, const int s_id) {
bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int s_id) {
Q_UNUSED(title);
DiscogsCoverSearchContext *s_ctx = new DiscogsCoverSearchContext;

View File

@@ -73,7 +73,7 @@ class DiscogsCoverProvider : public CoverProvider {
public:
explicit DiscogsCoverProvider(Application *app, QObject *parent = nullptr);
bool StartSearch(const QString &artist, const QString &album, const int s_id);
bool StartSearch(const QString &artist, const QString &album, const QString &title, const int s_id);
void CancelSearch(const int id);

View File

@@ -52,9 +52,11 @@ const char *LastFmCoverProvider::kUrl = "https://ws.audioscrobbler.com/2.0/";
const char *LastFmCoverProvider::kApiKey = "211990b4c96782c05d1536e7219eb56e";
const char *LastFmCoverProvider::kSecret = "80fd738f49596e9709b1bf9319c444a8";
LastFmCoverProvider::LastFmCoverProvider(Application *app, QObject *parent) : CoverProvider("last.fm", 1.0, true, app, parent), network_(new NetworkAccessManager(this)) {}
LastFmCoverProvider::LastFmCoverProvider(Application *app, QObject *parent) : CoverProvider("last.fm", 1.0, true, false, app, parent), network_(new NetworkAccessManager(this)) {}
bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) {
bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) {
Q_UNUSED(title);
typedef QPair<QString, QString> Param;
typedef QPair<QByteArray, QByteArray> EncodedParam;

View File

@@ -40,7 +40,7 @@ class LastFmCoverProvider : public CoverProvider {
public:
explicit LastFmCoverProvider(Application *app, QObject *parent = nullptr);
bool StartSearch(const QString &artist, const QString &album, const int id);
bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id);
private slots:
void QueryFinished(QNetworkReply *reply, const int id);

View File

@@ -47,9 +47,11 @@ const char *MusicbrainzCoverProvider::kReleaseSearchUrl = "https://musicbrainz.o
const char *MusicbrainzCoverProvider::kAlbumCoverUrl = "https://coverartarchive.org/release/%1/front";
const int MusicbrainzCoverProvider::kLimit = 8;
MusicbrainzCoverProvider::MusicbrainzCoverProvider(Application *app, QObject *parent): CoverProvider("MusicBrainz", 1.5, true, app, parent), network_(new NetworkAccessManager(this)) {}
MusicbrainzCoverProvider::MusicbrainzCoverProvider(Application *app, QObject *parent): CoverProvider("MusicBrainz", 1.5, true, false, app, parent), network_(new NetworkAccessManager(this)) {}
bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) {
bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) {
Q_UNUSED(title);
QString query = QString("release:\"%1\" AND artist:\"%2\"").arg(album.trimmed().replace('"', "\\\"")).arg(artist.trimmed().replace('"', "\\\""));

View File

@@ -39,7 +39,7 @@ class MusicbrainzCoverProvider : public CoverProvider {
public:
explicit MusicbrainzCoverProvider(Application *app, QObject *parent = nullptr);
bool StartSearch(const QString &artist, const QString &album, const int id);
bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id);
void CancelSearch(const int id);
private slots:

View File

@@ -52,13 +52,15 @@ const char *TidalCoverProvider::kResourcesUrl = "https://resources.tidal.com";
const int TidalCoverProvider::kLimit = 10;
TidalCoverProvider::TidalCoverProvider(Application *app, QObject *parent) :
CoverProvider("Tidal", 2.0, true, app, parent),
CoverProvider("Tidal", 2.0, true, false, app, parent),
service_(app->internet_services()->Service<TidalService>()),
network_(new NetworkAccessManager(this)) {
}
bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) {
bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) {
Q_UNUSED(title);
if (!service_ || !service_->authenticated()) return false;

View File

@@ -44,7 +44,7 @@ class TidalCoverProvider : public CoverProvider {
public:
explicit TidalCoverProvider(Application *app, QObject *parent = nullptr);
bool StartSearch(const QString &artist, const QString &album, const int id);
bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id);
void CancelSearch(int id);
private slots: