Improve cover providers score system

This commit is contained in:
Jonas Kvinge
2019-04-17 22:18:03 +02:00
parent ca928bdacb
commit 63d5018ad6
14 changed files with 190 additions and 66 deletions

View File

@@ -48,12 +48,12 @@ AlbumCoverFetcher::AlbumCoverFetcher(CoverProviders *cover_providers, QObject *p
quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, bool fetchall) { quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, bool fetchall) {
CoverSearchRequest request; CoverSearchRequest request;
request.id = next_id_++;
request.artist = artist; request.artist = artist;
request.album = album; request.album = album;
request.album.remove(Song::kAlbumRemoveDisc); request.album.remove(Song::kAlbumRemoveDisc);
request.album.remove(Song::kAlbumRemoveMisc); request.album.remove(Song::kAlbumRemoveMisc);
request.search = false; request.search = false;
request.id = next_id_++;
request.fetchall = fetchall; request.fetchall = fetchall;
AddRequest(request); AddRequest(request);
@@ -64,12 +64,12 @@ 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) {
CoverSearchRequest request; CoverSearchRequest request;
request.id = next_id_++;
request.artist = artist; request.artist = artist;
request.album = album; request.album = album;
request.album.remove(Song::kAlbumRemoveDisc); request.album.remove(Song::kAlbumRemoveDisc);
request.album.remove(Song::kAlbumRemoveMisc); request.album.remove(Song::kAlbumRemoveMisc);
request.search = true; request.search = true;
request.id = next_id_++;
request.fetchall = false; request.fetchall = false;
AddRequest(request); AddRequest(request);

View File

@@ -60,16 +60,20 @@ struct CoverSearchRequest {
}; };
// This structure represents a single result of some album's cover search request. // This structure represents a single result of some album's cover search request.
// It contains an URL that leads to a found cover plus its description (usually the "artist - album" string).
struct CoverSearchResult { struct CoverSearchResult {
// Used for grouping in the user interface. This is set automatically - don't set it manually in your cover provider. // Used for grouping in the user interface.
QString provider; QString provider;
// Description of this result (we suggest using the "artist - album" format) // Artist and album returned by the provider
QString description; QString artist;
QString album;
// An URL of a cover image described by this CoverSearchResult // An URL of a cover image
QUrl image_url; QUrl image_url;
// Total score for this result
float score;
}; };
Q_DECLARE_METATYPE(CoverSearchResult); Q_DECLARE_METATYPE(CoverSearchResult);

View File

@@ -43,12 +43,15 @@
#include "coverprovider.h" #include "coverprovider.h"
#include "coverproviders.h" #include "coverproviders.h"
using std::min;
using std::max;
using std::stable_sort; using std::stable_sort;
using std::sqrt;
const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 25000; const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 25000;
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 3000; const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 3000;
const int AlbumCoverFetcherSearch::kTargetSize = 500; const int AlbumCoverFetcherSearch::kTargetSize = 500;
const float AlbumCoverFetcherSearch::kGoodScore = 1.85; const float AlbumCoverFetcherSearch::kGoodScore = 4;
AlbumCoverFetcherSearch::AlbumCoverFetcherSearch( AlbumCoverFetcherSearch::AlbumCoverFetcherSearch(
const CoverSearchRequest &request, QNetworkAccessManager *network, QObject *parent) const CoverSearchRequest &request, QNetworkAccessManager *network, QObject *parent)
@@ -107,13 +110,19 @@ static bool CompareProviders(const CoverSearchResult &a, const CoverSearchResult
void AlbumCoverFetcherSearch::ProviderSearchFinished(int id, const QList<CoverSearchResult> &results) { void AlbumCoverFetcherSearch::ProviderSearchFinished(int id, const QList<CoverSearchResult> &results) {
if (!pending_requests_.contains(id)) return; if (!pending_requests_.contains(id)) return;
CoverProvider *provider = pending_requests_.take(id); CoverProvider *provider = pending_requests_.take(id);
CoverSearchResults results_copy(results); CoverSearchResults results_copy(results);
// Set categories on the 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].provider = provider->name();
results_copy[i].score = provider->quality();
if (results_copy[i].artist == request_.artist) {
results_copy[i].score += 0.5;
}
if (results_copy[i].album == request_.album) { results_copy[i].score += 0.5; }
if (results_copy[i].artist != request_.artist && results_copy[i].album != request_.album) {
results_copy[i].score -= 1.0;
}
} }
// Add results from the current provider to our pool // Add results from the current provider to our pool
@@ -126,6 +135,7 @@ void AlbumCoverFetcherSearch::ProviderSearchFinished(int id, const QList<CoverSe
} }
AllProvidersFinished(); AllProvidersFinished();
} }
void AlbumCoverFetcherSearch::AllProvidersFinished() { void AlbumCoverFetcherSearch::AllProvidersFinished() {
@@ -171,7 +181,7 @@ void AlbumCoverFetcherSearch::FetchMoreImages() {
RedirectFollower *image_reply = new RedirectFollower(network_->get(QNetworkRequest(result.image_url))); RedirectFollower *image_reply = new RedirectFollower(network_->get(QNetworkRequest(result.image_url)));
NewClosure(image_reply, SIGNAL(finished()), this, SLOT(ProviderCoverFetchFinished(RedirectFollower*)), image_reply); NewClosure(image_reply, SIGNAL(finished()), this, SLOT(ProviderCoverFetchFinished(RedirectFollower*)), image_reply);
pending_image_loads_[image_reply] = result.provider; pending_image_loads_[image_reply] = result;
image_load_timeout_->AddReply(image_reply); image_load_timeout_->AddReply(image_reply);
statistics_.network_requests_made_++; statistics_.network_requests_made_++;
@@ -187,7 +197,9 @@ void AlbumCoverFetcherSearch::FetchMoreImages() {
void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(RedirectFollower *reply) { void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(RedirectFollower *reply) {
reply->deleteLater(); reply->deleteLater();
const QString provider = pending_image_loads_.take(reply);
if (!pending_image_loads_.contains(reply)) return;
CoverSearchResult result = pending_image_loads_.take(reply);
statistics_.bytes_transferred_ += reply->bytesAvailable(); statistics_.bytes_transferred_ += reply->bytesAvailable();
@@ -196,18 +208,20 @@ void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(RedirectFollower *reply
} }
if (reply->error() != QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
qLog(Info) << "Error requesting" << reply->url() << reply->errorString(); qLog(Error) << "Error requesting" << reply->url() << reply->errorString();
} }
else { else {
QImage image; QImage image;
if (!image.loadFromData(reply->readAll())) { if (image.loadFromData(reply->readAll())) {
qLog(Info) << "Error decoding image data from" << reply->url();
result.score += ScoreImage(image);
candidate_images_.insertMulti(result.score, CandidateImage(result, image));
qLog(Debug) << reply->url() << "from" << result.provider << "scored" << result.score;
} }
else { else {
const float score = ScoreImage(image); qLog(Error) << "Error decoding image data from" << reply->url();
candidate_images_.insertMulti(score, CandidateImage(provider, image));
qLog(Debug) << reply->url() << "scored" << score;
} }
} }
@@ -241,7 +255,7 @@ float AlbumCoverFetcherSearch::ScoreImage(const QImage &image) const {
const float size_score = std::sqrt(float(image.width() * image.height())) / kTargetSize; const float size_score = std::sqrt(float(image.width() * image.height())) / kTargetSize;
// A 1:1 image scores 1.0, anything else scores less // A 1:1 image scores 1.0, anything else scores less
const float aspect_score = 1.0 - float(image.height() - image.width()) / std::max(image.height(), image.width()); const float aspect_score = 1.0 - float(std::max(image.width(), image.height()) - std::min(image.width(), image.height())) / std::max(image.height(), image.width());
return size_score + aspect_score; return size_score + aspect_score;
@@ -255,7 +269,9 @@ void AlbumCoverFetcherSearch::SendBestImage() {
const CandidateImage best_image = candidate_images_.values().back(); const CandidateImage best_image = candidate_images_.values().back();
image = best_image.second; image = best_image.second;
statistics_.chosen_images_by_provider_[best_image.first]++; qLog(Info) << "Using " << best_image.first.image_url << "from" << best_image.first.provider << "with score" << best_image.first.score;
statistics_.chosen_images_by_provider_[best_image.first.provider]++;
statistics_.chosen_images_++; statistics_.chosen_images_++;
statistics_.chosen_width_ += image.width(); statistics_.chosen_width_ += image.width();
statistics_.chosen_height_ += image.height(); statistics_.chosen_height_ += image.height();

View File

@@ -92,11 +92,11 @@ class AlbumCoverFetcherSearch : public QObject {
CoverSearchResults results_; CoverSearchResults results_;
QMap<int, CoverProvider*> pending_requests_; QMap<int, CoverProvider*> pending_requests_;
QMap<RedirectFollower*, QString> pending_image_loads_; QMap<RedirectFollower*, CoverSearchResult> pending_image_loads_;
NetworkTimeouts* image_load_timeout_; NetworkTimeouts* image_load_timeout_;
// QMap is sorted by key (score). Values are (provider_name, image) // QMap is sorted by key (score). Values are (result, image)
typedef QPair<QString, QImage> CandidateImage; typedef QPair<CoverSearchResult, QImage> CandidateImage;
QMap<float, CandidateImage> candidate_images_; QMap<float, CandidateImage> candidate_images_;
QNetworkAccessManager *network_; QNetworkAccessManager *network_;

View File

@@ -81,6 +81,7 @@
#include "ui_albumcovermanager.h" #include "ui_albumcovermanager.h"
using std::unique_ptr;
using std::stable_sort; using std::stable_sort;
const char *AlbumCoverManager::kSettingsGroup = "CoverManager"; const char *AlbumCoverManager::kSettingsGroup = "CoverManager";

View File

@@ -216,7 +216,7 @@ void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults &re
QStandardItem *item = new QStandardItem; QStandardItem *item = new QStandardItem;
item->setIcon(no_cover_icon_); item->setIcon(no_cover_icon_);
item->setText(result.description); item->setText(result.artist + " - " + result.album);
item->setData(result.image_url, Role_ImageURL); item->setData(result.image_url, Role_ImageURL);
item->setData(id, Role_ImageRequestId); item->setData(id, Role_ImageRequestId);
item->setData(false, Role_ImageFetchFinished); item->setData(false, Role_ImageFetchFinished);

View File

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

View File

@@ -38,10 +38,11 @@ class CoverProvider : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit CoverProvider(const QString &name, const bool &fetchall, Application *app, QObject *parent); explicit CoverProvider(const QString &name, const float &quality, const bool &fetchall, Application *app, QObject *parent);
// A name (very short description) of this provider, like "last.fm". // A name (very short description) of this provider, like "last.fm".
QString name() const { return name_; } QString name() const { return name_; }
bool quality() const { return quality_; }
bool fetchall() const { return fetchall_; } bool fetchall() const { return fetchall_; }
// Starts searching for covers matching the given query text. // Starts searching for covers matching the given query text.
@@ -57,6 +58,7 @@ class CoverProvider : public QObject {
private: private:
Application *app_; Application *app_;
QString name_; QString name_;
float quality_;
bool fetchall_; bool fetchall_;
}; };

View File

@@ -48,7 +48,7 @@
const char *DeezerCoverProvider::kApiUrl = "https://api.deezer.com"; const char *DeezerCoverProvider::kApiUrl = "https://api.deezer.com";
const int DeezerCoverProvider::kLimit = 10; const int DeezerCoverProvider::kLimit = 10;
DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): CoverProvider("Deezer", true, app, parent), network_(new NetworkAccessManager(this)) {} DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): CoverProvider("Deezer", 2.0, true, app, parent), network_(new NetworkAccessManager(this)) {}
bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, int id) { bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
@@ -269,8 +269,12 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, int id) {
} }
QUrl url(cover); QUrl url(cover);
album.remove(Song::kAlbumRemoveDisc);
album.remove(Song::kAlbumRemoveMisc);
CoverSearchResult cover_result; CoverSearchResult cover_result;
cover_result.description = artist + " " + album; cover_result.artist = artist;
cover_result.album = album;
cover_result.image_url = url; cover_result.image_url = url;
results << cover_result; results << cover_result;

View File

@@ -54,7 +54,7 @@ const char *DiscogsCoverProvider::kUrlReleases = "https://api.discogs.com/releas
const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk="; const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
const char *DiscogsCoverProvider::kSecretKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI="; const char *DiscogsCoverProvider::kSecretKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI=";
DiscogsCoverProvider::DiscogsCoverProvider(Application *app, QObject *parent) : CoverProvider("Discogs", false, app, parent), network_(new NetworkAccessManager(this)) {} DiscogsCoverProvider::DiscogsCoverProvider(Application *app, QObject *parent) : CoverProvider("Discogs", 0.0, false, app, parent), network_(new NetworkAccessManager(this)) {}
bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, int s_id) { bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, int s_id) {
@@ -76,7 +76,7 @@ void DiscogsCoverProvider::CancelSearch(int id) {
} }
bool DiscogsCoverProvider::StartRelease(DiscogsCoverSearchContext *s_ctx, int r_id, QString resource_url) { bool DiscogsCoverProvider::StartRelease(DiscogsCoverSearchContext *s_ctx, int r_id, QString &description, QString &resource_url) {
DiscogsCoverReleaseContext *r_ctx = new DiscogsCoverReleaseContext; DiscogsCoverReleaseContext *r_ctx = new DiscogsCoverReleaseContext;
@@ -225,7 +225,7 @@ QJsonObject DiscogsCoverProvider::ExtractJsonObj(const QByteArray &data) {
Error("Reply from server missing Json data.", data); Error("Reply from server missing Json data.", data);
return QJsonObject(); return QJsonObject();
} }
if (json_doc.isNull() || json_doc.isEmpty()) { if (json_doc.isEmpty()) {
Error("Received empty Json document.", json_doc); Error("Received empty Json document.", json_doc);
return QJsonObject(); return QJsonObject();
} }
@@ -307,7 +307,7 @@ void DiscogsCoverProvider::HandleSearchReply(QNetworkReply *reply, int s_id) {
QString title = json_obj["title"].toString(); QString title = json_obj["title"].toString();
QString resource_url = json_obj["resource_url"].toString(); QString resource_url = json_obj["resource_url"].toString();
if (resource_url.isEmpty()) continue; if (resource_url.isEmpty()) continue;
StartRelease(s_ctx, r_id, resource_url); StartRelease(s_ctx, r_id, title, resource_url);
i++; i++;
} }
@@ -337,41 +337,96 @@ void DiscogsCoverProvider::HandleReleaseReply(QNetworkReply *reply, int s_id, in
QByteArray data = GetReplyData(reply); QByteArray data = GetReplyData(reply);
if (data.isEmpty()) { if (data.isEmpty()) {
EndSearch(s_ctx);
return;
}
QJsonValue json_value = ExtractData(data, "images", true);
if (!json_value.isArray()) {
EndSearch(s_ctx, r_ctx); EndSearch(s_ctx, r_ctx);
return; return;
} }
QJsonArray json_images = json_value.toArray(); QJsonObject json_obj = ExtractJsonObj(data);
if (json_images.isEmpty()) { if (json_obj.isEmpty()) {
EndSearch(s_ctx, r_ctx);
return;
}
if (!json_obj.contains("artists") || !json_obj.contains("title") || !json_obj.contains("images")) {
Error("Json reply is missing artists, title or images.");
EndSearch(s_ctx, r_ctx);
return;
}
QJsonValue json_artists = json_obj["artists"];
if (!json_artists.isArray()) {
Error("Json artists is not a array.", json_artists);
EndSearch(s_ctx, r_ctx);
return;
}
QJsonArray json_array_artists = json_artists.toArray();
int i = 0;
QString artist;
for (const QJsonValue &value : json_array_artists) {
if (!value.isObject()) {
Error("Invalid Json reply, value is not a object.", value);
continue;
}
QJsonObject json_obj_artist = value.toObject();
if (!json_obj_artist.contains("name") ) {
Error("Invalid Json reply, artists is missing name.", json_obj_artist);
continue;
}
artist = json_obj_artist["name"].toString();
++i;
if (artist == s_ctx->artist) break;
}
if (artist.isEmpty()) {
EndSearch(s_ctx, r_ctx);
return;
}
if (i > 1 && artist != s_ctx->artist) artist = "Various artists";
QString album = json_obj["title"].toString();
if (artist != s_ctx->artist && album != s_ctx->album) {
EndSearch(s_ctx, r_ctx);
return;
}
QJsonValue json_images = json_obj["images"];
if (!json_images.isArray()) {
Error("Json images is not an array.");
EndSearch(s_ctx, r_ctx);
return;
}
QJsonArray json_array_images = json_images.toArray();
if (json_array_images.isEmpty()) {
Error("Json array is empty."); Error("Json array is empty.");
EndSearch(s_ctx, r_ctx); EndSearch(s_ctx, r_ctx);
return; return;
} }
int i = 0; i = 0;
for (const QJsonValue &value : json_images) { for (const QJsonValue &value : json_array_images) {
if (!value.isObject()) { if (!value.isObject()) {
Error("Invalid Json reply, value is not an object.", value); Error("Invalid Json reply, value is not an object.", value);
continue; continue;
} }
QJsonObject json_obj = value.toObject(); QJsonObject json_obj = value.toObject();
if (!json_obj.contains("type") || !json_obj.contains("resource_url")) { if (!json_obj.contains("type") || !json_obj.contains("resource_url") || !json_obj.contains("width") || !json_obj.contains("height") ) {
Error("Invalid Json reply, value is missing ID or resource_url.", json_obj); Error("Invalid Json reply, images value is missing type, resource_url, width or height.", json_obj);
continue; continue;
} }
CoverSearchResult cover_result;
cover_result.description = s_ctx->title;
QString type = json_obj["type"].toString(); QString type = json_obj["type"].toString();
i++; i++;
if (type != "primary") { if (type != "primary") {
continue; continue;
} }
int width = json_obj["width"].toInt();
int height = json_obj["height"].toInt();
if (width < 300 || height < 300) continue;
const float aspect_score = 1.0 - float(std::max(width, height) - std::min(width, height)) / std::max(height, width);
if (aspect_score < 0.85) continue;
CoverSearchResult cover_result;
cover_result.artist = artist;
cover_result.album = album;
cover_result.image_url = QUrl(json_obj["resource_url"].toString()); cover_result.image_url = QUrl(json_obj["resource_url"].toString());
if (cover_result.image_url.isEmpty()) continue; if (cover_result.image_url.isEmpty()) continue;
s_ctx->results.append(cover_result); s_ctx->results.append(cover_result);
@@ -400,7 +455,7 @@ void DiscogsCoverProvider::EndSearch(DiscogsCoverSearchContext *s_ctx) {
} }
void DiscogsCoverProvider::EndSearch(DiscogsCoverReleaseContext* r_ctx) { void DiscogsCoverProvider::EndSearch(DiscogsCoverReleaseContext *r_ctx) {
delete requests_release_.take(r_ctx->id); delete requests_release_.take(r_ctx->id);
} }

View File

@@ -47,7 +47,6 @@ struct DiscogsCoverSearchContext {
// The search query // The search query
QString artist; QString artist;
QString album; QString album;
QString title;
int r_count; int r_count;
CoverSearchResults results; CoverSearchResults results;
@@ -88,7 +87,7 @@ class DiscogsCoverProvider : public CoverProvider {
QHash<int, DiscogsCoverSearchContext*> requests_search_; QHash<int, DiscogsCoverSearchContext*> requests_search_;
QHash<int, DiscogsCoverReleaseContext*> requests_release_; QHash<int, DiscogsCoverReleaseContext*> requests_release_;
bool StartRelease(DiscogsCoverSearchContext *s_ctx, int r_id, QString resource_url); bool StartRelease(DiscogsCoverSearchContext *s_ctx, int r_id, QString &description, QString &resource_url);
void SendSearchRequest(DiscogsCoverSearchContext *s_ctx); void SendSearchRequest(DiscogsCoverSearchContext *s_ctx);
void SendReleaseRequest(DiscogsCoverSearchContext *s_ctx, DiscogsCoverReleaseContext *r_ctx); void SendReleaseRequest(DiscogsCoverSearchContext *s_ctx, DiscogsCoverReleaseContext *r_ctx);

View File

@@ -48,7 +48,7 @@ const char *LastFmCoverProvider::kUrl = "https://ws.audioscrobbler.com/2.0/";
const char *LastFmCoverProvider::kApiKey = "211990b4c96782c05d1536e7219eb56e"; const char *LastFmCoverProvider::kApiKey = "211990b4c96782c05d1536e7219eb56e";
const char *LastFmCoverProvider::kSecret = "80fd738f49596e9709b1bf9319c444a8"; const char *LastFmCoverProvider::kSecret = "80fd738f49596e9709b1bf9319c444a8";
LastFmCoverProvider::LastFmCoverProvider(Application *app, QObject *parent) : CoverProvider("last.fm", true, app, parent), network_(new NetworkAccessManager(this)) {} LastFmCoverProvider::LastFmCoverProvider(Application *app, QObject *parent) : CoverProvider("last.fm", 1.0, true, app, parent), network_(new NetworkAccessManager(this)) {}
bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, int id) { bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
@@ -156,7 +156,7 @@ void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, int id) {
continue; continue;
} }
QString artist = json_obj["artist"].toString(); QString artist = json_obj["artist"].toString();
QString name = json_obj["name"].toString(); QString album = json_obj["name"].toString();
QJsonValue json_image = json_obj["image"]; QJsonValue json_image = json_obj["image"];
if (!json_image.isArray()) { if (!json_image.isArray()) {
@@ -187,7 +187,8 @@ void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, int id) {
if (url.isEmpty()) continue; if (url.isEmpty()) continue;
CoverSearchResult cover_result; CoverSearchResult cover_result;
cover_result.description = artist + " " + name; cover_result.artist = artist;
cover_result.album = album;
cover_result.image_url = url; cover_result.image_url = url;
results << cover_result; results << cover_result;
} }

View File

@@ -49,7 +49,7 @@ const char *MusicbrainzCoverProvider::kReleaseSearchUrl = "https://musicbrainz.o
const char *MusicbrainzCoverProvider::kAlbumCoverUrl = "https://coverartarchive.org/release/%1/front"; const char *MusicbrainzCoverProvider::kAlbumCoverUrl = "https://coverartarchive.org/release/%1/front";
const int MusicbrainzCoverProvider::kLimit = 8; const int MusicbrainzCoverProvider::kLimit = 8;
MusicbrainzCoverProvider::MusicbrainzCoverProvider(Application *app, QObject *parent): CoverProvider("MusicBrainz", true, app, parent), network_(new NetworkAccessManager(this)) {} MusicbrainzCoverProvider::MusicbrainzCoverProvider(Application *app, QObject *parent): CoverProvider("MusicBrainz", 1.5, true, app, parent), network_(new NetworkAccessManager(this)) {}
bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, int id) { bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
@@ -117,22 +117,60 @@ void MusicbrainzCoverProvider::HandleSearchReply(QNetworkReply *reply, int searc
} }
for (const QJsonValue &value : json_releases) { for (const QJsonValue &value : json_releases) {
if (!value.isObject()) { if (!value.isObject()) {
Error("Invalid Json reply, album value is not an object.", value); Error("Invalid Json reply, album value is not an object.", value);
continue; continue;
} }
QJsonObject json_obj = value.toObject(); QJsonObject json_obj = value.toObject();
if (!json_obj.contains("id") || !json_obj.contains("title")) { if (!json_obj.contains("id") || !json_obj.contains("artist-credit") || !json_obj.contains("title")) {
Error("Invalid Json reply, album is missing id or title.", json_obj); Error("Invalid Json reply, album is missing id, artist-credit or title.", json_obj);
continue; continue;
} }
QJsonValue json_artists = json_obj["artist-credit"];
if (!json_artists.isArray()) {
Error("Json artist-credit is not an array.", json_artists);
continue;
}
QJsonArray json_array_artists = json_artists.toArray();
int i = 0;
QString artist;
for (const QJsonValue &json_value_artist : json_array_artists) {
if (!json_value_artist.isObject()) {
Error("Invalid Json reply, artist is not an object.", json_value_artist);
continue;
}
QJsonObject json_obj_artist = json_value_artist.toObject();
if (!json_obj_artist.contains("artist") ) {
Error("Invalid Json reply, artist is missing.", json_obj_artist);
continue;
}
QJsonValue json_value_artist2 = json_obj_artist["artist"];
if (!json_value_artist2.isObject()) {
Error("Invalid Json reply, artist is not an object.", json_value_artist2);
continue;
}
QJsonObject json_obj_artist2 = json_value_artist2.toObject();
if (!json_obj_artist2.contains("name") ) {
Error("Invalid Json reply, artist is missing name.", json_value_artist2);
continue;
}
artist = json_obj_artist2["name"].toString();
++i;
}
if (i > 1) artist = "Various artists";
QString id = json_obj["id"].toString(); QString id = json_obj["id"].toString();
QString title = json_obj["title"].toString(); QString album = json_obj["title"].toString();
CoverSearchResult result; CoverSearchResult cover_result;
QUrl url(QString(kAlbumCoverUrl).arg(id)); QUrl url(QString(kAlbumCoverUrl).arg(id));
result.description = title; cover_result.artist = artist;
result.image_url = url; cover_result.album = album;
results.append(result); cover_result.image_url = url;
results.append(cover_result);
} }
emit SearchFinished(search_id, results); emit SearchFinished(search_id, results);

View File

@@ -54,7 +54,7 @@ const char *TidalCoverProvider::kApiTokenB64 = "UDVYYmVvNUxGdkVTZUR5Ng==";
const int TidalCoverProvider::kLimit = 10; const int TidalCoverProvider::kLimit = 10;
TidalCoverProvider::TidalCoverProvider(Application *app, QObject *parent) : TidalCoverProvider::TidalCoverProvider(Application *app, QObject *parent) :
CoverProvider("Tidal", true, app, parent), CoverProvider("Tidal", 2.0, true, app, parent),
service_(app->internet_services()->Service<TidalService>()), service_(app->internet_services()->Service<TidalService>()),
network_(new NetworkAccessManager(this)) { network_(new NetworkAccessManager(this)) {
@@ -161,7 +161,7 @@ QJsonObject TidalCoverProvider::ExtractJsonObj(QByteArray &data, QString &error)
return QJsonObject(); return QJsonObject();
} }
if (json_doc.isNull() || json_doc.isEmpty()) { if (json_doc.isEmpty()) {
error = Error("Received empty Json document.", data); error = Error("Received empty Json document.", data);
return QJsonObject(); return QJsonObject();
} }
@@ -256,11 +256,15 @@ void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, int id) {
} }
QString artist = json_artist["name"].toString(); QString artist = json_artist["name"].toString();
album.remove(Song::kAlbumRemoveDisc);
album.remove(Song::kAlbumRemoveMisc);
cover = cover.replace("-", "/"); cover = cover.replace("-", "/");
QUrl cover_url (QString("%1/images/%2/%3.jpg").arg(kResourcesUrl).arg(cover).arg("1280x1280")); QUrl cover_url (QString("%1/images/%2/%3.jpg").arg(kResourcesUrl).arg(cover).arg("1280x1280"));
CoverSearchResult cover_result; CoverSearchResult cover_result;
cover_result.description = artist + " " + album; cover_result.artist = artist;
cover_result.album = album;
cover_result.image_url = cover_url; cover_result.image_url = cover_url;
results << cover_result; results << cover_result;