diff --git a/command.py b/command.py index 2a46905..cf84e41 100644 --- a/command.py +++ b/command.py @@ -137,13 +137,13 @@ def cmd_url_unban(bot, user, text, command, parameter): def cmd_play(bot, user, text, command, parameter): global log - if var.playlist.length() > 0: + if len(var.playlist) > 0: if parameter: if parameter.isdigit() and 1 <= int(parameter) <= len(var.playlist): var.playlist.point_to(int(parameter) - 1 - 1) # First "-1" transfer 12345 to 01234, second "-1" # point to the previous item. the loop will next to # the one you want - bot.interrupt_playing() + bot.interrupt() else: bot.send_msg(constants.strings('invalid_index', index=parameter), text) @@ -283,7 +283,7 @@ def cmd_play_url(bot, user, text, command, parameter): log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text) - if var.playlist.length() == 2: + if len(var.playlist) == 2: # If I am the second item on the playlist. (I am the next one!) bot.async_download_next() @@ -617,7 +617,7 @@ def cmd_current_music(bot, user, text, command, parameter): global log reply = "" - if var.playlist.length() > 0: + if len(var.playlist) > 0: bot.send_msg(var.playlist.current_item().format_current_playing()) else: reply = constants.strings('not_playing') @@ -627,9 +627,9 @@ def cmd_current_music(bot, user, text, command, parameter): def cmd_skip(bot, user, text, command, parameter): global log - if var.playlist.length() > 0: - bot.interrupt_playing() - else: + bot.interrupt() + + if len(var.playlist) == 0: bot.send_msg(constants.strings('queue_empty'), text) @@ -637,7 +637,7 @@ def cmd_last(bot, user, text, command, parameter): global log if len(var.playlist) > 0: - bot.interrupt_playing() + bot.interrupt() var.playlist.point_to(len(var.playlist) - 1) else: bot.send_msg(constants.strings('queue_empty'), text) @@ -648,7 +648,7 @@ def cmd_remove(bot, user, text, command, parameter): # Allow to remove specific music into the queue with a number if parameter and parameter.isdigit() and int(parameter) > 0 \ - and int(parameter) <= var.playlist.length(): + and int(parameter) <= len(var.playlist): index = int(parameter) - 1 @@ -658,14 +658,14 @@ def cmd_remove(bot, user, text, command, parameter): if index < len(var.playlist): if not bot.is_pause: - bot.interrupt_playing() + bot.interrupt() var.playlist.current_index -= 1 # then the bot will move to next item else: # if item deleted is the last item of the queue var.playlist.current_index -= 1 if not bot.is_pause: - bot.interrupt_playing() + bot.interrupt() else: removed = var.playlist.remove(index) @@ -730,7 +730,7 @@ def cmd_queue(bot, user, text, command, parameter): def cmd_random(bot, user, text, command, parameter): global log - bot.interrupt_playing() + bot.interrupt() var.playlist.randomize() def cmd_repeat(bot, user, text, command, parameter): @@ -760,14 +760,13 @@ def cmd_mode(bot, user, text, command, parameter): bot.send_msg(constants.strings('unknown_mode', mode=parameter), text) else: var.db.set('playlist', 'playback_mode', parameter) - var.playlist.set_mode(parameter) + var.playlist = media.playlist.get_playlist(parameter, var.playlist) log.info("command: playback mode changed to %s." % parameter) bot.send_msg(constants.strings("change_mode", mode=var.playlist.mode, user=bot.mumble.users[text.actor]['name']), text) if parameter == "random": - bot.stop() - var.playlist.randomize() - bot.launch_music(0) + bot.interrupt() + bot.launch_music() def cmd_drop_database(bot, user, text, command, parameter): global log diff --git a/configuration.default.ini b/configuration.default.ini index 0ea29da..f642005 100644 --- a/configuration.default.ini +++ b/configuration.default.ini @@ -193,7 +193,7 @@ url_from_playlist_item = {title} from playlist {title} added by {user} not_in_my_channel = You're not in my channel, command refused! pm_not_allowed = Private message aren't allowed. -too_long = This music is too long, skip! +too_long = {song} is too long, removed from playlist! download_in_progress = Download of {item} in progress... removing_item = Removed entry {item} from playlist. user_ban = You are banned, not allowed to do that! diff --git a/interface.py b/interface.py index 18a64f3..2aaa31f 100644 --- a/interface.py +++ b/interface.py @@ -120,7 +120,7 @@ def index(): @web.route("/playlist", methods=['GET']) @requires_auth def playlist(): - if var.playlist.length() == 0: + if len(var.playlist) == 0: return jsonify({'items': [render_template('playlist.html', m=False, index=-1 @@ -140,7 +140,7 @@ def playlist(): return jsonify({ 'items': items }) def status(): - if (var.playlist.length() > 0): + if len(var.playlist) > 0: return jsonify({'ver': var.playlist.version, 'empty': False, 'play': not var.bot.is_pause, @@ -184,8 +184,6 @@ def post(): if not folder.endswith('/'): folder += '/' - print('folder:', folder) - if os.path.isdir(var.music_folder + folder): files = util.get_recursive_file_list_sorted(var.music_folder) @@ -199,10 +197,10 @@ def post(): files = music_library.get_files(folder) music_wrappers = list(map( - lambda file: PlaylistItemWrapper(FileItem(var.bot, file), user), + lambda file: PlaylistItemWrapper(FileItem(var.bot, folder + file), user), files)) - var.playlist.extend(files) + var.playlist.extend(music_wrappers) for music_wrapper in music_wrappers: log.info('web: add to playlist: ' + music_wrapper.format_debug_string()) @@ -213,7 +211,7 @@ def post(): var.playlist.append(music_wrapper) log.info("web: add to playlist: " + music_wrapper.format_debug_string()) - if var.playlist.length() == 2: + if len(var.playlist) == 2: # If I am the second item on the playlist. (I am the next one!) var.bot.async_download_next() @@ -228,7 +226,7 @@ def post(): music_wrapper = var.playlist[int(request.form['delete_music'])] log.info("web: delete from playlist: " + music_wrapper.format_debug_string()) - if var.playlist.length() >= int(request.form['delete_music']): + if len(var.playlist) >= int(request.form['delete_music']): index = int(request.form['delete_music']) if index == var.playlist.current_index: @@ -236,14 +234,14 @@ def post(): if index < len(var.playlist): if not var.bot.is_pause: - var.bot.interrupt_playing() + var.bot.interrupt() var.playlist.current_index -= 1 # then the bot will move to next item else: # if item deleted is the last item of the queue var.playlist.current_index -= 1 if not var.bot.is_pause: - var.bot.interrupt_playing() + var.bot.interrupt() else: var.playlist.remove(index) @@ -254,7 +252,8 @@ def post(): if len(var.playlist) >= int(request.form['play_music']): var.playlist.point_to(int(request.form['play_music']) - 1) - var.bot.interrupt_playing() + var.bot.interrupt() + time.sleep(0.1) elif 'delete_music_file' in request.form and ".." not in request.form['delete_music_file']: path = var.music_folder + request.form['delete_music_file'] @@ -272,16 +271,16 @@ def post(): elif 'action' in request.form: action = request.form['action'] if action == "randomize": - var.bot.interrupt_playing() - var.playlist.set_mode("random") + var.playlist = media.playlist.get_playlist("random", var.playlist) + var.bot.interrupt() var.db.set('playlist', 'playback_mode', "random") log.info("web: playback mode changed to random.") if action == "one-shot": - var.playlist.set_mode("one-shot") + var.playlist = media.playlist.get_playlist("one-shot", var.playlist) var.db.set('playlist', 'playback_mode', "one-shot") log.info("web: playback mode changed to one-shot.") if action == "repeat": - var.playlist.set_mode("repeat") + var.playlist = media.playlist.get_playlist("epeat", var.playlist) var.db.set('playlist', 'playback_mode', "repeat") log.info("web: playback mode changed to repeat.") elif action == "stop": diff --git a/media/playlist.py b/media/playlist.py index 291af6c..d212d2a 100644 --- a/media/playlist.py +++ b/media/playlist.py @@ -43,13 +43,34 @@ def dict_to_item(dict): elif dict['type'] == 'radio': return PlaylistItemWrapper(RadioItem(var.bot, "", "", dict), dict['user']) +def get_playlist(mode, _list=None, index=None): + if _list and index is None: + index = _list.current_index -class PlayList(list): - def __init__(self, *args): - super().__init__(*args) + if _list is None: + if mode == "one-shot": + return OneshotPlaylist() + elif mode == "repeat": + return RepeatPlaylist() + elif mode == "random": + return RandomPlaylist() + else: + if mode == "one-shot": + return OneshotPlaylist().from_list(_list, index) + elif mode == "repeat": + return RepeatPlaylist().from_list(_list, index) + elif mode == "random": + return RandomPlaylist().from_list(_list, index) + + raise + + +class BasePlayList(list): + def __init__(self): + super().__init__() self.current_index = -1 self.version = 0 # increase by one after each change - self.mode = "one-shot" # "repeat", "random" + self.mode = "base" # "repeat", "random" self.pending_items = [] self.log = logging.getLogger("bot") self.validating_thread_lock = threading.Lock() @@ -57,19 +78,13 @@ class PlayList(list): def is_empty(self): return True if len(self) == 0 else False - def set_mode(self, mode): - # modes are "one-shot", "repeat", "random" - self.mode = mode + def from_list(self, _list, current_index): + self.version += 1 + super().clear() + self.extend(_list) + self.current_index = current_index - if mode == "random": - self.randomize() - - elif mode == "one-shot" and self.current_index > 0: - # remove items before current item - self.version += 1 - for i in range(self.current_index): - super().__delitem__(0) - self.current_index = 0 + return self def append(self, item: PlaylistItemWrapper): self.version += 1 @@ -95,9 +110,6 @@ class PlayList(list): return item - def length(self): - return len(self) - def extend(self, items): self.version += 1 super().extend(items) @@ -110,29 +122,15 @@ class PlayList(list): return False self.version += 1 - #logging.debug("playlist: Next into the queue") if self.current_index < len(self) - 1: - if self.mode == "one-shot" and self.current_index != -1: - super().__delitem__(self.current_index) - else: - self.current_index += 1 - + self.current_index += 1 return self[self.current_index] else: - self.current_index = 0 - if self.mode == "one-shot": - self.clear() - return False - elif self.mode == "repeat": - return self[0] - elif self.mode == "random": - self.randomize() - return self[0] - else: - raise TypeError("Unknown playlist mode '%s'." % self.mode) + return False def point_to(self, index): + self.version += 1 if -1 <= index < len(self): self.current_index = index @@ -142,25 +140,14 @@ class PlayList(list): return index return None - def update(self, item, id): - self.version += 1 - index = self.find(id) - if index: - self[index] = item - return True - return False - def __delitem__(self, key): return self.remove(key) - def remove(self, index=-1): + def remove(self, index): self.version += 1 if index > len(self) - 1: return False - if index == -1: - index = self.current_index - removed = self[index] super().__delitem__(index) @@ -170,9 +157,10 @@ class PlayList(list): return removed def remove_by_id(self, id): + self.version += 1 to_be_removed = [] - for index, item in enumerate(self): - if item.id == id: + for index, wrapper in enumerate(self): + if wrapper.item.id == id: to_be_removed.append(index) for index in to_be_removed: @@ -185,30 +173,16 @@ class PlayList(list): return self[self.current_index] def next_index(self): - if len(self) == 0 or (len(self) == 1 and self.mode == 'one_shot'): - return False - if self.current_index < len(self) - 1: return self.current_index + 1 else: - return 0 - - def next_item(self): - if len(self) == 0 or (len(self) == 1 and self.mode == 'one_shot'): return False - return self[self.next_index()] - - def jump(self, index): - if self.mode == "one-shot": - for i in range(index): - super().__delitem__(0) - self.current_index = 0 + def next_item(self): + if self.current_index < len(self) - 1: + return self[self.current_index + 1] else: - self.current_index = index - - self.version += 1 - return self[self.current_index] + return False def randomize(self): # current_index will lose track after shuffling, thus we take current music out before shuffling @@ -240,17 +214,15 @@ class PlayList(list): items = list(var.db.items("playlist_item")) items.sort(key=lambda v: int(v[0])) - self.extend(list(map(lambda v: dict_to_item(json.loads(v[1])), items))) - - self.current_index = current_index + self.from_list(list(map(lambda v: dict_to_item(json.loads(v[1])), items)), current_index) def _debug_print(self): print("===== Playlist(%d)=====" % self.current_index) for index, item_wrapper in enumerate(self): if index == self.current_index: - print("-> %d %s" % (index, item_wrapper.item.title)) + print("-> %d %s" % (index, item_wrapper.format_debug_string())) else: - print("%d %s" % (index, item_wrapper.item.title)) + print("%d %s" % (index, item_wrapper.format_debug_string())) print("===== End =====") def start_async_validating(self): @@ -271,3 +243,106 @@ class PlayList(list): self.log.debug("playlist: validating finished.") self.validating_thread_lock.release() + + +class OneshotPlaylist(BasePlayList): + def __init__(self): + super().__init__() + self.mode = "one-shot" + self.current_index = -1 + + def from_list(self, _list, current_index): + for i in range(current_index): + _list.pop() + return super().from_list(_list, -1) + + def next(self): + if len(self) == 0: + return False + + self.version += 1 + + if len(self) > 0: + if self.current_index != -1: + super().__delitem__(self.current_index) + if len(self) == 0: + return False + else: + self.current_index = 0 + return self[0] + + else: + self.clear() + return False + + def next_index(self): + if len(self) > 1: + return 1 + else: + return False + + def next_item(self): + if len(self) > 1: + return self[1] + else: + return False + + def point_to(self, index): + self.version += 1 + self.current_index = -1 + for i in range(index + 1): + super().__delitem__(0) + + +class RepeatPlaylist(BasePlayList): + def __init__(self): + super().__init__() + self.mode = "repeat" + + def next(self): + if len(self) == 0: + return False + + self.version += 1 + + if self.current_index < len(self) - 1: + self.current_index += 1 + return self[self.current_index] + else: + self.current_index = 0 + return self[0] + + def next_index(self): + if self.current_index < len(self) - 1: + return self.current_index + 1 + else: + return 0 + + def next_item(self): + return self[self.next_index()] + + +class RandomPlaylist(BasePlayList): + def __init__(self): + super().__init__() + self.mode = "random" + + def from_list(self, _list, current_index): + self.version += 1 + random.shuffle(_list) + return super().from_list(_list, -1) + + def next(self): + if len(self) == 0: + return False + + self.version += 1 + + if self.current_index < len(self) - 1: + self.current_index += 1 + return self[self.current_index] + else: + self.randomize() + self.current_index = 0 + return self[0] + diff --git a/media/radio.py b/media/radio.py index ebc2a4c..b130568 100644 --- a/media/radio.py +++ b/media/radio.py @@ -122,6 +122,9 @@ class RadioItem(BaseItem): def format_current_playing(self, user): return constants.strings("now_playing", item=self.format_song_string(user)) + def format_short_string(self): + return self.title if self.title else self.url + def display_type(self): return constants.strings("radio") diff --git a/media/url.py b/media/url.py index 908f27d..1b26c78 100644 --- a/media/url.py +++ b/media/url.py @@ -78,7 +78,7 @@ class URLItem(FileItem): # Check the length, useful in case of playlist, it wasn't checked before) log.info( "url: " + self.url + " has a duration of " + str(self.duration) + " min -- too long") - self.send_client_message(constants.strings('too_long')) + self.send_client_message(constants.strings('too_long', song=self.title)) return False else: self.ready = "validated" diff --git a/mumbleBot.py b/mumbleBot.py index a2fe252..ddf5bea 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -29,7 +29,7 @@ import media.url import media.file import media.radio import media.system -from media.playlist import PlayList +from media.playlist import BasePlayList class MumbleBot: @@ -472,19 +472,17 @@ class MumbleBot: self.log.info("bot: music stopped. playlist trashed.") def stop(self): - # stop and move to the next item in the playlist + self.interrupt() self.is_pause = True - self.interrupt_playing() - self.playhead = 0 - var.playlist.next() self.log.info("bot: music stopped.") - def interrupt_playing(self): + def interrupt(self): # Kill the ffmpeg thread if self.thread: self.thread.kill() self.thread = None self.song_start_at = -1 + self.playhead = 0 def pause(self): # Kill the ffmpeg thread @@ -522,7 +520,7 @@ class MumbleBot: if var.config.getboolean('bot', 'announce_current_music'): - self.send_msg(util.format_current_playing()) + self.send_msg(var.playlist.current_item().format_current_playing()) self.log.info("bot: execute ffmpeg command: " + " ".join(command)) # The ffmpeg process is a thread @@ -627,15 +625,6 @@ if __name__ == '__main__': bot_logger.addHandler(handler) var.bot_logger = bot_logger - var.playlist = PlayList() # playlist should be initialized after the database - var.bot = MumbleBot(args) - command.register_all_commands(var.bot) - - # load playlist - if var.config.getboolean('bot', 'save_playlist', fallback=True): - var.bot_logger.info("bot: load playlist from previous session") - var.playlist.load() - # load playback mode playback_mode = None if var.db.has_option("playlist", "playback_mode"): @@ -644,7 +633,17 @@ if __name__ == '__main__': playback_mode = var.config.get('bot', 'playback_mode', fallback="one-shot") if playback_mode in ["one-shot", "repeat", "random"]: - var.playlist.set_mode(playback_mode) + var.playlist = media.playlist.get_playlist(playback_mode) + else: + raise KeyError("Unknown playback mode '%s'" % playback_mode) + + var.bot = MumbleBot(args) + command.register_all_commands(var.bot) + + # load playlist + if var.config.getboolean('bot', 'save_playlist', fallback=True): + var.bot_logger.info("bot: load playlist from previous session") + var.playlist.load() # Start the main loop. var.bot.loop() diff --git a/templates/playlist.html b/templates/playlist.html index b677ac9..03bf9ff 100644 --- a/templates/playlist.html +++ b/templates/playlist.html @@ -26,7 +26,11 @@ {{ m.display_type() }}
{% if m.type == 'file' %} - {{ m.artist }} + {% if m.artist %} + {{ m.artist }} + {% else %} + ?? + {% endif %} {% elif m.type == 'url_from_playlist' %} {{ m.playlist_title|truncate(50) }} {% else %}