diff --git a/src/songinfo/echonestimages.cpp b/src/songinfo/echonestimages.cpp index 792712566..9e0c5bdcf 100644 --- a/src/songinfo/echonestimages.cpp +++ b/src/songinfo/echonestimages.cpp @@ -17,49 +17,126 @@ #include "echonestimages.h" +#include #include #include +#include +#include "core/closure.h" #include "core/logging.h" +#include "core/network.h" -struct EchoNestImages::Request { - Request(int id) : id_(id), artist_(new Echonest::Artist) {} - - int id_; - std::unique_ptr artist_; -}; - -void EchoNestImages::FetchInfo(int id, const Song& metadata) { - std::shared_ptr request(new Request(id)); - request->artist_->setName(metadata.artist()); - - QNetworkReply* reply = request->artist_->fetchImages(); - connect(reply, SIGNAL(finished()), SLOT(RequestFinished())); - requests_[reply] = request; +namespace { +static const char* kSpotifyBucket = "spotify"; +static const char* kSpotifyArtistUrl = "https://api.spotify.com/v1/artists/%1"; } -void EchoNestImages::RequestFinished() { - QNetworkReply* reply = qobject_cast(sender()); - if (!reply || !requests_.contains(reply)) return; +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(); - - RequestPtr request = requests_.take(reply); - try { - request->artist_->parseProfile(reply); - } - catch (Echonest::ParseError e) { + artist.parseProfile(reply); + } catch (Echonest::ParseError e) { qLog(Warning) << "Error parsing echonest reply:" << e.errorType() << e.what(); } - for (const Echonest::ArtistImage& image : request->artist_->images()) { + 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(request->id_, image.url()); + emit ImageReady(id, image.url()); } } - - emit Finished(request->id_); +} + +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> 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(), + [](decltype(image_urls)::const_reference a, + decltype(image_urls)::const_reference b) { + // Sorted by area ascending. + return (a.second.height() * a.second.width()) < + (b.second.height() * b.second.width()); + }); + 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); + } + }); } diff --git a/src/songinfo/echonestimages.h b/src/songinfo/echonestimages.h index d1763a674..6e67e8726 100644 --- a/src/songinfo/echonestimages.h +++ b/src/songinfo/echonestimages.h @@ -20,24 +20,33 @@ #include -#include "songinfoprovider.h" +#include +#include + +#include "songinfo/songinfoprovider.h" + +class NetworkAccessManager; class QNetworkReply; class EchoNestImages : public SongInfoProvider { Q_OBJECT public: + EchoNestImages(); + virtual ~EchoNestImages(); void FetchInfo(int id, const Song& metadata); private slots: - void RequestFinished(); + void RequestFinished(QNetworkReply*, int id, Echonest::Artist artist); + void IdsFound(QNetworkReply* reply, int id); private: - struct Request; - typedef std::shared_ptr RequestPtr; + void DoSpotifyImageRequest(const QString& id, int request_id); - QMap requests_; + void RegisterReply(QNetworkReply* reply, int id); + QMultiMap replies_; + std::unique_ptr network_; }; #endif // ECHONESTIMAGES_H