/* This file is part of Clementine. Copyright 2010, David Sansome 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 . */ #include "echonestimages.h" #include #include #include #include #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> 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& a, const QPair& 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); } }); }