Replace echonest artist images with spotify #5416
This commit is contained in:
parent
0a4eafafcd
commit
aeb493c016
|
@ -302,7 +302,6 @@ set(SOURCES
|
|||
songinfo/collapsibleinfoheader.cpp
|
||||
songinfo/collapsibleinfopane.cpp
|
||||
songinfo/echonestbiographies.cpp
|
||||
songinfo/echonestimages.cpp
|
||||
songinfo/songinfobase.cpp
|
||||
songinfo/songinfofetcher.cpp
|
||||
songinfo/songinfoprovider.cpp
|
||||
|
@ -312,6 +311,7 @@ set(SOURCES
|
|||
songinfo/songkickconcerts.cpp
|
||||
songinfo/songkickconcertwidget.cpp
|
||||
songinfo/songplaystats.cpp
|
||||
songinfo/spotifyimages.cpp
|
||||
songinfo/taglyricsinfoprovider.cpp
|
||||
songinfo/ultimatelyricslyric.cpp
|
||||
songinfo/ultimatelyricsprovider.cpp
|
||||
|
@ -594,7 +594,6 @@ set(HEADERS
|
|||
songinfo/collapsibleinfoheader.h
|
||||
songinfo/collapsibleinfopane.h
|
||||
songinfo/echonestbiographies.h
|
||||
songinfo/echonestimages.h
|
||||
songinfo/songinfobase.h
|
||||
songinfo/songinfofetcher.h
|
||||
songinfo/songinfoprovider.h
|
||||
|
@ -604,6 +603,7 @@ set(HEADERS
|
|||
songinfo/songkickconcerts.h
|
||||
songinfo/songkickconcertwidget.h
|
||||
songinfo/songplaystats.h
|
||||
songinfo/spotifyimages.h
|
||||
songinfo/taglyricsinfoprovider.h
|
||||
songinfo/ultimatelyricslyric.h
|
||||
songinfo/ultimatelyricsprovider.h
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
*/
|
||||
|
||||
#include "artistinfoview.h"
|
||||
#include "echonestbiographies.h"
|
||||
#include "echonestimages.h"
|
||||
#include "songinfofetcher.h"
|
||||
#include "songkickconcerts.h"
|
||||
|
||||
#include "songinfo/echonestbiographies.h"
|
||||
#include "songinfo/songinfofetcher.h"
|
||||
#include "songinfo/songkickconcerts.h"
|
||||
#include "songinfo/spotifyimages.h"
|
||||
#include "widgets/prettyimageview.h"
|
||||
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
|
@ -29,8 +30,8 @@
|
|||
|
||||
ArtistInfoView::ArtistInfoView(QWidget* parent) : SongInfoBase(parent) {
|
||||
fetcher_->AddProvider(new EchoNestBiographies);
|
||||
fetcher_->AddProvider(new EchoNestImages);
|
||||
fetcher_->AddProvider(new SongkickConcerts);
|
||||
fetcher_->AddProvider(new SpotifyImages);
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
fetcher_->AddProvider(new EchoNestSimilarArtists);
|
||||
fetcher_->AddProvider(new EchoNestTags);
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine 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.
|
||||
|
||||
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "echonestimages.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include <echonest/Artist.h>
|
||||
#include <qjson/parser.h>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
|
||||
namespace {
|
||||
static const char* kSpotifyBucket = "spotify";
|
||||
static const char* kSpotifyArtistUrl = "https://api.spotify.com/v1/artists/%1";
|
||||
}
|
||||
|
||||
EchoNestImages::EchoNestImages() : network_(new NetworkAccessManager) {}
|
||||
|
||||
EchoNestImages::~EchoNestImages() {}
|
||||
|
||||
void EchoNestImages::FetchInfo(int id, const Song& metadata) {
|
||||
Echonest::Artist artist;
|
||||
artist.setName(metadata.artist());
|
||||
|
||||
// Search for images directly on echonest.
|
||||
// This is currently a bit limited as most results are for last.fm urls that
|
||||
// no longer work.
|
||||
QNetworkReply* reply = artist.fetchImages();
|
||||
RegisterReply(reply, id);
|
||||
NewClosure(reply, SIGNAL(finished()), this,
|
||||
SLOT(RequestFinished(QNetworkReply*, int, Echonest::Artist)),
|
||||
reply, id, artist);
|
||||
|
||||
// Also look up the artist id for the spotify API so we can directly request
|
||||
// images from there too.
|
||||
Echonest::Artist::SearchParams params;
|
||||
params.push_back(
|
||||
qMakePair(Echonest::Artist::Name, QVariant(metadata.artist())));
|
||||
QNetworkReply* rosetta_reply = Echonest::Artist::search(
|
||||
params,
|
||||
Echonest::ArtistInformation(Echonest::ArtistInformation::NoInformation,
|
||||
QStringList() << kSpotifyBucket));
|
||||
RegisterReply(rosetta_reply, id);
|
||||
NewClosure(rosetta_reply, SIGNAL(finished()), this,
|
||||
SLOT(IdsFound(QNetworkReply*, int)), rosetta_reply, id);
|
||||
}
|
||||
|
||||
void EchoNestImages::RequestFinished(QNetworkReply* reply, int id,
|
||||
Echonest::Artist artist) {
|
||||
reply->deleteLater();
|
||||
try {
|
||||
artist.parseProfile(reply);
|
||||
} catch (Echonest::ParseError e) {
|
||||
qLog(Warning) << "Error parsing echonest reply:" << e.errorType()
|
||||
<< e.what();
|
||||
}
|
||||
|
||||
for (const Echonest::ArtistImage& image : artist.images()) {
|
||||
// Echonest still sends these broken URLs for last.fm.
|
||||
if (image.url().authority() != "userserve-ak.last.fm") {
|
||||
emit ImageReady(id, image.url());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EchoNestImages::IdsFound(QNetworkReply* reply, int request_id) {
|
||||
reply->deleteLater();
|
||||
try {
|
||||
Echonest::Artists artists = Echonest::Artist::parseSearch(reply);
|
||||
if (artists.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const Echonest::ForeignIds& foreign_ids = artists.first().foreignIds();
|
||||
for (const Echonest::ForeignId& id : foreign_ids) {
|
||||
if (id.catalog.contains("spotify")) {
|
||||
DoSpotifyImageRequest(id.foreign_id, request_id);
|
||||
}
|
||||
}
|
||||
} catch (Echonest::ParseError e) {
|
||||
qLog(Warning) << "Error parsing echonest reply:" << e.errorType()
|
||||
<< e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void EchoNestImages::DoSpotifyImageRequest(const QString& id, int request_id) {
|
||||
QString artist_id = id.split(":").last();
|
||||
QUrl url(QString(kSpotifyArtistUrl).arg(artist_id));
|
||||
QNetworkReply* reply = network_->get(QNetworkRequest(url));
|
||||
RegisterReply(reply, request_id);
|
||||
NewClosure(reply, SIGNAL(finished()), [this, reply, request_id]() {
|
||||
reply->deleteLater();
|
||||
QJson::Parser parser;
|
||||
QVariantMap result = parser.parse(reply).toMap();
|
||||
QVariantList images = result["images"].toList();
|
||||
QList<QPair<QUrl, QSize>> image_urls;
|
||||
for (const QVariant& image : images) {
|
||||
QVariantMap image_result = image.toMap();
|
||||
image_urls.append(qMakePair(image_result["url"].toUrl(),
|
||||
QSize(image_result["width"].toInt(),
|
||||
image_result["height"].toInt())));
|
||||
}
|
||||
// All the images are the same just different sizes; just pick the largest.
|
||||
std::sort(image_urls.begin(), image_urls.end(),
|
||||
[](const QPair<QUrl, QSize>& a,
|
||||
const QPair<QUrl, QSize>& b) {
|
||||
// Sorted by area ascending.
|
||||
return (a.second.height() * a.second.width()) <
|
||||
(b.second.height() * b.second.width());
|
||||
});
|
||||
if (!image_urls.isEmpty()) {
|
||||
emit ImageReady(request_id, image_urls.last().first);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Keeps track of replies and emits Finished() when all replies associated with
|
||||
// a request are finished with.
|
||||
void EchoNestImages::RegisterReply(QNetworkReply* reply, int id) {
|
||||
replies_.insert(id, reply);
|
||||
NewClosure(reply, SIGNAL(destroyed()), [this, reply, id]() {
|
||||
replies_.remove(id, reply);
|
||||
if (!replies_.contains(id)) {
|
||||
emit Finished(id);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
#include "spotifyimages.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <qjson/parser.h>
|
||||
|
||||
#include <QPair>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
|
||||
namespace {
|
||||
static const char* kSpotifySearchUrl = "https://api.spotify.com/v1/search";
|
||||
static const char* kSpotifyArtistUrl = "https://api.spotify.com/v1/artists/%1";
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
QString ExtractSpotifyId(const QString& spotify_uri) {
|
||||
return spotify_uri.split(':')[2];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SpotifyImages::SpotifyImages()
|
||||
: network_(new NetworkAccessManager) {
|
||||
}
|
||||
|
||||
SpotifyImages::~SpotifyImages() {}
|
||||
|
||||
void SpotifyImages::FetchInfo(int id, const Song& metadata) {
|
||||
if (metadata.artist().isEmpty()) {
|
||||
emit Finished(id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch artist id.
|
||||
QUrl search_url(kSpotifySearchUrl);
|
||||
search_url.addQueryItem("q", metadata.artist());
|
||||
search_url.addQueryItem("type", "artist");
|
||||
search_url.addQueryItem("limit", "1");
|
||||
|
||||
qLog(Debug) << "Fetching artist:" << search_url;
|
||||
|
||||
QNetworkRequest request(search_url);
|
||||
QNetworkReply* reply = network_->get(request);
|
||||
NewClosure(reply, SIGNAL(finished()), [this, id, reply]() {
|
||||
reply->deleteLater();
|
||||
QJson::Parser parser;
|
||||
QVariantMap result = parser.parse(reply).toMap();
|
||||
QVariantMap artists = result["artists"].toMap();
|
||||
if (artists.isEmpty()) {
|
||||
emit Finished(id);
|
||||
return;
|
||||
}
|
||||
QVariantList items = artists["items"].toList();
|
||||
if (items.isEmpty()) {
|
||||
emit Finished(id);
|
||||
return;
|
||||
}
|
||||
QVariantMap artist = items.first().toMap();
|
||||
QString spotify_uri = artist["uri"].toString();
|
||||
|
||||
FetchImagesForArtist(id, ExtractSpotifyId(spotify_uri));
|
||||
});
|
||||
}
|
||||
|
||||
void SpotifyImages::FetchImagesForArtist(int id, const QString& spotify_id) {
|
||||
QUrl artist_url(QString(kSpotifyArtistUrl).arg(spotify_id));
|
||||
qLog(Debug) << "Fetching images for artist:" << artist_url;
|
||||
QNetworkRequest request(artist_url);
|
||||
QNetworkReply* reply = network_->get(request);
|
||||
NewClosure(reply, SIGNAL(finished()), [this, id, reply]() {
|
||||
reply->deleteLater();
|
||||
QJson::Parser parser;
|
||||
QVariantMap result = parser.parse(reply).toMap();
|
||||
QVariantList images = result["images"].toList();
|
||||
QList<QPair<QUrl, QSize>> image_candidates;
|
||||
for (QVariant i : images) {
|
||||
QVariantMap image = i.toMap();
|
||||
int height = image["height"].toInt();
|
||||
int width = image["width"].toInt();
|
||||
QUrl url = image["url"].toUrl();
|
||||
image_candidates.append(qMakePair(url, QSize(width, height)));
|
||||
}
|
||||
QPair<QUrl, QSize> winner = *std::max_element(
|
||||
image_candidates.begin(),
|
||||
image_candidates.end(),
|
||||
[](const QPair<QUrl, QSize>& a, const QPair<QUrl, QSize>& b) {
|
||||
return (a.second.height() * a.second.width()) < (b.second.height() * b.second.width());
|
||||
});
|
||||
emit ImageReady(id, winner.first);
|
||||
emit Finished(id);
|
||||
});
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
Copyright 2016, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -15,38 +15,27 @@
|
|||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ECHONESTIMAGES_H
|
||||
#define ECHONESTIMAGES_H
|
||||
#ifndef SPOTIFYIMAGES_H
|
||||
#define SPOTIFYIMAGES_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QMultiMap>
|
||||
|
||||
#include <echonest/Artist.h>
|
||||
|
||||
#include "songinfo/songinfoprovider.h"
|
||||
|
||||
class NetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
class EchoNestImages : public SongInfoProvider {
|
||||
class SpotifyImages : public SongInfoProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
EchoNestImages();
|
||||
virtual ~EchoNestImages();
|
||||
void FetchInfo(int id, const Song& metadata);
|
||||
SpotifyImages();
|
||||
~SpotifyImages();
|
||||
|
||||
private slots:
|
||||
void RequestFinished(QNetworkReply*, int id, Echonest::Artist artist);
|
||||
void IdsFound(QNetworkReply* reply, int id);
|
||||
void FetchInfo(int id, const Song& metadata) override;
|
||||
|
||||
private:
|
||||
void DoSpotifyImageRequest(const QString& id, int request_id);
|
||||
void FetchImagesForArtist(int id, const QString& spotify_id);
|
||||
|
||||
void RegisterReply(QNetworkReply* reply, int id);
|
||||
QMultiMap<int, QNetworkReply*> replies_;
|
||||
std::unique_ptr<NetworkAccessManager> network_;
|
||||
};
|
||||
|
||||
#endif // ECHONESTIMAGES_H
|
||||
#endif // SPOTIFYIMAGES_H
|
Loading…
Reference in New Issue