Replace echonest artist images with spotify #5416

This commit is contained in:
John Maguire 2016-06-22 14:53:35 +01:00
parent 0a4eafafcd
commit aeb493c016
5 changed files with 111 additions and 171 deletions

View File

@ -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

View File

@ -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);

View File

@ -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);
}
});
}

View File

@ -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);
});
}

View File

@ -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