From bb0507d79ea8a96c3de6e37b6e551156d4de806a Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Wed, 31 Jul 2019 01:49:53 +0200 Subject: [PATCH] New stuff --- royalnet/audio/fileaudiosource.py | 41 +++++++++++++++++++++++++++++++ royalnet/audio/ytdldiscord.py | 28 ++++++++++++++++++++- royalnet/audio/ytdlfile.py | 8 +++--- 3 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 royalnet/audio/fileaudiosource.py diff --git a/royalnet/audio/fileaudiosource.py b/royalnet/audio/fileaudiosource.py new file mode 100644 index 00000000..442b5885 --- /dev/null +++ b/royalnet/audio/fileaudiosource.py @@ -0,0 +1,41 @@ +import discord + + +class FileAudioSource(discord.AudioSource): + """A :py:class:`discord.AudioSource` that uses a :py:class:`io.BufferedIOBase` as an input instead of memory. + + The stream should be in the usual PCM encoding. + + Warning: + This AudioSource will consume (and close) the passed stream""" + + def __init__(self, file): + self.file = file + + def __repr__(self): + if self.file.seekable(): + return f"<{self.__class__.__name__} @{self.file.tell()}>" + else: + return f"<{self.__class__.__name__}>" + + def is_opus(self): + """This audio file isn't Opus-encoded, but PCM-encoded. + + Returns: + ``False``.""" + return False + + def read(self): + """Reads 20ms worth of audio. + + If the audio is complete, then returning an empty :py:class:`bytes`-like object to signal this is the way to do so.""" + data: bytes = self.file.read(discord.opus.Encoder.FRAME_SIZE) + # If the stream is closed, it should stop playing immediatly + if self.file.closed: + return b"" + # If there is no more data to be streamed + if len(data) != discord.opus.Encoder.FRAME_SIZE: + # Close the file + self.file.close() + return b"" + return data diff --git a/royalnet/audio/ytdldiscord.py b/royalnet/audio/ytdldiscord.py index 2271322f..77010cf2 100644 --- a/royalnet/audio/ytdldiscord.py +++ b/royalnet/audio/ytdldiscord.py @@ -1,2 +1,28 @@ +import typing import discord -from .ytdlfile import YtdlFile \ No newline at end of file +import re +import ffmpeg +from .ytdlfile import YtdlFile +from .fileaudiosource import FileAudioSource + + +class YtdlDiscord: + def __init__(self, ytdl_file: YtdlFile): + self.ytdl_file: YtdlFile = ytdl_file + self.pcm_filename: typing.Optional[str] = None + + def convert_to_pcm(self) -> None: + if not self.ytdl_file.is_downloaded(): + raise FileNotFoundError("File hasn't been downloaded yet") + destination_filename = re.sub(r"\.[^.]+$", ".pcm", self.ytdl_file.filename) + ( + ffmpeg.input(self.ytdl_file.filename) + .output(destination_filename, format="s16le", ac=2, ar="48000") + .overwrite_output() + .run(quiet=True) + ) + self.pcm_filename = destination_filename + + def to_audiosource(self) -> discord.AudioSource: + stream = open(self.pcm_filename, "rb") + return FileAudioSource(stream) diff --git a/royalnet/audio/ytdlfile.py b/royalnet/audio/ytdlfile.py index 88647a27..8a9ae128 100644 --- a/royalnet/audio/ytdlfile.py +++ b/royalnet/audio/ytdlfile.py @@ -18,11 +18,11 @@ class YtdlFile: def __init__(self, url: str, - info: YtdlInfo = None, - filename: str = None): + info: typing.Optional[YtdlInfo] = None, + filename: typing.Optional[str] = None): self.url: str = url - self.info: YtdlInfo = info - self.filename: str = filename + self.info: typing.Optional[YtdlInfo] = info + self.filename: typing.Optional[str] = filename def has_info(self) -> bool: return self.info is not None