1 import urllib, threading, os, gzip, time, simplejson, re
2 _DUMP_URL = '''http://img.jamendo.com/data/dbdump_artistalbumtrack.xml.gz'''
3 _DUMP = os.path.expanduser('''~/.cache/jamaendo/dbdump.xml.gz''')
6 os.makedirs(os.path.dirname(_DUMP))
11 return os.path.isfile(_DUMP)
13 def _file_is_old(fil, old_age):
14 return os.path.getmtime(fil) < (time.time() - old_age)
17 return not has_dump() or _file_is_old(_DUMP, 60*60*24) # 1 day
19 def refresh_dump(complete_callback, progress_callback=None, force=False):
20 if force or _dump_is_old():
21 downloader = Downloader(complete_callback, progress_callback)
26 class Downloader(threading.Thread):
27 def __init__(self, complete_callback, progress_callback):
28 threading.Thread.__init__(self)
29 self.complete_callback = complete_callback
30 self.progress_callback = progress_callback
32 def actual_callback(self, numblocks, blocksize, filesize):
33 if self.progress_callback:
35 percent = min((numblocks*blocksize*100)/filesize, 100)
38 self.progress_callback(percent)
41 urllib.urlretrieve(_DUMP_URL, _DUMP, self.actual_callback)
42 self.complete_callback()
44 def fast_iter(context, func):
45 for event, elem in context:
48 while elem.getprevious() is not None:
49 del elem.getparent()[0]
52 from lxml import etree
57 if isinstance(v, basestring):
58 return v.encode('utf-8')
61 return "{%s}" % (", ".join("%s=%s"%(k.encode('utf-8'), printable(v)) \
62 for k,v in self.__dict__.iteritems() if not k.startswith('_')))
64 class LocalDB(object):
69 self.fil = gzip.open(_DUMP)
74 def make_album_brief(self, element):
78 ret['id'] = int(info.text)
79 elif info.tag == 'name':
80 ret['name'] = info.text
83 def make_artist_obj(self, element):
87 ret['id'] = int(child.text)
88 elif child.tag in ('name', 'image'):
89 ret[child.tag] = child.text
90 elif child.tag == 'Albums':
91 ret['albums'] = [self.make_album_brief(a) for a in child]
94 def make_track_obj(self, element):
100 ret['mp3'] = Query.track_mp3(_id)
101 ret['ogg'] = Query.track_ogg(_id)
102 elif info.tag in ('name', 'numalbum'):
103 ret[info.tag] = info.text
106 def make_album_obj(self, element):
108 artist = element.getparent().getparent()
109 if artist is not None:
111 if child.tag == 'name':
112 ret['artist'] = child.text
113 elif child.tag == 'id':
114 ret['artist_id'] = int(child.text)
115 for child in element:
116 if child.tag == 'id':
117 ret['id'] = int(child.text)
118 elif child.tag in ('name', 'image'):
120 ret[child.tag] = child.text
123 elif child.tag == 'Tracks':
124 ret['tracks'] = [self.make_track_obj(t) for t in child]
127 def artist_walker(self, name_match):
128 for event, element in etree.iterparse(self.fil, tag="artist"):
129 name = element.xpath('./name')[0].text.lower()
130 if name and name.find(name_match) > -1:
131 yield self.make_artist_obj(element)
133 while element.getprevious() is not None:
134 del element.getparent()[0]
137 def album_walker(self, name_match):
138 for event, element in etree.iterparse(self.fil, tag="album"):
139 name = element.xpath('./name')[0].text
140 if name and name.lower().find(name_match) > -1:
141 yield self.make_album_obj(element)
143 while element.getprevious() is not None:
144 del element.getparent()[0]
147 def artistid_walker(self, artistids):
148 for event, element in etree.iterparse(self.fil, tag="artist"):
149 _id = element.xpath('./id')[0].text
150 if _id and int(_id) in artistids:
151 yield self.make_artist_obj(element)
153 while element.getprevious() is not None:
154 del element.getparent()[0]
157 def albumid_walker(self, albumids):
158 for event, element in etree.iterparse(self.fil, tag="album"):
159 _id = element.xpath('./id')[0].text
160 if _id and (int(_id) in albumids):
161 yield self.make_album_obj(element)
163 while element.getprevious() is not None:
164 del element.getparent()[0]
167 def search_artists(self, substr):
168 substr = substr.lower()
169 return (artist for artist in self.artist_walker(substr))
171 def search_albums(self, substr):
172 substr = substr.lower()
173 return (album for album in self.album_walker(substr))
175 def get_artists(self, artistids):
176 return (artist for artist in self.artistid_walker(artistids))
178 def get_albums(self, albumids):
179 return (album for album in self.albumid_walker(albumids))
181 _GET2 = '''http://api.jamendo.com/get2/'''
184 last_query = time.time()
186 cache_time = 60*60*24
187 rate_limit = 1.0 # max queries per second
189 def __init__(self, order,
190 select=['id', 'name', 'image', 'artist_name'],
192 track=['track_album', 'album_artist'],
194 if request == 'track':
195 self.url = "%s%s/%s/json/%s?n=%s&order=%s" % (_GET2, '+'.join(select), request, '+'.join(track), count, order)
197 self.url = "%s%s/%s/json/?n=%s&order=%s" % (_GET2, '+'.join(select), request, count, order)
200 def _ratelimit(self):
202 if now - self.last_query < self.rate_limit:
203 time.sleep(self.rate_limit - (now - self.last_query))
204 self.last_query = now
207 """ratelimited query"""
209 f = urllib.urlopen(self.url)
210 ret = simplejson.load(f)
215 def album_cover(albumid, size=200):
216 to = '~/.cache/jamaendo/cover-%d-%d.jpg'%(albumid, size)
217 if not os.path.isfile(to):
218 url = _GET2+'image/album/redirect/?id=%d&imagesize=%d'%(albumid, size)
219 urllib.urlretrieve(url, to)
223 def track_ogg(trackid):
224 return _GET2+ 'stream/track/redirect/?id=%d&streamencoding=ogg2'%(trackid)
227 def track_mp3(trackid):
228 return _GET2+ 'stream/track/redirect/?id=%d&streamencoding=mp31'%(trackid)
230 class Queries(object):
231 albums_this_week = Query(order='ratingweek_desc')
232 albums_all_time = Query(order='ratingtotal_desc')
233 albums_this_month = Query(order='ratingmonth_desc')
234 albums_today = Query(order='ratingday_desc')
235 playlists_all_time = Query(select=['id','name', 'user_idstr'], request='playlist', order='ratingtotal_desc')
236 tracks_this_month = Query(select=['id', 'name',
238 'album_name', 'artist_name',
239 'album_id', 'artist_id'],
241 order='ratingmonth_desc')