Temporary metadata from Last.fm

This commit is contained in:
David Sansome 2009-12-26 22:15:57 +00:00
parent 7a3678e806
commit 9e285efea7
16 changed files with 137 additions and 28 deletions

View File

@ -1,6 +1,7 @@
#include "lastfmservice.h" #include "lastfmservice.h"
#include "lastfmconfig.h" #include "lastfmconfig.h"
#include "radioitem.h" #include "radioitem.h"
#include "song.h"
#include <lastfm/ws.h> #include <lastfm/ws.h>
#include <lastfm/misc.h> #include <lastfm/misc.h>
@ -107,24 +108,30 @@ void LastFMService::AuthenticateReplyFinished() {
emit AuthenticationComplete(true); emit AuthenticationComplete(true);
} }
QList<QUrl> LastFMService::UrlsForItem(RadioItem* item) { QList<RadioItem::PlaylistData> LastFMService::DataForItem(RadioItem* item) {
QList<QUrl> ret; QList<RadioItem::PlaylistData> ret;
const QString user(lastfm::ws::Username);
switch (item->type) { switch (item->type) {
case Type_MyRecommendations: case Type_MyRecommendations:
ret << QUrl("lastfm://user/" + lastfm::ws::Username + "/recommended"); ret << RadioItem::PlaylistData(user + "'s Recommended Radio",
"lastfm://user/" + lastfm::ws::Username + "/recommended");
break; break;
case Type_MyLoved: case Type_MyLoved:
ret << QUrl("lastfm://user/" + lastfm::ws::Username + "/loved"); ret << RadioItem::PlaylistData(user + "'s Loved Tracks",
"lastfm://user/" + lastfm::ws::Username + "/loved");
break; break;
case Type_MyNeighbourhood: case Type_MyNeighbourhood:
ret << QUrl("lastfm://user/" + lastfm::ws::Username + "/neighbours"); ret << RadioItem::PlaylistData(user + "'s Neighbour Radio",
"lastfm://user/" + lastfm::ws::Username + "/neighbours");
break; break;
case Type_MyRadio: case Type_MyRadio:
ret << QUrl("lastfm://user/" + lastfm::ws::Username + "/library"); ret << RadioItem::PlaylistData(user + "'s Library",
"lastfm://user/" + lastfm::ws::Username + "/library");
break; break;
} }
@ -192,4 +199,8 @@ void LastFMService::TunerTrackAvailable() {
lastfm::Track track = tuner_->takeNextTrack(); lastfm::Track track = tuner_->takeNextTrack();
emit StreamReady(last_url_, track.url()); emit StreamReady(last_url_, track.url());
Song metadata;
metadata.InitFromLastFM(track);
emit StreamMetadataFound(last_url_, metadata);
} }

View File

@ -26,7 +26,7 @@ class LastFMService : public RadioService {
// RadioService // RadioService
RadioItem* CreateRootItem(RadioItem* parent); RadioItem* CreateRootItem(RadioItem* parent);
void LazyPopulate(RadioItem *item); void LazyPopulate(RadioItem *item);
QList<QUrl> UrlsForItem(RadioItem* item); QList<RadioItem::PlaylistData> DataForItem(RadioItem* item);
void StartLoading(const QUrl& url); void StartLoading(const QUrl& url);
void Authenticate(const QString& username, const QString& password); void Authenticate(const QString& username, const QString& password);

View File

@ -143,6 +143,7 @@ MainWindow::MainWindow(QWidget *parent)
connect(radio_model_, SIGNAL(StreamError(QString)), SLOT(ReportError(QString))); connect(radio_model_, SIGNAL(StreamError(QString)), SLOT(ReportError(QString)));
connect(radio_model_, SIGNAL(StreamFinished()), player_, SLOT(Next())); connect(radio_model_, SIGNAL(StreamFinished()), player_, SLOT(Next()));
connect(radio_model_, SIGNAL(StreamReady(QUrl,QUrl)), player_, SLOT(StreamReady(QUrl,QUrl))); connect(radio_model_, SIGNAL(StreamReady(QUrl,QUrl)), player_, SLOT(StreamReady(QUrl,QUrl)));
connect(radio_model_, SIGNAL(StreamMetadataFound(QUrl,Song)), playlist_, SLOT(SetStreamMetadata(QUrl,Song)));
// Tray icon // Tray icon
QMenu* tray_menu = new QMenu(this); QMenu* tray_menu = new QMenu(this);

View File

@ -93,6 +93,8 @@ int Playlist::previous_item() const {
void Playlist::set_current_item(int i) { void Playlist::set_current_item(int i) {
QModelIndex old_current = current_item_; QModelIndex old_current = current_item_;
ClearStreamMetadata();
current_item_ = QPersistentModelIndex(index(i, 0, QModelIndex())); current_item_ = QPersistentModelIndex(index(i, 0, QModelIndex()));
if (old_current.isValid()) if (old_current.isValid())
@ -124,7 +126,7 @@ bool Playlist::dropMimeData(const QMimeData* data, Qt::DropAction action, int ro
InsertSongs(song_data->songs, row); InsertSongs(song_data->songs, row);
} else if (const RadioMimeData* radio_data = qobject_cast<const RadioMimeData*>(data)) { } else if (const RadioMimeData* radio_data = qobject_cast<const RadioMimeData*>(data)) {
// Dragged from the Radio pane // Dragged from the Radio pane
InsertRadioStations(radio_data->services, radio_data->urls(), row); InsertRadioStations(radio_data->services, radio_data->urls(), radio_data->titles, row);
} else if (data->hasFormat(kRowsMimetype)) { } else if (data->hasFormat(kRowsMimetype)) {
// Dragged from the playlist // Dragged from the playlist
// Rearranging it is tricky... // Rearranging it is tricky...
@ -237,12 +239,14 @@ QModelIndex Playlist::InsertSongs(const SongList& songs, int after) {
} }
QModelIndex Playlist::InsertRadioStations(const QList<RadioService*>& services, QModelIndex Playlist::InsertRadioStations(const QList<RadioService*>& services,
const QList<QUrl>& urls, int after) { const QList<QUrl>& urls,
const QStringList& titles, int after) {
Q_ASSERT(services.count() == urls.count()); Q_ASSERT(services.count() == urls.count());
Q_ASSERT(services.count() == titles.count());
QList<PlaylistItem*> items; QList<PlaylistItem*> items;
for (int i=0 ; i<services.count() ; ++i) { for (int i=0 ; i<services.count() ; ++i) {
items << new RadioPlaylistItem(services[i], urls[i]); items << new RadioPlaylistItem(services[i], urls[i], titles[i]);
} }
return InsertItems(items, after); return InsertItems(items, after);
} }
@ -403,3 +407,26 @@ void Playlist::StopAfter(int row) {
if (stop_after_.isValid()) if (stop_after_.isValid())
emit dataChanged(stop_after_, stop_after_.sibling(stop_after_.row(), ColumnCount)); emit dataChanged(stop_after_, stop_after_.sibling(stop_after_.row(), ColumnCount));
} }
void Playlist::SetStreamMetadata(const QUrl& url, const Song& song) {
if (!current_item_.isValid())
return;
PlaylistItem* item = items_[current_item_.row()];
if (item->Url() != url)
return;
item->SetTemporaryMetadata(song);
emit dataChanged(index(current_item_.row(), 0), index(current_item_.row(), ColumnCount));
}
void Playlist::ClearStreamMetadata() {
if (!current_item_.isValid())
return;
PlaylistItem* item = items_[current_item_.row()];
item->ClearTemporaryMetadata();
emit dataChanged(index(current_item_.row(), 0), index(current_item_.row(), ColumnCount));
}

View File

@ -52,7 +52,8 @@ class Playlist : public QAbstractListModel {
QModelIndex InsertItems(const QList<PlaylistItem*>& items, int after = -1); QModelIndex InsertItems(const QList<PlaylistItem*>& items, int after = -1);
QModelIndex InsertSongs(const SongList& items, int after = -1); QModelIndex InsertSongs(const SongList& items, int after = -1);
QModelIndex InsertRadioStations(const QList<RadioService*>& services, QModelIndex InsertRadioStations(const QList<RadioService*>& services,
const QList<QUrl>& urls, int after = -1); const QList<QUrl>& urls,
const QStringList& titles, int after = -1);
QModelIndex InsertPaths(QList<QUrl> urls, int after = -1); QModelIndex InsertPaths(QList<QUrl> urls, int after = -1);
void StopAfter(int row); void StopAfter(int row);
@ -76,6 +77,9 @@ class Playlist : public QAbstractListModel {
void Stopped(); void Stopped();
void IgnoreSorting(bool value) { ignore_sorting_ = value; } void IgnoreSorting(bool value) { ignore_sorting_ = value; }
void ClearStreamMetadata();
void SetStreamMetadata(const QUrl& url, const Song& song);
private: private:
void SetCurrentIsPaused(bool paused); void SetCurrentIsPaused(bool paused);

View File

@ -6,6 +6,8 @@
class QSettings; class QSettings;
class Song;
class PlaylistItem { class PlaylistItem {
public: public:
PlaylistItem() {} PlaylistItem() {}
@ -36,6 +38,9 @@ class PlaylistItem {
// directly to xine instead. // directly to xine instead.
virtual bool StartLoading() { return false; } virtual bool StartLoading() { return false; }
virtual QUrl Url() = 0; virtual QUrl Url() = 0;
virtual void SetTemporaryMetadata(const Song& metadata) {Q_UNUSED(metadata)}
virtual void ClearTemporaryMetadata() {}
}; };
#endif // PLAYLISTITEM_H #endif // PLAYLISTITEM_H

View File

@ -73,7 +73,7 @@ LengthItemDelegate::LengthItemDelegate(QTreeView* view)
{ {
} }
QString LengthItemDelegate::displayText(const QVariant& value, const QLocale& locale) const { QString LengthItemDelegate::displayText(const QVariant& value, const QLocale&) const {
bool ok = false; bool ok = false;
int seconds = value.toInt(&ok); int seconds = value.toInt(&ok);
QString ret = "-"; QString ret = "-";

View File

@ -2,6 +2,7 @@
#define RADIOITEM_H #define RADIOITEM_H
#include <QIcon> #include <QIcon>
#include <QUrl>
#include "simpletreeitem.h" #include "simpletreeitem.h"
@ -14,6 +15,13 @@ class RadioItem : public SimpleTreeItem<RadioItem> {
Type_Service, Type_Service,
}; };
struct PlaylistData {
PlaylistData(const QString& _title, const QUrl& _url) : title(_title), url(_url) {}
QString title;
QUrl url;
};
RadioItem(RadioService* _service, int type, const QString& key = QString::null, RadioItem(RadioService* _service, int type, const QString& key = QString::null,
RadioItem* parent = NULL); RadioItem* parent = NULL);

View File

@ -10,6 +10,7 @@ class RadioMimeData : public QMimeData {
public: public:
QList<RadioService*> services; QList<RadioService*> services;
QList<QString> titles;
}; };
#endif // RADIOMIMEDATA_H #endif // RADIOMIMEDATA_H

View File

@ -27,6 +27,7 @@ void RadioModel::AddService(RadioService *service) {
connect(service, SIGNAL(StreamReady(QUrl,QUrl)), SIGNAL(StreamReady(QUrl,QUrl))); connect(service, SIGNAL(StreamReady(QUrl,QUrl)), SIGNAL(StreamReady(QUrl,QUrl)));
connect(service, SIGNAL(StreamFinished()), SIGNAL(StreamFinished())); connect(service, SIGNAL(StreamFinished()), SIGNAL(StreamFinished()));
connect(service, SIGNAL(StreamError(QString)), SIGNAL(StreamError(QString))); connect(service, SIGNAL(StreamError(QString)), SIGNAL(StreamError(QString)));
connect(service, SIGNAL(StreamMetadataFound(QUrl,Song)), SIGNAL(StreamMetadataFound(QUrl,Song)));
} }
RadioService* RadioModel::ServiceByName(const QString& name) { RadioService* RadioModel::ServiceByName(const QString& name) {
@ -85,16 +86,18 @@ QStringList RadioModel::mimeTypes() const {
QMimeData* RadioModel::mimeData(const QModelIndexList& indexes) const { QMimeData* RadioModel::mimeData(const QModelIndexList& indexes) const {
QList<QUrl> urls; QList<QUrl> urls;
QList<RadioService*> services; QList<RadioService*> services;
QStringList titles;
foreach (const QModelIndex& index, indexes) { foreach (const QModelIndex& index, indexes) {
RadioItem* item = IndexToItem(index); RadioItem* item = IndexToItem(index);
if (!item || !item->service || !item->playable) if (!item || !item->service || !item->playable)
continue; continue;
QList<QUrl> service_urls(item->service->UrlsForItem(item)); QList<RadioItem::PlaylistData> item_data(item->service->DataForItem(item));
foreach (const QUrl& url, service_urls) { foreach (const RadioItem::PlaylistData& data, item_data) {
urls << url; urls << data.url;
services << item->service; services << item->service;
titles << data.title;
} }
} }
@ -104,6 +107,7 @@ QMimeData* RadioModel::mimeData(const QModelIndexList& indexes) const {
RadioMimeData* data = new RadioMimeData; RadioMimeData* data = new RadioMimeData;
data->setUrls(urls); data->setUrls(urls);
data->services = services; data->services = services;
data->titles = titles;
return data; return data;
} }

View File

@ -5,6 +5,7 @@
#include "simpletreemodel.h" #include "simpletreemodel.h"
class RadioService; class RadioService;
class Song;
class RadioModel : public SimpleTreeModel<RadioItem> { class RadioModel : public SimpleTreeModel<RadioItem> {
Q_OBJECT Q_OBJECT
@ -33,6 +34,7 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
void StreamReady(const QUrl& original_url, const QUrl& media_url); void StreamReady(const QUrl& original_url, const QUrl& media_url);
void StreamFinished(); void StreamFinished();
void StreamError(const QString& message); void StreamError(const QString& message);
void StreamMetadataFound(const QUrl& original_url, const Song& song);
protected: protected:
void LazyPopulate(RadioItem* parent); void LazyPopulate(RadioItem* parent);

View File

@ -9,51 +9,70 @@ RadioPlaylistItem::RadioPlaylistItem()
{ {
} }
RadioPlaylistItem::RadioPlaylistItem(RadioService* service, const QUrl& url) RadioPlaylistItem::RadioPlaylistItem(RadioService* service, const QUrl& url,
const QString& title)
: service_(service), : service_(service),
url_(url) url_(url),
title_(title)
{ {
} }
void RadioPlaylistItem::Save(QSettings& settings) const { void RadioPlaylistItem::Save(QSettings& settings) const {
settings.setValue("service", service_->name()); settings.setValue("service", service_->name());
settings.setValue("url", url_.toString()); settings.setValue("url", url_.toString());
settings.setValue("title", title_);
} }
void RadioPlaylistItem::Restore(const QSettings& settings) { void RadioPlaylistItem::Restore(const QSettings& settings) {
service_ = RadioModel::ServiceByName(settings.value("service").toString()); service_ = RadioModel::ServiceByName(settings.value("service").toString());
url_ = settings.value("url").toString(); url_ = settings.value("url").toString();
title_ = settings.value("title").toString();
} }
QString RadioPlaylistItem::Title() const { QString RadioPlaylistItem::Title() const {
if (service_) if (!service_)
return url_.toString(); return "Radio service couldn't be loaded :-(";
return "Radio service couldn't be loaded :-(";
if (metadata_.is_valid())
return metadata_.title();
if (!title_.isEmpty())
return title_;
return url_.toString();
} }
QString RadioPlaylistItem::Artist() const { QString RadioPlaylistItem::Artist() const {
return QString::null; return metadata_.is_valid() ? metadata_.artist() : QString::null;
} }
QString RadioPlaylistItem::Album() const { QString RadioPlaylistItem::Album() const {
return QString::null; return metadata_.is_valid() ? metadata_.album() : QString::null;
} }
int RadioPlaylistItem::Length() const { int RadioPlaylistItem::Length() const {
return -1; return metadata_.is_valid() ? metadata_.length() : -1;
} }
int RadioPlaylistItem::Track() const { int RadioPlaylistItem::Track() const {
return -1; return metadata_.is_valid() ? metadata_.track() : -1;
} }
bool RadioPlaylistItem::StartLoading() { bool RadioPlaylistItem::StartLoading() {
if (service_) if (service_)
service_->StartLoading(url_); service_->StartLoading(url_);
return false; return true;
} }
QUrl RadioPlaylistItem::Url() { QUrl RadioPlaylistItem::Url() {
return url_; return url_;
} }
void RadioPlaylistItem::SetTemporaryMetadata(const Song& metadata) {
metadata_ = metadata;
}
void RadioPlaylistItem::ClearTemporaryMetadata() {
metadata_ = Song();
}

View File

@ -2,6 +2,7 @@
#define RADIOPLAYLISTITEM_H #define RADIOPLAYLISTITEM_H
#include "playlistitem.h" #include "playlistitem.h"
#include "song.h"
#include <QUrl> #include <QUrl>
@ -10,7 +11,7 @@ class RadioService;
class RadioPlaylistItem : public PlaylistItem { class RadioPlaylistItem : public PlaylistItem {
public: public:
RadioPlaylistItem(); RadioPlaylistItem();
RadioPlaylistItem(RadioService* service, const QUrl& url); RadioPlaylistItem(RadioService* service, const QUrl& url, const QString& title);
Type type() const { return Type_Radio; } Type type() const { return Type_Radio; }
@ -26,9 +27,15 @@ class RadioPlaylistItem : public PlaylistItem {
bool StartLoading(); bool StartLoading();
QUrl Url(); QUrl Url();
void SetTemporaryMetadata(const Song& metadata);
void ClearTemporaryMetadata();
private: private:
RadioService* service_; RadioService* service_;
QUrl url_; QUrl url_;
QString title_;
Song metadata_;
}; };
#endif // RADIOPLAYLISTITEM_H #endif // RADIOPLAYLISTITEM_H

View File

@ -5,7 +5,9 @@
#include <QList> #include <QList>
#include <QUrl> #include <QUrl>
class RadioItem; #include "radioitem.h"
class Song;
class RadioService : public QObject { class RadioService : public QObject {
Q_OBJECT Q_OBJECT
@ -19,7 +21,7 @@ class RadioService : public QObject {
virtual RadioItem* CreateRootItem(RadioItem* parent) = 0; virtual RadioItem* CreateRootItem(RadioItem* parent) = 0;
virtual void LazyPopulate(RadioItem* item) = 0; virtual void LazyPopulate(RadioItem* item) = 0;
virtual QList<QUrl> UrlsForItem(RadioItem* item) = 0; virtual QList<RadioItem::PlaylistData> DataForItem(RadioItem* item) = 0;
virtual void StartLoading(const QUrl& url) = 0; virtual void StartLoading(const QUrl& url) = 0;
signals: signals:
@ -28,6 +30,7 @@ class RadioService : public QObject {
void StreamReady(const QUrl& original_url, const QUrl& media_url); void StreamReady(const QUrl& original_url, const QUrl& media_url);
void StreamFinished(); void StreamFinished();
void StreamError(const QString& message); void StreamError(const QString& message);
void StreamMetadataFound(const QUrl& original_url, const Song& song);
private: private:
QString name_; QString name_;

View File

@ -9,6 +9,8 @@
#include <taglib/vorbisfile.h> #include <taglib/vorbisfile.h>
#include <taglib/flacfile.h> #include <taglib/flacfile.h>
#include <lastfm/Track>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QTime> #include <QTime>
@ -193,6 +195,16 @@ void Song::InitFromQuery(const QSqlQuery& q) {
#undef tofloat #undef tofloat
} }
void Song::InitFromLastFM(const lastfm::Track& track) {
valid_ = true;
title_ = track.title();
album_ = track.album();
artist_ = track.artist();
track_ = track.trackNumber();
length_ = track.duration();
}
void Song::BindToQuery(QSqlQuery *query) const { void Song::BindToQuery(QSqlQuery *query) const {
#define intval(x) (x == -1 ? QVariant() : x) #define intval(x) (x == -1 ? QVariant() : x)

View File

@ -5,6 +5,10 @@
#include <QList> #include <QList>
#include <QSqlQuery> #include <QSqlQuery>
namespace lastfm {
class Track;
}
class Song { class Song {
public: public:
Song(); Song();
@ -16,6 +20,7 @@ class Song {
// Constructors // Constructors
void InitFromFile(const QString& filename, int directory_id); void InitFromFile(const QString& filename, int directory_id);
void InitFromQuery(const QSqlQuery& query); void InitFromQuery(const QSqlQuery& query);
void InitFromLastFM(const lastfm::Track& track);
// Save // Save
void BindToQuery(QSqlQuery* query) const; void BindToQuery(QSqlQuery* query) const;