diff --git a/theory/controllers/main.py b/theory/controllers/main.py
index f547c7e..c3b9ca9 100644
--- a/theory/controllers/main.py
+++ b/theory/controllers/main.py
@@ -161,6 +161,7 @@ def config(self, use_htmlfill=True):
'password':g.tc.password,'webpassword':g.tc.webpassword,
'awskey':g.tc.awskey,'timeout':g.tc.timeout,
'aws_secret':g.tc.aws_secret,
+ 'lastfmkey':g.tc.lastfmkey,
'default_search':g.tc.default_search,
'outputs': configured_outputs})
diff --git a/theory/model/albumart.py b/theory/model/albumart.py
index b05d040..0c979ed 100644
--- a/theory/model/albumart.py
+++ b/theory/model/albumart.py
@@ -33,7 +33,7 @@ class NoArtOnDisk(Exception):
pass
class AlbumArt:
- amazonurl = None
+ weburl = None
imgurl = None
logger = None
@@ -46,7 +46,7 @@ def album_fetch(self,artist,album):
"""
attempt to load an album's cover art from disk.
if it doesn't exist, make a request using Amazon's
- Web Services
+ Web Services and/or the Last.fm API.
"""
self.artist = artist
@@ -63,7 +63,10 @@ def album_fetch(self,artist,album):
try:
self.check_disk()
except NoArtOnDisk:
- self.amazon_fetch()
+ try:
+ self.amazon_fetch()
+ except NoArtError:
+ self.lastfm_fetch()
def artist_art(self,artist):
""" return all of the album covers for a particular artist """
@@ -154,11 +157,71 @@ def amazon_fetch(self):
imgnodes = doc.getElementsByTagName('LargeImage')
if len(imgnodes) > 0:
node = imgnodes[0]
- self.amazonurl = node.firstChild.firstChild.nodeValue
- self.log('Found album art: %s' % self.amazonurl)
+ self.weburl = node.firstChild.firstChild.nodeValue
+ self.log('Found album art: %s' % self.weburl)
break
- if not self.amazonurl:
+ if not self.weburl:
+ raise NoArtError
+
+ self.save_to_disk()
+
+ def lastfm_fetch(self):
+ """
+ attempts to fetch album cover art from last.fm and
+ calls save_to_disk() to save the largest image permanently
+ to avoid subsequent lookups.
+ no album? get a picture of the artist.
+ get a key for the last.fm API here: http://www.last.fm/api/account
+ example URL: http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=b25b959554ed76058ac220b7b2e0a026&artist=Cher&album=Believe
+ """
+
+ album = self.album if self.album != 'undefined' else ''
+ artist = self.artist if self.artist != 'undefined' else ''
+
+ if g.tc.lastfmkey == '' or not artist:
+ raise NoArtError
+
+ type = 'album' if (album and artist) else 'artist'
+
+ artist_safe = urllib2.quote(artist)
+ album_safe = urllib2.quote(album)
+
+ query_string = {'api_key': g.tc.lastfmkey, 'artist': artist_safe}
+
+ if type == 'album':
+ query_string.update({'method': 'album.getinfo','album': album_safe})
+
+ elif type == 'artist':
+ query_string.update({'method': 'artist.getinfo'})
+
+ query_string_sorted = '&'.join(['='.join(kv) for kv in sorted(query_string.items())])
+
+ url = {'verb': 'GET',
+ 'protocol': 'http://',
+ 'host': 'ws.audioscrobbler.com',
+ 'request_uri': '/2.0',
+ 'query_string': query_string_sorted.replace(':','%3A')}
+
+ real_url = url['protocol'] + url['host'] + url['request_uri'] + '?' + url['query_string']
+
+ try:
+ self.log('Fetching last.fm %s image: %s' % (type, real_url))
+ urlfile = urllib2.urlopen(real_url)
+ except urllib2.URLError:
+ # there are probably other exceptions that need to be caught here..
+ self.log('Error fetching last.fm XML')
+ raise NoArtError
+
+ doc = xml.dom.minidom.parse(urlfile)
+ urlfile.close()
+ images = doc.getElementsByTagName('image')
+ node = filter(lambda x: x.getAttribute('size') == 'extralarge' and x.parentNode.parentNode.nodeName == 'lfm', images)[0]
+ if node.hasChildNodes():
+ self.weburl = node.firstChild.nodeValue
+ self.log('Found %s art: %s' % (type, self.weburl))
+
+ if not self.weburl:
raise NoArtError
self.save_to_disk()
@@ -183,7 +246,7 @@ def check_disk(self):
def save_to_disk(self):
""" save the fetched cover image to disk permanently """
try:
- urlfile = urllib2.urlopen(self.amazonurl)
+ urlfile = urllib2.urlopen(self.weburl)
except urllib2.URLError:
raise NoArtError
diff --git a/theory/model/form.py b/theory/model/form.py
index 91eebd9..c5f0309 100644
--- a/theory/model/form.py
+++ b/theory/model/form.py
@@ -25,6 +25,7 @@ class ConfigForm(formencode.Schema):
default_search = formencode.validators.String(not_empty=True)
awskey = formencode.validators.String(strip=True,not_empty=False,if_missing=None)
aws_secret = formencode.validators.String(strip=True,not_empty=False,if_missing=None)
+ lastfmkey = formencode.validators.String(strip=True,not_empty=False,if_missing=None)
outputs = formencode.ForEach(OutputSchema(), if_missing=[])
class StreamNameInUse(formencode.validators.FancyValidator):
diff --git a/theory/model/tconfig.py b/theory/model/tconfig.py
index 58618a4..7d190f3 100644
--- a/theory/model/tconfig.py
+++ b/theory/model/tconfig.py
@@ -36,6 +36,7 @@ def __init__(self):
self.webpassword = ''
self.timeout = False
self.awskey = None
+ self.lastfmkey = None
self.aws_secret = None
self.streams = []
self.default_search = 'Any'
@@ -49,6 +50,7 @@ def __init__(self):
self.password = conf.get('mpd','password')
self.awskey = conf.get('services','awskey')
self.aws_secret = conf.get('services','aws_secret')
+ self.lastfmkey = conf.get('services','lastfmkey')
self.webpassword = conf.get('main','webpassword')
self.timeout = conf.getboolean('main','timeout')
self.default_search = conf.get('main','default_search')
@@ -75,6 +77,7 @@ def commit_config(self):
conf.add_section("services")
conf.set('services','awskey',self.awskey)
conf.set('services','aws_secret',self.aws_secret)
+ conf.set('services','lastfmkey',self.lastfmkey)
conf.add_section('main')
conf.set('main','webpassword',self.webpassword)
conf.set('main','timeout',self.timeout)
diff --git a/theory/templates/config.html b/theory/templates/config.html
index 7a0b4b3..d6ff239 100644
--- a/theory/templates/config.html
+++ b/theory/templates/config.html
@@ -95,6 +95,14 @@
${h.html.tags.text('aws_secret', None, size=45)}
+