From 21419765d37283808b482606b0223bed91cfd9bb Mon Sep 17 00:00:00 2001 From: John Maguire Date: Tue, 29 May 2012 16:31:27 -0700 Subject: [PATCH 1/5] Add beginnings of support for requesting events calendar for an artist from songkick (waiting on API key). --- 3rdparty/libechonest/Artist.cpp | 3 +- src/CMakeLists.txt | 2 + src/songinfo/artistinfoview.cpp | 2 + src/songinfo/songkickconcerts.cpp | 61 +++++++++++++++++++++++++++++++ src/songinfo/songkickconcerts.h | 26 +++++++++++++ 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/songinfo/songkickconcerts.cpp create mode 100644 src/songinfo/songkickconcerts.h diff --git a/3rdparty/libechonest/Artist.cpp b/3rdparty/libechonest/Artist.cpp index 56351dcb2..0b5510187 100644 --- a/3rdparty/libechonest/Artist.cpp +++ b/3rdparty/libechonest/Artist.cpp @@ -581,6 +581,8 @@ QByteArray Echonest::Artist::searchParamToString(Echonest::Artist::SearchParam p return "sort"; case Mood: return "mood"; + case IdSpace: + return "bucket"; default: return ""; } @@ -622,4 +624,3 @@ QDebug Echonest::operator<<(QDebug d, const Echonest::Artist& artist) { return d.maybeSpace() << QString::fromLatin1( "Artist(%1, %2)" ).arg( artist.name() ).arg( QString::fromLatin1(artist.id()) ); } - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6eac41b84..5eea6f231 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -276,6 +276,7 @@ set(SOURCES songinfo/songinfosettingspage.cpp songinfo/songinfotextview.cpp songinfo/songinfoview.cpp + songinfo/songkickconcerts.cpp songinfo/songplaystats.cpp songinfo/ultimatelyricsprovider.cpp songinfo/ultimatelyricsreader.cpp @@ -528,6 +529,7 @@ set(HEADERS songinfo/songinfosettingspage.h songinfo/songinfotextview.h songinfo/songinfoview.h + songinfo/songkickconcerts.h songinfo/songplaystats.h songinfo/ultimatelyricsprovider.h songinfo/ultimatelyricsreader.h diff --git a/src/songinfo/artistinfoview.cpp b/src/songinfo/artistinfoview.cpp index 6c13bea14..cdf3879ae 100644 --- a/src/songinfo/artistinfoview.cpp +++ b/src/songinfo/artistinfoview.cpp @@ -19,6 +19,7 @@ #include "echonestbiographies.h" #include "echonestimages.h" #include "songinfofetcher.h" +#include "songkickconcerts.h" #include "widgets/prettyimageview.h" #ifdef HAVE_LIBLASTFM @@ -31,6 +32,7 @@ ArtistInfoView::ArtistInfoView(QWidget *parent) { fetcher_->AddProvider(new EchoNestBiographies); fetcher_->AddProvider(new EchoNestImages); + fetcher_->AddProvider(new SongkickConcerts); #ifdef HAVE_LIBLASTFM fetcher_->AddProvider(new EchoNestSimilarArtists); fetcher_->AddProvider(new EchoNestTags); diff --git a/src/songinfo/songkickconcerts.cpp b/src/songinfo/songkickconcerts.cpp new file mode 100644 index 000000000..d030d2be5 --- /dev/null +++ b/src/songinfo/songkickconcerts.cpp @@ -0,0 +1,61 @@ +#include "songkickconcerts.h" + +#include +#include "core/closure.h" + +const char* SongkickConcerts::kSongkickArtistBucket = "id:songkick"; +const char* SongkickConcerts::kSongkickArtistCalendarUrl = + "http://api.songkick.com/api/3.0/artists/%1/calendar.json?apikey="; + +SongkickConcerts::SongkickConcerts() { + +} + +void SongkickConcerts::FetchInfo(int id, const Song& metadata) { + Echonest::Artist::SearchParams params; + params.push_back(qMakePair(Echonest::Artist::Name, QVariant(metadata.artist()))); + params.push_back(qMakePair(Echonest::Artist::IdSpace, QVariant(kSongkickArtistBucket))); + qLog(Debug) << "Params:" << params; + QNetworkReply* reply = Echonest::Artist::search(params); + qLog(Debug) << reply->request().url(); + NewClosure(reply, SIGNAL(finished()), this, SLOT(ArtistSearchFinished(QNetworkReply*, int)), reply, id); +} + +void SongkickConcerts::ArtistSearchFinished(QNetworkReply* reply, int id) { + reply->deleteLater(); + try { + Echonest::Artists artists = Echonest::Artist::parseSearch(reply); + if (artists.isEmpty()) { + qLog(Debug) << "Failed to find artist in echonest"; + return; + } + + const Echonest::Artist& artist = artists[0]; + const Echonest::ForeignIds& foreign_ids = artist.foreignIds(); + QString songkick_id; + foreach (const Echonest::ForeignId& id, foreign_ids) { + if (id.catalog == "songkick") { + songkick_id = id.foreign_id; + break; + } + } + + if (songkick_id.isEmpty()) { + qLog(Debug) << "Failed to fetch songkick foreign id for artist"; + return; + } + + FetchSongkickCalendar(songkick_id, id); + } catch (Echonest::ParseError& e) { + qLog(Error) << "Error parsing echonest reply:" << e.errorType() << e.what(); + } +} + +void SongkickConcerts::FetchSongkickCalendar(const QString& artist_id, int id) { + QUrl url(QString(kSongkickArtistCalendarUrl).arg(artist_id)); + qLog(Debug) << "Would send request to:" << url; +} + +void SongkickConcerts::CalendarRequestFinished() { + +} diff --git a/src/songinfo/songkickconcerts.h b/src/songinfo/songkickconcerts.h new file mode 100644 index 000000000..7c9b69bff --- /dev/null +++ b/src/songinfo/songkickconcerts.h @@ -0,0 +1,26 @@ +#ifndef SONGKICKCONCERTS_H +#define SONGKICKCONCERTS_H + +#include "songinfoprovider.h" + +class QNetworkReply; + +class SongkickConcerts : public SongInfoProvider { + Q_OBJECT + + public: + SongkickConcerts(); + void FetchInfo(int id, const Song& metadata); + + private slots: + void ArtistSearchFinished(QNetworkReply* reply, int id); + void CalendarRequestFinished(); + + private: + void FetchSongkickCalendar(const QString& artist_id, int id); + + static const char* kSongkickArtistBucket; + static const char* kSongkickArtistCalendarUrl; +}; + +#endif From 32a2cbe6df9caac0bc79c1da97132dd332a27c5c Mon Sep 17 00:00:00 2001 From: John Maguire Date: Tue, 29 May 2012 16:32:34 -0700 Subject: [PATCH 2/5] Add copyright headers --- src/songinfo/songkickconcerts.cpp | 17 +++++++++++++++++ src/songinfo/songkickconcerts.h | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/songinfo/songkickconcerts.cpp b/src/songinfo/songkickconcerts.cpp index d030d2be5..9c4b1b326 100644 --- a/src/songinfo/songkickconcerts.cpp +++ b/src/songinfo/songkickconcerts.cpp @@ -1,3 +1,20 @@ +/* This file is part of Clementine. + Copyright 2012, 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 "songkickconcerts.h" #include diff --git a/src/songinfo/songkickconcerts.h b/src/songinfo/songkickconcerts.h index 7c9b69bff..e7fc341ec 100644 --- a/src/songinfo/songkickconcerts.h +++ b/src/songinfo/songkickconcerts.h @@ -1,3 +1,20 @@ +/* This file is part of Clementine. + Copyright 2012, 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 . +*/ + #ifndef SONGKICKCONCERTS_H #define SONGKICKCONCERTS_H From 1453086a808fab35caca7b14b3e836dd9027653a Mon Sep 17 00:00:00 2001 From: John Maguire Date: Wed, 30 May 2012 13:30:02 -0700 Subject: [PATCH 3/5] Add API key for Songkick and make artist calendar request. --- src/songinfo/songkickconcerts.cpp | 20 +++++++++++++++----- src/songinfo/songkickconcerts.h | 6 +++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/songinfo/songkickconcerts.cpp b/src/songinfo/songkickconcerts.cpp index 9c4b1b326..c4f25dda4 100644 --- a/src/songinfo/songkickconcerts.cpp +++ b/src/songinfo/songkickconcerts.cpp @@ -22,7 +22,9 @@ const char* SongkickConcerts::kSongkickArtistBucket = "id:songkick"; const char* SongkickConcerts::kSongkickArtistCalendarUrl = - "http://api.songkick.com/api/3.0/artists/%1/calendar.json?apikey="; + "http://api.songkick.com/api/3.0/artists/%1/calendar.json?" + "per_page=5&" + "apikey=8rgKfy1WU6IlJFfN"; SongkickConcerts::SongkickConcerts() { @@ -62,7 +64,13 @@ void SongkickConcerts::ArtistSearchFinished(QNetworkReply* reply, int id) { return; } - FetchSongkickCalendar(songkick_id, id); + QStringList split = songkick_id.split(':'); + if (split.count() != 3) { + qLog(Error) << "Weird songkick id"; + return; + } + + FetchSongkickCalendar(split[2], id); } catch (Echonest::ParseError& e) { qLog(Error) << "Error parsing echonest reply:" << e.errorType() << e.what(); } @@ -70,9 +78,11 @@ void SongkickConcerts::ArtistSearchFinished(QNetworkReply* reply, int id) { void SongkickConcerts::FetchSongkickCalendar(const QString& artist_id, int id) { QUrl url(QString(kSongkickArtistCalendarUrl).arg(artist_id)); - qLog(Debug) << "Would send request to:" << url; + qLog(Debug) << url; + QNetworkReply* reply = network_.get(QNetworkRequest(url)); + NewClosure(reply, SIGNAL(finished()), this, SLOT(CalendarRequestFinished(QNetworkReply*, int)), reply, id); } -void SongkickConcerts::CalendarRequestFinished() { - +void SongkickConcerts::CalendarRequestFinished(QNetworkReply* reply, int id) { + qLog(Debug) << reply->readAll(); } diff --git a/src/songinfo/songkickconcerts.h b/src/songinfo/songkickconcerts.h index e7fc341ec..6eb98d469 100644 --- a/src/songinfo/songkickconcerts.h +++ b/src/songinfo/songkickconcerts.h @@ -20,6 +20,8 @@ #include "songinfoprovider.h" +#include "core/network.h" + class QNetworkReply; class SongkickConcerts : public SongInfoProvider { @@ -31,11 +33,13 @@ class SongkickConcerts : public SongInfoProvider { private slots: void ArtistSearchFinished(QNetworkReply* reply, int id); - void CalendarRequestFinished(); + void CalendarRequestFinished(QNetworkReply* reply, int id); private: void FetchSongkickCalendar(const QString& artist_id, int id); + NetworkAccessManager network_; + static const char* kSongkickArtistBucket; static const char* kSongkickArtistCalendarUrl; }; From 0bacedf4652e17995bb1334276188ad0371446f6 Mon Sep 17 00:00:00 2001 From: John Maguire Date: Mon, 4 Jun 2012 14:06:43 +0200 Subject: [PATCH 4/5] Parse SongKick results and generate some simple HTML. --- src/songinfo/songkickconcerts.cpp | 68 ++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/songinfo/songkickconcerts.cpp b/src/songinfo/songkickconcerts.cpp index c4f25dda4..fc1a9600e 100644 --- a/src/songinfo/songkickconcerts.cpp +++ b/src/songinfo/songkickconcerts.cpp @@ -17,8 +17,14 @@ #include "songkickconcerts.h" +#include + #include + +#include + #include "core/closure.h" +#include "songinfotextview.h" const char* SongkickConcerts::kSongkickArtistBucket = "id:songkick"; const char* SongkickConcerts::kSongkickArtistCalendarUrl = @@ -84,5 +90,65 @@ void SongkickConcerts::FetchSongkickCalendar(const QString& artist_id, int id) { } void SongkickConcerts::CalendarRequestFinished(QNetworkReply* reply, int id) { - qLog(Debug) << reply->readAll(); + static const char* kStaticMapUrl = + "http://maps.googleapis.com/maps/api/staticmap" + "?key=AIzaSyDDJqmLOeE1mY_EBONhnQmdXbKtasgCtqg" + "&sensor=false" + "&size=100x100" + "&zoom=12" + "¢er=%1,%2" + "&markers=%1,%2"; + + QJson::Parser parser; + bool ok = false; + QVariant result = parser.parse(reply, &ok); + + if (!ok) { + qLog(Error) << "Error parsing Songkick reply"; + return; + } + + QString html; + QXmlStreamWriter writer(&html); + + QVariantMap root = result.toMap(); + QVariantMap results_page = root["resultsPage"].toMap(); + QVariantMap results = results_page["results"].toMap(); + QVariantList events = results["event"].toList(); + foreach (const QVariant& v, events) { + QVariantMap event = v.toMap(); + { + writer.writeStartElement("div"); + { + writer.writeStartElement("a"); + writer.writeAttribute("href", event["uri"].toString()); + writer.writeCharacters(event["displayName"].toString()); + writer.writeEndElement(); + } + QVariantMap venue = event["venue"].toMap(); + if (venue.contains("lng") && venue.contains("lat")) { + writer.writeStartElement("img"); + QString maps_url = QString(kStaticMapUrl).arg( + venue["lat"].toString(), + venue["lng"].toString()); + writer.writeAttribute("src", maps_url); + writer.writeEndElement(); + qLog(Debug) << maps_url; + } + writer.writeEndElement(); + } + } + + CollapsibleInfoPane::Data data; + data.type_ = CollapsibleInfoPane::Data::Type_Biography; + data.id_ = QString("songkick/%1").arg(id); + data.title_ = tr("Upcoming Concerts"); + data.icon_ = QIcon(); + + SongInfoTextView* text_view = new SongInfoTextView; + text_view->SetHtml(html); + data.contents_ = text_view; + + emit InfoReady(id, data); + emit Finished(id); } From 8452c5428ef8d18df97d4f64b309498ebea36a1b Mon Sep 17 00:00:00 2001 From: John Maguire Date: Mon, 4 Jun 2012 14:40:08 +0200 Subject: [PATCH 5/5] Load images from Google Maps API for Songkick concerts. --- data/data.qrc | 1 + data/providers/songkick.png | Bin 0 -> 898 bytes src/songinfo/songinfotextview.cpp | 21 ++++++++++++++++++++- src/songinfo/songinfotextview.h | 2 ++ src/songinfo/songkickconcerts.cpp | 26 ++++++++++++++++++++++---- src/songinfo/songkickconcerts.h | 2 ++ 6 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 data/providers/songkick.png diff --git a/data/data.qrc b/data/data.qrc index 238f90acd..25e8707a3 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -282,6 +282,7 @@ providers/mtvmusic.png providers/cdbaby.png providers/echonest.png + providers/songkick.png lumberjacksong.txt schema/schema-18.sql star-off.png diff --git a/data/providers/songkick.png b/data/providers/songkick.png new file mode 100644 index 0000000000000000000000000000000000000000..d5294641875abd0bfd124b4560671f0f34580f16 GIT binary patch literal 898 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87?@Ujx;Tbdoc5jU-!U~*;#mEA%lqZ;=USUt zTuulw4it)Nj`l8D*!`}@lxJdM>LsNUisFjf-kw)7xh3&*neUbhD`H%eOeQ!92s(NQ zM87}wm7ci#U#R~dDux|C1m+`|uc`-{(g-nX7LD&gD&1{NR2;EL@tBd)!& z`aHF6UxdEdd9&O{_s-|$e36#=dS+XW&#$-VQyM*^Rc-jzFe{v2z;mTk?60AJ;s3Z3 zQUOQJzW(7f|8QV^=1+GSm4|mvACNI<5;P3sS6o@mE||>h`qO*UY2zuTMg`IxFD1^2 zE(vf}O-RW8#qikd;^I^X*6V*YrScS*za_I}E`2s{-Vw82$K^_ME!)q{-T#%tAY$_5 zy+sVAr*!0gvV1r%@MoPqvvo+Vg}P^u*WIU&_Apsj{bs$J(*0=Vdlk>82fqg=EN_}^ zHS1w*^NWxAJ`1^K+zxxkbo_1=H-qzOv*DpfpQ zefQixtEc6=WauCM3Wt7^_i2;9Fb5|8o+d{ zG4_D-fdc+b9DlB4?{GcvHR-2(+{AFX&A!*C1Jkf-iEBhjN@7W>RdP`(kYX@0Ff`IN zFw-?K2{AOWGB&a@Hr6&UvNA9@!EN~oMMG|WN@iLmNQ0rSp@FWUafqRjm8pf50aU|f T^R+jD8W=oX{an^LB{Ts5o*jw7 literal 0 HcmV?d00001 diff --git a/src/songinfo/songinfotextview.cpp b/src/songinfo/songinfotextview.cpp index 77c882b7c..385dcc79b 100644 --- a/src/songinfo/songinfotextview.cpp +++ b/src/songinfo/songinfotextview.cpp @@ -23,12 +23,15 @@ #include #include +#include "core/logging.h" + const qreal SongInfoTextView::kDefaultFontSize = 8.5; const char* SongInfoTextView::kSettingsGroup = "SongInfo"; SongInfoTextView::SongInfoTextView(QWidget* parent) : QTextBrowser(parent), - last_width_(-1) + last_width_(-1), + recursion_filter_(false) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -88,3 +91,19 @@ void SongInfoTextView::SetHtml(const QString& html) { setHtml(copy); } + +// Prevents QTextDocument from trying to load remote images before they are +// ready. +QVariant SongInfoTextView::loadResource(int type, const QUrl& name) { + if (recursion_filter_) { + recursion_filter_ = false; + return QVariant(); + } + recursion_filter_ = true; + if (type == QTextDocument::ImageResource && name.scheme() == "http") { + if (document()->resource(type, name).isNull()) { + return QVariant(); + } + } + return QTextBrowser::loadResource(type, name); +} diff --git a/src/songinfo/songinfotextview.h b/src/songinfo/songinfotextview.h index 902b74472..3b9fe7b7e 100644 --- a/src/songinfo/songinfotextview.h +++ b/src/songinfo/songinfotextview.h @@ -42,9 +42,11 @@ protected: void resizeEvent(QResizeEvent* e); void wheelEvent(QWheelEvent* e); void contextMenuEvent(QContextMenuEvent* e); + QVariant loadResource(int type, const QUrl& name); private: int last_width_; + bool recursion_filter_; }; #endif // SONGINFOTEXTVIEW_H diff --git a/src/songinfo/songkickconcerts.cpp b/src/songinfo/songkickconcerts.cpp index fc1a9600e..1c8421819 100644 --- a/src/songinfo/songkickconcerts.cpp +++ b/src/songinfo/songkickconcerts.cpp @@ -17,6 +17,7 @@ #include "songkickconcerts.h" +#include #include #include @@ -110,6 +111,7 @@ void SongkickConcerts::CalendarRequestFinished(QNetworkReply* reply, int id) { QString html; QXmlStreamWriter writer(&html); + SongInfoTextView* text_view = new SongInfoTextView; QVariantMap root = result.toMap(); QVariantMap results_page = root["resultsPage"].toMap(); @@ -126,14 +128,21 @@ void SongkickConcerts::CalendarRequestFinished(QNetworkReply* reply, int id) { writer.writeEndElement(); } QVariantMap venue = event["venue"].toMap(); - if (venue.contains("lng") && venue.contains("lat")) { + if (venue["lng"].isValid() && venue["lat"].isValid()) { writer.writeStartElement("img"); QString maps_url = QString(kStaticMapUrl).arg( venue["lat"].toString(), venue["lng"].toString()); writer.writeAttribute("src", maps_url); writer.writeEndElement(); - qLog(Debug) << maps_url; + + // QTextDocument does not support loading remote images, so we load + // them here and then inject them into the document later. + QNetworkRequest request(maps_url); + QNetworkReply* reply = network_.get(request); + NewClosure(reply, SIGNAL(finished()), this, + SLOT(InjectImage(QNetworkReply*, SongInfoTextView*)), + reply, text_view); } writer.writeEndElement(); } @@ -143,12 +152,21 @@ void SongkickConcerts::CalendarRequestFinished(QNetworkReply* reply, int id) { data.type_ = CollapsibleInfoPane::Data::Type_Biography; data.id_ = QString("songkick/%1").arg(id); data.title_ = tr("Upcoming Concerts"); - data.icon_ = QIcon(); + data.icon_ = QIcon(":providers/songkick.png"); - SongInfoTextView* text_view = new SongInfoTextView; text_view->SetHtml(html); data.contents_ = text_view; emit InfoReady(id, data); emit Finished(id); } + +void SongkickConcerts::InjectImage( + QNetworkReply* reply, SongInfoTextView* text_view) { + QImage image; + image.load(reply, "png"); + text_view->document()->addResource( + QTextDocument::ImageResource, + reply->request().url(), + QVariant(image)); +} diff --git a/src/songinfo/songkickconcerts.h b/src/songinfo/songkickconcerts.h index 6eb98d469..79206d488 100644 --- a/src/songinfo/songkickconcerts.h +++ b/src/songinfo/songkickconcerts.h @@ -23,6 +23,7 @@ #include "core/network.h" class QNetworkReply; +class SongInfoTextView; class SongkickConcerts : public SongInfoProvider { Q_OBJECT @@ -34,6 +35,7 @@ class SongkickConcerts : public SongInfoProvider { private slots: void ArtistSearchFinished(QNetworkReply* reply, int id); void CalendarRequestFinished(QNetworkReply* reply, int id); + void InjectImage(QNetworkReply* reply, SongInfoTextView* text_view); private: void FetchSongkickCalendar(const QString& artist_id, int id);