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''')
4 _DUMP_TMP = os.path.expanduser('''~/.cache/jamaendo/new_dbdump.xml.gz''')
7 os.makedirs(os.path.dirname(_DUMP))
12 return os.path.isfile(_DUMP)
14 def _file_is_old(fil, old_age):
15 return os.path.getmtime(fil) < (time.time() - old_age)
18 return not has_dump() or _file_is_old(_DUMP, 60*60*24) # 1 day
20 def refresh_dump(complete_callback, progress_callback=None, force=False):
21 if force or _dump_is_old():
22 downloader = Downloader(complete_callback, progress_callback)
25 complete_callback(True)
27 class Downloader(threading.Thread):
28 def __init__(self, complete_callback, progress_callback):
29 threading.Thread.__init__(self)
30 self.complete_callback = complete_callback
31 self.progress_callback = progress_callback
33 def actual_callback(self, numblocks, blocksize, filesize):
34 if self.progress_callback:
36 percent = min((numblocks*blocksize*100)/filesize, 100)
39 self.progress_callback(percent)
44 urllib.urlretrieve(_DUMP_URL, _DUMP_TMP, self.actual_callback)
45 if os.path.isfile(_DUMP):
47 os.rename(_DUMP_TMP, _DUMP)
50 self.complete_callback(success)
52 def fast_iter(context, func):
53 for event, elem in context:
56 while elem.getprevious() is not None:
57 del elem.getparent()[0]
60 from lxml import etree
65 if isinstance(v, basestring):
66 return v.encode('utf-8')
69 return "{%s}" % (", ".join("%s=%s"%(k.encode('utf-8'), printable(v)) \
70 for k,v in self.__dict__.iteritems() if not k.startswith('_')))
72 class LocalDB(object):
77 self.fil = gzip.open(_DUMP)
82 def make_album_brief(self, element):
86 ret['id'] = int(info.text)
87 elif info.tag == 'name':
88 ret['name'] = info.text
91 def make_artist_obj(self, element):
95 ret['id'] = int(child.text)
96 elif child.tag in ('name', 'image'):
97 ret[child.tag] = child.text
98 elif child.tag == 'Albums':
99 ret['albums'] = [self.make_album_brief(a) for a in child]
102 def make_track_obj(self, element):
108 ret['mp3'] = Query.track_mp3(_id)
109 ret['ogg'] = Query.track_ogg(_id)
110 elif info.tag in ('name', 'numalbum'):
111 ret[info.tag] = info.text
114 def make_album_obj(self, element):
116 artist = element.getparent().getparent()
117 if artist is not None:
119 if child.tag == 'name':
120 ret['artist_name'] = child.text
121 elif child.tag == 'id':
122 ret['artist_id'] = int(child.text)
123 for child in element:
124 if child.tag == 'id':
125 ret['id'] = int(child.text)
126 elif child.tag in ('name', 'image'):
128 ret[child.tag] = child.text
131 elif child.tag == 'Tracks':
132 ret['tracks'] = [self.make_track_obj(t) for t in child]
135 def artist_walker(self, name_match):
136 for event, element in etree.iterparse(self.fil, tag="artist"):
137 name = element.xpath('./name')[0].text.lower()
138 if name and name.find(name_match) > -1:
139 yield self.make_artist_obj(element)
141 while element.getprevious() is not None:
142 del element.getparent()[0]
145 def album_walker(self, name_match):
146 for event, element in etree.iterparse(self.fil, tag="album"):
147 name = element.xpath('./name')[0].text
148 if name and name.lower().find(name_match) > -1:
149 yield self.make_album_obj(element)
151 while element.getprevious() is not None:
152 del element.getparent()[0]
155 def artistid_walker(self, artistids):
156 for event, element in etree.iterparse(self.fil, tag="artist"):
157 _id = element.xpath('./id')[0].text
158 if _id and int(_id) in artistids:
159 yield self.make_artist_obj(element)
161 while element.getprevious() is not None:
162 del element.getparent()[0]
165 def albumid_walker(self, albumids):
166 for event, element in etree.iterparse(self.fil, tag="album"):
167 _id = element.xpath('./id')[0].text
168 if _id and (int(_id) in albumids):
169 yield self.make_album_obj(element)
171 while element.getprevious() is not None:
172 del element.getparent()[0]
175 def search_artists(self, substr):
176 substr = substr.lower()
177 return (artist for artist in self.artist_walker(substr))
179 def search_albums(self, substr):
180 substr = substr.lower()
181 return (album for album in self.album_walker(substr))
183 def get_artists(self, artistids):
184 return (artist for artist in self.artistid_walker(artistids))
186 def get_albums(self, albumids):
187 return (album for album in self.albumid_walker(albumids))
189 _GET2 = '''http://api.jamendo.com/get2/'''
192 last_query = time.time()
194 cache_time = 60*60*24
195 rate_limit = 1.0 # max queries per second
198 select=['id', 'name', 'image', 'artist_name', 'artist_id'],
200 track=['track_album', 'album_artist']):
201 if request == 'track':
202 self.url = "%s%s/%s/json/%s" % (_GET2, '+'.join(select), request, '+'.join(track))
204 self.url = "%s%s/%s/json/" % (_GET2, '+'.join(select), request)
206 def __call__(self, order=None, count=5, query=None, albumids=None):
207 return self.emit(order=order, count=count, query=query, albumids=albumids)
209 def emit(self, order=None, count=5, query=None, albumids=None):
210 """ratelimited query"""
212 paramdict = {'n':count}
213 if order is not None:
214 paramdict['order'] = order
215 if query is not None:
216 paramdict['searchquery'] = query
217 if albumids is not None:
218 paramdict['album_id'] = " ".join(str(_id) for _id in albumids)
219 params = urllib.urlencode(paramdict)
220 url = self.url + "?%s" % (params)
221 f = urllib.urlopen(url)
222 ret = simplejson.load(f)
226 def _ratelimit(self):
228 if now - self.last_query < self.rate_limit:
229 time.sleep(self.rate_limit - (now - self.last_query))
230 self.last_query = now
234 def album_cover(albumid, size=200):
235 to = '~/.cache/jamaendo/cover-%d-%d.jpg'%(albumid, size)
236 if not os.path.isfile(to):
237 url = _GET2+'image/album/redirect/?id=%d&imagesize=%d'%(albumid, size)
238 urllib.urlretrieve(url, to)
242 def track_ogg(trackid):
243 return _GET2+ 'stream/track/redirect/?id=%d&streamencoding=ogg2'%(trackid)
246 def track_mp3(trackid):
247 return _GET2+ 'stream/track/redirect/?id=%d&streamencoding=mp31'%(trackid)
249 class Queries(object):
251 def albums_this_week():
252 return Query().emit(order='ratingweek_desc')
254 def albums_all_time():
255 return Query().emit(order='ratingtotal_desc')
257 def albums_this_month():
258 return Query().emit(order='ratingmonth_desc')
261 return Query().emit(order='ratingday_desc')
263 def playlists_all_time():
264 q = Query(select=['id','name', 'user_idstr'], request='playlist')
265 return q.emit(order='ratingtotal_desc')
268 def tracks_this_month():
269 q = Query(select=['id', 'name',
271 'album_name', 'artist_name',
272 'album_id', 'artist_id'],
274 return q.emit(order='ratingmonth_desc')
277 def search_albums(query):
279 return q.emit(order='searchweight_desc', query=query)
282 def search_artists(query):
283 q = Query(request='artist', select=['id', 'name', 'image'])
284 return q.emit(order='searchweight_desc', query=query)
287 def album_tracks(albumids, select=['id', 'name', 'numalbum']):
288 #http://api.jamendo.com/get2/id+name/track/jsonpretty/?album_id=33+46
289 q = Query(select=select,
291 ret = q.emit(albumids=albumids, count=100)
293 track['mp3'] = Query.track_mp3(int(track['id']))
294 track['ogg'] = Query.track_ogg(int(track['id']))