From 78173bd68cacfc15bbe7455649ce49538f451f6e Mon Sep 17 00:00:00 2001 From: Mark van Alphen Date: Sun, 20 Aug 2017 20:15:23 +0200 Subject: [PATCH] [Audio] Add error handling on failed song load (#887) * Add error handling and user feedback to Audio cog * Remove verbose * Maybe? fix the OSError * Replace is with isinstance() * Attempt to force UTF-8 encoding * Fix unbound variable * Escape mass mentions on error output --- cogs/audio.py | 298 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 207 insertions(+), 91 deletions(-) diff --git a/cogs/audio.py b/cogs/audio.py index be1e582b2..bc4103d67 100644 --- a/cogs/audio.py +++ b/cogs/audio.py @@ -5,7 +5,7 @@ import os from random import shuffle, choice from cogs.utils.dataIO import dataIO from cogs.utils import checks -from cogs.utils.chat_formatting import pagify +from cogs.utils.chat_formatting import pagify, escape from urllib.parse import urlparse from __main__ import send_cmd_help, settings from json import JSONDecodeError @@ -18,6 +18,7 @@ import math import time import inspect import subprocess +from enum import Enum __author__ = "tekulvw" __version__ = "0.1.1" @@ -45,11 +46,12 @@ youtube_dl_options = { 'extractaudio': True, 'audioformat': "mp3", 'nocheckcertificate': True, - 'ignoreerrors': True, + 'ignoreerrors': False, 'quiet': True, 'no_warnings': True, 'outtmpl': "data/audio/cache/%(id)s", - 'default_search': 'auto' + 'default_search': 'auto', + 'encoding': 'utf-8' } @@ -59,7 +61,15 @@ class MaximumLength(Exception): def __str__(self): return self.message + +class YouTubeDlError(Exception): + def __init__(self, m): + self.message = m + + def __str__(self): + return self.message + class NotConnected(Exception): pass @@ -104,7 +114,6 @@ class InvalidSong(InvalidURL): class InvalidPlaylist(InvalidSong): pass - class deque(collections.deque): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -119,6 +128,14 @@ class deque(collections.deque): self.appendleft(ret) return copy.deepcopy(ret) +class QueueKey(Enum): + REPEAT = 1 + PLAYLIST = 2 + VOICE_CHANNEL_ID = 3 + QUEUE = 4 + TEMP_QUEUE = 5 + NOW_PLAYING = 6 + NOW_PLAYING_CHANNEL = 7 class Song: def __init__(self, **kwargs): @@ -129,6 +146,10 @@ class Song: self.webpage_url = kwargs.pop('webpage_url', "") self.duration = kwargs.pop('duration', 60) +class QueuedSong: + def __init__(self, url, channel): + self.url = url + self.channel = channel class Playlist: def __init__(self, server=None, sid=None, name=None, author=None, url=None, @@ -226,20 +247,22 @@ class Downloader(threading.Thread): self.max_duration = max_duration self.done = threading.Event() self.song = None - self.failed = False self._download = download self.hit_max_length = threading.Event() self._yt = None + self.error = None def run(self): try: self.get_info() if self._download: self.download() + except youtube_dl.utils.DownloadError as e: + self.error = str(e) except MaximumLength: self.hit_max_length.set() - except: - self.failed = True + except OSError as e: + log.warning("An operating system error occurred while downloading URL '{}':\n'{}'".format(self.url, str(e))) self.done.set() def download(self): @@ -272,7 +295,8 @@ class Downloader(threading.Thread): video = self._yt.extract_info(self.url, download=False, process=False) - self.song = Song(**video) + if(video is not None): + self.song = Song(**video) class Audio: @@ -307,20 +331,23 @@ class Audio: await self.bot.change_presence(status=status, game=game) log.debug('Bot status changed to song title: ' + song.title) - def _add_to_queue(self, server, url): + def _add_to_queue(self, server, url, channel): if server.id not in self.queue: self._setup_queue(server) - self.queue[server.id]["QUEUE"].append(url) + queued_song = QueuedSong(url, channel) + self.queue[server.id][QueueKey.QUEUE].append(queued_song) - def _add_to_temp_queue(self, server, url): + def _add_to_temp_queue(self, server, url, channel): if server.id not in self.queue: self._setup_queue(server) - self.queue[server.id]["TEMP_QUEUE"].append(url) + queued_song = QueuedSong(url, channel) + self.queue[server.id][QueueKey.TEMP_QUEUE].append(queued_song) - def _addleft_to_queue(self, server, url): + def _addleft_to_queue(self, server, url, channel): if server.id not in self.queue: self._setup_queue() - self.queue[server.id]["QUEUE"].appendleft(url) + queued_song = QueuedSong(url, channel) + self.queue[server.id][QueueKey.QUEUE].appendleft(queued_song) def _cache_desired_files(self): filelist = [] @@ -345,7 +372,7 @@ class Audio: queue = copy.deepcopy(self.queue) filelist = [] for server in queue: - now_playing = queue[server].get("NOW_PLAYING") + now_playing = queue[server].get(QueueKey.NOW_PLAYING) try: filelist.append(now_playing.id) except AttributeError: @@ -366,13 +393,13 @@ class Audio: def _clear_queue(self, server): if server.id not in self.queue: return - self.queue[server.id]["QUEUE"] = deque() - self.queue[server.id]["TEMP_QUEUE"] = deque() + self.queue[server.id][QueueKey.QUEUE] = deque() + self.queue[server.id][QueueKey.TEMP_QUEUE] = deque() async def _create_ffmpeg_player(self, server, filename, local=False): """This function will guarantee we have a valid voice client, even if one doesn't exist previously.""" - voice_channel_id = self.queue[server.id]["VOICE_CHANNEL_ID"] + voice_channel_id = self.queue[server.id][QueueKey.VOICE_CHANNEL_ID] voice_client = self.voice_client(server) if voice_client is None: @@ -389,7 +416,7 @@ class Audio: await self._join_voice_channel(to_connect) # SHIT elif voice_client.channel.id != voice_channel_id: # This was decided at 3:45 EST in #advanced-testing by 26 - self.queue[server.id]["VOICE_CHANNEL_ID"] = voice_client.channel.id + self.queue[server.id][QueueKey.VOICE_CHANNEL_ID] = voice_client.channel.id log.debug("reconnect chan id for sid {} is wrong, fixing".format( server.id)) @@ -446,20 +473,27 @@ class Audio: await voice_client.disconnect() - async def _download_all(self, url_list): + async def _download_all(self, queued_song_list, channel): """ Doesn't actually download, just get's info for uses like queue_list """ downloaders = [] - for url in url_list: - d = Downloader(url) + for queued_song in queued_song_list: + d = Downloader(queued_song.url) d.start() downloaders.append(d) while any([d.is_alive() for d in downloaders]): await asyncio.sleep(0.1) + + songs = [d.song for d in downloaders if d.song is not None and d.error is None] + + invalid_downloads = [d for d in downloaders if d.error is not None] + invalid_number = len(invalid_downloads) + if(invalid_number > 0): + await self.bot.send_message(channel, "The queue contains {} item(s)" + " that can not be played.".format(invalid_number)) - songs = [d.song for d in downloaders if d.song is not None] return songs async def _download_next(self, server, curr_dl, next_dl): @@ -475,6 +509,10 @@ class Audio: while next_dl.is_alive(): await asyncio.sleep(0.5) + + error = next_dl.error + if(error is not None): + raise YouTubeDlError(error) if curr_dl.song.id != next_dl.song.id: log.debug("downloader ID's mismatch on sid {}".format(server.id) + @@ -539,7 +577,7 @@ class Audio: ret = [] for i in range(limit): try: - ret.append(self.queue[server.id]["QUEUE"][i]) + ret.append(self.queue[server.id][QueueKey.QUEUE][i]) except IndexError: pass @@ -549,19 +587,25 @@ class Audio: if server.id not in self.queue: return None - return self.queue[server.id]["NOW_PLAYING"] + return self.queue[server.id][QueueKey.NOW_PLAYING] + + def _get_queue_nowplaying_channel(self, server): + if server.id not in self.queue: + return None + + return self.queue[server.id][QueueKey.NOW_PLAYING_CHANNEL] def _get_queue_playlist(self, server): if server.id not in self.queue: return None - return self.queue[server.id]["PLAYLIST"] + return self.queue[server.id][QueueKey.PLAYLIST] def _get_queue_repeat(self, server): if server.id not in self.queue: return None - return self.queue[server.id]["REPEAT"] + return self.queue[server.id][QueueKey.REPEAT] def _get_queue_tempqueue(self, server, limit): if server.id not in self.queue: @@ -570,7 +614,7 @@ class Audio: ret = [] for i in range(limit): try: - ret.append(self.queue[server.id]["TEMP_QUEUE"][i]) + ret.append(self.queue[server.id][QueueKey.TEMP_QUEUE][i]) except IndexError: pass return ret @@ -598,6 +642,11 @@ class Audio: # Getting info w/o download self.downloaders[server.id].done.wait() + + # Youtube-DL threw an exception. + error = self.downloaders[server.id].error + if(error is not None): + raise YouTubeDlError(error) # This will throw a maxlength exception if required self.downloaders[server.id].duration_check() @@ -626,7 +675,7 @@ class Audio: if server.id not in self.queue: return False - return self.queue[server.id]["PLAYLIST"] + return self.queue[server.id][QueueKey.PLAYLIST] async def _join_voice_channel(self, channel): server = channel.server @@ -636,7 +685,7 @@ class Audio: raise ConnectTimeout("You are on connect cooldown for another {}" " seconds.".format(diff)) if server.id in self.queue: - self.queue[server.id]["VOICE_CHANNEL_ID"] = channel.id + self.queue[server.id][QueueKey.VOICE_CHANNEL_ID] = channel.id try: await asyncio.wait_for(self.bot.join_voice_channel(channel), timeout=5, loop=self.bot.loop) @@ -761,6 +810,10 @@ class Audio: while d.is_alive(): await asyncio.sleep(0.5) + error = d.error + if(error is not None): + raise YouTubeDlError(error) + for entry in d.song.entries: if entry["url"][4] != "s": song_url = "https{}".format(entry["url"][4:]) @@ -778,6 +831,10 @@ class Audio: while d.is_alive(): await asyncio.sleep(0.5) + error = d.error + if(error is not None): + raise YouTubeDlError(error) + for entry in d.song.entries: try: song_url = "https://www.youtube.com/watch?v={}".format( @@ -792,7 +849,7 @@ class Audio: return playlist - async def _play(self, sid, url): + async def _play(self, sid, url, channel): """Returns the song object of what's playing""" if type(sid) is not discord.Server: server = self.bot.get_server(sid) @@ -803,13 +860,21 @@ class Audio: log.debug('starting to play on "{}"'.format(server.name)) if self._valid_playable_url(url) or "[SEARCH:]" in url: + clean_url = self._clean_url(url) try: song = await self._guarantee_downloaded(server, url) + except YouTubeDlError as e: + message = ("I'm unable to play '{}' because of an error:\n" + "'{}'".format(clean_url, str(e))) + message = escape(message, mass_mentions=True) + await self.bot.send_message(channel, message) + return except MaximumLength: - log.warning("I can't play URL below because it is too long." - " Use [p]audioset maxlength to change this.\n\n" - "{}".format(url)) - raise + message = ("I'm unable to play '{}' because it exceeds the " + "maximum audio length.".format(clean_url)) + message = escape(message, mass_mentions=True) + await self.bot.send_message(channel, message) + return local = False else: # Assume local try: @@ -827,13 +892,15 @@ class Audio: return song - def _play_playlist(self, server, playlist): + def _play_playlist(self, server, playlist, channel): try: songlist = playlist.playlist name = playlist.name except AttributeError: songlist = playlist name = True + + songlist = self._songlist_change_url_to_queued_song(songlist, channel) log.debug("setting up playlist {} on sid {}".format(name, server.id)) @@ -848,7 +915,7 @@ class Audio: self._set_queue_repeat(server, True) self._set_queue(server, songlist) - def _play_local_playlist(self, server, name): + def _play_local_playlist(self, server, name, channel): songlist = self._local_playlist_songlist(name) ret = [] @@ -856,7 +923,15 @@ class Audio: ret.append(os.path.join(name, song)) ret_playlist = Playlist(server=server, name=name, playlist=ret) - self._play_playlist(server, ret_playlist) + self._play_playlist(server, ret_playlist, channel) + + def _songlist_change_url_to_queued_song(self, songlist, channel): + queued_songlist = [] + for song in songlist: + queued_song = QueuedSong(song, channel) + queued_songlist.append(queued_song) + + return queued_songlist def _player_count(self): count = 0 @@ -924,10 +999,10 @@ class Audio: dataIO.save_json(f, playlist) def _shuffle_queue(self, server): - shuffle(self.queue[server.id]["QUEUE"]) + shuffle(self.queue[server.id][QueueKey.QUEUE]) def _shuffle_temp_queue(self, server): - shuffle(self.queue[server.id]["TEMP_QUEUE"]) + shuffle(self.queue[server.id][QueueKey.TEMP_QUEUE]) def _server_count(self): return max([1, len(self.bot.servers)]) @@ -937,7 +1012,7 @@ class Audio: self._clear_queue(server) else: self._setup_queue(server) - self.queue[server.id]["QUEUE"].extend(songlist) + self.queue[server.id][QueueKey.QUEUE].extend(songlist) def _set_queue_channel(self, server, channel): if server.id not in self.queue: @@ -948,31 +1023,32 @@ class Audio: except AttributeError: pass - self.queue[server.id]["VOICE_CHANNEL_ID"] = channel + self.queue[server.id][QueueKey.VOICE_CHANNEL_ID] = channel - def _set_queue_nowplaying(self, server, song): + def _set_queue_nowplaying(self, server, song, channel): if server.id not in self.queue: return - self.queue[server.id]["NOW_PLAYING"] = song + self.queue[server.id][QueueKey.NOW_PLAYING] = song + self.queue[server.id][QueueKey.NOW_PLAYING_CHANNEL] = channel def _set_queue_playlist(self, server, name=True): if server.id not in self.queue: self._setup_queue(server) - self.queue[server.id]["PLAYLIST"] = name + self.queue[server.id][QueueKey.PLAYLIST] = name def _set_queue_repeat(self, server, value): if server.id not in self.queue: self._setup_queue(server) - self.queue[server.id]["REPEAT"] = value + self.queue[server.id][QueueKey.REPEAT] = value def _setup_queue(self, server): - self.queue[server.id] = {"REPEAT": False, "PLAYLIST": False, - "VOICE_CHANNEL_ID": None, - "QUEUE": deque(), "TEMP_QUEUE": deque(), - "NOW_PLAYING": None} + self.queue[server.id] = {QueueKey.REPEAT: False, QueueKey.PLAYLIST: False, + QueueKey.VOICE_CHANNEL_ID: None, + QueueKey.QUEUE: deque(), QueueKey.TEMP_QUEUE: deque(), + QueueKey.NOW_PLAYING: None, QueueKey.NOW_PLAYING_CHANNEL: None} def _stop(self, server): self._setup_queue(server) @@ -1011,7 +1087,7 @@ class Audio: return if len(active_servers) == 1: server = active_servers[0].server - song = self.queue[server.id]["NOW_PLAYING"] + song = self._get_queue_nowplaying(server) if song: await self._add_song_status(song) else: @@ -1031,6 +1107,12 @@ class Audio: if yt or sc: # TODO: Add sc check return True return False + + def _clean_url(self, url): + if(self._valid_playable_url(url)): + return "<{}>".format(url) + + return url.replace("[SEARCH:]", "") @commands.group(pass_context=True) async def audioset(self, ctx): @@ -1245,6 +1327,7 @@ class Audio: server = ctx.message.server author = ctx.message.author voice_channel = author.voice_channel + channel = ctx.message.channel # Checking already connected, will join if not @@ -1291,7 +1374,7 @@ class Audio: await self.bot.say("Local playlist not found.") return - self._play_local_playlist(server, name) + self._play_local_playlist(server, name, channel) @local.command(name="list", no_pm=True) async def list_local(self): @@ -1330,6 +1413,7 @@ class Audio: server = ctx.message.server author = ctx.message.author voice_channel = author.voice_channel + channel = ctx.message.channel # Checking if playing in current server @@ -1386,13 +1470,14 @@ class Audio: self._stop_player(server) self._clear_queue(server) - self._add_to_queue(server, url) + self._add_to_queue(server, url, channel) @commands.command(pass_context=True, no_pm=True) async def prev(self, ctx): """Goes back to the last song.""" # Current song is in NOW_PLAYING server = ctx.message.server + channel = ctx.message.channel if self.is_playing(server): curr_url = self._get_queue_nowplaying(server).webpage_url @@ -1400,17 +1485,17 @@ class Audio: if self._is_queue_playlist(server): # need to reorder queue try: - last_url = self.queue[server.id]["QUEUE"].pop() + last_url = self.queue[server.id][QueueKey.QUEUE].pop() except IndexError: pass log.debug("prev on sid {}, curr_url {}".format(server.id, curr_url)) - self._addleft_to_queue(server, curr_url) + self._addleft_to_queue(server, curr_url, channel) if last_url: - self._addleft_to_queue(server, last_url) - self._set_queue_nowplaying(server, None) + self._addleft_to_queue(server, last_url, channel) + self._set_queue_nowplaying(server, None, None) self.voice_client(server).audio_player.stop() @@ -1463,7 +1548,11 @@ class Audio: except InvalidPlaylist: await self.bot.say("That playlist URL is invalid.") return - + except YouTubeDlError as e: + await self.bot.say("An error occurred while enumerating the playlist:\n" + "'{}'".format(str(e))) + return + playlist = self._make_playlist(author, url, songlist) # Returns a Playlist object @@ -1522,6 +1611,7 @@ class Audio: Does NOT write to disk.""" server = ctx.message.server + channel = ctx.message.channel if not self.voice_connected(server): await self.bot.say("Not voice connected in this server.") return @@ -1535,7 +1625,7 @@ class Audio: " happen.") # We have a queue to modify - self._add_to_queue(server, url) + self._add_to_queue(server, url, channel) await self.bot.say("Queued.") @@ -1570,6 +1660,7 @@ class Audio: server = ctx.message.server author = ctx.message.author voice_channel = ctx.message.author.voice_channel + channel = ctx.message.channel caller = inspect.currentframe().f_back.f_code.co_name @@ -1606,7 +1697,7 @@ class Audio: if caller == "playlist_start_mix": shuffle(playlist.playlist) - self._play_playlist(server, playlist) + self._play_playlist(server, playlist, channel) await self.bot.say("Playlist queued.") else: await self.bot.say("That playlist does not exist.") @@ -1627,6 +1718,7 @@ class Audio: if url is None: return await self._queue_list(ctx) server = ctx.message.server + channel = ctx.message.channel if not self.voice_connected(server): await ctx.invoke(self.play, url_or_search_terms=url) return @@ -1652,23 +1744,24 @@ class Audio: url = url.split("&")[0] # Temp fix for the &list issue # We have a queue to modify - if self.queue[server.id]["PLAYLIST"]: + if self.queue[server.id][QueueKey.PLAYLIST]: log.debug("queueing to the temp_queue for sid {}".format( server.id)) - self._add_to_temp_queue(server, url) + self._add_to_temp_queue(server, url, channel) else: log.debug("queueing to the actual queue for sid {}".format( server.id)) - self._add_to_queue(server, url) + self._add_to_queue(server, url, channel) await self.bot.say("Queued.") async def _queue_list(self, ctx): """Not a command, use `queue` with no args to call this.""" server = ctx.message.server + channel = ctx.message.channel if server.id not in self.queue: await self.bot.say("Nothing playing on this server!") return - elif len(self.queue[server.id]["QUEUE"]) == 0: + elif len(self.queue[server.id][QueueKey.QUEUE]) == 0: await self.bot.say("Nothing queued on this server.") return @@ -1679,13 +1772,13 @@ class Audio: if now_playing is not None: msg += "\n***Now playing:***\n{}\n".format(now_playing.title) - queue_url_list = self._get_queue(server, 5) - tempqueue_url_list = self._get_queue_tempqueue(server, 5) + queued_song_list = self._get_queue(server, 5) + tempqueued_song_list = self._get_queue_tempqueue(server, 5) await self.bot.say("Gathering information...") - queue_song_list = await self._download_all(queue_url_list) - tempqueue_song_list = await self._download_all(tempqueue_url_list) + queue_song_list = await self._download_all(queued_song_list, channel) + tempqueue_song_list = await self._download_all(tempqueued_song_list, channel) song_info = [] for num, song in enumerate(tempqueue_song_list, 1): @@ -1711,7 +1804,7 @@ class Audio: server = ctx.message.server if ctx.invoked_subcommand is None: if self.is_playing(server): - if self.queue[server.id]["REPEAT"]: + if self.queue[server.id][QueueKey.REPEAT]: msg = "The queue is currently looping." else: msg = "The queue is currently not looping." @@ -1730,8 +1823,8 @@ class Audio: " Try playing something first.") return - self._set_queue_repeat(server, not self.queue[server.id]["REPEAT"]) - repeat = self.queue[server.id]["REPEAT"] + self._set_queue_repeat(server, not self.queue[server.id][QueueKey.REPEAT]) + repeat = self.queue[server.id][QueueKey.REPEAT] if repeat: await self.bot.say("Repeat toggled on.") else: @@ -1784,7 +1877,7 @@ class Audio: if self.can_instaskip(msg.author): vc.audio_player.stop() if self._get_queue_repeat(server) is False: - self._set_queue_nowplaying(server, None) + self._set_queue_nowplaying(server, None, None) await self.bot.say("Skipping...") else: if msg.author.id in self.skip_votes[server.id]: @@ -1804,7 +1897,7 @@ class Audio: if vote >= thresh: vc.audio_player.stop() if self._get_queue_repeat(server) is False: - self._set_queue_nowplaying(server, None) + self._set_queue_nowplaying(server, None, None) self.skip_votes[server.id] = [] await self.bot.say("Vote threshold met. Skipping...") return @@ -2028,13 +2121,14 @@ class Audio: max_length = self.settings["MAX_LENGTH"] # This is a reference, or should be at least - temp_queue = self.queue[server.id]["TEMP_QUEUE"] - queue = self.queue[server.id]["QUEUE"] - repeat = self.queue[server.id]["REPEAT"] - last_song = self.queue[server.id]["NOW_PLAYING"] + temp_queue = self.queue[server.id][QueueKey.TEMP_QUEUE] + queue = self.queue[server.id][QueueKey.QUEUE] + repeat = self.queue[server.id][QueueKey.REPEAT] + last_song = self.queue[server.id][QueueKey.NOW_PLAYING] + last_song_channel = self.queue[server.id][QueueKey.NOW_PLAYING_CHANNEL] - assert temp_queue is self.queue[server.id]["TEMP_QUEUE"] - assert queue is self.queue[server.id]["QUEUE"] + assert temp_queue is self.queue[server.id][QueueKey.TEMP_QUEUE] + assert queue is self.queue[server.id][QueueKey.QUEUE] # _play handles creating the voice_client and player for us @@ -2047,21 +2141,27 @@ class Audio: # Fake queue for irdumb's temp playlist songs log.debug("calling _play because temp_queue is non-empty") try: - song = await self._play(sid, temp_queue.popleft()) + queued_song = temp_queue.popleft() + url = queued_song.url + channel = queued_song.channel + song = await self._play(sid, url, channel) except MaximumLength: return elif len(queue) > 0: # We're in the normal queue - url = queue.popleft() + queued_song = queue.popleft() + url = queued_song.url + channel = queued_song.channel log.debug("calling _play on the normal queue") try: - song = await self._play(sid, url) + song = await self._play(sid, url, channel) except MaximumLength: return if repeat and last_song: + queued_last_song = QueuedSong(last_song.webpage_url, last_song_channel) queue.append(last_song.webpage_url) else: song = None - self.queue[server.id]["NOW_PLAYING"] = song + self._set_queue_nowplaying(server, song, channel) log.debug("set now_playing for sid {}".format(server.id)) self.bot.loop.create_task(self._update_bot_status()) @@ -2069,25 +2169,41 @@ class Audio: # We're playing but we might be able to download a new song curr_dl = self.downloaders.get(server.id) if len(temp_queue) > 0: - next_dl = Downloader(temp_queue.peekleft(), - max_length) + queued_next_song = temp_queue.peekleft() + next_url = queued_next_song.url + next_channel = queued_next_song.channel + next_dl = Downloader(next_url, max_length) elif len(queue) > 0: - next_dl = Downloader(queue.peekleft(), max_length) + queued_next_song = queue.peekleft() + next_url = queued_next_song.url + next_channel = queued_next_song.channel + next_dl = Downloader(next_url, max_length) else: next_dl = None if next_dl is not None: - # Download next song - next_dl.start() - await self._download_next(server, curr_dl, next_dl) + try: + # Download next song + next_dl.start() + await self._download_next(server, curr_dl, next_dl) + except YouTubeDlError as e: + if len(temp_queue) > 0: + temp_queue.popleft() + elif len(queue) > 0: + queue.popleft() + clean_url = self._clean_url(next_url) + message = ("I'm unable to play '{}' because of an " + "error:\n'{}'".format(clean_url, str(e))) + message = escape(message, mass_mentions=True) + await self.bot.send_message(next_channel, message) async def queue_scheduler(self): while self == self.bot.get_cog('Audio'): tasks = [] queue = copy.deepcopy(self.queue) for sid in queue: - if len(queue[sid]["QUEUE"]) == 0 and \ - len(queue[sid]["TEMP_QUEUE"]) == 0: + if len(queue[sid][QueueKey.QUEUE]) == 0 and \ + len(queue[sid][QueueKey.TEMP_QUEUE]) == 0: continue # log.debug("scheduler found a non-empty queue" # " for sid: {}".format(sid))