diff --git a/configuration.ini b/configuration.ini index 4917f06..894a72f 100644 --- a/configuration.ini +++ b/configuration.ini @@ -1,8 +1,9 @@ [bot] -comment = Coucou, Je suis née du savoir du Azlux, accès au https://azlux.fr/bot +comment = Coucou, Je suis née du savoir du Azlux, accès au https://azlux.fr/bot volume = 0.1 admin = Azlux;AzMobile music_folder = /home/dmichel/botamusique/music/ +tmp_folder = /tmp/ is_proxified = True [debug] @@ -10,14 +11,20 @@ ffmpeg = False mumbleConnection = False [command] -play_file = play +play_file = file +play_url = url +play_radio = radio + +help = help stop = stop +list = list +next = next current_music = np volume = v kill = kill stop_and_getout = oust joinme = joinme -list = list + [strings] current_volume = volume : %d%% @@ -27,3 +34,14 @@ not_admin = T'es pas admin, patate ! not_playing = Aucun stream en lecture bad_file = Bad file asked no_file = Not file here +bad_url = Bad URL asked + +help = Command available: +
!play_file +
!play_url - youtube or soundcloud +
!play_radio - url of a stream +
!next - jump to the next music of the playlist +
!stop - stop and clear the playlist +
!play_file - stop + Go to default channel +
!v - get or change the volume (in %) +
!joinme diff --git a/interface.py b/interface.py index 1fcd62f..8307595 100644 --- a/interface.py +++ b/interface.py @@ -64,14 +64,14 @@ def index(): files[director] = [f for f in listdir(folder_path + director) if os.path.isfile(os.path.join(folder_path + director, f))] if request.method == 'POST': - if 'add_music' in request.form and ".." not in request.form['add_music']: - var.playlist.append(request.form['add_music']) + if 'add_file' in request.form and ".." not in request.form['add_music']: + var.playlist.append((request.form['type'], request.form['add_music'])) if 'add_folder' in request.form and ".." not in request.form['add_folder']: - dir_files = [request.form['add_folder'] + '/' + i for i in files[request.form['add_folder']]] + dir_files = [("file", request.form['add_folder'] + '/' + i) for i in files[request.form['add_folder']]] var.playlist.extend(dir_files) elif 'delete_music' in request.form: try: - var.playlist.remove(request.form['delete_music']) + var.playlist.remove("file", request.form['delete_music']) except ValueError: pass elif 'action' in request.form: diff --git a/media.py b/media.py new file mode 100644 index 0000000..64af452 --- /dev/null +++ b/media.py @@ -0,0 +1,67 @@ +import re +import urllib +import logging +import json +import http.client +import struct + + +def get_radio_server_description(url): + p = re.compile('(https?\:\/\/[^\/]*)', re.IGNORECASE) + res = re.search(p, url) + base_url = res.group(1) + url_icecast = base_url + '/status-json.xsl' + url_shoutcast = base_url + '/stats?json=1' + title_server = None + try: + request = urllib.request.Request(url_shoutcast) + response = urllib.request.urlopen(request) + data = json.loads(response.read().decode("utf-8")) + title_server = data['servertitle'] + logging.debug("TITLE FOUND SHOUTCAST: " + title_server) + except urllib.error.HTTPError: + pass + except http.client.BadStatusLine: + pass + except ValueError: + return False + + if not title_server: + try: + request = urllib.request.Request(url_icecast) + response = urllib.request.urlopen(request) + data = json.loads(response.read().decode('utf-8',errors='ignore'),strict=False) + title_server = data['icestats']['source'][0]['server_name'] + ' - ' + data['icestats']['source'][0]['server_description'] + logging.debug("TITLE FOUND ICECAST: " + title_server) + if not title_server: + title_server = url + except urllib.error.URLError: + title_server = url + except urllib.error.HTTPError: + return False + except http.client.BadStatusLine: + pass + return title_server + + +def get_radio_title(url): + request = urllib.request.Request(url, headers={'Icy-MetaData': 1}) + try: + + response = urllib.request.urlopen(request) + icy_metaint_header = int(response.headers['icy-metaint']) + if icy_metaint_header is not None: + response.read(icy_metaint_header) + + metadata_length = struct.unpack('B', response.read(1))[0] * 16 # length byte + metadata = response.read(metadata_length).rstrip(b'\0') + logging.debug(metadata) + # extract title from the metadata + m = re.search(br"StreamTitle='([^']*)';", metadata) + if m: + title = m.group(1) + if title: + return title.decode() + except (urllib.error.URLError, urllib.error.HTTPError): + pass + return 'Impossible to get the music title' diff --git a/mumbleBot.py b/mumbleBot.py index a9b60d7..66f7637 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -1,6 +1,8 @@ #!/usr/bin/python3 -import threading +from __future__ import unicode_literals +import re +import threading import time import sys import signal @@ -13,6 +15,9 @@ from os import listdir import pymumble.pymumble_py3 as pymumble import interface import variables as var +import hashlib +import youtube_dl +import media class MumbleBot: @@ -34,7 +39,24 @@ class MumbleBot: self.channel = args.channel var.current_music = None + + ###### + ## Format of the Playlist : + ## [("","")] + ## [("",""), ("","")] + ## types : file, radio, url + ###### + + ###### + ## Format of the current_music variable + # len(var.current_music) = 4 + # var.current_music[0] = + # var.current_music[1] = if url of radio + # var.current_music[2] = + # var.current_music[3] = <path> if url or file + var.playlist = [] + var.user = args.user var.music_folder = self.config.get('bot', 'music_folder') var.is_proxified = self.config.getboolean("bot", "is_proxified") @@ -44,9 +66,9 @@ class MumbleBot: interface.init_proxy() - t = threading.Thread(target=start_web_interface) - t.daemon = True - t.start() + # t = threading.Thread(target=start_web_interface) + # t.daemon = True + # t.start() self.mumble = pymumble.Mumble(args.host, user=args.user, port=args.port, password=args.password, debug=self.config.getboolean('debug', 'mumbleConnection')) @@ -91,10 +113,19 @@ class MumbleBot: if "/" in parameter: self.mumble.users[text.actor].send_message(self.config.get('strings', 'bad_file')) elif os.path.isfile(path): - self.launch_play_file(path) + var.playlist.append(["file", path]) else: self.mumble.users[text.actor].send_message(self.config.get('strings', 'bad_file')) + elif command == self.config.get('command', 'play_url') and parameter: + var.playlist.append(["url", parameter]) + + elif command == self.config.get('command', 'play_radio') and parameter: + var.playlist.append(["radio", parameter]) + + elif command == self.config.get('command', 'help'): + self.send_msg_channel(self.config.get('strings', 'help')) + elif command == self.config.get('command', 'stop'): self.stop() @@ -123,17 +154,17 @@ class MumbleBot: elif command == self.config.get('command', 'current_music'): if var.current_music is not None: - self.send_msg_channel(var.current_music) + if var.current_music[0] == "radio": + self.send_msg_channel(media.get_radio_title(var.current_music[1]) + " sur " + var.current_music[2]) + else: + self.send_msg_channel(var.current_music[2] + "<br />" + var.current_music[1]) else: self.mumble.users[text.actor].send_message(self.config.get('strings', 'not_playing')) - elif command == self.config.get('command', 'list'): - folder_path = self.config.get('bot', 'music_folder') - files = [f for f in listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))] - if files: - self.mumble.users[text.actor].send_message('<br>'.join(files)) - else: - self.mumble.users[text.actor].send_message(self.config.get('strings', 'no_file')) + elif command == self.config.get('command', 'next'): + var.current_music = var.playlist[0] + var.playlist.pop(0) + self.launch_next() else: self.mumble.users[text.actor].send_message(self.config.get('strings', 'bad_command')) @@ -145,21 +176,69 @@ class MumbleBot: else: return False - def launch_play_file(self, path=None): - if not path: - path = self.config.get('bot', 'music_folder') + var.current_music + def launch_next(self): + path = "" + title = "" + if var.current_music[0] == "url": + regex = re.compile("<a href=\"(.*?)\"") + m = regex.match(var.current_music[1]) + url = m.group(1) + path, title = self.download_music(url) + var.current_music[1] = url + + elif var.current_music[0] == "file": + path = self.config.get('bot', 'music_folder') + var.current_music[1] + title = var.current_music[1] + + elif var.current_music[0] == "radio": + regex = re.compile("<a href=\"(.*?)\"") + m = regex.match(var.current_music[1]) + url = m.group(1) + var.current_music[1] = url + path = url + title = media.get_radio_server_description(url) + if self.config.getboolean('debug', 'ffmpeg'): ffmpeg_debug = "debug" else: ffmpeg_debug = "warning" + command = ["ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i', path, '-ac', '1', '-f', 's16le', '-ar', '48000', '-'] self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480) - var.current_music = path + var.current_music.append(title) + var.current_music.append(path) + + def download_music(self, url): + url_hash = hashlib.md5(url.encode()).hexdigest() + path = self.config.get('bot', 'tmp_folder') + url_hash + ".mp3" + ydl_opts = { + 'format': 'bestaudio/best', + 'outtmpl': path, + 'noplaylist': True, + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': 'mp3', + 'preferredquality': '192', + }] + } + video_title = "" + with youtube_dl.YoutubeDL(ydl_opts) as ydl: + for i in range(2): + try: + info_dict = ydl.extract_info(url, download=False) + video_title = info_dict['title'] + ydl.download([url]) + except youtube_dl.utils.DownloadError: + pass + else: + break + return path, video_title def loop(self): - while not self.exit: + raw_music = "" + while not self.exit and self.mumble.isAlive(): - while self.mumble.sound_output.get_buffer_size() > 0.5: + while self.mumble.sound_output.get_buffer_size() > 0.5 and not self.exit: time.sleep(0.01) if self.thread: raw_music = self.thread.stdout.read(480) @@ -173,7 +252,7 @@ class MumbleBot: if (self.thread is None or not raw_music) and len(var.playlist) != 0: var.current_music = var.playlist[0] var.playlist.pop(0) - self.launch_play_file() + self.launch_next() while self.mumble.sound_output.get_buffer_size() > 0: time.sleep(0.01) diff --git a/variables.py b/variables.py index f0396d3..f7eb8ce 100644 --- a/variables.py +++ b/variables.py @@ -1,5 +1,5 @@ -current_music = "" +current_music = ("", "") playlist = [] user = "" music_folder = "" -is_proxified = False \ No newline at end of file +is_proxified = False