Changeset 1850
- Timestamp:
- 31/10/08 22:36:10 (2 months ago)
- svm:headrev:
cc3e1ea1-1e01-0410-8d68-8b121e83a9d5:11131- Files:
-
- 1 modified
-
freevo/src/audio/plugins/lastfm2.py (modified) (28 diffs)
Legend:
- Unmodified
- Added
- Removed
-
freevo/src/audio/plugins/lastfm2.py
r1826 r1850 1 1 # -*- coding: iso-8859-1 -*- 2 2 # ----------------------------------------------------------------------- 3 # 3 # 4 4 # ----------------------------------------------------------------------- 5 5 # $Id$ … … 31 31 32 32 import sys, os, time 33 import md5, urllib, urllib2, re 33 import md5, urllib, urllib2, httplib, re 34 from threading import Thread 34 35 35 36 import kaa … … 39 40 import plugin 40 41 import rc 42 from event import PLAY_END 41 43 from menu import MenuItem, Menu 44 from gui import AlertBox 42 45 from audio.audioitem import AudioItem 43 46 from audio.player import PlayerGUI … … 56 59 57 60 61 class LastFMError(Exception): 62 """ 63 An exception class for last.fm 64 """ 65 @benchmark(benchmarking, benchmarkcall) 66 def __init__(self, why): 67 Exception.__init__(self) 68 self.why = why 69 70 def __str__(self): 71 return self.why 72 73 74 58 75 class PluginInterface(plugin.MainMenuPlugin): 59 76 """ … … 65 82 | LASTFM_USER = '<last fm user name>' 66 83 | LASTFM_PASS = '<last fm password>' 67 | LASTFM_MPLAYER_AF_TRACK = True68 84 | LASTFM_LOCATIONS = [ 69 85 | ('Last Fm - Neighbours', 'lastfm://user/%s/neighbours' % LASTFM_USER), … … 88 104 return 89 105 plugin.MainMenuPlugin.__init__(self) 106 self.menuitem = None 107 if not os.path.isdir(config.LASTFM_DIR): 108 os.makedirs(config.LASTFM_DIR, 0777) 109 110 90 111 @benchmark(benchmarking, benchmarkcall) 91 112 def config(self): … … 98 119 ('LASTFM_PASS', None, 'Password for www.last.fm'), 99 120 ('LASTFM_LANG', 'en', 'Language of last fm metadata (cn,de,en,es,fr,it,jp,pl,ru,sv,tr)'), 100 #('LASTFM_MPLAYER_AF_TRACK', False, 'Set to True if using mplayer\'s -af track'),121 ('LASTFM_DIR', os.path.join(config.FREEVO_CACHEDIR, 'lastfm'), 'Directory to save lastfm files'), 101 122 ('LASTFM_LOCATIONS', [], 'LastFM locations') 102 123 ] … … 106 127 def items(self, parent): 107 128 _debug_('items(parent=%r)' % (parent,), 2) 108 return [ LastFMMainMenuItem(parent) ]109 110 111 112 class LastFMError(Exception): 113 @benchmark(benchmarking, benchmarkcall)114 def __init__(self, why):115 Exception.__init__(self)116 self.why = why129 self.menuitem = LastFMMainMenuItem(parent) 130 return [ self.menuitem ] 131 132 133 @benchmark(benchmarking, benchmarkcall) 134 def shutdown(self): 135 print 'PluginInterface.shutdown' 136 if self.menuitem is not None: 137 self.menuitem.shutdown() 117 138 118 139 … … 120 141 class LastFMMainMenuItem(MenuItem): 121 142 """ 122 this is the item for the main menu and creates the list123 of commands in asubmenu.143 This is the item for the main menu and creates the list of commands in a 144 submenu. 124 145 """ 125 146 @benchmark(benchmarking, benchmarkcall) … … 153 174 menuw.pushmenu(lfm_menu) 154 175 menuw.refresh() 155 176 177 178 @benchmark(benchmarking, benchmarkcall) 179 def shutdown(self): 180 print 'LastFMMainMenuItem.shutdown' 181 if self.webservices is not None: 182 self.webservices.shutdown() 183 156 184 157 185 … … 162 190 and for displaying stdout and stderr of last command run. 163 191 """ 192 poll_interval = 4 193 poll_interval = 1 164 194 @benchmark(benchmarking, benchmarkcall) 165 195 def __init__(self, parent, name, station, webservices): … … 195 225 196 226 197 @benchmark(benchmarking, benchmarkcall)227 @benchmark(benchmarking, True) #benchmarkcall) 198 228 def eventhandler(self, event, menuw=None): 199 229 _debug_('LastFMItem.eventhandler(event=%s, menuw=%r)' % (event, menuw), 2) … … 223 253 self.menuw = menuw 224 254 225 if self.feed is None or self.entry > len(self.feed.entries):255 if self.feed is None or self.entry >= len(self.feed.entries): 226 256 try: 227 self.feed = self.xspf.parse(self.webservices.request_xspf()) 257 for i in range(3): 258 xspf = self.webservices.request_xspf() 259 if xspf != 'No recs :(': 260 break 261 time.sleep(2) 262 else: 263 if menuw: 264 AlertBox(text='No recs :(').show() 265 rc.post_event(PLAY_END) 266 return 267 268 self.feed = self.xspf.parse(xspf) 269 if self.feed is None: 270 if menuw: 271 AlertBox(text=_('Cannot get XSFP')).show() 272 rc.post_event(PLAY_END) 273 return 228 274 except LastFMError, why: 229 _debug_( 'why=%r' % (why,), DWARNING)275 _debug_(why, DWARNING) 230 276 if menuw: 231 AlertBox(text= why).show()232 rc.post_event( rc.PLAY_END)277 AlertBox(text=str(why)).show() 278 rc.post_event(PLAY_END) 233 279 self.entry = 0 234 280 … … 238 284 self.title = entry.title 239 285 self.length = entry.duration 240 self.image = self.webservices.download_cover(entry.image_url) 241 self.url = entry.location_url 286 basename = entry.artist + '-' + entry.album + '-' + entry.title 287 self.basename = basename.lower().replace(' ', '_').replace('.', '').replace('\'', '').replace(':', '') 288 self.url = os.path.join(config.LASTFM_DIR, self.basename + os.path.splitext(entry.location_url)[1]) 289 self.trackpath = os.path.join(config.LASTFM_DIR, self.basename + os.path.splitext(entry.location_url)[1]) 290 self.location_url = entry.location_url 291 self.track_downloader = self.webservices.download(self.location_url, self.trackpath) 292 self.stream_name = urllib.unquote_plus(self.feed.feed.title) 293 self.imagepath = os.path.join(config.LASTFM_DIR, self.basename + os.path.splitext(entry.image_url)[1]) 294 self.image_downloader = self.webservices.download(entry.image_url, self.imagepath) 295 #self.is_playlist = True 296 # Wait for a bit of the file to be downloaded 297 while self.track_downloader.filesize() < 1024 * 20: 298 if not self.track_downloader.isrunning(): 299 rc.post_event(PLAY_END) 300 return 301 time.sleep(0.1) 242 302 self.player = PlayerGUI(self, menuw) 303 if self.timer is not None and self.timer.active(): 304 self.timer.stop() 243 305 self.timer = kaa.OneShotTimer(self.timerhandler) 244 306 self.timer.start(entry.duration) … … 248 310 if menuw: 249 311 AlertBox(text=error).show() 250 rc.post_event( rc.PLAY_END)312 rc.post_event(PLAY_END) 251 313 252 314 … … 257 319 """ 258 320 if self.timer is None: 259 print 'DJW:timer is None'321 _debug_('timer is not running', DINFO) 260 322 return 261 poll_interval = 3 262 polling_itme = 600 263 now_playing = self.webservices.now_playing() 264 if now_playing is None: 323 if self.track_downloader is None: 324 _debug_('downloader is not running', DERROR) 325 return 326 if self.track_downloader.isrunning(): 327 _debug_('still playing', DINFO) 328 self.timer.start(LastFMItem.poll_interval) 329 else: 265 330 self.entry += 1 266 print 'DJW:', self.entry, len(self.feed.entries)267 331 self.play(self.arg, self.menuw) 268 else:269 _debug_('now_playing: still playing=%r' % (now_playing,))270 #self.timer = kaa.OneShotTimer(self.timerhandler)271 self.timer.start(poll_interval)272 332 273 333 … … 278 338 """ 279 339 _debug_('LastFMItem.stop(arg=%r, menuw=%r)' % (arg, menuw), 1) 280 if self.timer is not None :340 if self.timer is not None and self.timer.active(): 281 341 self.timer.stop() 282 self.timer = None342 self.timer = None 283 343 284 344 … … 286 346 def skip(self): 287 347 """Skip song""" 288 _debug_('skip()', 2)348 _debug_('skip()', 1) 289 349 self.entry += 1 290 if self.timer is not None :350 if self.timer is not None and self.timer.active(): 291 351 self.timer.stop() 292 self.timer = None352 self.timer = None 293 353 self.play(self.arg, self.menuw) 294 354 … … 297 357 def love(self): 298 358 """Send "Love" information to audioscrobbler""" 299 _debug_('love()', 2)359 _debug_('love()', 1) 300 360 self.webservices.love() 301 361 … … 304 364 def ban(self): 305 365 """Send "Ban" information to audioscrobbler""" 306 _debug_('ban()', 2)366 _debug_('ban()', 1) 307 367 self.webservices.ban() 368 369 370 371 372 class SmartRedirectHandler(urllib2.HTTPRedirectHandler): 373 def http_error_301(self, req, fp, code, msg, headers): 374 result = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers) 375 result.status = code 376 return result 377 378 def http_error_302(self, req, fp, code, msg, headers): 379 result = urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers) 380 result.status = code 381 return result 382 383 384 385 class LastFMDownloader(Thread): 386 """ 387 Download the stream to a file 388 389 There is a bad bug im mplayer that corrupts the url passed, so we have to 390 download it to a file and then play it 391 """ 392 def __init__(self, url, filename, headers=None): 393 Thread.__init__(self) 394 self.url = url 395 self.filename = filename 396 self.headers = headers 397 self.running = True 398 self.size = 0 399 400 401 def run(self): 402 """ 403 Execute a download operation. Stop when finished downloading or 404 requested to stop. 405 """ 406 httplib.HTTPConnection.debuglevel = 1 407 request = urllib2.Request(self.url, headers=self.headers) 408 opener = urllib2.build_opener(SmartRedirectHandler()) 409 try: 410 httplib.HTTPConnection.debuglevel = 1 411 f = opener.open(request) 412 fd = open(self.filename, 'wb') 413 while self.running: 414 reply = f.read(1024 * 100) 415 fd.write(reply) 416 if len(reply) == 0: 417 self.running = False 418 _debug_('%s downloaded' % self.filename) 419 # what we could do now is to add tags to track 420 break 421 self.size += len(reply) 422 else: 423 _debug_('%s aborted' % self.filename) 424 fd.close() 425 f.close() 426 except urllib2.HTTPError, why: 427 _debug_('%s: %s' % (self.filename, why), DWARNING) 428 httplib.HTTPConnection.debuglevel = 0 429 430 431 def stop(self): 432 """ 433 Stop the download thead running 434 """ 435 # this does not stop the download thread 436 self.running = False 437 438 439 def filesize(self): 440 """ 441 Get the downloaded file size 442 """ 443 return self.size 444 445 446 def isrunning(self): 447 """ 448 See if the thread running 449 """ 450 return self.running 308 451 309 452 … … 325 468 self.base_url = self.cachefd.readline().strip('\n') 326 469 self.base_path = self.cachefd.readline().strip('\n') 470 self.downloader = None 327 471 except IOError, why: 328 472 self._login() 473 474 475 @benchmark(benchmarking, benchmarkcall) 476 def shutdown(self): 477 """ 478 Shutdown the lasf.fm webservices 479 """ 480 print 'LastFMWebServices.shutdown' 481 if self.downloader is not None: 482 self.downloader.stop() 329 483 330 484 … … 341 495 @returns: reply from request 342 496 """ 343 _debug_('url=%r, data=%r' % (url, data), 1) 344 print 'DJW:url=%r, data=%r' % (url, data) 345 if lines: 346 reply = [] 347 try: 348 lines = urllib.urlopen(url, data).readlines() 349 if lines is None: 350 return [] 351 for line in lines: 352 reply.append(line.strip('\n')) 353 except Exception, why: 354 _debug_('%s: %s' % (url, why), DWARNING) 355 raise 356 _debug_('reply=%r' % (reply,), 1) 357 #DJW# 358 import pprint 359 if len(reply) == 1: 360 print 'DJW:reply', reply 497 httplib.HTTPConnection.debuglevel = 1 498 try: 499 _debug_('url=%r, data=%r' % (url, data), 1) 500 request = urllib2.Request(url) 501 opener = urllib2.build_opener(SmartRedirectHandler()) 502 if lines: 503 reply = [] 504 try: 505 f = opener.open(request) 506 lines = f.readlines() 507 if lines is None: 508 return [] 509 for line in lines: 510 reply.append(line.strip('\n')) 511 except httplib.BadStatusLine, why: 512 print 'BadStatusLine:', why 513 reply = None 514 except AttributeError, why: 515 reply = None 516 except Exception, why: 517 _debug_('%s: %s' % (url, why), DWARNING) 518 raise 519 _debug_('reply=%r' % (reply,), 1) 520 return reply 361 521 else: 362 print 'DJW:reply:', len(reply), 'lines' 363 pprint.pprint(lines) 364 #DJW# 365 return reply 366 else: 367 reply = '' 368 try: 369 reply = urllib.urlopen(url, data).read() 370 except Exception, why: 371 _debug_('%s: %s' % (url, why), DWARNING) 372 raise 373 _debug_('reply=%r' % (reply,), 1) 374 return reply 522 reply = '' 523 try: 524 f = opener.open(request) 525 reply = f.read() 526 except Exception, why: 527 _debug_('%s: %s' % (url, why), DWARNING) 528 raise 529 _debug_('len(reply)=%r' % (len(reply),), 1) 530 return reply 531 finally: 532 httplib.HTTPConnection.debuglevel = 0 375 533 376 534 … … 413 571 if not self.session: 414 572 self._login() 415 request_url = 'http://%s%s/xspf.php?sk=%s&discovery=0&desktop=%s' % \ 573 #request_url = 'http://%s%s/xspf.php?sk=%s&discovery=0&desktop=%s' % \ 574 request_url = 'http://%s%s/xspf.php?sk=%s&discovery=1&desktop=%s' % \ 416 575 (self.base_url, self.base_path, self.session, LastFMWebServices._version) 417 576 return self._urlopen(request_url, lines=False) … … 453 612 454 613 @benchmark(benchmarking, benchmarkcall) 455 def download_cover(self, image_url): 456 """ 457 Download album cover to freevo cache directory 458 XXX don't need to save a file, can use the image directly using imlib2 459 """ 460 _debug_('download_cover(image_url=%r)' % (image_url,), 2) 461 462 os.system('rm -f %s' % os.path.join(config.FREEVO_CACHEDIR, 'lfmcover_*.jpg')) 614 def download(self, url, filename): 615 """ 616 Download album cover or track to last.fm directory. 617 618 Add the session as a cookie to the request 619 620 @param url: location of item to download 621 @param filename: path to downloaded file 622 """ 623 _debug_('download(url=%r, filename=%r)' % (url, filename), 1) 463 624 if not self.session: 464 625 self._login() 465 try: 466 pic_file = self._urlopen(image_url, lines=False) 467 filename = os.path.join(config.FREEVO_CACHEDIR, 'lfmcover_'+str(time.time())+'.jpg') 468 save = open(filename, 'w') 469 try: 470 print >>save, pic_file 471 return filename 472 finally: 473 save.close() 474 except IOError: 475 return None 626 headers = { 627 'Session': self.session, 628 } 629 self.downloader = LastFMDownloader(url, filename, headers) 630 self.downloader.start() 631 return self.downloader 476 632 477 633 … … 525 681 * AUTH1: md5( md5(password) + Timestamp), An md5 sum of an md5 sum of the password, plus the timestamp as salt. 526 682 * AUTH2: Second possible Password. The client uses md5( md5(toLower(password)) + Timestamp) 527 * PLAYER: See Appendix 528 """ 529 return True 683 * PLAYER: See Appendix 684 """ 685 timestamp = time.strftime('%s', time.gmtime(time.time())) 686 username = config.LASTFM_USER 687 password = config.LASTFM_PASS 688 auth = md5.new(md5.new(password).hexdigest()+timestamp).hexdigest() 689 auth2 = md5.new(md5.new(password.lower()).hexdigest()+timestamp).hexdigest() 690 url = 'http://ws.audioscrobbler.com//ass/pwcheck.php?' + \ 691 'time=%s&' % timestamp + \ 692 'username=%s&' % username + \ 693 'auth=%s&' % auth + \ 694 'auth2=%s&' % auth2 + \ 695 'defaultplayer=fvo' 696 return self._urlopen(url) 530 697 531 698 … … 537 704 XSPF is documented at U{http://www.xspf.org/quickstart/} 538 705 """ 539 _LASTFM_NS = 'http://www.audioscrobbler.net/dtd/xspf-lastfm /'706 _LASTFM_NS = 'http://www.audioscrobbler.net/dtd/xspf-lastfm' 540 707 541 708 @benchmark(benchmarking, benchmarkcall) … … 564 731 for track_elem in tracklist.findall('track'): 565 732 track = feedparser.FeedParserDict() 733 track_map = dict((c, p) for p in track_elem.getiterator() for c in p) 566 734 title = track_elem.find('title') 567 735 track.title = title is not None and title.text or u'' … … 576 744 duration_ms = track_elem.find('duration') 577 745 track.duration = duration_ms is not None and int(float(duration_ms.text)/1000.0+0.5) or 0 746 trackauth = track_elem.find('{%s}trackauth' % LastFMXSPF._LASTFM_NS) 747 track.trackauth = trackauth is not None and trackauth.text or u'' 578 748 self.entries.append(track) 579 749 return self 750 751 752 753 if __name__ == '__main__': 754 """ 755 To run this test harness need to have defined in local_conf.py: 756 757 - LASTFM_USER 758 - LASTFM_PASS 759 - LASTFM_LANG 760 """ 761 762 station = 'lastfm://globaltags/jazz' 763 station_url = urllib.quote_plus(station) 764 webservices = LastFMWebServices() 765 print webservices.test_user_pass() 766 print webservices._login() 767 print webservices.adjust_station(station_url) 768 print webservices.request_xspf() 769 print 'sleep(10)' 770 time.sleep(10) 771 for i in range(3): 772 xspf = self.webservices.request_xspf() 773 print xspf 774 if xspf != 'No recs :(': 775 break 776 time.sleep(2) 777 else: 778 print 'Failed to get second playlist'

