1 import urllib, threading, os, gzip, time, json, 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_artist_obj(self, element):
75 if element.text is not None and element.text != "":
80 if child.tag in ['name', 'id', 'image']:
81 ret[child.tag] = child.text
82 if child.tag == 'Albums':
86 for albumitem in album:
87 if albumitem.tag in ['name', 'id']:
88 albie[albumitem.tag] = albumitem.text
90 ret['albums'] = albums
93 def make_album_obj(self, element):
94 if element.text is not None and element.text != "":
98 artist = element.getparent().getparent()
99 if artist is not None:
101 if child.tag == 'name':
102 ret['artist'] = child.text
103 elif child.tag == 'id':
104 ret['artist_id'] = child.text
105 for child in element:
106 if child.tag in ['name', 'id', 'image']:
108 ret[child.tag] = child.text
111 if child.tag == 'Tracks':
115 for trackinfo in track:
116 if trackinfo.tag in ['name', 'id', 'numalbum']:
117 trackd[trackinfo.tag] = trackinfo.text
118 tracks.append(trackd)
119 ret['tracks'] = tracks
122 def artist_walker(self, name_match):
123 for event, element in etree.iterparse(self.fil, tag="artist"):
124 name = element.xpath('./name')[0].text.lower()
125 if name and name.find(name_match) > -1:
126 yield self.make_artist_obj(element)
128 while element.getprevious() is not None:
129 del element.getparent()[0]
132 def album_walker(self, name_match):
133 for event, element in etree.iterparse(self.fil, tag="album"):
134 name = element.xpath('./name')[0].text
135 if name and name.lower().find(name_match) > -1:
136 yield self.make_album_obj(element)
138 while element.getprevious() is not None:
139 del element.getparent()[0]
142 def artistid_walker(self, artistids):
143 for event, element in etree.iterparse(self.fil, tag="artist"):
144 _id = element.xpath('./id')[0].text
145 if _id and int(_id) in artistids:
146 yield self.make_artist_obj(element)
148 while element.getprevious() is not None:
149 del element.getparent()[0]
152 def albumid_walker(self, albumids):
153 for event, element in etree.iterparse(self.fil, tag="album"):
154 _id = element.xpath('./id')[0].text
155 if _id and (int(_id) in albumids):
156 yield self.make_album_obj(element)
158 while element.getprevious() is not None:
159 del element.getparent()[0]
162 def search_artists(self, substr):
163 substr = substr.lower()
164 return (artist for artist in self.artist_walker(substr))
166 def search_albums(self, substr):
167 substr = substr.lower()
168 return (album for album in self.album_walker(substr))
170 def get_artists(self, artistids):
171 return (artist for artist in self.artistid_walker(artistids))
173 def get_albums(self, albumids):
174 return (album for album in self.albumid_walker(albumids))
176 _GET2 = '''http://api.jamendo.com/get2/'''
179 last_query = time.time()
181 cache_time = 60*60*24
182 rate_limit = 1.0 # max queries per second
184 def __init__(self, order,
185 select=['id', 'name', 'image', 'artist_name'],
187 track=['track_album', 'album_artist'],
189 if request == 'track':
190 self.url = "%s%s/%s/json/%s?n=%s&order=%s" % (_GET2, '+'.join(select), request, '+'.join(track), count, order)
192 self.url = "%s%s/%s/json/?n=%s&order=%s" % (_GET2, '+'.join(select), request, count, order)
195 def _ratelimit(self):
197 if now - self.last_query < self.rate_limit:
198 time.sleep(self.rate_limit - (now - self.last_query))
199 self.last_query = now
202 """ratelimited query"""
204 f = urllib.urlopen(self.url)
210 def album_cover(albumid, size=200):
211 to = '~/.cache/jamaendo/cover-%d-%d.jpg'%(albumid, size)
212 if not os.path.isfile(to):
213 url = _GET2+'image/album/redirect/?id=%d&imagesize=%d'%(albumid, size)
214 urllib.urlretrieve(url, to)
218 def track_ogg(trackid):
219 return _GET2+ 'stream/track/redirect/?id=%d&streamencoding=ogg2'%(trackid)
222 def track_mp3(trackid):
223 return _GET2+ 'stream/track/redirect/?id=%d&streamencoding=mp31'%(trackid)
225 class Queries(object):
226 albums_this_week = Query(order='ratingweek_desc')
227 albums_all_time = Query(order='ratingtotal_desc')
228 albums_this_month = Query(order='ratingmonth_desc')
229 albums_today = Query(order='ratingday_desc')
230 playlists_all_time = Query(select=['id','name', 'user_idstr'], request='playlist', order='ratingtotal_desc')
231 tracks_this_month = Query(select=['id', 'name',
233 'album_name', 'artist_name',
234 'album_id', 'artist_id'],
236 order='ratingmonth_desc')