[rhythmbox/wip/python3: 31/35] context: ask last.fm for json responses rather than xml
- From: Jonathan Matthew <jmatthew src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rhythmbox/wip/python3: 31/35] context: ask last.fm for json responses rather than xml
- Date: Sun, 21 Apr 2013 00:05:24 +0000 (UTC)
commit f32009f3200c11546f41a3c727a8ead332ee8d06
Author: Jonathan Matthew <jonathan d14n org>
Date: Sat Apr 20 23:25:07 2013 +1000
context: ask last.fm for json responses rather than xml
minidom wasn't up to the job, json is easier.
plugins/context/AlbumTab.py | 136 ++++++++++++++---------------------
plugins/context/ArtistTab.py | 83 +++++----------------
plugins/context/tmpl/album-tmpl.html | 12 ++--
3 files changed, 74 insertions(+), 157 deletions(-)
---
diff --git a/plugins/context/AlbumTab.py b/plugins/context/AlbumTab.py
index 7d24f97..f0212b3 100644
--- a/plugins/context/AlbumTab.py
+++ b/plugins/context/AlbumTab.py
@@ -28,7 +28,7 @@ import os
import cgi
import urllib
from mako.template import Template
-import xml.dom.minidom as dom
+import json
import LastFM
@@ -131,7 +131,7 @@ class AlbumView (GObject.GObject):
def album_list_ready (self, ds):
self.file = self.album_template.render (error = ds.get_error(),
- list = ds.get_top_albums(),
+ albums = ds.get_top_albums(),
artist = ds.get_artist(),
datasource = LastFM.datasource_link (self.basepath),
stylesheet = self.styles)
@@ -150,19 +150,10 @@ class AlbumDataSource (GObject.GObject):
self.error = None
self.artist = None
self.max_albums_fetched = 8
+ self.fetching = 0
self.info_cache = info_cache
self.ranking_cache = ranking_cache
- def extract (self, data, position):
- """
- Safely extract the data from an xml node. Returns data
- at position or None if position does not exist
- """
- try:
- return data[position].firstChild.data
- except Exception as e:
- return None
-
def get_artist (self):
return self.artist
@@ -178,106 +169,83 @@ class AlbumDataSource (GObject.GObject):
self.artist = artist
qartist = urllib.parse.quote_plus(artist)
self.error = None
- url = "%sartist.gettopalbums&artist=%s&api_key=%s" % (LastFM.URL_PREFIX,
- qartist,
- LastFM.API_KEY)
- cachekey = 'lastfm:artist:gettopalbums:%s' % qartist
+ url = "%sartist.gettopalbums&artist=%s&api_key=%s&format=json" % (
+ LastFM.URL_PREFIX, qartist, LastFM.API_KEY)
+ cachekey = 'lastfm:artist:gettopalbumsjson:%s' % qartist
self.ranking_cache.fetch(cachekey, url, self.parse_album_list, artist)
def parse_album_list (self, data, artist):
if data is None:
print("Nothing fetched for %s top albums" % artist)
- return
+ return False
try:
- parsed = dom.parseString (data)
+ parsed = json.loads(data.decode("utf-8"))
except Exception as e:
print("Error parsing album list: %s" % e)
return False
- lfm = parsed.getElementsByTagName ('lfm')[0]
- if lfm.attributes['status'].value == 'failed':
- self.error = lfm.childNodes[1].firstChild.data
+ self.error = parsed.get('error')
+ if self.error:
self.emit ('albums-ready')
- return
+ return False
- self.albums = []
- album_nodes = parsed.getElementsByTagName ('album')
- print("num albums: %d" % len(album_nodes))
- if len(album_nodes) == 0:
+ albums = parsed['topalbums'].get('album', [])
+ if len(albums) == 0:
self.error = "No albums found for %s" % artist
self.emit('albums-ready')
- return
-
- self.album_info_fetched = min (len (album_nodes) - 1, self.max_albums_fetched)
-
- for i, album in enumerate (album_nodes):
- if i >= self.album_info_fetched:
- break
+ return True
+
+ self.albums = []
+ albums = parsed['topalbums'].get('album', [])[:self.max_albums_fetched]
+ self.fetching = len(albums)
+ for i, a in enumerate(albums):
+ images = [img['#text'] for img in a.get('image', [])]
+ self.albums.append({'title': a.get('name'), 'images': images[:3]})
+ self.fetch_album_info(artist, a.get('name'), i)
- album_name = self.extract(album.getElementsByTagName ('name'), 0)
- imgs = album.getElementsByTagName ('image')
- images = (self.extract(imgs, 0), self.extract(imgs, 1), self.extract(imgs, 2))
- self.albums.append ({'title' : album_name, 'images' : images })
- self.fetch_album_info (artist, album_name, i)
+ print(self.albums)
+ return True
def get_top_albums (self):
return self.albums
def fetch_album_info (self, artist, album, index):
qartist = urllib.parse.quote_plus(artist)
- qalbum = urllib.parse.quote_plus(album.encode('utf-8'))
- cachekey = "lastfm:album:getinfo:%s:%s" % (qartist, qalbum)
- url = "%salbum.getinfo&artist=%s&album=%s&api_key=%s" % (LastFM.URL_PREFIX,
- qartist,
- qalbum,
- LastFM.API_KEY)
- self.info_cache.fetch(cachekey, url, self.fetch_album_tracklist, album, index)
-
- def fetch_album_tracklist (self, data, album, index):
- if data is None:
- self.assemble_info(None, None, None)
+ qalbum = urllib.parse.quote_plus(album)
+ cachekey = "lastfm:album:getinfojson:%s:%s" % (qartist, qalbum)
+ url = "%salbum.getinfo&artist=%s&album=%s&api_key=%s&format=json" % (
+ LastFM.URL_PREFIX, qartist, qalbum, LastFM.API_KEY)
+ self.info_cache.fetch(cachekey, url, self.parse_album_info, album, index)
+ def parse_album_info (self, data, album, index):
+ rv = True
try:
- parsed = dom.parseString (data)
- self.albums[index]['id'] = parsed.getElementsByTagName ('id')[0].firstChild.data
- except Exception as e:
- print("Error parsing album tracklist: %s" % e)
- return False
+ parsed = json.loads(data.decode('utf-8'))
+ self.albums[index]['id'] = parsed['album']['id']
- self.albums[index]['releasedate'] = self.extract(parsed.getElementsByTagName ('releasedate'),0)
- self.albums[index]['summary'] = self.extract(parsed.getElementsByTagName ('summary'), 0)
+ for k in ('releasedate', 'summary'):
+ self.albums[index][k] = parsed['album'].get(k)
- cachekey = "lastfm:album:tracks:%s" % self.albums[index]['id']
- url = "%splaylist.fetch&playlistURL=lastfm://playlist/album/%s&api_key=%s" % (
- LastFM.URL_PREFIX, self.albums[index]['id'], LastFM.API_KEY)
+ tracklist = []
+ tracks = parsed['album']['tracks'].get('track', [])
+ for i, t in enumerate(tracks):
+ title = t['name']
+ duration = int(t['duration'])
+ tracklist.append((i, title, duration))
- self.info_cache.fetch(cachekey, url, self.assemble_info, album, index)
+ print(tracklist)
+ self.albums[index]['tracklist'] = tracklist
+ self.albums[index]['duration'] = sum([t[2] for t in tracklist])
+ print("duration: %s" % self.albums[index]['duration'])
- def assemble_info (self, data, album, index):
- rv = True
- if data is None:
- print("nothing fetched for %s tracklist" % album)
- else:
- try:
- parsed = dom.parseString (data)
- list = parsed.getElementsByTagName ('track')
- tracklist = []
- album_length = 0
- for i, track in enumerate(list):
- title = track.getElementsByTagName ('title')[0].firstChild.data
- duration = int(track.getElementsByTagName ('duration')[0].firstChild.data) / 1000
- album_length += duration
- tracklist.append ((i, title, duration))
- self.albums[index]['tracklist'] = tracklist
- self.albums[index]['duration'] = album_length
- except Exception as e:
- print("Error parsing album playlist: %s" % e)
- rv = False
-
- self.album_info_fetched -= 1
- print("%s albums left to process" % self.album_info_fetched)
- if self.album_info_fetched == 0:
+ except Exception as e:
+ print("Error parsing album tracklist: %s" % e)
+ rv = False
+
+ self.fetching -= 1
+ print("%s albums left to process" % self.fetching)
+ if self.fetching == 0:
self.emit('albums-ready')
return rv
diff --git a/plugins/context/ArtistTab.py b/plugins/context/ArtistTab.py
index 77a6de9..bac9293 100644
--- a/plugins/context/ArtistTab.py
+++ b/plugins/context/ArtistTab.py
@@ -28,6 +28,7 @@ import re, os
import cgi
import urllib
import xml.dom.minidom as dom
+import json
from mako.template import Template
@@ -187,39 +188,8 @@ class ArtistDataSource (GObject.GObject):
'cache' : ranking_cache,
'parsed' : False,
},
-
- 'top_tracks' : {
- 'data' : None,
- 'signal' : 'artist-top-tracks-ready',
- 'function' : 'gettoptracks',
- 'cache' : ranking_cache,
- 'parsed' : False,
- },
}
- def extract (self, data, position):
- """
- Safely extract the data from an xml node. Returns data
- at position or None if position does not exist
- """
-
- try:
- return data[position].firstChild.data
- except Exception as e:
- return None
-
- def fetch_top_tracks (self, artist):
- if LastFM.user_has_account() is False:
- return
-
- artist = urllib.parse.quote_plus(artist)
- function = self.artist['top_tracks']['function']
- cache = self.artist['top_tracks']['cache']
- cachekey = "lastfm:artist:%s:%s" % (function, artist)
- url = '%sartist.%s&artist=%s&api_key=%s' % (LastFM.URL_PREFIX,
- function, artist, LastFM.API_KEY)
- cache.fetch(cachekey, url, self.fetch_artist_data_cb, self.artist['top_tracks'])
-
def fetch_artist_data (self, artist):
"""
Initiate the fetching of all artist data. Fetches artist info, similar
@@ -236,9 +206,10 @@ class ArtistDataSource (GObject.GObject):
self.error = None
artist = urllib.parse.quote_plus(artist)
for key, value in self.artist.items():
- cachekey = "lastfm:artist:%s:%s" % (value['function'], artist)
- url = '%sartist.%s&artist=%s&api_key=%s' % (LastFM.URL_PREFIX,
+ cachekey = "lastfm:artist:%sjson:%s" % (value['function'], artist)
+ url = '%sartist.%s&artist=%s&api_key=%s&format=json' % (LastFM.URL_PREFIX,
value['function'], artist, LastFM.API_KEY)
+ print("fetching %s" % url)
value['cache'].fetch(cachekey, url, self.fetch_artist_data_cb, value)
def fetch_artist_data_cb (self, data, category):
@@ -247,7 +218,7 @@ class ArtistDataSource (GObject.GObject):
return
try:
- category['data'] = dom.parseString (data)
+ category['data'] = json.loads(data.decode('utf-8'))
category['parsed'] = False
self.emit (category['signal'])
except Exception as e:
@@ -263,11 +234,11 @@ class ArtistDataSource (GObject.GObject):
def get_top_albums (self):
if not self.artist['top_albums']['parsed']:
albums = []
- for album in self.artist['top_albums']['data'].getElementsByTagName ('album'):
- album_name = self.extract(album.getElementsByTagName ('name'), 0)
- imgs = album.getElementsByTagName ('image')
- images = self.extract(imgs, 0), self.extract(imgs, 1), self.extract(imgs,2)
- albums.append ((album_name, images))
+ d = self.artist['top_albums']['data']
+ for album in d['topalbums'].get('album', []):
+ images = [img['#text'] for img in album.get('image', ())]
+ albums.append((album.get('name'), images[:3]))
+
self.artist['top_albums']['data'] = albums
self.artist['top_albums']['parsed'] = True
@@ -283,11 +254,9 @@ class ArtistDataSource (GObject.GObject):
if not self.artist['similar']['parsed']:
lst = []
- for node in data.getElementsByTagName ('artist'):
- artist = self.extract(node.getElementsByTagName('name'), 0)
- similar = self.extract(node.getElementsByTagName('match') ,0)
- image = self.extract(node.getElementsByTagName('image'), 0)
- lst.append ((artist, similar, image))
+ for node in data['similarartists'].get('artist', []):
+ image = [img['#text'] for img in node.get('image', [])]
+ lst.append ((node.get('name'), node.get('match'), image[:1]))
data = lst
self.artist['similar']['parsed'] = True
self.artist['similar']['data'] = data
@@ -302,8 +271,8 @@ class ArtistDataSource (GObject.GObject):
if data is None:
return None
- images = data.getElementsByTagName ('image')
- return self.extract(images,0), self.extract(images,1), self.extract(images,2)
+ images = [img['#text'] for img in data['artist'].get('image', ())]
+ return images[:3]
def get_artist_bio (self):
"""
@@ -314,8 +283,8 @@ class ArtistDataSource (GObject.GObject):
return None
if not self.artist['info']['parsed']:
- content = self.extract(data.getElementsByTagName ('content'), 0)
- summary = self.extract(data.getElementsByTagName ('summary'), 0)
+ content = data['artist']['bio']['content']
+ summary = data['artist']['bio']['summary']
return summary, content
return self.artist['info']['data']['bio']
@@ -332,21 +301,3 @@ class ArtistDataSource (GObject.GObject):
self.artist['info']['parsed'] = True
return self.artist['info']['data']
-
- def get_top_tracks (self):
- """
- Returns a list of the top track titles
- """
- data = self.artist['top_tracks']['data']
- if data is None:
- return None
-
- if not self.artist['top_tracks']['parsed']:
- tracks = []
- for track in data.getElementsByTagName ('track'):
- name = self.extract(track.getElementsByTagName('name'), 0)
- tracks.append (name)
- self.artist['top_tracks']['data'] = tracks
- self.artist['top_tracks']['parsed'] = True
-
- return self.artist['top_tracks']['data']
diff --git a/plugins/context/tmpl/album-tmpl.html b/plugins/context/tmpl/album-tmpl.html
index 90b736a..a1b1f7b 100644
--- a/plugins/context/tmpl/album-tmpl.html
+++ b/plugins/context/tmpl/album-tmpl.html
@@ -1,4 +1,4 @@
-<%page args="error, list, artist, stylesheet, datasource" />
+<%page args="error, albums, artist, stylesheet, datasource" />
<html> <head> <meta http-equiv="content-type" content="text-html; charset=utf-8">
<%!
import re
@@ -52,15 +52,13 @@
<body>
%if error is None:
<%
- num_albums = min(8, len(list))
+ num_albums = len(albums)
%>
<h1>${ _("Top albums by %s") % ("<em>" + cgi.escape(artist, True) + "</em>") }</h1>
-%for i, entry in enumerate(list) :
+%for i, entry in enumerate(albums) :
<%
if 'tracklist' not in entry or len(entry['tracklist']) == 0:
continue
- if i == num_albums:
- break
%>
<div id="album${entry['id'] | h}" class="album">
<img width="64" src="${entry['images'][1] | h}" alt="${entry['images'] | h}"/>
@@ -73,9 +71,9 @@
<%
album_time = sec2hms(entry['duration'])
tracks = len(entry['tracklist'])
- str = ngettext("%s (%d track)", "%s (%d tracks)", tracks)
+ s = ngettext("%s (%d track)", "%s (%d tracks)", tracks)
%>
- <p class="duration">${ str % (album_time, tracks) }</p>
+ <p class="duration">${ s % (album_time, tracks) }</p>
%endif
%if 'tracklist' in entry:
<button id="btn_${entry['id'] | h}" onclick="toggle_vis(${entry['id'] | h})">
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]