new playlist format
This commit is contained in:
parent
b4ae33602e
commit
6aca72eaee
@ -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 %)
|
||||||
|
5
db.ini
5
db.ini
@ -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
0
media/__init__.py
Normal file
0
media/file.py
Normal file
0
media/file.py
Normal file
0
media/playlist.py
Normal file
0
media/playlist.py
Normal file
71
media/radio.py
Normal file
71
media/radio.py
Normal 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
39
media/system.py
Normal 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
17
media/url.py
Normal 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
|
361
mumbleBot.py
361
mumbleBot.py
@ -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', '-']
|
||||||
|
|
||||||
|
if var.playlist[0]["type"] == "readio":
|
||||||
|
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
|
||||||
|
|
||||||
@staticmethod
|
def download_music(self, index, next_with_playlist=False):
|
||||||
def download_music(url, index=None):
|
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
37
playlist.txt
Normal 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
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user