diff --git a/command.py b/command.py
index 8fe4015..b46d213 100644
--- a/command.py
+++ b/command.py
@@ -47,6 +47,7 @@ def register_all_commands(bot):
bot.register_command(constants.commands('list_file'), cmd_list_file)
bot.register_command(constants.commands('queue'), cmd_queue)
bot.register_command(constants.commands('random'), cmd_random)
+ bot.register_command(constants.commands('mode'), cmd_mode)
bot.register_command(constants.commands('drop_database'), cmd_drop_database)
def send_multi_lines(bot, lines, text):
@@ -533,9 +534,17 @@ def cmd_remove(bot, user, text, command, parameter):
removed = None
if index == var.playlist.current_index:
removed = var.playlist.remove(index)
- if bot.is_playing and not bot.is_pause:
- bot.stop()
- bot.launch_music(index)
+
+ if index < len(var.playlist):
+ if not bot.is_pause:
+ bot.kill_ffmpeg()
+ 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.kill_ffmpeg()
else:
removed = var.playlist.remove(index)
@@ -593,12 +602,25 @@ def cmd_queue(bot, user, text, command, parameter):
send_multi_lines(bot, msgs, text)
-
def cmd_random(bot, user, text, command, parameter):
bot.stop()
var.playlist.randomize()
bot.launch_music(0)
+def cmd_mode(bot, user, text, command, parameter):
+ if not parameter:
+ bot.send_msg(constants.strings("current_mode", mode=var.playlist.mode), text)
+ return
+ if not parameter in ["one-shot", "loop", "random"]:
+ bot.send_msg(constants.strings('unknown_mode', mode=parameter), text)
+ else:
+ var.playlist.set_mode(parameter)
+ if parameter == "random":
+ bot.stop()
+ var.playlist.randomize()
+ bot.launch_music(0)
+
+
def cmd_drop_database(bot, user, text, command, parameter):
var.db.drop_table()
var.db = Database(var.dbfile)
diff --git a/configuration.default.ini b/configuration.default.ini
index 7047275..e71393d 100644
--- a/configuration.default.ini
+++ b/configuration.default.ini
@@ -38,6 +38,7 @@ admin = User1;User2;
music_folder = music_folder/
# Folder that stores the downloaded music.
tmp_folder = /tmp/
+database_path = database.db
pip3_path = venv/bin/pip
auto_check_update = True
logfile =
@@ -131,6 +132,7 @@ joinme = joinme
queue = queue
repeat = repeat
random = random
+mode = mode
update = update
list_file = listfile
@@ -183,6 +185,8 @@ database_dropped = Database dropped. All records have gone.
new_version_found =
Update Available!
New version of botamusique is available, send !update to update!
start_updating = Start updating...
file_missed = Music file '{file}' missed! This item has been removed from the playlist.
+unknown_mode = Unknown playback mode '{mode}'. It should be one of one-shot, loop, random.
+current_mode = Current playback mode is {mode}.
help = Commands
Control
@@ -192,6 +196,8 @@ help = Commands
!stop - stop playing
!skip - jump to the next song
!volume {volume} - get or change the volume (from 0 to 100)
+ !mode [{mode}] - get or set the playback mode, {mode} should be one of one-shot (play the playlist
+ once), loop (looping through the playlist), random (randomize the playlist)
!duck on/off - enable or disable ducking function
!duckv - set the volume of the bot when ducking is activated
!duckthres - set the threshold of volume to activate ducking (3000 by default)
diff --git a/interface.py b/interface.py
index e92aa1e..d9fb77c 100644
--- a/interface.py
+++ b/interface.py
@@ -220,13 +220,23 @@ def post():
logging.info("web: delete from playlist: " + util.format_debug_song_string(music))
if var.playlist.length() >= int(request.form['delete_music']):
- if int(request.form['delete_music']) == var.playlist.current_index:
- var.playlist.remove(int(request.form['delete_music']))
- if var.botamusique.is_playing and not var.botamusique.is_pause:
- var.botamusique.stop()
- var.botamusique.launch_music(int(request.form['delete_music']))
+ index = int(request.form['delete_music'])
+
+ if index == var.playlist.current_index:
+ var.playlist.remove(index)
+
+ if index < len(var.playlist):
+ if not var.botamusique.is_pause:
+ var.botamusique.kill_ffmpeg()
+ 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.botamusique.is_pause:
+ var.botamusique.kill_ffmpeg()
else:
- var.playlist.remove(int(request.form['delete_music']))
+ var.playlist.remove(index)
elif 'play_music' in request.form:
@@ -254,8 +264,12 @@ def post():
action = request.form['action']
if action == "randomize":
var.botamusique.stop()
- var.playlist.randomize()
+ var.playlist.set_mode("random")
var.botamusique.resume()
+ if action == "one-shot":
+ var.playlist.set_mode("one-shot")
+ if action == "loop":
+ var.playlist.set_mode("loop")
elif action == "stop":
var.botamusique.stop()
elif action == "pause":
@@ -269,12 +283,14 @@ def post():
var.botamusique.volume_set = var.botamusique.volume_set + 0.03
else:
var.botamusique.volume_set = 1.0
+ var.db.set('bot', 'volume', str(var.botamusique.volume_set))
logging.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
elif action == "volume_down":
if var.botamusique.volume_set - 0.03 > 0:
var.botamusique.volume_set = var.botamusique.volume_set - 0.03
else:
var.botamusique.volume_set = 0
+ var.db.set('bot', 'volume', str(var.botamusique.volume_set))
logging.info("web: volume up to %d" % (var.botamusique.volume_set * 100))
if(var.playlist.length() > 0):
diff --git a/media/playlist.py b/media/playlist.py
index fbb833a..7e569f7 100644
--- a/media/playlist.py
+++ b/media/playlist.py
@@ -6,12 +6,20 @@ import json
import logging
class PlayList(list):
- current_index = 0
+ current_index = -1
version = 0 # increase by one after each change
+ mode = "one-shot" # "loop", "random"
def __init__(self, *args):
super().__init__(*args)
+ def set_mode(self, mode):
+ # modes are "one-shot", "loop", "random"
+ self.mode = mode
+ var.db.set('playlist', 'mode', mode)
+ if mode == "random":
+ self.randomize()
+
def append(self, item):
self.version += 1
item = util.get_music_tag_info(item)
@@ -45,15 +53,27 @@ class PlayList(list):
return items
def next(self):
- self.version += 1
if len(self) == 0:
return False
+ self.version += 1
logging.debug("playlist: Next into the queue")
- self.current_index = self.next_index()
-
- return self[self.current_index]
+ if self.current_index < len(self) - 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 == "loop":
+ return self[0]
+ elif self.mode == "random":
+ self.randomize()
+ return self[0]
+ else:
+ raise TypeError("Unknown playlist mode '%s'." % self.mode)
def update(self, item, index=-1):
self.version += 1
@@ -116,8 +136,8 @@ class PlayList(list):
def clear(self):
self.version += 1
- self.current_index = 0
- self.clear()
+ self.current_index = -1
+ super().clear()
def save(self):
var.db.remove_section("playlist_item")
diff --git a/mumbleBot.py b/mumbleBot.py
index 2cafa87..b0210a1 100644
--- a/mumbleBot.py
+++ b/mumbleBot.py
@@ -90,8 +90,6 @@ class MumbleBot:
root.setLevel(logging.ERROR)
logging.error("Starting in ERROR loglevel")
- var.playlist = PlayList()
-
var.user = args.user
var.music_folder = var.config.get('bot', 'music_folder')
var.is_proxified = var.config.getboolean(
@@ -100,7 +98,6 @@ class MumbleBot:
self.nb_exit = 0
self.thread = None
self.thread_stderr = None
- self.is_playing = False
self.is_pause = False
self.playhead = -1
self.song_start_at = -1
@@ -378,7 +375,6 @@ class MumbleBot:
util.pipe_no_wait(pipe_rd) # Let the pipe work in non-blocking mode
self.thread_stderr = os.fdopen(pipe_rd)
self.thread = sp.Popen(command, stdout=sp.PIPE, stderr=pipe_wd, bufsize=480)
- self.is_playing = True
self.is_pause = False
self.song_start_at = -1
self.playhead = 0
@@ -560,11 +556,7 @@ class MumbleBot:
if self.thread is None or not raw_music:
# Not music into the buffet
- if self.is_playing:
- # get next music
- self.is_playing = False
- if not self.is_pause and len(var.playlist) > 0:
- var.playlist.next()
+ if not self.is_pause and var.playlist.next():
self.launch_music()
self.async_download_next()
@@ -610,35 +602,38 @@ class MumbleBot:
self.thread.kill()
self.thread = None
var.playlist.clear()
- self.is_playing = False
logging.info("bot: music stopped. playlist trashed.")
def stop(self):
+ # stop and move to the next item in the playlist
+ self.is_pause = True
+ self.kill_ffmpeg()
+ self.playhead = 0
+ var.playlist.next()
+ logging.info("bot: music stopped.")
+
+ def kill_ffmpeg(self):
# Kill the ffmpeg thread
if self.thread:
self.thread.kill()
self.thread = None
- self.is_playing = False
- self.is_pause = True
self.song_start_at = -1
- self.playhead = 0
- var.playlist.next()
- logging.info("bot: music stopped.")
def pause(self):
# Kill the ffmpeg thread
if self.thread:
self.thread.kill()
self.thread = None
- self.is_playing = False
self.is_pause = True
self.song_start_at = -1
logging.info("bot: music paused at %.2f seconds." % self.playhead)
def resume(self):
- self.is_playing = True
self.is_pause = False
+ if var.playlist.current_index == -1:
+ var.playlist.next()
+
music = var.playlist.current_item()
if music['type'] == 'radio' or self.playhead == 0 or not self.check_item_path_or_remove():
@@ -700,7 +695,7 @@ if __name__ == '__main__':
parser.add_argument("--config", dest='config', type=str, default='configuration.ini',
help='Load configuration from this file. Default: configuration.ini')
parser.add_argument("--db", dest='db', type=str,
- default='database.db', help='database file. Default: database.db')
+ default=None, help='database file. Default: database.db')
parser.add_argument("-q", "--quiet", dest="quiet",
action="store_true", help="Only Error logs")
@@ -725,9 +720,9 @@ if __name__ == '__main__':
args = parser.parse_args()
- var.dbfile = args.db
config = configparser.ConfigParser(interpolation=None, allow_no_value=True)
parsed_configs = config.read(['configuration.default.ini', args.config], encoding='utf-8')
+ var.dbfile = args.db if args.db is not None else config.get("bot", "database_path", fallback="database.db")
if len(parsed_configs) == 0:
logging.error('Could not read configuration from file \"{}\"'.format(
@@ -753,6 +748,7 @@ if __name__ == '__main__':
handler.setFormatter(formatter)
root.addHandler(handler)
+ var.playlist = PlayList() # playlist should be initialized after the database
var.botamusique = MumbleBot(args)
command.register_all_commands(var.botamusique)