예제 #1
0
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
예제 #2
0
파일: pyfm.py 프로젝트: beanmoon/pyfm
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()
예제 #3
0
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 {}
예제 #4
0
파일: pyfm.py 프로젝트: JamesTing/pyfm
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()
예제 #5
0
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