diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 91a83335b..8cb0706fa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/songinfo/artistinfoview.cpp b/src/songinfo/artistinfoview.cpp index 5acfac3d4..e1fc5a18c 100644 --- a/src/songinfo/artistinfoview.cpp +++ b/src/songinfo/artistinfoview.cpp @@ -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); diff --git a/src/songinfo/echonestimages.cpp b/src/songinfo/echonestimages.cpp deleted file mode 100644 index ce13645a8..000000000 --- a/src/songinfo/echonestimages.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* 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); - } - }); -} diff --git a/src/songinfo/spotifyimages.cpp b/src/songinfo/spotifyimages.cpp new file mode 100644 index 000000000..052484c5d --- /dev/null +++ b/src/songinfo/spotifyimages.cpp @@ -0,0 +1,94 @@ +#include "spotifyimages.h" + +#include + +#include + +#include + +#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> 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 winner = *std::max_element( + image_candidates.begin(), + image_candidates.end(), + [](const QPair& a, const QPair& b) { + return (a.second.height() * a.second.width()) < (b.second.height() * b.second.width()); + }); + emit ImageReady(id, winner.first); + emit Finished(id); + }); +} diff --git a/src/songinfo/echonestimages.h b/src/songinfo/spotifyimages.h similarity index 56% rename from src/songinfo/echonestimages.h rename to src/songinfo/spotifyimages.h index 6e67e8726..fe0457ca6 100644 --- a/src/songinfo/echonestimages.h +++ b/src/songinfo/spotifyimages.h @@ -1,5 +1,5 @@ /* This file is part of Clementine. - Copyright 2010, David Sansome + Copyright 2016, John Maguire 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 . */ -#ifndef ECHONESTIMAGES_H -#define ECHONESTIMAGES_H +#ifndef SPOTIFYIMAGES_H +#define SPOTIFYIMAGES_H #include -#include - -#include - #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 replies_; std::unique_ptr network_; }; -#endif // ECHONESTIMAGES_H +#endif // SPOTIFYIMAGES_H