add Playlist class

This commit is contained in:
Terry Geng 2020-02-04 14:30:03 +08:00
parent 6ea0ac8fe5
commit 30879db7b8
4 changed files with 166 additions and 100 deletions

View File

@ -1,6 +1,65 @@
import youtube_dl import youtube_dl
import variables as var import variables as var
class PlayList:
playlist = []
current_index = 0
def append(self, item):
self.playlist.append(item)
def length(self):
return len(self.playlist)
def extend(self, items):
self.playlist.extend(items)
def next(self):
if len(self.playlist) == 0:
return False
self.current_index = self.next_index()
return self.playlist[self.current_index]
def update(self, item, index=-1):
if index == -1:
index = self.current_index
self.playlist[index] = item
def remove(self, index=-1):
if index > len(self.playlist) - 1:
return False
if index == -1:
index = self.current_index
del self.playlist[index]
if self.current_index <= index:
self.next()
def current_item(self):
return self.playlist[self.current_index]
def next_index(self):
if len(self.playlist) == 0:
return False
if self.current_index < len(self.playlist) - 1:
return self.current_index + 1
else:
return 0
def next_item(self):
if len(self.playlist) == 0:
return False
return self.playlist[self.next_index()]
def clear(self):
self.playlist = []
self.current_index = 0
def get_playlist_info(url, start_index=0, user=""): def get_playlist_info(url, start_index=0, user=""):
ydl_opts = { ydl_opts = {
@ -41,15 +100,15 @@ def get_music_info(index=0):
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 = ydl.extract_info(var.playlist[0]['url'], download=False) info = ydl.extract_info(var.playlist.playlist[index]['url'], download=False)
# Check if the Duration is longer than the config # Check if the Duration is longer than the config
if var.playlist[0]['current_index'] == index: if var.playlist[index]['current_index'] == index:
var.playlist[0]['current_duration'] = info['entries'][0]['duration'] / 60 var.playlist[index]['current_duration'] = info['entries'][0]['duration'] / 60
var.playlist[0]['current_title'] = info['entries'][0]['title'] var.playlist[index]['current_title'] = info['entries'][0]['title']
# Check if the Duration of the next music is longer than the config (async download) # Check if the Duration of the next music is longer than the config (async download)
elif var.playlist[0]['current_index'] == index - 1: elif var.playlist[index]['current_index'] == index - 1:
var.playlist[0]['next_duration'] = info['entries'][0]['duration'] / 60 var.playlist[index]['next_duration'] = info['entries'][0]['duration'] / 60
var.playlist[0]['next_title'] = info['entries'][0]['title'] var.playlist[index]['next_title'] = info['entries'][0]['title']
except youtube_dl.utils.DownloadError: except youtube_dl.utils.DownloadError:
pass pass
else: else:

View File

@ -2,22 +2,22 @@ import youtube_dl
import variables as var import variables as var
def get_url_info(index=-1): def get_url_info(music):
ydl_opts = { ydl_opts = {
'noplaylist': True 'noplaylist': True
} }
var.playlist[index]['duration'] = 0 music['duration'] = 0
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:
print(var.playlist) print(var.playlist)
info = ydl.extract_info(var.playlist[index]['url'], download=False) info = ydl.extract_info(music['url'], download=False)
var.playlist[index]['duration'] = info['duration'] / 60 music['duration'] = info['duration'] / 60
var.playlist[index]['title'] = info['title'] music['title'] = info['title']
except youtube_dl.utils.DownloadError: except youtube_dl.utils.DownloadError:
pass pass
except KeyError: except KeyError:
return True return music
else: else:
return True return music
return False return False

View File

@ -28,6 +28,7 @@ import media.playlist
import media.radio import media.radio
import media.system import media.system
from librb import radiobrowser from librb import radiobrowser
from media.playlist import PlayList
""" """
FORMAT OF A MUSIC INTO THE PLAYLIST FORMAT OF A MUSIC INTO THE PLAYLIST
@ -68,23 +69,29 @@ class MumbleBot:
self.channel = args.channel self.channel = args.channel
# Set specific format for the log root = logging.getLogger()
FORMAT = '%(asctime)s: %(message)s' formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
loglevel = logging.INFO root.setLevel(logging.INFO)
logfile = var.config.get('bot', 'logfile')
handler = None
if logfile:
handler = logging.FileHandler(logfile)
else:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
root.addHandler(handler)
if args.verbose: if args.verbose:
loglevel = logging.DEBUG root.setLevel(logging.DEBUG)
logging.debug("Starting in DEBUG loglevel") logging.debug("Starting in DEBUG loglevel")
elif args.quiet: elif args.quiet:
loglevel = logging.ERROR root.setLevel(logging.ERROR)
logging.error("Starting in ERROR loglevel") logging.error("Starting in ERROR loglevel")
logfile = var.config.get('bot', 'logfile')
if logfile:
logging.basicConfig(filename=logfile,format=FORMAT, level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S')
else:
logging.basicConfig(format=FORMAT, level=loglevel, datefmt='%Y-%m-%d %H:%M:%S')
# the playlist is... a list (Surprise !!) var.playlist = PlayList()
var.playlist = []
var.user = args.user var.user = args.user
var.music_folder = var.config.get('bot', 'music_folder') var.music_folder = var.config.get('bot', 'music_folder')
@ -306,27 +313,25 @@ class MumbleBot:
'url': self.get_url_from_input(parameter), 'url': self.get_url_from_input(parameter),
'user': user, 'user': user,
'ready': 'validation'} 'ready': 'validation'}
var.playlist.append(music)
if media.url.get_url_info(): if media.url.get_url_info():
if var.playlist[-1]['duration'] > var.config.getint('bot', 'max_track_duration'): if music['duration'] > var.config.getint('bot', 'max_track_duration'):
var.playlist.pop()
self.send_msg(var.config.get( self.send_msg(var.config.get(
'strings', 'too_long'), text) 'strings', 'too_long'), text)
else: else:
for i in var.db.options("url_ban"): for i in var.db.options("url_ban"):
if var.playlist[-1]['url'] == i: if music['url'] == i:
self.mumble.users[text.actor].send_text_message( self.mumble.users[text.actor].send_text_message(
var.config.get('strings', 'url_ban')) var.config.get('strings', 'url_ban'))
var.playlist.pop()
return return
var.playlist[-1]['ready'] = "no" music['ready'] = "no"
var.playlist.append(music)
self.async_download_next() self.async_download_next()
else: else:
var.playlist.pop()
self.send_msg(var.config.get( self.send_msg(var.config.get(
'strings', 'unable_download'), text) '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 # if you want to start the playlist at a specific index offset = 1 # if you want to start the playlist at a specific index
try: try:
@ -500,32 +505,33 @@ class MumbleBot:
'strings', 'current_volume') % int(self.volume * 100), text) '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 len(var.playlist) > 0: if len(var.playlist.playlist) > 0:
source = var.playlist[0]["type"] current_music = var.playlist.current_item()
source = current_music["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.radio.get_radio_title( title=media.radio.get_radio_title(
var.playlist[0]["url"]), current_music["url"]),
url=var.playlist[0]["title"], url=current_music["title"],
user=var.playlist[0]["user"] user=current_music["user"]
) )
elif source == "url" and 'from_playlist' in var.playlist[0]: elif source == "url" and 'from_playlist' in current_music:
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.playlist[0]["title"], title=current_music["title"],
url=var.playlist[0]["playlist_url"], url=current_music["playlist_url"],
playlist=var.playlist[0]["playlist_title"], playlist=current_music["playlist_title"],
user=var.playlist[0]["user"] user=current_music["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.playlist[0]["title"], title=current_music["title"],
url=var.playlist[0]["url"], url=current_music["url"],
user=var.playlist[0]["user"] user=current_music["user"]
) )
elif source == "file": elif source == "file":
reply = "[file] {title} by {user}".format( reply = "[file] {title} by {user}".format(
title=var.playlist[0]["title"], title=current_music["title"],
user=var.playlist[0]["user"]) user=current_music["user"])
else: else:
reply = "ERROR" reply = "ERROR"
logging.error(var.playlist) logging.error(var.playlist)
@ -537,7 +543,7 @@ class MumbleBot:
elif command == var.config.get('command', 'skip'): elif command == var.config.get('command', 'skip'):
# Allow to remove specific music into the queue with a number # Allow to remove specific music into the queue with a number
if parameter is not None and parameter.isdigit() and int(parameter) > 0: if parameter is not None and parameter.isdigit() and int(parameter) > 0:
if int(parameter) < len(var.playlist): if int(parameter) < len(var.playlist.playlist):
removed = var.playlist.pop(int(parameter)) removed = var.playlist.pop(int(parameter))
# the Title isn't here if the music wasn't downloaded # the Title isn't here if the music wasn't downloaded
@ -564,20 +570,20 @@ class MumbleBot:
self.send_msg(var.config.get('strings', 'no_file'), text) 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) <= 1: if len(var.playlist.playlist) <= 1:
msg = var.config.get('strings', 'queue_empty') msg = var.config.get('strings', 'queue_empty')
else: else:
msg = var.config.get( msg = var.config.get(
'strings', 'queue_contents') + '<br />' 'strings', 'queue_contents') + '<br />'
i = 1 i = 1
for value in var.playlist[1:]: for value in var.playlist.playlist:
msg += '[{}] ({}) {}<br />'.format(i, value['type'], value['title'] if 'title' in value else value['url']) msg += '[{}] ({}) {}<br />'.format(i, value['type'], value['title'] if 'title' in value else value['url'])
i += 1 i += 1
self.send_msg(msg, text) self.send_msg(msg, text)
elif command == var.config.get('command', 'repeat'): elif command == var.config.get('command', 'repeat'):
var.playlist.append(var.playlist[0]) var.playlist.append(var.playlist.current_item())
else: else:
self.mumble.users[text.actor].send_text_message( self.mumble.users[text.actor].send_text_message(
@ -594,41 +600,38 @@ class MumbleBot:
@staticmethod @staticmethod
def next(): def next():
logging.debug("Next into the queue") logging.debug("Next into the queue")
if len(var.playlist) > 1: return var.playlist.next()
var.playlist.pop(0)
return True
elif len(var.playlist) == 1:
var.playlist.pop(0)
return False
else:
return False
def launch_music(self): def launch_music(self):
uri = "" uri = ""
logging.debug("launch_music asked" + str(var.playlist[0])) music = var.playlist.next()
if var.playlist[0]["type"] == "url": logging.debug("launch_music asked" + str(music))
if music["type"] == "url":
# Delete older music is the tmp folder is too big # Delete older music is the tmp folder is too big
media.system.clear_tmp_folder(var.config.get( media.system.clear_tmp_folder(var.config.get(
'bot', 'tmp_folder'), var.config.getint('bot', 'tmp_folder_max_size')) 'bot', 'tmp_folder'), var.config.getint('bot', 'tmp_folder_max_size'))
# Check if the music is ready to be played # Check if the music is ready to be played
if var.playlist[0]["ready"] == "downloading": if music["ready"] == "downloading":
return return
elif var.playlist[0]["ready"] != "yes": elif music["ready"] != "yes":
logging.info("Current music wasn't ready, Downloading...") logging.info("Current music wasn't ready, Downloading...")
self.download_music(index=0) self.download_music(music)
if music == False:
var.playlist.remove()
# get the Path # get the Path
uri = var.playlist[0]['path'] uri = music['path']
if os.path.isfile(uri): if os.path.isfile(uri):
audio = EasyID3(uri) audio = EasyID3(uri)
print(audio["title"])
title = "" title = ""
if audio["title"]: if audio["title"]:
# take the title from the file tag # take the title from the file tag
title = audio["title"][0] title = audio["title"][0]
# Remove .mp3 and add .jpg # Remove .mp3 and add .jpg
path_thumbnail = var.playlist[0]['path'][:-4] + '.jpg' path_thumbnail = music['path'][:-4] + '.jpg'
thumbnail_html = "" thumbnail_html = ""
if os.path.isfile(path_thumbnail): if os.path.isfile(path_thumbnail):
# Create the image message # Create the image message
@ -648,14 +651,14 @@ class MumbleBot:
logging.error("Error with the path during launch_music") logging.error("Error with the path during launch_music")
pass pass
elif var.playlist[0]["type"] == "file": elif music["type"] == "file":
uri = var.config.get('bot', 'music_folder') + \ uri = var.config.get('bot', 'music_folder') + \
var.playlist[0]["path"] var.playlist.current_item()["path"]
elif var.playlist[0]["type"] == "radio": elif music["type"] == "radio":
uri = var.playlist[0]["url"] uri = music["url"]
title = media.radio.get_radio_server_description(uri) title = media.radio.get_radio_server_description(uri)
var.playlist[0]["title"] = title music["title"] = title
if var.config.getboolean('bot', 'announce_current_music'): if var.config.getboolean('bot', 'announce_current_music'):
self.send_msg(var.config.get('strings', 'now_playing') % self.send_msg(var.config.get('strings', 'now_playing') %
(title, "URL : " + uri)) (title, "URL : " + uri))
@ -672,36 +675,40 @@ class MumbleBot:
self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480) self.thread = sp.Popen(command, stdout=sp.PIPE, bufsize=480)
self.is_playing = True self.is_playing = True
def download_music(self, index): def download_music(self, index=-1):
if var.playlist[index]['type'] == 'url' and var.playlist[index]['ready'] == "validation": if index == -1:
if media.url.get_url_info(index=index): index = var.playlist.current_index
if var.playlist[index]['duration'] > var.config.getint('bot', 'max_track_duration'):
music = var.playlist.playlist[index]
if music['type'] == 'url' and music['ready'] == "validation":
music = media.url.get_url_info(music)
if music:
if music['duration'] > var.config.getint('bot', 'max_track_duration'):
# Check the length, useful in case of playlist, it wasn't checked before) # Check the length, useful in case of playlist, it wasn't checked before)
var.playlist.pop()
logging.info( logging.info(
"the music " + var.playlist[index]["url"] + " has a duration of " + var.playlist[index]['duration'] + "s -- too long") "the music " + music["url"] + " has a duration of " + music['duration'] + "s -- too long")
self.send_msg(var.config.get('strings', 'too_long')) self.send_msg(var.config.get('strings', 'too_long'))
return return False
else: else:
var.playlist[index]['ready'] = "no" music['ready'] = "no"
else: else:
var.playlist.pop(index)
logging.error("Error while fetching info from the URL") logging.error("Error while fetching info from the URL")
self.send_msg(var.config.get('strings', 'unable_download')) self.send_msg(var.config.get('strings', 'unable_download'))
return False
if var.playlist[index]['type'] == 'url' and var.playlist[index]['ready'] == "no": if music['type'] == 'url' and music['ready'] == "no":
# download the music # download the music
var.playlist[index]['ready'] = "downloading" music['ready'] = "downloading"
logging.debug("Download index:" + str(index)) url = music['url']
logging.debug(var.playlist[index])
url = var.playlist[index]['url']
url_hash = hashlib.md5(url.encode()).hexdigest() url_hash = hashlib.md5(url.encode()).hexdigest()
logging.debug("Download url:" + url)
logging.debug(music)
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")
var.playlist[index]['path'] = mp3 music['path'] = mp3
# if os.path.isfile(mp3): # if os.path.isfile(mp3):
# audio = EasyID3(mp3) # audio = EasyID3(mp3)
@ -721,29 +728,29 @@ class MumbleBot:
{'key': 'FFmpegMetadata'}] {'key': 'FFmpegMetadata'}]
} }
self.send_msg(var.config.get( self.send_msg(var.config.get(
'strings', "download_in_progress") % var.playlist[index]['title']) 'strings', "download_in_progress") % music['title'])
logging.info("Information before start downloading :" + logging.info("Information before start downloading :" +
str(var.playlist[index])) str(music))
with youtube_dl.YoutubeDL(ydl_opts) as ydl: with youtube_dl.YoutubeDL(ydl_opts) as ydl:
for i in range(2): # Always try 2 times for i in range(2): # Always try 2 times
try: try:
ydl.extract_info(url) ydl.extract_info(url)
if 'ready' in var.playlist[index] and var.playlist[index]['ready'] == "downloading": if 'ready' in music and music['ready'] == "downloading":
var.playlist[index]['ready'] = "yes" music['ready'] = "yes"
except youtube_dl.utils.DownloadError: except youtube_dl.utils.DownloadError:
pass pass
else: else:
break break
return var.playlist.playlist[index] = music
def async_download_next(self): def async_download_next(self):
# Function start if the next music isn't ready # Function start if the next music isn't ready
# Do nothing in case the next music is already downloaded # Do nothing in case the next music is already downloaded
logging.info("Async download next asked") logging.info("Async download next asked")
if len(var.playlist) > 1 and var.playlist[1]['type'] == 'url' and var.playlist[1]['ready'] in ["no", "validation"]: if len(var.playlist.playlist) > 1 and var.playlist.next_item()['type'] == 'url' and var.playlist.next_item()['ready'] in ["no", "validation"]:
th = threading.Thread( th = threading.Thread(
target=self.download_music, kwargs={'index': 1}) target=self.download_music, kwargs={'index': var.playlist.next_index()})
else: else:
return return
logging.info("Start downloading next in thread") logging.info("Start downloading next in thread")
@ -788,9 +795,9 @@ class MumbleBot:
# get next music # get next music
self.is_playing = False self.is_playing = False
self.next() self.next()
if len(var.playlist) > 0: if len(var.playlist.playlist) > 0:
if var.playlist[0]['type'] in ['radio', 'file'] \ if var.playlist.current_item()['type'] in ['radio', 'file'] \
or (var.playlist[0]['type'] == 'url' and var.playlist[0]['ready'] not in ['validation', 'downloading']): or (var.playlist.current_item()['type'] == 'url' and var.playlist.current_item()['ready'] not in ['validation', 'downloading']):
# Check if the music can be start before launch the music # Check if the music can be start before launch the music
self.launch_music() self.launch_music()
self.async_download_next() self.async_download_next()
@ -809,7 +816,7 @@ class MumbleBot:
if self.thread: if self.thread:
self.thread.kill() self.thread.kill()
self.thread = None self.thread = None
var.playlist = [] var.playlist.clear()
self.is_playing = False self.is_playing = False
def set_comment(self): def set_comment(self):

View File

@ -1,5 +1,5 @@
current_music = None current_music = None
playlist = [] playlist = None
user = "" user = ""
music_folder = "" music_folder = ""
is_proxified = False is_proxified = False