1
# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
3
# Copyright (C) 2009 John Iacona
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2, or (at your option)
10
# The Rhythmbox authors hereby grant permission for non-GPL compatible
11
# GStreamer plugins to be used and distributed together with GStreamer
12
# and Rhythmbox. This permission is above and beyond the permissions granted
13
# by the GPL license by which Rhythmbox is covered. If you modify this code
14
# you may extend this exception to your version of the code, but you are not
15
# obligated to do so. If you do not wish to do so, delete this exception
16
# statement from your version.
18
# This program is distributed in the hope that it will be useful,
19
# but WITHOUT ANY WARRANTY; without even the implied warranty of
20
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
# GNU General Public License for more details.
23
# You should have received a copy of the GNU General Public License
24
# along with this program; if not, write to the Free Software
25
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
31
from mako.template import Template
32
import xml.dom.minidom as dom
37
from gi.repository import RB
38
from gi.repository import Gtk
39
from gi.repository import WebKit
41
class AlbumTab (gobject.GObject):
44
'switch-tab' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
45
(gobject.TYPE_STRING,))
48
def __init__ (self, shell, buttons, ds, view):
49
gobject.GObject.__init__ (self)
51
self.sp = shell.get_player ()
52
self.db = shell.get_property ('db')
53
self.buttons = buttons
55
self.button = Gtk.ToggleButton (label=_("Albums"))
62
self.button.set_relief (Gtk.ReliefStyle.NONE)
63
self.button.set_focus_on_click(False)
64
self.button.connect ('clicked',
65
lambda button: self.emit ('switch-tab', 'album'))
66
buttons.pack_start (self.button, True, True, 0)
69
self.button.set_active(True)
73
def deactivate (self):
74
self.button.set_active(False)
78
entry = self.sp.get_playing_entry ()
82
artist = entry.get_string (RB.RhythmDBPropType.ARTIST)
83
album = entry.get_string (RB.RhythmDBPropType.ALBUM)
84
if self.active and artist != self.artist:
85
self.view.loading(artist)
86
self.ds.fetch_album_list (artist)
92
class AlbumView (gobject.GObject):
94
def __init__ (self, shell, plugin, webview, ds):
95
gobject.GObject.__init__ (self)
96
self.webview = webview
102
plugindir = os.path.split(plugin.find_file ('context.rb-plugin'))[0]
103
self.basepath = "file://" + urllib.pathname2url (plugindir)
106
self.connect_signals ()
108
def load_view (self):
109
self.webview.load_string(self.file, 'text/html', 'utf-8', self.basepath)
111
def connect_signals (self):
112
self.ds.connect('albums-ready', self.album_list_ready)
114
def loading (self, current_artist):
115
self.loading_file = self.loading_template.render (
116
artist = current_artist,
117
# Translators: 'top' here means 'most popular'. %s is replaced by the artist name.
118
info = _("Loading top albums for %s") % current_artist,
120
basepath = self.basepath)
121
self.webview.load_string (self.loading_file, 'text/html', 'utf-8', self.basepath)
123
def load_tmpl (self):
124
self.path = self.plugin.find_file ('tmpl/album-tmpl.html')
125
self.loading_path = self.plugin.find_file ('tmpl/loading.html')
126
self.album_template = Template (filename = self.path,
127
module_directory = '/tmp/context')
128
self.loading_template = Template (filename = self.loading_path,
129
module_directory = '/tmp/context')
130
self.styles = self.basepath + '/tmpl/main.css'
132
def album_list_ready (self, ds):
133
self.file = self.album_template.render (error = ds.get_error(),
134
list = ds.get_top_albums(),
135
artist = ds.get_artist(),
136
datasource = LastFM.datasource_link (self.basepath),
137
stylesheet = self.styles)
141
class AlbumDataSource (gobject.GObject):
144
'albums-ready' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
147
def __init__ (self, info_cache, ranking_cache):
148
gobject.GObject.__init__ (self)
152
self.max_albums_fetched = 8
153
self.info_cache = info_cache
154
self.ranking_cache = ranking_cache
156
def extract (self, data, position):
158
Safely extract the data from an xml node. Returns data
159
at position or None if position does not exist
162
return data[position].firstChild.data
166
def get_artist (self):
169
def get_error (self):
172
def fetch_album_list (self, artist):
173
if LastFM.user_has_account() is False:
174
self.error = LastFM.NO_ACCOUNT_ERROR
175
self.emit ('albums-ready')
179
qartist = urllib.quote_plus (artist)
181
url = "%sartist.gettopalbums&artist=%s&api_key=%s" % (LastFM.URL_PREFIX,
184
cachekey = 'lastfm:artist:gettopalbums:%s' % qartist
185
self.ranking_cache.fetch(cachekey, url, self.parse_album_list, artist)
187
def parse_album_list (self, data, artist):
189
print "Nothing fetched for %s top albums" % artist
193
parsed = dom.parseString (data)
195
print "Error parsing album list: %s" % e
198
lfm = parsed.getElementsByTagName ('lfm')[0]
199
if lfm.attributes['status'].value == 'failed':
200
self.error = lfm.childNodes[1].firstChild.data
201
self.emit ('albums-ready')
205
album_nodes = parsed.getElementsByTagName ('album')
206
print "num albums: %d" % len(album_nodes)
207
if len(album_nodes) == 0:
208
self.error = "No albums found for %s" % artist
209
self.emit('albums-ready')
212
self.album_info_fetched = min (len (album_nodes) - 1, self.max_albums_fetched)
214
for i, album in enumerate (album_nodes):
215
if i >= self.album_info_fetched:
218
album_name = self.extract(album.getElementsByTagName ('name'), 0)
219
imgs = album.getElementsByTagName ('image')
220
images = (self.extract(imgs, 0), self.extract(imgs, 1), self.extract(imgs, 2))
221
self.albums.append ({'title' : album_name, 'images' : images })
222
self.fetch_album_info (artist, album_name, i)
224
def get_top_albums (self):
227
def fetch_album_info (self, artist, album, index):
228
qartist = urllib.quote_plus (artist)
229
qalbum = urllib.quote_plus (album.encode('utf-8'))
230
cachekey = "lastfm:album:getinfo:%s:%s" % (qartist, qalbum)
231
url = "%salbum.getinfo&artist=%s&album=%s&api_key=%s" % (LastFM.URL_PREFIX,
235
self.info_cache.fetch(cachekey, url, self.fetch_album_tracklist, album, index)
237
def fetch_album_tracklist (self, data, album, index):
239
self.assemble_info(None, None, None)
242
parsed = dom.parseString (data)
243
self.albums[index]['id'] = parsed.getElementsByTagName ('id')[0].firstChild.data
245
print "Error parsing album tracklist: %s" % e
248
self.albums[index]['releasedate'] = self.extract(parsed.getElementsByTagName ('releasedate'),0)
249
self.albums[index]['summary'] = self.extract(parsed.getElementsByTagName ('summary'), 0)
251
cachekey = "lastfm:album:tracks:%s" % self.albums[index]['id']
252
url = "%splaylist.fetch&playlistURL=lastfm://playlist/album/%s&api_key=%s" % (
253
LastFM.URL_PREFIX, self.albums[index]['id'], LastFM.API_KEY)
255
self.info_cache.fetch(cachekey, url, self.assemble_info, album, index)
257
def assemble_info (self, data, album, index):
260
print "nothing fetched for %s tracklist" % album
263
parsed = dom.parseString (data)
264
list = parsed.getElementsByTagName ('track')
267
for i, track in enumerate(list):
268
title = track.getElementsByTagName ('title')[0].firstChild.data
269
duration = int(track.getElementsByTagName ('duration')[0].firstChild.data) / 1000
270
album_length += duration
271
tracklist.append ((i, title, duration))
272
self.albums[index]['tracklist'] = tracklist
273
self.albums[index]['duration'] = album_length
275
print "Error parsing album playlist: %s" % e
278
self.album_info_fetched -= 1
279
print "%s albums left to process" % self.album_info_fetched
280
if self.album_info_fetched == 0:
281
self.emit('albums-ready')