1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 AudioScrobbler music stats submission
19 """
20
21 __maintainer__ = "Philippe Normand <philippe@fluendo.com>"
22
23 from elisa.base_components import service_provider
24 from elisa.core import log, common, component, player
25 from elisa.core.observers.observer import Observer
26 from elisa.core.bus.bus_message import PlayerModel, ComponentsLoaded
27 from twisted.internet import defer, threads
28
29 import gst
30 import urllib2, urllib, threading
31 import re, time, md5, os, socket
32 import xml.utils.iso8601
33 from cPickle import dump, load
34
35
36 UNKNOWN = 'unknown'
37 SAVED_TRACKS_FILE = os.path.expanduser('~/.elisa/unsubmitted_tracks.txt')
38
39 socket.setdefaulttimeout(7)
40
43 if media.artist != 'unknown artist':
44 self.artist = media.artist
45 else:
46 self.artist = UNKNOWN
47 if media.album != 'unknown album':
48 self.album = media.album
49 else:
50 self.album = UNKNOWN
51 self.name = media.song
52 self.length = str(length)
53
54
55 self.mbid = None
56
57 self.date = re.sub("(\d\d\d\d-\d\d-\d\d)T(\d\d:\d\d:\d\d).*","\\1 \\2",
58 xml.utils.iso8601.tostring(time.time()))
59
61 return UNKNOWN not in (self.artist, self.album)
62
64 return "%r by %r from %r (%r @ %r)" % (self.name, self.artist,
65 self.album,
66 self.length, self.date)
67
69 encode = ""
70
71 encode += "a["+str(num)+"]="+urllib.quote_plus(self.artist.encode('utf-8'))
72 encode += "&t["+str(num)+"]="+urllib.quote_plus(self.name.encode('utf-8'))
73 encode += "&l["+str(num)+"]="+urllib.quote_plus(self.length)
74 encode += "&i["+str(num)+"]="+(self.date)
75 if self.mbid is not None:
76 encode += "&m["+str(num)+"]="+urllib.quote_plus(self.mbid)
77 else:
78 encode += "&m["+str(num)+"]="
79 encode += "&b["+str(num)+"]="+urllib.quote_plus(self.album.encode('utf-8'))
80 return encode
81
122
124 """
125 DOCME
126 """
127
128 config_doc = {'user': 'Last.FM username',
129 'password': 'Last.FM password for the user :-)'
130 }
131
132 default_config = {'user': 'fill_me',
133 'password': 'fill_me'
134 }
135
137 user = self.config.get('user')
138 if user == 'fill_me':
139 msg = "Please configure your Last.FM account settings"
140 raise component.InitializeFailure(self.name, msg)
141 self.url = "http://post.audioscrobbler.com/"
142 self.user = user
143 self.password = self.config.get('password')
144 self.client = "eli"
145 self.version = "0.1"
146 self.lock = threading.Lock()
147 self.loadSavedTracks()
148
153
156
161
170
172 if self.tracksToSubmit:
173 self.debug('Saving %s tracks' % len(self.tracksToSubmit))
174 f = open(SAVED_TRACKS_FILE, 'w')
175 dump(self.tracksToSubmit, f)
176 f.close()
177
179 self.logged = False
180 self.debug("Handshaking...")
181 url = self.url+"?"+urllib.urlencode({
182 "hs":"true",
183 "p":"1.1",
184 "c":self.client,
185 "v":self.version,
186 "u":self.user
187 })
188
189 try:
190 result = urllib2.urlopen(url).readlines()
191 except urllib2.URLError, ex:
192 self.debug("Could not connect to AudioScrobbler: %s", ex.reason.message)
193 except Exception, ex:
194 self.debug("Could not connect to AudioScrobbler: %s", ex)
195 else:
196 status = result[0]
197 if status.startswith("BADUSER"):
198 return self.baduser(result[1:])
199 if status.startswith("FAILED"):
200 return self.failed(result)
201 self.logged = True
202 if status.startswith("UPTODATE") or status.startswith("UPDATE"):
203 return self.uptodate(result[1:])
204 return True
205 return False
206
208 self.md5 = re.sub("\n$","", lines[0])
209 self.debug("MD5 = %r" % self.md5)
210 self.submiturl = re.sub("\n$","", lines[1])
211 self.debug("submiturl = %r" % self.submiturl)
212 self.interval(lines[2])
213 return True
214
219
224
226 match = re.match("INTERVAL (\d+)", line)
227 if match is not None:
228 secs = int(match.group(1))
229 self.debug("Sleeping a while (%s seconds)" % secs)
230 time.sleep(secs)
231
233 if not self.logged:
234
235 if not self.handshake():
236 if track not in self.tracksToSubmit:
237 self.debug("Queued submission of track %s" % track)
238 self.tracksToSubmit.append(track)
239 self.debug("%s track(s) currently queued" % len(self.tracksToSubmit))
240 self.saveTracks()
241 return
242
243 if track not in self.tracksToSubmit:
244 self.tracksToSubmit.append(track)
245
246 self.debug("Will try to submit tracks : %s" % str(self.tracksToSubmit))
247
248 try:
249 md5response = md5.md5(md5.md5(self.password).hexdigest()+self.md5).hexdigest()
250 self.debug('md5response: %s' % md5response)
251
252 post = "u="+self.user+"&s="+md5response
253 count = 0
254 self.debug('post: %r' % post)
255
256 for track in self.tracksToSubmit:
257 l = int(track.length)
258 if not track.isSubmitable():
259 self.debug("Missing informations from track, skipping submit")
260 self.debug(track)
261 elif l < 30 or l > (30*60):
262 self.debug("Track is too short or too long, skipping submit")
263 self.debug(track)
264 else:
265 self.debug('encoded: %r' % track.urlencoded(count))
266 post += "&"
267 post += track.urlencoded(count)
268 count += 1
269 except Exception, ex:
270 self.debug('Exception : %s' % ex)
271
272 self.debug('count = %s' % count)
273 self.debug(post)
274 post = unicode(post)
275 if count:
276 try:
277 result = urllib2.urlopen(self.submiturl,post)
278 except Exception,ex:
279 self.debug(ex)
280 self.logged = False
281 else:
282 results = result.readlines()
283 self.debug("submit result : %r" % results)
284 if results[0].startswith("OK"):
285 self.interval(results[1])
286 self.tracksToSubmit = []
287 self.saveTracks()
288 elif results[0].startswith("FAILED"):
289 self.failed(results)
290 self.handshake()
291 elif results[0].startswith("BADAUTH"):
292 self.baduser(results)
293 self.handshake()
294