Merge branch 'songkick'

Conflicts:
	data/data.qrc
	src/songinfo/artistinfoview.cpp
This commit is contained in:
John Maguire 2012-06-04 14:41:47 +02:00
commit 1f836d8e29
8 changed files with 248 additions and 1 deletions

View File

@ -282,6 +282,7 @@
<file>providers/mtvmusic.png</file>
<file>providers/cdbaby.png</file>
<file>providers/echonest.png</file>
<file>providers/songkick.png</file>
<file>providers/twitter.png</file>
<file>lumberjacksong.txt</file>
<file>schema/schema-18.sql</file>

BIN
data/providers/songkick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 898 B

View File

@ -276,6 +276,7 @@ set(SOURCES
songinfo/songinfosettingspage.cpp
songinfo/songinfotextview.cpp
songinfo/songinfoview.cpp
songinfo/songkickconcerts.cpp
songinfo/songplaystats.cpp
songinfo/twitterartistinfo.cpp
songinfo/ultimatelyricsprovider.cpp
@ -529,6 +530,7 @@ set(HEADERS
songinfo/songinfosettingspage.h
songinfo/songinfotextview.h
songinfo/songinfoview.h
songinfo/songkickconcerts.h
songinfo/songplaystats.h
songinfo/twitterartistinfo.h
songinfo/ultimatelyricsprovider.h

View File

@ -19,6 +19,7 @@
#include "echonestbiographies.h"
#include "echonestimages.h"
#include "songinfofetcher.h"
#include "songkickconcerts.h"
#include "twitterartistinfo.h"
#include "widgets/prettyimageview.h"
@ -32,6 +33,7 @@ ArtistInfoView::ArtistInfoView(QWidget *parent)
{
fetcher_->AddProvider(new EchoNestBiographies);
fetcher_->AddProvider(new EchoNestImages);
fetcher_->AddProvider(new SongkickConcerts);
fetcher_->AddProvider(new TwitterArtistInfo);
#ifdef HAVE_LIBLASTFM
fetcher_->AddProvider(new EchoNestSimilarArtists);

View File

@ -23,12 +23,15 @@
#include <QWheelEvent>
#include <QtDebug>
#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);
}

View File

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

View File

@ -0,0 +1,172 @@
/* This file is part of Clementine.
Copyright 2012, 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 "songkickconcerts.h"
#include <QImage>
#include <QXmlStreamWriter>
#include <echonest/Artist.h>
#include <qjson/parser.h>
#include "core/closure.h"
#include "songinfotextview.h"
const char* SongkickConcerts::kSongkickArtistBucket = "id:songkick";
const char* SongkickConcerts::kSongkickArtistCalendarUrl =
"http://api.songkick.com/api/3.0/artists/%1/calendar.json?"
"per_page=5&"
"apikey=8rgKfy1WU6IlJFfN";
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;
}
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();
}
}
void SongkickConcerts::FetchSongkickCalendar(const QString& artist_id, int id) {
QUrl url(QString(kSongkickArtistCalendarUrl).arg(artist_id));
qLog(Debug) << url;
QNetworkReply* reply = network_.get(QNetworkRequest(url));
NewClosure(reply, SIGNAL(finished()), this, SLOT(CalendarRequestFinished(QNetworkReply*, int)), reply, id);
}
void SongkickConcerts::CalendarRequestFinished(QNetworkReply* reply, int id) {
static const char* kStaticMapUrl =
"http://maps.googleapis.com/maps/api/staticmap"
"?key=AIzaSyDDJqmLOeE1mY_EBONhnQmdXbKtasgCtqg"
"&sensor=false"
"&size=100x100"
"&zoom=12"
"&center=%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);
SongInfoTextView* text_view = new SongInfoTextView;
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["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();
// 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();
}
}
CollapsibleInfoPane::Data data;
data.type_ = CollapsibleInfoPane::Data::Type_Biography;
data.id_ = QString("songkick/%1").arg(id);
data.title_ = tr("Upcoming Concerts");
data.icon_ = QIcon(":providers/songkick.png");
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));
}

View File

@ -0,0 +1,49 @@
/* This file is part of Clementine.
Copyright 2012, 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/>.
*/
#ifndef SONGKICKCONCERTS_H
#define SONGKICKCONCERTS_H
#include "songinfoprovider.h"
#include "core/network.h"
class QNetworkReply;
class SongInfoTextView;
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(QNetworkReply* reply, int id);
void InjectImage(QNetworkReply* reply, SongInfoTextView* text_view);
private:
void FetchSongkickCalendar(const QString& artist_id, int id);
NetworkAccessManager network_;
static const char* kSongkickArtistBucket;
static const char* kSongkickArtistCalendarUrl;
};
#endif