Ejemplo n.º 1
0
 def gettLyric():
     if 'tlyric' not in self.songs[str(self.playing_id)].keys():
         self.songs[str(self.playing_id)]["tlyric"] = []
     if len(self.songs[str(self.playing_id)]["tlyric"]) > 0:
         return
     netease = NetEase()
     tlyric = netease.song_tlyric(self.playing_id)
     if tlyric == [] or tlyric == '未找到歌词翻译':
         return
     tlyric = tlyric.split('\n')
     self.songs[str(self.playing_id)]["tlyric"] = tlyric
     return
Ejemplo n.º 2
0
 def gettLyric():
     if 'tlyric' not in self.songs[str(self.playing_id)].keys():
         self.songs[str(self.playing_id)]["tlyric"] = []
     if len(self.songs[str(self.playing_id)]["tlyric"]) > 0:
         return
     netease = NetEase()
     tlyric = netease.song_tlyric(self.playing_id)
     if tlyric == [] or tlyric == '未找到歌词翻译':
         return
     tlyric = tlyric.split('\n')
     self.songs[str(self.playing_id)]["tlyric"] = tlyric
     return
Ejemplo n.º 3
0
class Player(object):
    MODE_ORDERED = 0
    MODE_ORDERED_LOOP = 1
    MODE_SINGLE_LOOP = 2
    MODE_RANDOM = 3
    MODE_RANDOM_LOOP = 4

    def __init__(self):
        self.config = Config()
        # self.ui = Ui()
        self.popen_handler = None
        # flag stop, prevent thread start
        self.playing_flag = False
        self.refrese_url_flag = False
        self.process_length = 0
        self.process_location = 0
        self.storage = Storage()
        self.cache = Cache()
        self.end_callback = None
        self.playing_song_changed_callback = None
        self.api = NetEase()

    @property
    def info(self):
        return self.storage.database["player_info"]

    @property
    def songs(self):
        return self.storage.database["songs"]

    @property
    def index(self):
        return self.info["idx"]

    @property
    def list(self):
        return self.info["player_list"]

    @property
    def order(self):
        return self.info["playing_order"]

    @property
    def mode(self):
        return self.info["playing_mode"]

    @property
    def is_ordered_mode(self):
        return self.mode == Player.MODE_ORDERED

    @property
    def is_ordered_loop_mode(self):
        return self.mode == Player.MODE_ORDERED_LOOP

    @property
    def is_single_loop_mode(self):
        return self.mode == Player.MODE_SINGLE_LOOP

    @property
    def is_random_mode(self):
        return self.mode == Player.MODE_RANDOM

    @property
    def is_random_loop_mode(self):
        return self.mode == Player.MODE_RANDOM_LOOP

    @property
    def config_notifier(self):
        return self.config.get("notifier")

    @property
    def config_mpg123(self):
        return self.config.get("mpg123_parameters")

    @property
    def current_song(self):
        if not self.songs:
            return {}

        if not self.is_index_valid:
            return {}
        song_id = self.list[self.index]
        return self.songs.get(song_id, {})

    @property
    def playing_id(self):
        return self.current_song["song_id"]

    @property
    def playing_name(self):
        return self.current_song["song_name"]

    @property
    def is_empty(self):
        return len(self.list) == 0

    @property
    def is_index_valid(self):
        return 0 <= self.index < len(self.list)

    def notify_playing(self):
        if not self.current_song:
            return

        if not self.config_notifier:
            return

        song = self.current_song
        notify("正在播放: {}\n{}-{}".format(song["song_name"], song["artist"],
                                        song["album_name"]))

    def notify_copyright_issue(self):
        log.warning("Song {} is unavailable due to copyright issue.".format(
            self.playing_id))
        notify("版权限制,无法播放此歌曲")

    def change_mode(self, step=1):
        self.info["playing_mode"] = (self.info["playing_mode"] + step) % 5

    def build_playinfo(self):
        if not self.current_song:
            return

        # self.ui.build_playinfo(
        #     self.current_song["song_name"],
        #     self.current_song["artist"],
        #     self.current_song["album_name"],
        #     self.current_song["quality"],
        #     time.time(),
        #     pause=not self.playing_flag,
        # )

    def add_songs(self, songs):
        for song in songs:
            song_id = str(song["song_id"])
            self.info["player_list"].append(song_id)
            if song_id in self.songs:
                self.songs[song_id].update(song)
            else:
                self.songs[song_id] = song

    def refresh_urls(self):
        songs = self.api.dig_info(self.list, "refresh_urls")
        if songs:
            for song in songs:
                song_id = str(song["song_id"])
                if song_id in self.songs:
                    self.songs[song_id]["mp3_url"] = song["mp3_url"]
                    self.songs[song_id]["expires"] = song["expires"]
                    self.songs[song_id]["get_time"] = song["get_time"]
                else:
                    self.songs[song_id] = song
            self.refrese_url_flag = True

    def stop(self):
        if not self.popen_handler:
            return

        self.playing_flag = False
        self.popen_handler.stdin.write(b"Q\n")
        self.popen_handler.stdin.flush()
        self.popen_handler.kill()
        self.popen_handler = None
        # wait process to be killed
        time.sleep(0.01)

    def tune_volume(self, up=0):
        if not self.popen_handler:
            return

        new_volume = self.info["playing_volume"] + up
        if new_volume > 100:
            new_volume = 100
        elif new_volume < 0:
            new_volume = 0

        self.info["playing_volume"] = new_volume
        self.popen_handler.stdin.write("V {}\n".format(
            self.info["playing_volume"]).encode())
        self.popen_handler.stdin.flush()

    def switch(self):
        if not self.popen_handler:
            return

        self.playing_flag = not self.playing_flag
        self.popen_handler.stdin.write(b"P\n")
        self.popen_handler.stdin.flush()

        self.build_playinfo()

    def run_mpg123(self, on_exit, url, expires=-1, get_time=-1):
        para = ["mpg123", "-R"] + self.config_mpg123
        self.popen_handler = subprocess.Popen(para,
                                              stdin=subprocess.PIPE,
                                              stdout=subprocess.PIPE,
                                              stderr=subprocess.PIPE)

        self.tune_volume()
        self.popen_handler.stdin.write(b"L " + url.encode("utf-8") + b"\n")
        self.popen_handler.stdin.flush()

        endless_loop_cnt = 0
        while True:
            if not self.popen_handler:
                break

            strout = self.popen_handler.stdout.readline().decode(
                "utf-8").strip()
            if strout[:2] == "@F":
                # playing, update progress
                out = strout.split(" ")
                self.process_location = int(float(out[3]))
                self.process_length = int(float(out[3]) + float(out[4]))
            elif strout[:2] == "@E":
                self.playing_flag = True
                if (expires >= 0 and get_time >= 0
                        and time.time() - expires - get_time >= 0):
                    # 刷新URL
                    self.refresh_urls()
                else:
                    # error, stop song and move to next
                    self.notify_copyright_issue()
                break
            elif strout == "@P 0":
                # end, moving to next
                self.playing_flag = True
                break
            elif strout == "":
                endless_loop_cnt += 1
                # 有播放后没有退出,mpg123一直在发送空消息的情况,此处直接终止处理
                if endless_loop_cnt > 100:
                    log.warning(
                        "mpg123 error, halt, endless loop and high cpu use, then we kill it"
                    )
                    break

        if self.playing_flag:
            if self.refrese_url_flag:
                self.stop()
                self.replay()
                self.refrese_url_flag = False
            else:
                self.next()
        else:
            self.stop()

    def download_lyric(self, is_transalted=False):
        key = "lyric" if not is_transalted else "tlyric"

        if key not in self.songs[str(self.playing_id)]:
            self.songs[str(self.playing_id)][key] = []

        if len(self.songs[str(self.playing_id)][key]) > 0:
            return

        if not is_transalted:
            lyric = self.api.song_lyric(self.playing_id)
        else:
            lyric = self.api.song_tlyric(self.playing_id)

        self.songs[str(self.playing_id)][key] = lyric

    def download_song(self, song_id, song_name, artist, url):
        def write_path(song_id, path):
            self.songs[str(song_id)]["cache"] = path

        self.cache.add(song_id, song_name, artist, url, write_path)
        self.cache.start_download()

    def start_playing(self, on_exit, args):
        """
        Runs the given args in subprocess.Popen, and then calls the function
        on_exit when the subprocess completes.
        on_exit is a callable object, and args is a lists/tuple of args
        that would give to subprocess.Popen.
        """
        # log.debug("%s,%s,%s" % (args['song_id'], args['song_name'], args['mp3_url']))
        if "cache" in args.keys() and os.path.isfile(args["cache"]):
            thread = threading.Thread(target=self.run_mpg123,
                                      args=(on_exit, args["cache"]))
        else:
            new_url = NetEase().songs_url([args["song_id"]])[0]["url"]  #使用新地址
            if not new_url:  #如果没有获得新地址
                new_url = args["mp3_url"]  #使用老地址传给mpg123
            thread = threading.Thread(
                target=self.run_mpg123,
                args=(on_exit, new_url, args["expires"], args["get_time"]),
            )
            cache_thread = threading.Thread(
                target=self.download_song,
                args=(
                    args["song_id"],
                    args["song_name"],
                    args["artist"],
                    args["mp3_url"],
                ),
            )
            cache_thread.start()

        thread.start()
        lyric_download_thread = threading.Thread(target=self.download_lyric)
        lyric_download_thread.start()
        tlyric_download_thread = threading.Thread(target=self.download_lyric,
                                                  args=(True, ))
        tlyric_download_thread.start()
        # returns immediately after the thread starts
        return thread

    def replay(self):
        if not self.is_index_valid:
            self.stop()
            if self.end_callback:
                log.debug("Callback")
                self.end_callback()
            return

        if not self.current_song:
            return

        self.stop()

        self.playing_flag = True
        self.build_playinfo()
        self.notify_playing()
        self.start_playing(lambda: 0, self.current_song)

    def shuffle_order(self):
        del self.order[:]
        self.order.extend(list(range(0, len(self.list))))
        random.shuffle(self.order)
        self.info["random_index"] = 0

    def new_player_list(self, type, title, datalist, offset):
        self.info["player_list_type"] = type
        self.info["player_list_title"] = title
        # self.info['idx'] = offset
        self.info["player_list"] = []
        self.info["playing_order"] = []
        self.info["random_index"] = 0
        self.songs.clear()
        self.add_songs(datalist)

    def append_songs(self, datalist):
        self.add_songs(datalist)

    # switch_flag为true表示:
    # 在播放列表中 || 当前所在列表类型不在"songs"、"djchannels"、"fmsongs"中
    def play_or_pause(self, idx, switch_flag):
        if self.is_empty:
            return

        # print('flag:',switch_flag)

        # if same "list index" and "playing index" --> same song :: pause/resume it
        if self.index == idx and switch_flag:
            if not self.popen_handler:
                # print('aaaaaa')
                self.stop()
                self.replay()
            else:
                # print('bbbbbb')
                self.switch()

        else:
            # print('cccccccc')
            self.info["idx"] = idx
            self.stop()
            self.replay()

    def _swap_song(self):
        now_songs = self.order.index(self.index)
        self.order[0], self.order[now_songs] = self.order[
            now_songs], self.order[0]

    def _need_to_shuffle(self):
        playing_order = self.order
        random_index = self.info["random_index"]
        if (random_index >= len(playing_order)
                or playing_order[random_index] != self.index):
            return True
        else:
            return False

    def next_idx(self):
        if not self.is_index_valid:
            return self.stop()
        playlist_len = len(self.list)

        if self.mode == Player.MODE_ORDERED:
            # make sure self.index will not over
            if self.info["idx"] < playlist_len:
                self.info["idx"] += 1

        elif self.mode == Player.MODE_ORDERED_LOOP:
            self.info["idx"] = (self.index + 1) % playlist_len

        elif self.mode == Player.MODE_SINGLE_LOOP:
            self.info["idx"] = self.info["idx"]

        else:
            playing_order_len = len(self.order)
            if self._need_to_shuffle():
                self.shuffle_order()
                # When you regenerate playing list
                # you should keep previous song same.
                self._swap_song()
                playing_order_len = len(self.order)

            self.info["random_index"] += 1

            # Out of border
            if self.mode == Player.MODE_RANDOM_LOOP:
                self.info["random_index"] %= playing_order_len

            # Random but not loop, out of border, stop playing.
            if self.info["random_index"] >= playing_order_len:
                self.info["idx"] = playlist_len
            else:
                self.info["idx"] = self.order[self.info["random_index"]]

        if self.playing_song_changed_callback is not None:
            self.playing_song_changed_callback()

    def next(self):
        self.stop()
        self.next_idx()
        self.replay()

    def prev_idx(self):
        if not self.is_index_valid:
            self.stop()
            return
        playlist_len = len(self.list)

        if self.mode == Player.MODE_ORDERED:
            if self.info["idx"] > 0:
                self.info["idx"] -= 1

        elif self.mode == Player.MODE_ORDERED_LOOP:
            self.info["idx"] = (self.info["idx"] - 1) % playlist_len

        elif self.mode == Player.MODE_SINGLE_LOOP:
            self.info["idx"] = self.info["idx"]

        else:
            playing_order_len = len(self.order)
            if self._need_to_shuffle():
                self.shuffle_order()
                playing_order_len = len(self.order)

            self.info["random_index"] -= 1
            if self.info["random_index"] < 0:
                if self.mode == Player.MODE_RANDOM:
                    self.info["random_index"] = 0
                else:
                    self.info["random_index"] %= playing_order_len
            self.info["idx"] = self.order[self.info["random_index"]]

        if self.playing_song_changed_callback is not None:
            self.playing_song_changed_callback()

    def prev(self):
        self.stop()
        self.prev_idx()
        self.replay()

    def shuffle(self):
        self.stop()
        self.info["playing_mode"] = Player.MODE_RANDOM
        self.shuffle_order()
        self.info["idx"] = self.info["playing_order"][
            self.info["random_index"]]
        self.replay()

    def volume_up(self):
        self.tune_volume(5)

    def volume_down(self):
        self.tune_volume(-5)

    def update_size(self):
        self.ui.update_size()
        self.build_playinfo()

    def cache_song(self, song_id, song_name, artist, song_url):
        def on_exit(song_id, path):
            self.songs[str(song_id)]["cache"] = path
            self.cache.enable = False

        self.cache.enable = True
        self.cache.add(song_id, song_name, artist, song_url, on_exit)
        self.cache.start_download()