Audio patch (#292)

* PEP8
* deal with duration problem
* Limit output stream to 64k
* stop duration checks over and over for _download_next
* reset state correctly on stop and skip
This commit is contained in:
Will 2016-06-05 08:25:31 -04:00 committed by Twentysix
parent a2e79e1c5a
commit c6f0f1ee1c

View File

@ -70,15 +70,19 @@ class AuthorNotConnected(NotConnected):
class VoiceNotConnected(NotConnected):
pass
class UnauthorizedConnect(Exception):
pass
class UnauthorizedSpeak(Exception):
pass
class UnauthorizedSave(Exception):
pass
class ConnectTimeout(NotConnected):
pass
@ -127,7 +131,7 @@ class Playlist:
self.name = name
self.author = author
self.url = url
self.main_class = main_class # reference to Audio
self.main_class = main_class # reference to Audio
self.path = path
if url is None and "link" in kwargs:
@ -173,10 +177,11 @@ class Downloader(threading.Thread):
super().__init__(*args, **kwargs)
self.url = url
self.max_duration = max_duration
self.done = False
self.done = threading.Event()
self.song = None
self.failed = False
self._download = download
self.hit_max_length = threading.Event()
self._yt = None
def run(self):
@ -184,22 +189,27 @@ class Downloader(threading.Thread):
self.get_info()
if self._download:
self.download()
except MaximumLength:
self.hit_max_length.set()
except:
self.done = True
self.failed = True
else:
self.done = True
self.done.set()
def download(self):
if self.max_duration and self.song.duration > self.max_duration:
log.debug("not downloading {} because of duration".format(
self.song.id))
return
self.duration_check()
if not os.path.isfile('data/audio/cache' + self.song.id):
video = self._yt.extract_info(self.url)
self.song = Song(**video)
def duration_check(self):
log.debug("duration {} for songid {}".format(self.song.duration,
self.song.id))
if self.max_duration and self.song.duration > self.max_duration:
log.debug("songid {} too long".format(self.song.id))
raise MaximumLength("songid {} has duration {} > {}".format(
self.song.id, self.song.duration, self.max_duration))
def get_info(self):
if self._yt is None:
self._yt = youtube_dl.YoutubeDL(youtube_dl_options)
@ -208,8 +218,9 @@ class Downloader(threading.Thread):
process=False)
else:
self.url = self.url[9:]
yt_id = self._yt.extract_info(self.url,
download=False)["entries"][0]["id"] # Should handle errors here.
yt_id = self._yt.extract_info(
self.url, download=False)["entries"][0]["id"]
# Should handle errors here ^
self.url = "https://youtube.com/watch?v={}".format(yt_id)
video = self._yt.extract_info(self.url, download=False,
process=False)
@ -325,7 +336,8 @@ class Audio:
use_avconv = self.settings["AVCONV"]
volume = self.get_server_settings(server)["VOLUME"] / 100
options = '-filter "volume=volume={}"'.format(volume)
options = \
'-filter "volume=volume={}" -b:a 64k -bufsize 64k'.format(volume)
try:
voice_client.audio_player.process.kill()
@ -384,6 +396,10 @@ class Audio:
log.debug("downloader ID's mismatch on sid {}".format(server.id) +
" gonna start dl-ing the next thing on the queue"
" id {}".format(next_dl.song.id))
try:
next_dl.duration_check()
except MaximumLength:
return
self.downloaders[server.id] = Downloader(next_dl.url, max_length,
download=True)
self.downloaders[server.id].start()
@ -428,6 +444,18 @@ class Audio:
return self.queue[server.id]["NOW_PLAYING"]
def _get_queue_playlist(self, server):
if server.id not in self.queue:
return None
return self.queue[server.id]["PLAYLIST"]
def _get_queue_repeat(self, server):
if server.id not in self.queue:
return None
return self.queue[server.id]["REPEAT"]
async def _guarantee_downloaded(self, server, url):
max_length = self.settings["MAX_LENGTH"]
if server.id not in self.downloaders: # We don't have a downloader
@ -449,10 +477,13 @@ class Audio:
# Queue manager already started it for us, isn't that nice?
pass
while self.downloaders[server.id].is_alive(): # Getting info w/o DL
await asyncio.sleep(0.5)
# Getting info w/o download
self.downloaders[server.id].done.wait()
# This will throw a maxlength exception if required
self.downloaders[server.id].duration_check()
song = self.downloaders[server.id].song
log.debug("sid {} wants to play songid {}".format(server.id, song.id))
# Now we check to see if we have a cache hit
@ -645,7 +676,13 @@ class Audio:
log.debug('starting to play on "{}"'.format(server.name))
if self._valid_playable_url(url) or "[SEARCH:]" in url:
song = await self._guarantee_downloaded(server, url)
try:
song = await self._guarantee_downloaded(server, url)
except MaximumLength:
log.warning("I can't play URL below because it is too long."
" Use {}audioset maxlength to change this.\n\n"
"{}".format(self.bot.command_prefix[0], url))
raise
local = False
else: # Assume local
try:
@ -803,7 +840,7 @@ class Audio:
"NOW_PLAYING": None}
def _stop(self, server):
self._clear_queue(server)
self._setup_queue(server)
self._stop_player(server)
self._stop_downloader(server)
@ -1000,7 +1037,7 @@ class Audio:
if not self.voice_connected(server):
try:
can_connect = self.has_connect_perm(author, server)
self.has_connect_perm(author, server)
except AuthorNotConnected:
await self.bot.say("You must join a voice channel before I can"
" play anything.")
@ -1075,11 +1112,9 @@ class Audio:
# Checking already connected, will join if not
caller = inspect.currentframe().f_back.f_code.co_name
if not self.voice_connected(server):
try:
can_connect = self.has_connect_perm(author, server)
self.has_connect_perm(author, server)
except AuthorNotConnected:
await self.bot.say("You must join a voice channel before I can"
" play anything.")
@ -1179,8 +1214,8 @@ class Audio:
playlist.server = server
self._save_playlist(server, name, playlist)
await self.bot.say("Playlist '{}' saved. Tracks: {}".format(name,
len(songlist)))
await self.bot.say("Playlist '{}' saved. Tracks: {}".format(
name, len(songlist)))
else:
await self.bot.say("That URL is not a valid Soundcloud or YouTube"
" playlist link. If you think this is in error"
@ -1195,9 +1230,8 @@ class Audio:
if name not in self._list_playlists(server):
await self.bot.say("There is no playlist with that name.")
return
playlist = self._load_playlist(server, name,
local=self._playlist_exists_local(
server, name))
playlist = self._load_playlist(
server, name, local=self._playlist_exists_local(server, name))
try:
playlist.append_song(author, url)
except UnauthorizedSave:
@ -1213,7 +1247,6 @@ class Audio:
# Need better wording ^
await self.bot.say("Not implemented yet.")
@playlist.command(pass_context=True, no_pm=True, name="list")
async def playlist_list(self, ctx):
"""Lists all available playlists"""
@ -1279,18 +1312,18 @@ class Audio:
if self._playlist_exists(server, name):
if not self.voice_connected(server):
try:
can_connect = self.has_connect_perm(author, server)
self.has_connect_perm(author, server)
except AuthorNotConnected:
await self.bot.say("You must join a voice channel before I can"
" play anything.")
await self.bot.say("You must join a voice channel before"
" I can play anything.")
return
except UnauthorizedConnect:
await self.bot.say("I don't have permissions to join your"
" voice channel.")
return
except UnauthorizedSpeak:
await self.bot.say("I don't have permissions to speak in your"
" voice channel.")
await self.bot.say("I don't have permissions to speak in"
" your voice channel.")
return
else:
await self._join_voice_channel(voice_channel)
@ -1421,6 +1454,8 @@ class Audio:
if self.is_playing(server):
vc = self.voice_client(server)
vc.audio_player.stop()
if self._get_queue_repeat(server) is False:
self._set_queue_nowplaying(server, None)
await self.bot.say("Skipping...")
else:
await self.bot.say("Can't skip if I'm not playing.")
@ -1441,8 +1476,9 @@ class Audio:
song = self.queue[server.id]["NOW_PLAYING"]
if song:
msg = ("\n**Title:** {}\n**Author:** {}\n**Uploader:** {}\n"
"**Views:** {}\n\n<{}>".format(song.title, song.creator,
song.uploader, song.view_count, song.webpage_url))
"**Views:** {}\n\n<{}>".format(
song.title, song.creator, song.uploader,
song.view_count, song.webpage_url))
await self.bot.say(msg.replace("**Author:** None\n", ""))
else:
await self.bot.say("I don't know what this song is either.")
@ -1582,13 +1618,18 @@ class Audio:
if len(temp_queue) > 0:
# Fake queue for irdumb's temp playlist songs
log.debug("calling _play because temp_queue is non-empty")
song = await self._play(sid, temp_queue.popleft())
try:
song = await self._play(sid, temp_queue.popleft())
except MaximumLength:
return
elif len(queue) > 0: # We're in the normal queue
url = queue.popleft()
log.debug("calling _play on the normal queue")
song = await self._play(sid, url)
try:
song = await self._play(sid, url)
except MaximumLength:
return
if repeat and last_song:
# TODO: stick NOW_PLAYING.url back into queue AFTER ending
queue.append(last_song.webpage_url)
else:
song = None
@ -1716,17 +1757,14 @@ def setup(bot):
check_files()
if youtube_dl is None:
raise RuntimeError("You need to run `pip3 install youtube_dl`")
return
if opus is False:
raise RuntimeError(
"Your opus library's bitness must match your python installation's"
" bitness. They both must be either 32bit or 64bit.")
return
elif opus is None:
raise RuntimeError(
"You need to install ffmpeg and opus. See \"https://github.com/"
"Twentysix26/Red-DiscordBot/wiki/Requirements\"")
return
try:
bot.voice_clients
except AttributeError: