diff --git a/royalgames.py b/royalgames.py index 7bc782f2..b9eb6d42 100644 --- a/royalgames.py +++ b/royalgames.py @@ -20,13 +20,11 @@ commands = [PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziComm KvrollCommand, VideoinfoCommand, SummonCommand, PlayCommand] master = RoyalnetServer("localhost", 1234, "sas") -tg_bot = TelegramBot(os.environ["TG_AK"], "ws://localhost:1234", "sas", commands, os.environ["DB_PATH"], Royal, Telegram, "tg_id", error_command=ErrorHandlerCommand) +# tg_bot = TelegramBot(os.environ["TG_AK"], "ws://localhost:1234", "sas", commands, os.environ["DB_PATH"], Royal, Telegram, "tg_id", error_command=ErrorHandlerCommand) ds_bot = DiscordBot(os.environ["DS_AK"], "ws://localhost:1234", "sas", commands, os.environ["DB_PATH"], Royal, Discord, "discord_id", error_command=ErrorHandlerCommand) loop.run_until_complete(master.run()) # Dirty hack, remove me asap -loop.create_task(tg_bot.run()) +# loop.create_task(tg_bot.run()) loop.create_task(ds_bot.run()) -print("Commands enabled:") -print(tg_bot.generate_botfather_command_string()) print("Starting loop...") loop.run_forever() diff --git a/royalnet/audio/__init__.py b/royalnet/audio/__init__.py index 3c4a9547..bd1458bf 100644 --- a/royalnet/audio/__init__.py +++ b/royalnet/audio/__init__.py @@ -1,5 +1,5 @@ from .playmodes import PlayMode, Playlist, Pool from .youtubedl import YtdlFile, YtdlInfo -from .royalaudiofile import RoyalAudioFile +from .royalpcmfile import RoyalPCMFile -__all__ = ["PlayMode", "Playlist", "Pool", "YtdlFile", "YtdlInfo", "RoyalAudioFile"] +__all__ = ["PlayMode", "Playlist", "Pool", "YtdlFile", "YtdlInfo", "RoyalPCMFile"] diff --git a/royalnet/audio/royalpcmaudio.py b/royalnet/audio/royalpcmaudio.py new file mode 100644 index 00000000..90144913 --- /dev/null +++ b/royalnet/audio/royalpcmaudio.py @@ -0,0 +1,26 @@ +from discord import AudioSource +from discord.opus import Encoder as OpusEncoder +import typing +if typing.TYPE_CHECKING: + from .royalpcmfile import RoyalPCMFile + + +class RoyalPCMAudio(AudioSource): + def __init__(self, rpf: "RoyalPCMFile"): + self.rpf: "RoyalPCMFile" = rpf + self._file = open(rpf.audio_filename, "rb") + + def cleanup(self): + self._file.close() + + def is_opus(self): + return False + + def read(self): + data: bytes = self._file.read(OpusEncoder.FRAME_SIZE) + if len(data) != OpusEncoder.FRAME_SIZE: + return b"" + return data + + def __repr__(self): + return f"" diff --git a/royalnet/audio/royalaudiofile.py b/royalnet/audio/royalpcmfile.py similarity index 60% rename from royalnet/audio/royalaudiofile.py rename to royalnet/audio/royalpcmfile.py index 8a49b048..39452a90 100644 --- a/royalnet/audio/royalaudiofile.py +++ b/royalnet/audio/royalpcmfile.py @@ -5,26 +5,28 @@ import os import typing import logging as _logging from .youtubedl import YtdlFile, YtdlInfo +from .royalpcmaudio import RoyalPCMAudio log = _logging.getLogger(__name__) -class RoyalAudioFile(YtdlFile): +class RoyalPCMFile(YtdlFile): ytdl_args = { "logger": log, # Log messages to a logging.Logger instance. "format": "bestaudio" # Fetch the best audio format available } def __init__(self, info: "YtdlInfo", **ytdl_args): + # Preemptively initialize info to be able to generate the filename + self.info = info # Overwrite the new ytdl_args self.ytdl_args = {**self.ytdl_args, **ytdl_args} log.info(f"Now downloading {info.webpage_url}") - super().__init__(info, outtmpl="./downloads/%(title)s-%(id)s.ytdl", **self.ytdl_args) + super().__init__(info, outtmpl=self._ytdl_filename, **self.ytdl_args) # Find the audio_filename with a regex (should be video.opus) log.info(f"Preparing {self.video_filename}...") - # Convert the video to opus - # Actually not needed, but we do this anyways for compression reasons - ffmpeg.input(self.video_filename) \ + # Convert the video to pcm + ffmpeg.input(f"./{self.video_filename}") \ .output(self.audio_filename, format="s16le", acodec="pcm_s16le", ac=2, ar="48000") \ .overwrite_output() \ .run(quiet=not __debug__) @@ -32,21 +34,24 @@ class RoyalAudioFile(YtdlFile): log.info(f"Deleting {self.video_filename}") self.delete_video_file() + def __repr__(self): + return f"" + @staticmethod - def create_from_url(url, **ytdl_args) -> typing.List["RoyalAudioFile"]: + def create_from_url(url, **ytdl_args) -> typing.List["RoyalPCMFile"]: info_list = YtdlInfo.create_from_url(url) - return [RoyalAudioFile(info) for info in info_list] + return [RoyalPCMFile(info) for info in info_list] + + @property + def _ytdl_filename(self): + return f"./downloads/{self.info.title}-{self.info.id}.ytdl" @property def audio_filename(self): return f"./downloads/{self.info.title}-{self.info.id}.pcm" - def as_audio_source(self): - # TODO: find a way to close this - file = open(self.audio_filename, "rb") - return discord.PCMAudio(file) + def create_audio_source(self): + return RoyalPCMAudio(self) def delete_audio_file(self): - # TODO: can't delete it yet, see as_audio_source() - # os.remove(self.audio_filename) - pass + os.remove(self.audio_filename) diff --git a/royalnet/audio/youtubedl.py b/royalnet/audio/youtubedl.py index bd2476d4..d397ccb2 100644 --- a/royalnet/audio/youtubedl.py +++ b/royalnet/audio/youtubedl.py @@ -39,6 +39,9 @@ class YtdlFile: # Final checks assert os.path.exists(self.video_filename) + def __repr__(self): + return f"" + @staticmethod def create_from_url(url, outtmpl="%(title)s-%(id)s.%(ext)s", **ytdl_args) -> typing.List["YtdlFile"]: info_list = YtdlInfo.create_from_url(url) diff --git a/royalnet/bots/discord.py b/royalnet/bots/discord.py index 1028e571..f1afa704 100644 --- a/royalnet/bots/discord.py +++ b/royalnet/bots/discord.py @@ -9,7 +9,7 @@ from ..utils import asyncify, Call, Command from ..error import UnregisteredError, NoneFoundError, TooManyFoundError from ..network import RoyalnetLink, Message, RequestSuccessful, RequestError from ..database import Alchemy, relationshiplinkchain -from ..audio import RoyalAudioFile, PlayMode, Playlist, Pool +from ..audio import RoyalPCMFile, PlayMode, Playlist, Pool loop = asyncio.get_event_loop() log = _logging.getLogger(__name__) @@ -213,7 +213,7 @@ class DiscordBot: async def network_handler(self, message: Message) -> Message: """Handle a Royalnet request.""" - log.debug(f"Received a {message.__class__.__name__}") + log.debug(f"Received {message} from Royalnet") if isinstance(message, SummonMessage): return await self.nh_summon(message) elif isinstance(message, PlayMessage): @@ -229,27 +229,34 @@ class DiscordBot: async def add_to_music_data(self, url: str, guild: discord.Guild): """Add a file to the corresponding music_data object.""" - files: typing.List[RoyalAudioFile] = await asyncify(RoyalAudioFile.create_from_url, url) + log.debug(f"Downloading {url} to add to music_data") + files: typing.List[RoyalPCMFile] = await asyncify(RoyalPCMFile.create_from_url, url) guild_music_data = self.music_data[guild] for file in files: + log.debug(f"Adding {file} to music_data") guild_music_data.add(file) if guild_music_data.now_playing is None: + log.debug(f"Starting playback chain") await self.advance_music_data(guild) async def advance_music_data(self, guild: discord.Guild): """Try to play the next song, while it exists. Otherwise, just return.""" guild_music_data = self.music_data[guild] voice_client = self.find_voice_client(guild) - next_file: RoyalAudioFile = await guild_music_data.next() + next_file: RoyalPCMFile = await guild_music_data.next() if next_file is None: + log.debug(f"Ending playback chain") return def advance(error=None): + log.debug(f"Deleting {next_file}") next_file.delete_audio_file() loop.create_task(self.advance_music_data(guild)) - log.info(f"Starting playback of {next_file.info.title}") - voice_client.play(next_file.as_audio_source(), after=advance) + log.debug(f"Creating AudioSource of {next_file}") + next_source = next_file.create_audio_source() + log.debug(f"Starting playback of {next_source}") + voice_client.play(next_source, after=advance) async def nh_play(self, message: PlayMessage): """Handle a play Royalnet request. That is, add audio to a PlayMode."""