Playlist and colume management into db

#27 #16 #11
This commit is contained in:
azlux 2018-07-12 00:40:29 +02:00
parent 578ffbd172
commit 3a6aba9545
7 changed files with 170 additions and 52 deletions

View File

@ -15,6 +15,7 @@ Bot the can play :
You need to create a folder for all your music. Organize your music by subfolder. You need to create a folder for all your music. Organize your music by subfolder.
The main folder needs to be declared in the config (with a '/' at the end) The main folder needs to be declared in the config (with a '/' at the end)
You can enable the web interface into the configuration.ini file.
### Installation ### Installation
1. You need python 3 with opuslib and protobuf (look at the requirement of pymumble) 1. You need python 3 with opuslib and protobuf (look at the requirement of pymumble)

View File

@ -24,6 +24,9 @@ announce_current_music = True
allow_other_channel_message = False allow_other_channel_message = False
allow_private_message = True allow_private_message = True
# Maximum track played when a playlist is added.
max_track_playlist = 20
[webinterface] [webinterface]
enabled = False enabled = False
is_web_proxified = True is_web_proxified = True
@ -34,6 +37,7 @@ listening_port = 8181
play_file = file play_file = file
play_url = url play_url = url
play_radio = radio play_radio = radio
play_playlist = playlist
help = help help = help
stop = stop stop = stop
@ -74,6 +78,7 @@ pm_not_allowed = Private message aren't allowed.
help = Command available: help = Command available:
<br />!file [path] <br />!file [path]
<br />!url [url] - youtube or soundcloud <br />!url [url] - youtube or soundcloud
<br />!playlist [url] [offset] - youtube or soundcloud playlist (the offset is the track number the bot will start to play - 1 by default)
<br />!radio [url] - url of a stream <br />!radio [url] - url of a stream
<br />!list - display list of available tracks <br />!list - display list of available tracks
<br />!queue - display items in queue <br />!queue - display items in queue

4
db.ini Normal file
View File

@ -0,0 +1,4 @@
[bot]
volume = 0.1
ban_music = []
ban_user = []

View File

@ -108,7 +108,7 @@ def clear_tmp_folder(path, size):
size_tp += os.path.getsize(file) size_tp += os.path.getsize(file)
if int(size_tp/(1024*1024)) > size: if int(size_tp/(1024*1024)) > size:
logging.info("Cleaning tmp folder") logging.info("Cleaning tmp folder")
to_remove = all_files[idx:] to_remove = all_files[:idx]
print(to_remove) print(to_remove)
for f in to_remove: for f in to_remove:
logging.debug("Removing " + f) logging.debug("Removing " + f)

View File

@ -39,19 +39,21 @@ class MumbleBot:
###### ######
## Format of the Playlist : ## Format of the Playlist :
## [("<type>","<path/url>")] ## [("<type>","<path/url>")]
## types : file, radio, url ## types : file, radio, url, is_playlist, number_music_to_play
###### ######
###### ######
## Format of the current_music variable ## Format of the current_music variable
# var.current_music = { "type" : str, # var.current_music = { "type" : str,
# "path" : str, # "path" : str, # path of the file to play
# "url" : str # url to download
# "title" : str, # "title" : str,
# "user" : str } # "user" : str,
# len(var.current_music) = 4 # "is_playlist": boolean,
# var.current_music["type"] = <Type> # "number_track_to_play": int, # FOR PLAYLIST ONLY
# var.current_music["path"] = <url> if youtube/radio, <path> if file # "start_index" : int, # FOR PLAYLIST ONLY
# var.current_music["title"] = <title> # "current_index" : int} # FOR PLAYLIST ONLY
# len(var.current_music) = 6
var.playlist = [] var.playlist = []
@ -161,14 +163,24 @@ class MumbleBot:
self.mumble.users[text.actor].send_message(msg) self.mumble.users[text.actor].send_message(msg)
else: else:
self.mumble.users[text.actor].send_message(var.config.get('strings', 'bad_file')) self.mumble.users[text.actor].send_message(var.config.get('strings', 'bad_file'))
self.async_download_next()
elif command == var.config.get('command', 'play_url') and parameter: elif command == var.config.get('command', 'play_url') and parameter:
var.playlist.append(["url", parameter, user]) var.playlist.append(["url", parameter, user])
self.async_download_next()
elif command == var.config.get('command', 'play_playlist') and parameter:
offset = 1
if len(message) > 2:
offset = int(message[2])
var.playlist.append(["playlist", parameter, user, var.config.getint('bot', 'max_track_playlist'), offset])
self.async_download_next()
elif command == var.config.get('command', 'play_radio') and parameter: elif command == var.config.get('command', 'play_radio') and parameter:
if var.config.has_option('radio', parameter): if var.config.has_option('radio', parameter):
parameter = var.config.get('radio', parameter) parameter = var.config.get('radio', parameter)
var.playlist.append(["radio", parameter, user]) var.playlist.append(["radio", parameter, user])
self.async_download_next()
elif command == var.config.get('command', 'help'): elif command == var.config.get('command', 'help'):
self.send_msg_channel(var.config.get('strings', 'help')) self.send_msg_channel(var.config.get('strings', 'help'))
@ -186,15 +198,15 @@ class MumbleBot:
elif command == var.config.get('command', 'update'): elif command == var.config.get('command', 'update'):
if not self.is_admin(user): if not self.is_admin(user):
self.mumble.users[text.actor].send_message("Starting the update") self.mumble.users[text.actor].send_message("Starting the update")
tp = sp.check_output([var.config.get('bot', 'pip3_path'), 'install','--upgrade','youtube-dl']).decode() tp = sp.check_output([var.config.get('bot', 'pip3_path'), 'install', '--upgrade', 'youtube-dl']).decode()
msg="" msg = ""
if "Requirement already up-to-date" in tp: if "Requirement already up-to-date" in tp:
msg += "Youtube-dl is up-to-date" msg += "Youtube-dl is up-to-date"
else : else:
msg += "Update done : " + tp.split('Successfully installed')[1] msg += "Update done : " + tp.split('Successfully installed')[1]
if 'Your branch is up-to-date' in sp.check_output(['/usr/bin/env','git','status']).decode(): if 'Your branch is up-to-date' in sp.check_output(['/usr/bin/env', 'git', 'status']).decode():
msg += "<br /> Botamusique is up-to-date" msg += "<br /> Botamusique is up-to-date"
else : else:
msg += "<br /> Botamusique have available update" msg += "<br /> Botamusique have available update"
self.mumble.users[text.actor].send_message(msg) self.mumble.users[text.actor].send_message(msg)
else: else:
@ -210,7 +222,7 @@ class MumbleBot:
self.volume = float(float(parameter) / 100) self.volume = float(float(parameter) / 100)
self.send_msg_channel(var.config.get('strings', 'change_volume') % ( self.send_msg_channel(var.config.get('strings', 'change_volume') % (
int(self.volume * 100), self.mumble.users[text.actor]['name'])) int(self.volume * 100), self.mumble.users[text.actor]['name']))
var.config.set('bot', 'volume', str(self.volume)) var.db.set('bot', 'volume', str(self.volume))
else: else:
self.send_msg_channel(var.config.get('strings', 'current_volume') % int(self.volume * 100)) self.send_msg_channel(var.config.get('strings', 'current_volume') % int(self.volume * 100))
@ -233,6 +245,13 @@ class MumbleBot:
reply = "[file] {title} by {user}".format( reply = "[file] {title} by {user}".format(
title=var.current_music["title"], title=var.current_music["title"],
user=var.current_music["user"]) user=var.current_music["user"])
elif source == "playlist":
reply = "[playlist] {title} (from the playlist <a href=\"{url}\">{playlist}</a> by {user}".format(
title=var.current_music["title"],
url=var.current_music["path"],
playlist=var.current_music["playlist_title"],
user=var.current_music["user"]
)
else: else:
reply = "(?)[{}] {} {} by {}".format( reply = "(?)[{}] {} {} by {}".format(
var.current_music["type"], var.current_music["type"],
@ -246,13 +265,9 @@ class MumbleBot:
self.mumble.users[text.actor].send_message(reply) self.mumble.users[text.actor].send_message(reply)
elif command == var.config.get('command', 'next'): elif command == var.config.get('command', 'next'):
if var.playlist: if self.get_next():
var.current_music = {'type': var.playlist[0][0],
'path': var.playlist[0][1],
'title': None,
'user': var.playlist[0][2]}
var.playlist.pop(0)
self.launch_next() self.launch_next()
self.async_download_next()
else: else:
self.mumble.users[text.actor].send_message(var.config.get('strings', 'queue_empty')) self.mumble.users[text.actor].send_message(var.config.get('strings', 'queue_empty'))
self.stop() self.stop()
@ -292,25 +307,68 @@ class MumbleBot:
self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480) self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480)
self.playing = True self.playing = True
def is_admin(self, user): @staticmethod
def is_admin(user):
list_admin = var.config.get('bot', 'admin').split(';') list_admin = var.config.get('bot', 'admin').split(';')
if user in list_admin: if user in list_admin:
return True return True
else: else:
return False return False
@staticmethod
def get_next():
# Return True is next is possible
if var.current_music and var.current_music['type'] == "playlist":
var.current_music['current_index'] += 1
if var.current_music['current_index'] <= (var.current_music['start_index'] + var.current_music['number_track_to_play']):
return True
if not var.playlist:
return False
if var.playlist[0][0] == "playlist":
var.current_music = {'type': var.playlist[0][0],
'url': var.playlist[0][1],
'title': None,
'user': var.playlist[0][2],
'is_playlist': True,
'number_track_to_play': var.playlist[0][3],
'start_index': var.playlist[0][4],
'current_index': var.playlist[0][4]
}
else:
var.current_music = {'type': var.playlist[0][0],
'url': var.playlist[0][1],
'title': None,
'user': var.playlist[0][2]}
var.playlist.pop(0)
return True
def launch_next(self): def launch_next(self):
path = "" path = ""
title = "" title = ""
if var.current_music["type"] == "url": var.next_downloaded = False
url = media.get_url(var.current_music["path"]) logging.debug(var.current_music)
if var.current_music["type"] == "url" or var.current_music["type"] == "playlist":
url = media.get_url(var.current_music["url"])
if not url: if not url:
return return
media.clear_tmp_folder(var.config.get('bot', 'tmp_folder'), var.config.getint('bot', 'tmp_folder_max_size'))
path, title = self.download_music(url)
var.current_music["path"] = url
path_thumbnail = var.config.get('bot', 'tmp_folder') + hashlib.md5(url.encode()).hexdigest() + '.jpg' media.clear_tmp_folder(var.config.get('bot', 'tmp_folder'), var.config.getint('bot', 'tmp_folder_max_size'))
if var.current_music["type"] == "playlist":
path, title = self.download_music(url, var.current_music["current_index"])
var.current_music["playlist_title"] = title
else:
path, title = self.download_music(url)
var.current_music["path"] = path
audio = EasyID3(path)
if audio["title"]:
title = audio["title"][0]
path_thumbnail = var.config.get('bot', 'tmp_folder') + hashlib.md5(path.encode()).hexdigest() + '.jpg'
thumbnail_html = "" thumbnail_html = ""
if os.path.isfile(path_thumbnail): if os.path.isfile(path_thumbnail):
im = Image.open(path_thumbnail) im = Image.open(path_thumbnail)
@ -336,6 +394,8 @@ class MumbleBot:
path = url path = url
title = media.get_radio_server_description(url) title = media.get_radio_server_description(url)
var.current_music["title"] = title
if var.config.getboolean('debug', 'ffmpeg'): if var.config.getboolean('debug', 'ffmpeg'):
ffmpeg_debug = "debug" ffmpeg_debug = "debug"
else: else:
@ -343,15 +403,31 @@ class MumbleBot:
command = ["ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i', path, '-ac', '1', '-f', 's16le', '-ar', '48000', '-'] command = ["ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i', path, '-ac', '1', '-f', 's16le', '-ar', '48000', '-']
self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480) self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480)
var.current_music["title"] = title
def download_music(self, url): @staticmethod
def download_music(url, index=None):
url_hash = hashlib.md5(url.encode()).hexdigest() url_hash = hashlib.md5(url.encode()).hexdigest()
if index:
url_hash = url_hash + "-" + str(index)
path = var.config.get('bot', 'tmp_folder') + url_hash + ".%(ext)s" path = var.config.get('bot', 'tmp_folder') + url_hash + ".%(ext)s"
mp3 = path.replace(".%(ext)s", ".mp3") mp3 = path.replace(".%(ext)s", ".mp3")
if os.path.isfile(mp3): if os.path.isfile(mp3):
audio = EasyID3(mp3) audio = EasyID3(mp3)
video_title = audio["title"][0] video_title = audio["title"][0]
else:
if index:
ydl_opts = {
'format': 'bestaudio/best',
'outtmpl': path,
'writethumbnail': True,
'updatetime': False,
'playlist_items': str(index),
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192'},
{'key': 'FFmpegMetadata'}]
}
else: else:
ydl_opts = { ydl_opts = {
'format': 'bestaudio/best', 'format': 'bestaudio/best',
@ -377,6 +453,31 @@ class MumbleBot:
break break
return mp3, video_title return mp3, video_title
def async_download_next(self):
if not var.next_downloaded:
var.next_downloaded = True
logging.info("Start download in thread")
th = threading.Thread(target=self.download_next, args=())
th.daemon = True
th.start()
def download_next(self):
if not var.current_music:
return
else:
if var.current_music["type"] == "playlist":
if var.current_music['current_index'] + 1 <= (var.current_music['start_index'] + var.current_music['number_track_to_play']):
self.download_music(media.get_url(var.current_music['url']), var.current_music["current_index"] + 1)
if var.playlist:
url = media.get_url(var.playlist[0][1])
if not url:
return
if var.playlist[0][0] == "playlist":
self.download_music(url, var.current_music["current_index"])
elif var.playlist[0][0] == "playlist":
self.download_music(url)
def loop(self): def loop(self):
raw_music = "" raw_music = ""
while not self.exit and self.mumble.isAlive(): while not self.exit and self.mumble.isAlive():
@ -393,10 +494,9 @@ class MumbleBot:
time.sleep(0.1) time.sleep(0.1)
if self.thread is None or not raw_music: if self.thread is None or not raw_music:
if len(var.playlist) != 0: if self.get_next():
var.current_music = {'type': var.playlist[0][0], 'path': var.playlist[0][1], 'user': var.playlist[0][2]}
var.playlist.pop(0)
self.launch_next() self.launch_next()
self.async_download_next()
else: else:
var.current_music = None var.current_music = None
@ -405,7 +505,7 @@ class MumbleBot:
time.sleep(0.5) time.sleep(0.5)
if self.exit: if self.exit:
util.write_config() util.write_db()
def stop(self): def stop(self):
if self.thread: if self.thread:
@ -433,6 +533,8 @@ if __name__ == '__main__':
# General arguments # General arguments
parser.add_argument("--config", dest='config', type=str, default='configuration.ini', help='Load configuration from this file. Default: configuration.ini') 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='db.ini', help='database file. Default db.ini')
parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", help="Only Error logs") parser.add_argument("-q", "--quiet", dest="quiet", action="store_true", help="Only Error logs")
# Mumble arguments # Mumble arguments
@ -443,13 +545,17 @@ if __name__ == '__main__':
parser.add_argument("-c", "--channel", dest="channel", type=str, help="Default channel for the bot") parser.add_argument("-c", "--channel", dest="channel", type=str, help="Default channel for the bot")
args = parser.parse_args() args = parser.parse_args()
var.configfile = args.config var.dbfile = args.db
config = configparser.ConfigParser(interpolation=None, allow_no_value=True) config = configparser.ConfigParser(interpolation=None, allow_no_value=True)
parsed_configs = config.read(['configuration.default.ini', var.configfile], encoding='latin-1') parsed_configs = config.read(['configuration.default.ini', args.config, var.dbfile], encoding='latin-1')
db = configparser.ConfigParser(interpolation=None, allow_no_value=True)
db.read([var.dbfile], encoding='latin-1')
if len(parsed_configs) == 0: if len(parsed_configs) == 0:
print('Could not read configuration from file \"{}\"'.format(args.config), file=sys.stderr) print('Could not read configuration from file \"{}\"'.format(args.config), file=sys.stderr)
sys.exit() sys.exit()
var.config = config var.config = config
var.db = db
botamusique = MumbleBot(args) botamusique = MumbleBot(args)

View File

@ -66,9 +66,9 @@ def zipdir(zippath, zipname_prefix=None):
return zipname return zipname
def write_config(): def write_db():
with open(var.configfile, 'w') as f: with open(var.dbfile, 'w') as f:
var.config.write(f) var.db.write(f)
class Dir(object): class Dir(object):

View File

@ -3,5 +3,7 @@ playlist = []
user = "" user = ""
music_folder = "" music_folder = ""
is_proxified = False is_proxified = False
configfile = None dbfile = None
db = None
config = None config = None
next_downloaded = False