new playlist format

This commit is contained in:
Azlux 2018-11-13 15:48:09 +01:00
parent b4ae33602e
commit 6aca72eaee
10 changed files with 357 additions and 185 deletions

View File

@ -27,6 +27,9 @@ allow_private_message = True
# Maximum track played when a playlist is added. # Maximum track played when a playlist is added.
max_track_playlist = 20 max_track_playlist = 20
# Maximum music duration (minutes)
max_track_duration = 60
[webinterface] [webinterface]
enabled = False enabled = False
is_web_proxified = True is_web_proxified = True
@ -42,7 +45,7 @@ play_playlist = playlist
help = help help = help
stop = stop stop = stop
list = list list = list
next = skip skip = skip
current_music = np current_music = np
volume = v volume = v
kill = kill kill = kill
@ -68,12 +71,17 @@ not_playing = No music right now
bad_file = Bad file requested bad_file = Bad file requested
no_file = File not found no_file = File not found
bad_url = Bad URL requested bad_url = Bad URL requested
unable_download = Error while downloading the music...
multiple_matches = Track not found! Possible candidates: multiple_matches = Track not found! Possible candidates:
queue_contents = The next items in the queue are: queue_contents = The next items in the queue are:
queue_empty = No more music in the playlist! queue_empty = No more music in the playlist!
now_playing = Now playing %s<br />%s now_playing = Now playing %s<br />%s
not_in_my_channel = You're not in my channel, command refused ! not_in_my_channel = You're not in my channel, command refused !
pm_not_allowed = Private message aren't allowed. pm_not_allowed = Private message aren't allowed.
too_long = This music is too long, skipping !
download_in_progress = Download of %s in progress
no_possible = it's not possible to do that
removing_item = Removing entry %s from queue
help = Command available: help = Command available:
<br />!file [path] <br />!file [path]
@ -82,7 +90,7 @@ help = Command available:
<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
<br />!next - jump to the next music of the playlist <br />!skip - jump to the next music of the playlist (of remove the X items if you add a number)
<br />!stop - stop and clear the playlist <br />!stop - stop and clear the playlist
<br />!oust - stop + Go to default channel <br />!oust - stop + Go to default channel
<br />!v - get or change the volume (in %) <br />!v - get or change the volume (in %)

3
db.ini
View File

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

0
media/__init__.py Normal file
View File

0
media/file.py Normal file
View File

0
media/playlist.py Normal file
View File

71
media/radio.py Normal file
View File

@ -0,0 +1,71 @@
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.info("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)
source = data['icestats']['source']
if type(source) is list:
source = source[0]
title_server = source['server_name'] + ' - ' + source['server_description']
logging.info("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.info(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 'Unable to get the music title'

39
media/system.py Normal file
View File

@ -0,0 +1,39 @@
import logging
import os
def get_size_folder(path):
folder_size = 0
for (path, dirs, files) in os.walk(path):
for file in files:
filename = os.path.join(path, file)
folder_size += os.path.getsize(filename)
return int(folder_size / (1024 * 1024))
def clear_tmp_folder(path, size):
if size == -1:
return
elif size == 0:
for (path, dirs, files) in os.walk(path):
for file in files:
filename = os.path.join(path, file)
os.remove(filename)
else:
if get_size_folder(path=path) > size:
all_files = ""
for (path, dirs, files) in os.walk(path):
all_files = [os.path.join(path, file) for file in files]
all_files.sort(key=lambda x: os.path.getmtime(x))
size_tp = 0
print(all_files)
for idx, file in enumerate(all_files):
size_tp += os.path.getsize(file)
if int(size_tp / (1024 * 1024)) > size:
logging.info("Cleaning tmp folder")
to_remove = all_files[:idx]
print(to_remove)
for f in to_remove:
logging.debug("Removing " + f)
os.remove(os.path.join(path, f))
return

17
media/url.py Normal file
View File

@ -0,0 +1,17 @@
import youtube_dl
import variables as var
def get_url_info():
with youtube_dl.YoutubeDL() as ydl:
for i in range(2):
try:
print(var.playlist)
info = ydl.extract_info(var.playlist[-1]['url'], download=False)
var.playlist[-1]['duration'] = info['duration'] / 60
var.playlist[-1]['title'] = info['title']
except youtube_dl.utils.DownloadError:
pass
else:
return True
return False

View File

@ -14,13 +14,18 @@ import interface
import variables as var import variables as var
import hashlib import hashlib
import youtube_dl import youtube_dl
import media
import logging import logging
import util import util
import base64 import base64
from PIL import Image from PIL import Image
from io import BytesIO from io import BytesIO
from mutagen.easyid3 import EasyID3 from mutagen.easyid3 import EasyID3
import re
import media.url
import media.file
import media.playlist
import media.radio
import media.system
class MumbleBot: class MumbleBot:
@ -28,7 +33,6 @@ class MumbleBot:
signal.signal(signal.SIGINT, self.ctrl_caught) signal.signal(signal.SIGINT, self.ctrl_caught)
self.volume = var.config.getfloat('bot', 'volume') self.volume = var.config.getfloat('bot', 'volume')
self.channel = args.channel self.channel = args.channel
var.current_music = {}
FORMAT = '%(asctime)s: %(message)s' FORMAT = '%(asctime)s: %(message)s'
if args.quiet: if args.quiet:
@ -36,25 +40,6 @@ class MumbleBot:
else: else:
logging.basicConfig(format=FORMAT, level=logging.DEBUG, datefmt='%Y-%m-%d %H:%M:%S') logging.basicConfig(format=FORMAT, level=logging.DEBUG, datefmt='%Y-%m-%d %H:%M:%S')
######
## Format of the Playlist :
## [("<type>","<path/url>")]
## types : file, radio, url, is_playlist, number_music_to_play
######
######
## Format of the current_music variable
# var.current_music = { "type" : str,
# "path" : str, # path of the file to play
# "url" : str # url to download
# "title" : str,
# "user" : str,
# "is_playlist": boolean,
# "number_track_to_play": int, # FOR PLAYLIST ONLY
# "start_index" : int, # FOR PLAYLIST ONLY
# "current_index" : int} # FOR PLAYLIST ONLY
# len(var.current_music) = 6
var.playlist = [] var.playlist = []
var.user = args.user var.user = args.user
@ -63,7 +48,7 @@ class MumbleBot:
self.exit = False self.exit = False
self.nb_exit = 0 self.nb_exit = 0
self.thread = None self.thread = None
self.playing = False self.is_playing = False
if var.config.getboolean("webinterface", "enabled"): if var.config.getboolean("webinterface", "enabled"):
wi_addr = var.config.get("webinterface", "listening_addr") wi_addr = var.config.get("webinterface", "listening_addr")
@ -107,7 +92,7 @@ class MumbleBot:
self.loop() self.loop()
def ctrl_caught(self, signal, frame): def ctrl_caught(self, signal, frame):
logging.info("\nSIGINT caught, quitting") logging.info("\nSIGINT caught, quitting, {} more to kill".format(2 - self.nb_exit))
self.exit = True self.exit = True
self.stop() self.stop()
if self.nb_exit > 1: if self.nb_exit > 1:
@ -150,25 +135,46 @@ class MumbleBot:
if path.startswith(music_folder): if path.startswith(music_folder):
if os.path.isfile(path): if os.path.isfile(path):
filename = path.replace(music_folder, '') filename = path.replace(music_folder, '')
var.playlist.append(["file", filename, user]) music = {'type': 'file',
'path': filename,
'user': user}
var.playlist.append(music)
else: else:
# try to do a partial match # try to do a partial match
matches = [file for file in util.get_recursive_filelist_sorted(music_folder) if parameter.lower() in file.lower()] matches = [file for file in util.get_recursive_filelist_sorted(music_folder) if parameter.lower() in file.lower()]
if len(matches) == 0: if len(matches) == 0:
self.mumble.users[text.actor].send_message(var.config.get('strings', 'no_file')) self.send_msg(var.config.get('strings', 'no_file'), text)
elif len(matches) == 1: elif len(matches) == 1:
var.playlist.append(["file", matches[0], user]) music = {'type': 'file',
'path': matches[0],
'user': user}
var.playlist.append(music)
else: else:
msg = var.config.get('strings', 'multiple_matches') + '<br />' msg = var.config.get('strings', 'multiple_matches') + '<br />'
msg += '<br />'.join(matches) msg += '<br />'.join(matches)
self.mumble.users[text.actor].send_message(msg) self.send_msg(msg, text)
else: else:
self.mumble.users[text.actor].send_message(var.config.get('strings', 'bad_file')) self.send_msg(var.config.get('strings', 'bad_file'), text)
self.async_download_next() 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])
self.async_download_next() music = {'type': 'url',
'url': self.get_url_from_input(parameter),
'user': user,
'ready': 'validation'}
var.playlist.append(music)
if media.url.get_url_info():
if var.playlist[-1]['duration'] > var.config.getint('bot', 'max_track_duration'):
var.playlist.pop()
self.send_msg(var.config.get('strings', 'too_long'), text)
else:
var.playlist[-1]['ready'] = "no"
self.async_download_next()
else:
var.playlist.pop()
self.send_msg(var.config.get('strings', 'unable_download'), text)
elif command == var.config.get('command', 'play_playlist') and parameter: elif command == var.config.get('command', 'play_playlist') and parameter:
offset = 1 offset = 1
@ -176,17 +182,26 @@ class MumbleBot:
offset = int(parameter.split(" ")[-1]) offset = int(parameter.split(" ")[-1])
except ValueError: except ValueError:
pass pass
var.playlist.append(["playlist", parameter, user, var.config.getint('bot', 'max_track_playlist'), offset]) music = {'type': 'playlist',
'url': self.get_url_from_input(parameter),
'user': user,
'max_track_allowed': var.config.getint('bot', 'max_track_playlist'),
'current_index': 1,
'start_index': offset}
var.playlist.append(music)
self.async_download_next() 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]) music = {'type': 'radio',
'url': self.get_url_from_input(parameter),
'user': user}
var.playlist.append(music)
self.async_download_next() 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(var.config.get('strings', 'help'), text)
elif command == var.config.get('command', 'stop'): elif command == var.config.get('command', 'stop'):
self.stop() self.stop()
@ -208,9 +223,9 @@ class MumbleBot:
else: else:
msg += "Update done : " + tp.split('Successfully installed')[1] msg += "Update done : " + tp.split('Successfully installed')[1]
if 'up-to-date' not in sp.check_output(['/usr/bin/env', 'git', 'pull']).decode(): if 'up-to-date' not in sp.check_output(['/usr/bin/env', 'git', 'pull']).decode():
msg += "<br /> Botamusique is up-to-date" msg += "<br /> I'm up-to-date"
else: else:
msg += "<br /> Botamusique have available update" msg += "<br /> I have available updates, need to do it manually"
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', 'not_admin')) self.mumble.users[text.actor].send_message(var.config.get('strings', 'not_admin'))
@ -223,56 +238,62 @@ class MumbleBot:
elif command == var.config.get('command', 'volume'): elif command == var.config.get('command', 'volume'):
if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100: if parameter is not None and parameter.isdigit() and 0 <= int(parameter) <= 100:
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(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']), text)
var.db.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(var.config.get('strings', 'current_volume') % int(self.volume * 100), text)
elif command == var.config.get('command', 'current_music'): elif command == var.config.get('command', 'current_music'):
if var.current_music: if len(var.playlist) > 0:
source = var.current_music["type"] source = var.playlist[0]["type"]
if source == "radio": if source == "radio":
reply = "[radio] {title} on {url} by {user}".format( reply = "[radio] {title} on {url} by {user}".format(
title=media.get_radio_title(var.current_music["path"]), title=media.radio.get_radio_title(var.playlist[0]["url"]),
url=var.current_music["title"], url=var.playlist[0]["title"],
user=var.current_music["user"] user=var.playlist[0]["user"]
) )
elif source == "url": elif source == "url":
reply = "[url] {title} (<a href=\"{url}\">{url}</a>) by {user}".format( reply = "[url] {title} (<a href=\"{url}\">{url}</a>) by {user}".format(
title=var.current_music["title"], title=var.playlist[0]["title"],
url=var.current_music["path"], url=var.playlist[0]["url"],
user=var.current_music["user"] user=var.playlist[0]["user"]
) )
elif source == "file": elif source == "file":
reply = "[file] {title} by {user}".format( reply = "[file] {title} by {user}".format(
title=var.current_music["title"], title=var.playlist[0]["title"],
user=var.current_music["user"]) user=var.playlist[0]["user"])
elif source == "playlist": elif source == "playlist":
reply = "[playlist] {title} (from the playlist <a href=\"{url}\">{playlist}</a> by {user}".format( reply = "[playlist] {title} (from the playlist <a href=\"{url}\">{playlist}</a> by {user}".format(
title=var.current_music["title"], title=var.playlist[0]["title"],
url=var.current_music["path"], url=var.playlist[0]["url"],
playlist=var.current_music["playlist_title"], playlist=var.playlist[0]["playlist_title"],
user=var.current_music["user"] user=var.playlist[0]["user"]
) )
else: else:
reply = "(?)[{}] {} {} by {}".format( reply = "(?)[{}] {} {} by {}".format(
var.current_music["type"], var.playlist[0]["type"],
var.current_music["path"], var.playlist[0]["url"] if 'url' in var.playlist[0] else var.playlist[0]["path"],
var.current_music["title"], var.playlist[0]["title"],
var.current_music["user"] var.playlist[0]["user"]
) )
else: else:
reply = var.config.get('strings', 'not_playing') reply = var.config.get('strings', 'not_playing')
self.mumble.users[text.actor].send_message(reply) self.send_msg(reply, text)
elif command == var.config.get('command', 'next'): elif command == var.config.get('command', 'skip'):
if self.get_next(): if parameter is not None and parameter.isdigit() and int(parameter) > 0:
self.launch_next() if int(parameter) < len(var.playlist):
removed = var.playlist.pop(int(parameter))
self.send_msg(var.config.get('strings', 'removing_item') % (removed['title'] if 'title' in removed else removed['url']), text)
else:
self.send_msg(var.config.get('strings', 'no_possible'), text)
elif self.next():
self.launch_music()
self.async_download_next() self.async_download_next()
else: else:
self.mumble.users[text.actor].send_message(var.config.get('strings', 'queue_empty')) self.send_msg(var.config.get('strings', 'queue_empty'), text)
self.stop() self.stop()
elif command == var.config.get('command', 'list'): elif command == var.config.get('command', 'list'):
@ -280,36 +301,28 @@ class MumbleBot:
files = util.get_recursive_filelist_sorted(folder_path) files = util.get_recursive_filelist_sorted(folder_path)
if files: if files:
self.mumble.users[text.actor].send_message('<br>'.join(files)) self.send_msg('<br>'.join(files), text)
else: else:
self.mumble.users[text.actor].send_message(var.config.get('strings', 'no_file')) self.send_msg(var.config.get('strings', 'no_file'), text)
elif command == var.config.get('command', 'queue'): elif command == var.config.get('command', 'queue'):
if len(var.playlist) == 0: if len(var.playlist) <= 1:
msg = var.config.get('strings', 'queue_empty') msg = var.config.get('strings', 'queue_empty')
else: else:
msg = var.config.get('strings', 'queue_contents') + '<br />' msg = var.config.get('strings', 'queue_contents') + '<br />'
for (music_type, path, user) in var.playlist: i = 1
msg += '({}) {}<br />'.format(music_type, path) for value in var.playlist[1:]:
msg += '[{}] ({}) {}<br />'.format(i, value['type'], value['title'] if 'title' in value else value['url'])
i += 1
self.send_msg_channel(msg) self.send_msg(msg, text)
elif command == var.config.get('command', 'repeat'): elif command == var.config.get('command', 'repeat'):
var.playlist.append([var.current_music["type"], var.current_music["path"], var.current_music["user"]]) var.playlist.append([var.playlist[0]["type"], var.playlist[0]["path"], var.playlist[0]["user"]])
else: else:
self.mumble.users[text.actor].send_message(var.config.get('strings', 'bad_command')) self.mumble.users[text.actor].send_message(var.config.get('strings', 'bad_command'))
def launch_play_file(self, path):
self.stop()
if var.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)
self.playing = True
@staticmethod @staticmethod
def is_admin(user): def is_admin(user):
list_admin = var.config.get('bot', 'admin').split(';') list_admin = var.config.get('bot', 'admin').split(';')
@ -319,60 +332,38 @@ class MumbleBot:
return False return False
@staticmethod @staticmethod
def get_next(): def next():
# Return True is next is possible # Return True is next is possible
if var.current_music and var.current_music['type'] == "playlist": if len(var.playlist) > 0 and var.playlist[0]['type'] == "playlist":
var.current_music['current_index'] += 1 var.playlist[0]['current_index'] = var.playlist[0]['current_index'] + 1
if var.current_music['current_index'] <= (var.current_music['start_index'] + var.current_music['number_track_to_play']): if var.playlist[0]['current_index'] <= (var.playlist[0]['start_index'] + var.playlist[0]['max_track_allowed']):
return True return True
if not var.playlist: if len(var.playlist) > 1:
var.playlist.pop(0)
return True
elif len(var.playlist) == 1:
var.playlist.pop(0)
return False
else:
return False return False
if var.playlist[0][0] == "playlist": def launch_music(self):
var.current_music = {'type': var.playlist[0][0], uri = ""
'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):
path = ""
title = ""
var.next_downloaded = False var.next_downloaded = False
logging.debug(var.current_music) logging.debug(var.playlist)
if var.current_music["type"] == "url" or var.current_music["type"] == "playlist": if var.playlist[0]["type"] == "url" or var.playlist[0]["type"] == "playlist":
url = media.get_url(var.current_music["url"]) media.system.clear_tmp_folder(var.config.get('bot', 'tmp_folder'), var.config.getint('bot', 'tmp_folder_max_size'))
if not url: self.download_music(index=0)
return uri = var.playlist[0]['path']
if os.path.isfile(uri):
media.clear_tmp_folder(var.config.get('bot', 'tmp_folder'), var.config.getint('bot', 'tmp_folder_max_size')) audio = EasyID3(uri)
title = ""
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
if os.path.isfile(path):
audio = EasyID3(path)
if audio["title"]: if audio["title"]:
title = audio["title"][0] title = audio["title"][0]
path_thumbnail = var.config.get('bot', 'tmp_folder') + hashlib.md5(path.encode()).hexdigest() + '.jpg' path_thumbnail = var.config.get('bot', 'tmp_folder') + hashlib.md5(uri.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)
@ -384,54 +375,59 @@ class MumbleBot:
logging.debug(thumbnail_html) logging.debug(thumbnail_html)
if var.config.getboolean('bot', 'announce_current_music'): if var.config.getboolean('bot', 'announce_current_music'):
self.send_msg_channel(var.config.get('strings', 'now_playing') % (title, thumbnail_html)) self.send_msg(var.config.get('strings', 'now_playing') % (title, thumbnail_html))
else: else:
if var.current_music["type"] == "playlist": pass
var.current_music['current_index'] = var.current_music['number_track_to_play']
if self.get_next():
self.launch_next()
self.async_download_next()
elif var.current_music["type"] == "file": elif var.playlist[0]["type"] == "file":
path = var.config.get('bot', 'music_folder') + var.current_music["path"] uri = var.config.get('bot', 'music_folder') + var.playlist[0]["path"]
title = var.current_music["path"]
elif var.current_music["type"] == "radio": elif var.playlist[0]["type"] == "radio":
url = media.get_url(var.current_music["url"]) uri = var.playlist[0]["url"]
if not url: title = media.radio.get_radio_server_description(uri)
return var.playlist[0]["title"] = title
var.current_music["path"] = url
path = 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:
ffmpeg_debug = "warning" ffmpeg_debug = "warning"
command = ["ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i', path, '-ac', '1', '-f', 's16le', '-ar', '48000', '-'] print(var.playlist)
self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480) command = ["ffmpeg", '-v', ffmpeg_debug, '-nostdin', '-i', uri, '-ac', '1', '-f', 's16le', '-ar', '48000', '-']
@staticmethod if var.playlist[0]["type"] == "readio":
def download_music(url, index=None): command = ["ffmpeg", '-v', ffmpeg_debug,'-reconnect', '1', '-reconnect_at_eof', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '4294', '-nostdin', '-i', uri, '-ac', '1', '-f', 's16le', '-ar', '48000', '-']
self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480)
self.is_playing = True
def download_music(self, index, next_with_playlist=False):
url = var.playlist[index]['url']
url_hash = hashlib.md5(url.encode()).hexdigest() url_hash = hashlib.md5(url.encode()).hexdigest()
if index:
url_hash = url_hash + "-" + str(index) if var.playlist[index]['type'] == 'playlist':
url_hash = url_hash + "-" + str(var.playlist[index]['current_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): var.playlist[index]['path'] = mp3
audio = EasyID3(mp3) # if os.path.isfile(mp3):
video_title = audio["title"][0] # audio = EasyID3(mp3)
else: # var.playlist[index]['title'] = audio["title"][0]
if index: if var.playlist[index]['ready'] == "no":
var.playlist[index]['ready'] = "downloading"
self.send_msg(var.config.get('strings', "download_in_progress") % var.playlist[index]['title'])
if var.playlist[index]['type'] == 'playlist':
item = str(var.playlist[index]['current_index'])
if next_with_playlist:
item = str(var.playlist[index]['current_index'] + 1)
ydl_opts = { ydl_opts = {
'format': 'bestaudio/best', 'format': 'bestaudio/best',
'outtmpl': path, 'outtmpl': path,
'writethumbnail': True, 'writethumbnail': True,
'updatetime': False, 'updatetime': False,
'playlist_items': str(index), 'playlist_items': item,
'postprocessors': [{ 'postprocessors': [{
'key': 'FFmpegExtractAudio', 'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3', 'preferredcodec': 'mp3',
@ -451,42 +447,42 @@ class MumbleBot:
'preferredquality': '192'}, 'preferredquality': '192'},
{'key': 'FFmpegMetadata'}] {'key': 'FFmpegMetadata'}]
} }
video_title = "" print(ydl_opts)
with youtube_dl.YoutubeDL(ydl_opts) as ydl: with youtube_dl.YoutubeDL(ydl_opts) as ydl:
for i in range(2): for i in range(2):
try: try:
info_dict = ydl.extract_info(url) ydl.extract_info(url)
video_title = info_dict['title'] var.playlist[index]['ready'] = "yes"
except youtube_dl.utils.DownloadError: except youtube_dl.utils.DownloadError:
pass pass
else: else:
break break
return mp3, video_title return True
def async_download_next(self): def async_download_next(self):
if not var.next_downloaded: if not var.next_downloaded:
if len(var.playlist) > 0 and var.playlist[0]['type'] == 'playlist':
th = threading.Thread(target=self.download_music, kwargs={'index': 0, 'next_with_playlist': True})
elif len(var.playlist) > 1 and var.playlist[1]['type'] == 'url':
th = threading.Thread(target=self.download_music, kwargs={'index': 1})
else:
return
var.next_downloaded = True var.next_downloaded = True
logging.info("Start download in thread") logging.info("Start download in thread")
th = threading.Thread(target=self.download_next, args=())
th.daemon = True th.daemon = True
th.start() th.start()
def download_next(self): @staticmethod
if not var.current_music: def get_url_from_input(string):
return if string.startswith('http'):
return string
p = re.compile('href="(.+)"', re.IGNORECASE)
res = re.search(p, string)
if res:
return res.group(1)
else: else:
if var.current_music["type"] == "playlist": return False
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 = ""
@ -504,11 +500,12 @@ 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 self.get_next(): if self.is_playing:
self.launch_next() self.is_playing = False
self.next()
if len(var.playlist) > 0 and ('ready' not in var.playlist[0] or var.playlist[0]['ready'] != 'validation'):
self.launch_music()
self.async_download_next() self.async_download_next()
else:
var.current_music = None
while self.mumble.sound_output.get_buffer_size() > 0: while self.mumble.sound_output.get_buffer_size() > 0:
time.sleep(0.01) time.sleep(0.01)
@ -519,18 +516,20 @@ class MumbleBot:
def stop(self): def stop(self):
if self.thread: if self.thread:
var.current_music = None
self.thread.kill() self.thread.kill()
self.thread = None self.thread = None
var.playlist = [] var.playlist = []
self.is_playing = False
def set_comment(self): def set_comment(self):
self.mumble.users.myself.comment(var.config.get('bot', 'comment')) self.mumble.users.myself.comment(var.config.get('bot', 'comment'))
def send_msg_channel(self, msg, channel=None): def send_msg(self, msg, text=None):
if not channel: if not text.session:
channel = self.mumble.channels[self.mumble.users.myself['channel_id']] own_channel = self.mumble.channels[self.mumble.users.myself['channel_id']]
channel.send_text_message(msg) own_channel.send_text_message(msg)
else:
self.mumble.users[text.actor].send_message(msg)
def start_web_interface(addr, port): def start_web_interface(addr, port):

37
playlist.txt Normal file
View File

@ -0,0 +1,37 @@
type : url
url
title
path
duration
thundnail
user
ready (validation, no, downloading, yes)
type : playlist
url
path
playlist_title
nb_track_allowed
nb_track_playlist
max_track_allowed
start_index
current_index
current url
current_title
current_duration
current_thundnail
user
ready (validation, No, in progress, Yes)
type : radio
url
name
current_title
user
type : file
path
title
duration
user