class Doubanfm(object): def __init__(self): self.login_data = {} self.lastfm = True # lastfm 登陆 def init_login(self): print ''' ──╔╗─────╔╗────────╔═╗ ──║║─────║║────────║╔╝ ╔═╝╠══╦╗╔╣╚═╦══╦═╗╔╝╚╦╗╔╗ ║╔╗║╔╗║║║║╔╗║╔╗║╔╗╬╗╔╣╚╝║ ║╚╝║╚╝║╚╝║╚╝║╔╗║║║╠╣║║║║║ ╚══╩══╩══╩══╩╝╚╩╝╚╩╩╝╚╩╩╝ ''' self.douban_login() # 登陆 self.login_lastfm() # 登陆 last.fm print '\033[31m♥\033[0m Get channels ', # self.get_channels() # 获取频道列表 print '[\033[32m OK \033[0m]' # self.get_channellines() # 重构列表用以显示 print '\033[31m♥\033[0m Check PRO ', # self.is_pro() print '[\033[32m OK \033[0m]' def win_login(self): '''登陆界面''' email = raw_input('Email:') password = getpass.getpass('Password:'******'''Last.fm登陆''' if self.lastfm and self.last_fm_username and self.last_fm_password: self.scrobbler = Scrobbler(self.last_fm_username, self.last_fm_password) r, err = self.scrobbler.handshake() if r: logger.info("Last.fm login succeeds!") print '\033[31m♥\033[0m Last.fm logged in: %s' % self.last_fm_username else: logger.error("Last.fm login fails: " + err) self.lastfm = False else: self.lastfm = False def last_fm_account_required(fun): '''装饰器,用于需要登录Last.fm后才能使用的接口''' @wraps(fun) def wrapper(self, *args, **kwds): if not self.lastfm: return return fun(self, *args, **kwds) return wrapper @last_fm_account_required def submit_current_song(self): '''提交播放过的曲目''' # Submit the track if total playback time of the track > 30s if self.playingsong['length'] > 30: self.scrobbler.submit(self.playingsong['artist'], self.playingsong['title'], self.playingsong['albumtitle'], self.playingsong['length']) @last_fm_account_required def scrobble_now_playing(self): '''提交当前正在播放曲目''' self.scrobbler.now_playing(self.playingsong['artist'], self.playingsong['title'], self.playingsong['albumtitle'], self.playingsong['length']) def douban_login(self): '''登陆douban.fm获取token''' path_token = os.path.expanduser('~/.douban_token.txt') if os.path.exists(path_token): # 已登陆 logger.info("Found existing Douban.fm token.") with open(path_token, 'r') as f: self.login_data = pickle.load(f) self.token = self.login_data['token'] self.user_name = self.login_data['user_name'] self.user_id = self.login_data['user_id'] self.expire = self.login_data['expire'] self.default_volume = int(self.login_data['volume'])\ if 'volume' in self.login_data else 50 self.default_channel = int(self.login_data['channel'])\ if 'channel' in self.login_data else 1 # 存储的default_channel是行数而不是真正发送数据的channel_id # 这里需要进行转化一下 self.set_channel(self.default_channel) print '\033[31m♥\033[0m Get local token - Username: \033[33m%s\033[0m' % self.user_name else: # 未登陆 logger.info('First time logging in Douban.fm.') while True: self.email, self.password = self.win_login() login_data = { 'app_name': 'radio_desktop_win', 'version': '100', 'email': self.email, 'password': self.password } s = requests.post('http://www.douban.com/j/app/login', login_data) dic = eval(s.text) if dic['r'] == 1: logger.debug(dic['err']) continue else: self.token = dic['token'] self.user_name = dic['user_name'] self.user_id = dic['user_id'] self.expire = dic['expire'] self.default_volume = 50 self.default_channel = 1 self.login_data = { 'app_name': 'radio_desktop_win', 'version': '100', 'user_id': self.user_id, 'expire': self.expire, 'token': self.token, 'user_name': self.user_name, 'volume': '50', 'channel': '0' } logger.info('Logged in username: '******'w') as f: pickle.dump(self.login_data, f) logger.debug('Write data to ' + path_token) break self.last_fm_username = \ self.login_data['last_fm_username'] if 'last_fm_username' in self.login_data\ else None self.last_fm_password = \ self.login_data['last_fm_password'] if 'last_fm_password' in self.login_data\ else None # last.fm登陆 try: if sys.argv[1] == 'last.fm': from hashlib import md5 username = raw_input('last.fm username:'******'last.fm password:'******'r') as f: data = pickle.load(f) with open(path_token, 'w') as f: data['last_fm_username'] = username data['last_fm_password'] = self.last_fm_password pickle.dump(data, f) except IndexError: pass # 配置文件 path_config = os.path.expanduser('~/.doubanfm_config') if not os.path.exists(path_config): print '\033[31m♥\033[0m Get default config [\033[32m OK \033[0m]' config = '''[key] UP = k DOWN = j TOP = g BOTTOM = G OPENURL = w RATE = r NEXT = n BYE = b QUIT = q PAUSE = p LOOP = l MUTE = m LRC = o ''' # 这个很丑,怎么办 with open(path_config, 'w') as F: F.write(config) else: print '\033[31m♥\033[0m Get local config [\033[32m OK \033[0m]' @property def channels(self): '''获取channel,存入self.channels''' # 红心兆赫需要手动添加 channels = [{'name': '红心兆赫', 'channel_id': -3}] r = requests.get('http://www.douban.com/j/app/radio/channels') channels += eval(r.text)['channels'] # 格式化频道列表,以便display lines = [] for channel in channels: lines.append(channel['name']) return lines def requests_url(self, ptype, **data): '''这里包装了一个函数,发送post_data''' post_data = self.login_data.copy() post_data['type'] = ptype for x in data: post_data[x] = data[x] url = 'http://www.douban.com/j/app/radio/people?' + urllib.urlencode( post_data) try: s = requests.get(url) except requests.exceptions.RequestException: logger.error("Error communicating with Douban.fm API.") return s.text def set_channel(self, channel): '''把行数转化成channel_id''' self.default_channel = channel channel = -3 if channel == 0 else channel - 1 self.login_data['channel'] = channel def get_playlist(self, channel): '''获取播放列表,返回一个list''' if self.default_channel != channel: self.set_channel(channel) s = self.requests_url('n') return eval(s)['song'] def skip_song(self, playingsong): '''下一首,返回一个list''' s = self.requests_url('s', sid=playingsong['sid']) return eval(s)['song'] def bye(self, playingsong): '''不再播放,返回一个list''' s = self.requests_url('b', sid=playingsong['sid']) return eval(s)['song'] def rate_music(self, playingsong): '''标记喜欢歌曲''' self.requests_url('r', sid=playingsong['sid']) # self.playlist = eval(s)['song'] def unrate_music(self, playingsong): '''取消标记喜欢歌曲''' self.requests_url('u', sid=playingsong['sid']) # self.playlist = eval(s)['song'] def submit_music(self, playingsong): '''歌曲结束标记''' self.requests_url('e', sid=playingsong['sid']) def get_pic(self, playingsong, tempfile_path): '''获取专辑封面''' url = playingsong['picture'].replace('\\', '') for i in range(3): try: urllib.urlretrieve(url, tempfile_path) logger.debug('Get cover art success!') return True except (IOError, urllib.ContentTooShortError): pass logger.error('Get cover art failed!') return False def get_lrc(self, playingsong): '''获取歌词''' try: url = "http://api.douban.com/v2/fm/lyric" postdata = { 'sid': playingsong['sid'], 'ssid': playingsong['ssid'], } s = requests.session() response = s.post(url, data=postdata) lyric = eval(response.text) logger.debug(response.text) lrc_dic = lrc2dic.lrc2dict(lyric['lyric']) # 原歌词用的unicode,为了兼容 for key, value in lrc_dic.iteritems(): lrc_dic[key] = value.decode('utf-8') if lrc_dic: logger.debug('Get lyric success!') return lrc_dic except requests.exceptions.RequestException: logger.error('Get lyric failed!') return 0
class Doubanfm: def __init__(self): self._load_config() self.douban = Douban(self.email, self.password, self.user_id, self.expire, self.token, self.user_name) self.player = Player() self.current_channel = 0 self.current_song = None self.current_play_list = None self.get_channels() self.palette = [('selected', 'bold', 'default'), ('title', 'yellow', 'default')] self.selected_button = None self.main_loop = None self.song_change_alarm = None if self.scrobbling: self.scrobbler = Scrobbler(self.last_fm_username, self.last_fm_password) r = self.scrobbler.handshake() if r: print("Last.FM logged in.") else: print("Last.FM login failed") if self.douban_account: r, err = self.douban.do_login() if r: print("Douban logged in.") self._save_cache() else: print("Douban login failed: " + err) def _load_config(self): self.email = None self.password = None self.user_id = None self.expire = None self.token = None self.user_name = None self.lasf_fm_username = None self.last_fm_password = None self.scrobbling = True self.douban_account = True self.channels = None config = None token = None try: f = open('config.json', 'r') config = json.load(f) self.email = config['email'] self.password = config['password'] except (KeyError,ValueError): self.douban_account = False print("Douban account not found. Personal FM disabled.") try: if config == None: raise ValueError self.last_fm_username = config['last_fm_username'] self.last_fm_password = config['last_fm_password'] except (KeyError,ValueError): self.scrobbling = False print("Last.fm account not found. Scrobbling disabled.") try: f = open('channels.json', 'r') self.channels = json.load(f) print("Load channel file.") except FileNotFoundError: print("Channels file not found.") def _save_cache(self): f = None try: f = open('cache.json', 'w') f2 = open('channels.json', 'w') json.dump({ 'user_name': self.douban.user_name, 'user_id': self.douban.user_id, 'expire': self.douban.expire, 'token': self.douban.token }, f) json.dump(self.channels, f2) except IOError: raise Exception("Unable to write cache file") def get_channels(self): if self.channels is None: self.channels = self.douban.get_channels() return self.channels def _choose_channel(self, channel): self.current_channel = channel self.current_play_list = deque(self.douban.get_new_play_list(self.current_channel)) def _play_track(self): _song = self.current_play_list.popleft() self.current_song = Song(_song) self.song_change_alarm = self.main_loop.set_alarm_in(self.current_song.length_in_sec, self.next_song, None); self.selected_button.set_text(self.selected_button.text[0:7].strip()) heart = u'\N{WHITE HEART SUIT}'; if self.current_song.like: heart = u'\N{BLACK HEART SUIT}' self.selected_button.set_text(self.selected_button.text + ' ' + heart + ' ' + self.current_song.artist + ' - ' + self.current_song.title) if self.scrobbling: self.scrobbler.now_playing(self.current_song.artist, self.current_song.title, self.current_song.album_title, self.current_song.length_in_sec) self.player.stop() self.player.play(self.current_song) # Currently playing the second last song in queue if len(self.current_play_list) == 1: playing_list = self.douban.get_playing_list(self.current_song.sid, self.current_channel) self.current_play_list.extend(deque(playing_list)) def next_song(self, loop, user_data): # Scrobble the track if scrobbling is enabled # and total playback time of the track > 30s if self.scrobbling and self.current_song.length_in_sec > 30: self.scrobbler.submit(self.current_song.artist, self.current_song.title, self.current_song.album_title, self.current_song.length_in_sec) self.douban.end_song(self.current_song.sid, self.current_channel) if self.song_change_alarm: self.main_loop.remove_alarm(self.song_change_alarm) self._play_track() def skip_current_song(self): self.douban.skip_song(self.current_song.sid, self.current_channel) if self.song_change_alarm: self.main_loop.remove_alarm(self.song_change_alarm) self._play_track() def rate_current_song(self): self.douban.rate_song(self.current_song.sid, self.current_channel) self.selected_button.set_text(self.selected_button.text.replace(u'\N{WHITE HEART SUIT}', u'\N{BLACK HEART SUIT}')) def unrate_current_song(self): self.douban.unrate_song(self.current_song.sid, self.current_channel) self.selected_button.set_text(self.selected_button.text.replace(u'\N{BLACK HEART SUIT}', u'\N{WHITE HEART SUIT}')) def quit(self): self.player.stop() def start(self): title = urwid.AttrMap(urwid.Text('豆瓣FM'), 'title') divider = urwid.Divider() pile = urwid.Padding(urwid.Pile([divider, title, divider]), left=4, right=4) box = urwid.Padding(self.ChannelListBox(), left=2, right=4) frame = urwid.Frame(box, header=pile, footer=divider) self.main_loop = urwid.MainLoop(frame, self.palette, handle_mouse=False) self.main_loop.run() def ChannelListBox(self): body = [] for c in self.channels: _channel = ChannelButton(c['name']) urwid.connect_signal(_channel, 'click', self.channel_chosen, c['channel_id']) body.append(urwid.AttrMap(_channel, None, focus_map="channel")) return MyListBox(urwid.SimpleFocusListWalker(body), self) def channel_chosen(self, button, choice): # Choose the channel which is playing right now # ignore this if self.selected_button == button: return # Choose a different channel if self.player.is_playing: self.player.stop() self._choose_channel(choice) if self.selected_button != None and button != self.selected_button: self.selected_button.set_text(self.selected_button.text[0:7].strip()) self.selected_button = button self._play_track()
class Doubanfm(object): def __init__(self): self.login_data = {} self.lastfm = True # lastfm 登陆 def init_login(self): print LOGO self.douban_login() # 登陆 self.lastfm_login() # 登陆 last.fm print '\033[31m♥\033[0m Get channels ', self.get_channels() # 获取频道列表 print '[\033[32m OK \033[0m]' # 存储的default_channel是行数而不是真正发送数据的channel_id # 这里需要进行转化一下 self.set_channel(self.default_channel) print '\033[31m♥\033[0m Check PRO ', # self.is_pro() print '[\033[32m OK \033[0m]' def win_login(self): '''登陆界面''' email = raw_input('Email: ') password = getpass.getpass('Password: '******'''Last.fm登陆''' # username & password self.last_fm_username = \ self.login_data['last_fm_username'] if 'last_fm_username' in self.login_data\ else None self.last_fm_password = \ self.login_data['last_fm_password'] if 'last_fm_password' in self.login_data\ else None if len(sys.argv) > 1 and sys.argv[1] == 'last.fm': from hashlib import md5 username = raw_input('Last.fm username: '******'Last.fm password :'******'r') as f: data = pickle.load(f) with open(config.PATH_TOKEN, 'w') as f: data['last_fm_username'] = username data['last_fm_password'] = self.last_fm_password pickle.dump(data, f) # login if self.lastfm and self.last_fm_username and self.last_fm_password: self.scrobbler = Scrobbler( self.last_fm_username, self.last_fm_password) r, err = self.scrobbler.handshake() if r: logger.info("Last.fm login succeeds!") print '\033[31m♥\033[0m Last.fm logged in: %s' % self.last_fm_username else: logger.error("Last.fm login fails: " + err) self.lastfm = False else: self.lastfm = False def __last_fm_account_required(func): '''装饰器,用于需要登录Last.fm后才能使用的接口''' @wraps(func) def wrapper(self, *args, **kwds): if not self.lastfm: return # Disable pylint callable check due to pylint's incompability # with using a class method as decorator. # Pylint will consider func as "self" return func(self, *args, **kwds) # pylint: disable=not-callable return wrapper @__last_fm_account_required def submit_current_song(self): '''提交播放过的曲目''' # Submit the track if total playback time of the track > 30s if self.playingsong['length'] > 30: self.scrobbler.submit( self.playingsong['artist'], self.playingsong['title'], self.playingsong['albumtitle'], self.playingsong['length'] ) @__last_fm_account_required def scrobble_now_playing(self): '''提交当前正在播放曲目''' self.scrobbler.now_playing( self.playingsong['artist'], self.playingsong['title'], self.playingsong['albumtitle'], self.playingsong['length'] ) def douban_login(self): '''登陆douban.fm获取token''' if os.path.exists(config.PATH_TOKEN): # 已登陆 logger.info("Found existing Douban.fm token.") with open(config.PATH_TOKEN, 'r') as f: self.login_data = pickle.load(f) self.token = self.login_data['token'] self.user_name = self.login_data['user_name'] self.user_id = self.login_data['user_id'] self.expire = self.login_data['expire'] self.default_volume = int(self.login_data['volume'])\ if 'volume' in self.login_data else 50 # Value stored in login_data in token file is lien number # instead of channel_id! Will do set_channel later. self.default_channel = int(self.login_data['channel'])\ if 'channel' in self.login_data else 0 print '\033[31m♥\033[0m Get local token - Username: \033[33m%s\033[0m' %\ self.user_name else: # 未登陆 logger.info('First time logging in Douban.fm.') while True: self.email, self.password = self.win_login() login_data = { 'app_name': 'radio_desktop_win', 'version': '100', 'email': self.email, 'password': self.password } s = requests.post('http://www.douban.com/j/app/login', login_data) dic = json.loads(s.text, object_hook=_decode_dict) if dic['r'] == 1: logger.debug(dic['err']) continue else: self.token = dic['token'] self.user_name = dic['user_name'] self.user_id = dic['user_id'] self.expire = dic['expire'] self.default_volume = 50 self.default_channel = 1 self.login_data = { 'app_name': 'radio_desktop_win', 'version': '100', 'user_id': self.user_id, 'expire': self.expire, 'token': self.token, 'user_name': self.user_name, 'volume': '50', 'channel': '0' } logger.info('Logged in username: '******'w') as f: pickle.dump(self.login_data, f) logger.debug('Write data to ' + config.PATH_TOKEN) break # set config config.init_config() def get_channels(self): '''获取channel列表,将channel name/id存入self._channel_list''' # 红心兆赫需要手动添加 self._channel_list = [{ 'name': '红心兆赫', 'channel_id': -3 }] r = requests.get('http://www.douban.com/j/app/radio/channels') self._channel_list += json.loads(r.text, object_hook=_decode_dict)['channels'] @property def channels(self): '''返回channel名称列表(一个list,不包括id)''' # 格式化频道列表,以便display lines = [ch['name'] for ch in self._channel_list] return lines def requests_url(self, ptype, **data): '''这里包装了一个函数,发送post_data''' post_data = self.login_data.copy() post_data['type'] = ptype for x in data: post_data[x] = data[x] url = 'http://www.douban.com/j/app/radio/people?' + urllib.urlencode(post_data) try: s = requests.get(url) except requests.exceptions.RequestException: logger.error("Error communicating with Douban.fm API.") return s.text def set_channel(self, line): '''把行数转化成channel_id''' self.default_channel = line self.login_data['channel'] = self._channel_list[line]['channel_id'] def get_playlist(self): '''获取播放列表,返回一个list''' s = self.requests_url('n') return json.loads(s, object_hook=_decode_dict)['song'] def skip_song(self, playingsong): '''下一首,返回一个list''' s = self.requests_url('s', sid=playingsong['sid']) return json.loads(s, object_hook=_decode_dict)['song'] def bye(self, playingsong): '''不再播放,返回一个list''' s = self.requests_url('b', sid=playingsong['sid']) return json.loads(s, object_hook=_decode_dict)['song'] def rate_music(self, playingsong): '''标记喜欢歌曲''' self.requests_url('r', sid=playingsong['sid']) def unrate_music(self, playingsong): '''取消标记喜欢歌曲''' self.requests_url('u', sid=playingsong['sid']) def submit_music(self, playingsong): '''歌曲结束标记''' self.requests_url('e', sid=playingsong['sid']) def get_lrc(self, playingsong): '''获取歌词''' try: url = "http://api.douban.com/v2/fm/lyric" postdata = { 'sid': playingsong['sid'], 'ssid': playingsong['ssid'], } s = requests.session() response = s.post(url, data=postdata) lyric = json.loads(response.text, object_hook=_decode_dict) logger.debug(response.text) lrc_dic = lrc2dic.lrc2dict(lyric['lyric']) # 原歌词用的unicode,为了兼容 for key, value in lrc_dic.iteritems(): lrc_dic[key] = value.decode('utf-8') if lrc_dic: logger.debug('Get lyric success!') return lrc_dic except requests.exceptions.RequestException: logger.error('Get lyric failed!') return {}
class Doubanfm: def __init__(self): self.email = None self.password = None self.user_name = None self.user_id = None self.expire = None self.token = None self.cookies = None self.last_fm_username = None self.last_fm_password = None self.scrobbling = True self.douban_account = True self.channels = None # Set up config try: arg = sys.argv[1] self._do_config() except IndexError: self._load_config() # Init API tools self.douban = Douban( self.email, self.password, self.user_id, self.expire, self.token, self.user_name, self.cookies) self.player = Player() self.current_channel = 0 self.current_song = None self.current_play_list = None # Init terminal ui self.palette = [('selected', 'bold', 'default'), ('title', 'yellow', 'default')] self.selected_button = None self.main_loop = None self.song_change_alarm = None # Try to login if self.last_fm_username is None or self.last_fm_username == "": self.scrobbling = False if (self.email is None or self.email == "") and self.cookies == None: self.douban_account = False if self.scrobbling: self.scrobbler = Scrobbler( self.last_fm_username, self.last_fm_password) r, err = self.scrobbler.handshake() if r: print("Last.FM 已登陆") else: print("Last.FM 登录失败: " + err) if self.douban_account: r, err = self.douban.do_login() if r: print("Douban 已登陆") else: print("Douban 登录失败: " + err) self.get_channels() self._save_cache() def _do_config(self): self.email = input('豆瓣账户 (Email地址): ') or None self.password = getpass('豆瓣密码: ') or None self.last_fm_username = input('Last.fm 用户名: ') or None password = getpass('Last.fm 密码: ') or None self.last_fm_password = md5(password.encode('utf-8')).hexdigest() def _load_config(self): try: f = open('channels.json', 'r') self.channels = deque(json.load(f)) logger.debug("Load channel file.") except FileNotFoundError: logger.debug("Channels file not found.") try: f = open('cache.json', 'r') cache = json.load(f) try: self.user_name = cache['user_name'] self.user_id = cache['user_id'] self.expire = cache['expire'] self.token = cache['token'] self.cookies = cache['cookies'] except (KeyError, ValueError): self.douban_account = False try: self.last_fm_username = cache['last_fm_username'] self.last_fm_password = cache['last_fm_password'] except (KeyError, ValueError): self.scrobbling = False except FileNotFoundError: logger.debug("Cache file not found.") def _save_cache(self): f = None try: f = open('cache.json', 'w') f2 = open('channels.json', 'w') json.dump({ 'user_name': self.douban.user_name, 'user_id': self.douban.user_id, 'expire': self.douban.expire, 'token': self.douban.token, 'cookies': self.douban.cookies, 'last_fm_username': self.last_fm_username, 'last_fm_password': self.last_fm_password }, f) json.dump(list(self.channels), f2) except IOError: raise Exception("Unable to write cache file") def get_channels(self): if self.channels is None: self.channels = deque(self.douban.get_channels()) def _choose_channel(self, channel): self.current_channel = channel self.current_play_list = deque( self.douban.get_new_play_list(self.current_channel)) def _play_track(self): _song = self.current_play_list.popleft() self.current_song = Song(_song) logger.debug('Playing Track') logger.debug('Artist: ' + self.current_song.artist) logger.debug('Title: ' + self.current_song.song_title) logger.debug('Album: ' + self.current_song.album_title) logger.debug('Length: ' + self.current_song.length_in_str) logger.debug('Sid: ' + self.current_song.sid) logger.debug( '{0} tracks remaining in the playlist'.format(len(self.current_play_list))) self.song_change_alarm = self.main_loop.set_alarm_in(self.current_song.length_in_sec, self.next_song, None) self.selected_button.set_text(self.selected_button.text[0:11].strip()) heart = u'\N{WHITE HEART SUIT}' if self.current_song.like: heart = u'\N{BLACK HEART SUIT}' if not self.douban_account: heart = ' ' self.selected_button.set_text(self.selected_button.text + ' ' + heart + ' ' + self.current_song.artist + ' - ' + self.current_song.song_title) if self.scrobbling: self.scrobbler.now_playing(self.current_song.artist, self.current_song.song_title, self.current_song.album_title, self.current_song.length_in_sec) self.player.stop() self.player.play(self.current_song) # Currently playing the second last song in queue if len(self.current_play_list) == 1: # Extend the playing list playing_list = self.douban.get_playing_list( self.current_song.sid, self.current_channel) logger.debug('Got {0} more tracks'.format(len(playing_list))) self.current_play_list.extend(deque(playing_list)) def next_song(self, loop, user_data): # Scrobble the track if scrobbling is enabled # and total playback time of the track > 30s if self.scrobbling and self.current_song.length_in_sec > 30: self.scrobbler.submit(self.current_song.artist, self.current_song.song_title, self.current_song.album_title, self.current_song.length_in_sec) if self.douban_account: r, err = self.douban.end_song( self.current_song.sid, self.current_channel) if r: logger.debug('End song OK') else: logger.error(err) if self.song_change_alarm: self.main_loop.remove_alarm(self.song_change_alarm) self._play_track() def skip_current_song(self): if self.douban_account: r, err = self.douban.skip_song( self.current_song.sid, self.current_channel) if r: logger.debug('Skip song OK') else: logger.error(err) if self.song_change_alarm: self.main_loop.remove_alarm(self.song_change_alarm) self._play_track() def rate_current_song(self): if not self.douban_account: return r, err = self.douban.rate_song( self.current_song.sid, self.current_channel) if r: self.current_song.like = True self.selected_button.set_text(self.selected_button.text.replace( u'\N{WHITE HEART SUIT}', u'\N{BLACK HEART SUIT}')) logger.debug('Rate song OK') else: logger.error(err) def unrate_current_song(self): if not self.douban_account: return r, err = self.douban.unrate_song( self.current_song.sid, self.current_channel) if r: self.current_song.like = False self.selected_button.set_text(self.selected_button.text.replace( u'\N{BLACK HEART SUIT}', u'\N{WHITE HEART SUIT}')) logger.debug('Unrate song OK') else: logger.error(err) def trash_current_song(self): if not self.douban_account: return r, err = self.douban.bye_song( self.current_song.sid, self.current_channel) if r: # play next song if self.song_change_alarm: self.main_loop.remove_alarm(self.song_change_alarm) self._play_track() logger.debug('Trash song OK') else: logger.error(err) def quit(self): logger.debug('Quit') self.player.stop() def start(self): title = urwid.AttrMap(urwid.Text('豆瓣FM'), 'title') divider = urwid.Divider() pile = urwid.Padding( urwid.Pile([divider, title, divider]), left=4, right=4) box = urwid.Padding(self.ChannelListBox(), left=2, right=4) frame = urwid.Frame(box, header=pile, footer=divider) self.main_loop = urwid.MainLoop( frame, self.palette, handle_mouse=False) self.main_loop.run() def ChannelListBox(self): body = [] for c in self.channels: _channel = ChannelButton(c['name']) urwid.connect_signal( _channel, 'click', self.channel_chosen, c['channel_id']) body.append(urwid.AttrMap(_channel, None, focus_map="channel")) return MyListBox(urwid.SimpleFocusListWalker(body), self) def channel_chosen(self, button, choice): # Choose the channel which is playing right now # ignore this if self.selected_button == button: return # Choose a different channel if self.player.is_playing: self.player.stop() self._choose_channel(choice) if self.selected_button != None and button != self.selected_button: self.selected_button.set_text( self.selected_button.text[0:11].strip()) self.selected_button = button if self.song_change_alarm: self.main_loop.remove_alarm(self.song_change_alarm) self._play_track()
class Doubanfm(object): def __init__(self): self.login_data = {} self.lastfm = True # lastfm 登陆 def init_login(self): print ''' ──╔╗─────╔╗────────╔═╗ ──║║─────║║────────║╔╝ ╔═╝╠══╦╗╔╣╚═╦══╦═╗╔╝╚╦╗╔╗ ║╔╗║╔╗║║║║╔╗║╔╗║╔╗╬╗╔╣╚╝║ ║╚╝║╚╝║╚╝║╚╝║╔╗║║║╠╣║║║║║ ╚══╩══╩══╩══╩╝╚╩╝╚╩╩╝╚╩╩╝ ''' self.douban_login() # 登陆 self.login_lastfm() # 登陆 last.fm print '\033[31m♥\033[0m Get channels ', # self.get_channels() # 获取频道列表 print '[\033[32m OK \033[0m]' # self.get_channellines() # 重构列表用以显示 print '\033[31m♥\033[0m Check PRO ', # self.is_pro() print '[\033[32m OK \033[0m]' def win_login(self): '''登陆界面''' email = raw_input('Email:') password = getpass.getpass('Password:'******'''Last.fm登陆''' if self.lastfm and self.last_fm_username and self.last_fm_password: self.scrobbler = Scrobbler( self.last_fm_username, self.last_fm_password) r, err = self.scrobbler.handshake() if r: logger.info("Last.fm login succeeds!") print '\033[31m♥\033[0m Last.fm logged in: %s' % self.last_fm_username else: logger.error("Last.fm login fails: " + err) self.lastfm = False else: self.lastfm = False def last_fm_account_required(fun): '''装饰器,用于需要登录Last.fm后才能使用的接口''' @wraps(fun) def wrapper(self, *args, **kwds): if not self.lastfm: return return fun(self, *args, **kwds) return wrapper @last_fm_account_required def submit_current_song(self): '''提交播放过的曲目''' # Submit the track if total playback time of the track > 30s if self.playingsong['length'] > 30: self.scrobbler.submit( self.playingsong['artist'], self.playingsong['title'], self.playingsong['albumtitle'], self.playingsong['length'] ) @last_fm_account_required def scrobble_now_playing(self): '''提交当前正在播放曲目''' self.scrobbler.now_playing( self.playingsong['artist'], self.playingsong['title'], self.playingsong['albumtitle'], self.playingsong['length'] ) def douban_login(self): '''登陆douban.fm获取token''' path_token = os.path.expanduser('~/.douban_token.txt') if os.path.exists(path_token): # 已登陆 logger.info("Found existing Douban.fm token.") with open(path_token, 'r') as f: self.login_data = pickle.load(f) self.token = self.login_data['token'] self.user_name = self.login_data['user_name'] self.user_id = self.login_data['user_id'] self.expire = self.login_data['expire'] self.default_volume = int(self.login_data['volume'])\ if 'volume' in self.login_data else 50 self.default_channel = int(self.login_data['channel'])\ if 'channel' in self.login_data else 1 # 存储的default_channel是行数而不是真正发送数据的channel_id # 这里需要进行转化一下 self.set_channel(self.default_channel) print '\033[31m♥\033[0m Get local token - Username: \033[33m%s\033[0m' % self.user_name else: # 未登陆 logger.info('First time logging in Douban.fm.') while True: self.email, self.password = self.win_login() login_data = { 'app_name': 'radio_desktop_win', 'version': '100', 'email': self.email, 'password': self.password } s = requests.post('http://www.douban.com/j/app/login', login_data) dic = eval(s.text) if dic['r'] == 1: logger.debug(dic['err']) continue else: self.token = dic['token'] self.user_name = dic['user_name'] self.user_id = dic['user_id'] self.expire = dic['expire'] self.default_volume = 50 self.default_channel = 1 self.login_data = { 'app_name': 'radio_desktop_win', 'version': '100', 'user_id': self.user_id, 'expire': self.expire, 'token': self.token, 'user_name': self.user_name, 'volume': '50', 'channel': '0' } logger.info('Logged in username: '******'w') as f: pickle.dump(self.login_data, f) logger.debug('Write data to ' + path_token) break self.last_fm_username = \ self.login_data['last_fm_username'] if 'last_fm_username' in self.login_data\ else None self.last_fm_password = \ self.login_data['last_fm_password'] if 'last_fm_password' in self.login_data\ else None # last.fm登陆 try: if sys.argv[1] == 'last.fm': from hashlib import md5 username = raw_input('last.fm username:'******'last.fm password:'******'r') as f: data = pickle.load(f) with open(path_token, 'w') as f: data['last_fm_username'] = username data['last_fm_password'] = self.last_fm_password pickle.dump(data, f) except IndexError: pass # 配置文件 path_config = os.path.expanduser('~/.doubanfm_config') if not os.path.exists(path_config): print '\033[31m♥\033[0m Get default config [\033[32m OK \033[0m]' config = '''[key] UP = k DOWN = j TOP = g BOTTOM = G OPENURL = w RATE = r NEXT = n BYE = b QUIT = q PAUSE = p LOOP = l MUTE = m LRC = o ''' # 这个很丑,怎么办 with open(path_config, 'w') as F: F.write(config) else: print '\033[31m♥\033[0m Get local config [\033[32m OK \033[0m]' @property def channels(self): '''获取channel,存入self.channels''' # 红心兆赫需要手动添加 channels = [{ 'name': '红心兆赫', 'channel_id': -3 }] r = requests.get('http://www.douban.com/j/app/radio/channels') channels += eval(r.text)['channels'] # 格式化频道列表,以便display lines = [] for channel in channels: lines.append(channel['name']) return lines def requests_url(self, ptype, **data): '''这里包装了一个函数,发送post_data''' post_data = self.login_data.copy() post_data['type'] = ptype for x in data: post_data[x] = data[x] url = 'http://www.douban.com/j/app/radio/people?' + urllib.urlencode(post_data) try: s = requests.get(url) except requests.exceptions.RequestException: logger.error("Error communicating with Douban.fm API.") return s.text def set_channel(self, channel): '''把行数转化成channel_id''' self.default_channel = channel channel = -3 if channel == 0 else channel - 1 self.login_data['channel'] = channel def get_playlist(self, channel): '''获取播放列表,返回一个list''' if self.default_channel != channel: self.set_channel(channel) s = self.requests_url('n') return eval(s)['song'] def skip_song(self, playingsong): '''下一首,返回一个list''' s = self.requests_url('s', sid=playingsong['sid']) return eval(s)['song'] def bye(self, playingsong): '''不再播放,返回一个list''' s = self.requests_url('b', sid=playingsong['sid']) return eval(s)['song'] def rate_music(self, playingsong): '''标记喜欢歌曲''' self.requests_url('r', sid=playingsong['sid']) # self.playlist = eval(s)['song'] def unrate_music(self, playingsong): '''取消标记喜欢歌曲''' self.requests_url('u', sid=playingsong['sid']) # self.playlist = eval(s)['song'] def submit_music(self, playingsong): '''歌曲结束标记''' self.requests_url('e', sid=playingsong['sid']) def get_pic(self, playingsong, tempfile_path): '''获取专辑封面''' url = playingsong['picture'].replace('\\', '') for i in range(3): try: urllib.urlretrieve(url, tempfile_path) logger.debug('Get cover art success!') return True except (IOError, urllib.ContentTooShortError): pass logger.error('Get cover art failed!') return False def get_lrc(self, playingsong): '''获取歌词''' try: url = "http://api.douban.com/v2/fm/lyric" postdata = { 'sid': playingsong['sid'], 'ssid': playingsong['ssid'], } s = requests.session() response = s.post(url, data=postdata) lyric = eval(response.text) logger.debug(response.text) lrc_dic = lrc2dic.lrc2dict(lyric['lyric']) # 原歌词用的unicode,为了兼容 for key, value in lrc_dic.iteritems(): lrc_dic[key] = value.decode('utf-8') if lrc_dic: logger.debug('Get lyric success!') return lrc_dic except requests.exceptions.RequestException: logger.error('Get lyric failed!') return 0