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