Improve cover providers score system
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {}
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,8 +156,8 @@ 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()) {
|
||||||
Error("Invalid Json reply, album image is not an array.", json_image);
|
Error("Invalid Json reply, album image is not an array.", json_image);
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user