1
Fork 0
mirror of https://github.com/RYGhub/royalnet.git synced 2024-11-23 19:44:20 +00:00

Automatically delete files when playback is complete or mode is changed

This commit is contained in:
Steffo 2019-04-20 18:03:19 +02:00
parent 2e7ef6f008
commit 6c8a93a33e
11 changed files with 142 additions and 34 deletions

View file

@ -24,7 +24,7 @@ logging.getLogger("royalnet.bots.telegram").setLevel(logging.DEBUG)
commands = [PingCommand, ShipCommand, SmecdsCommand, ColorCommand, CiaoruoziCommand, DebugCreateCommand, SyncCommand,
AuthorCommand, DiarioCommand, RageCommand, DateparserCommand, ReminderCommand, KvactiveCommand, KvCommand,
KvrollCommand, VideoinfoCommand, SummonCommand, PlayCommand, SkipCommand]
KvrollCommand, VideoinfoCommand, SummonCommand, PlayCommand, SkipCommand, PlaymodeCommand]
address, port = "localhost", 1234

View file

@ -1,11 +1,13 @@
import math
import random
import typing
from .royalpcmaudio import RoyalPCMAudio
class PlayMode:
def __init__(self):
self.now_playing = None
self.generator = self._generator()
self.now_playing: typing.Optional[RoyalPCMAudio] = None
self.generator: typing.AsyncGenerator = self._generator()
async def next(self):
return await self.generator.__anext__()
@ -14,21 +16,27 @@ class PlayMode:
raise NotImplementedError()
async def _generator(self):
"""Get the next video from the list and advance it."""
"""Get the next RPA from the list and advance it."""
raise NotImplementedError()
# This is needed to make the coroutine an async generator
# noinspection PyUnreachableCode
yield NotImplemented
def add(self, item):
"""Add a new video to the PlayMode."""
"""Add a new RPA to the PlayMode."""
raise NotImplementedError()
def delete(self):
"""Delete all RPAs contained inside this PlayMode."""
class Playlist(PlayMode):
"""A video list. Videos played are removed from the list."""
def __init__(self, starting_list=None):
"""A video list. RPAs played are removed from the list."""
def __init__(self, starting_list: typing.List[RoyalPCMAudio] = None):
super().__init__()
if starting_list is None:
starting_list = []
self.list = starting_list
self.list: typing.List[RoyalPCMAudio] = starting_list
def videos_left(self):
return len(self.list)
@ -42,26 +50,33 @@ class Playlist(PlayMode):
else:
self.now_playing = next_video
yield self.now_playing
if self.now_playing is not None:
self.now_playing.delete()
def add(self, item):
self.list.append(item)
def delete(self):
while self.list:
self.list.pop(0).delete()
self.now_playing.delete()
class Pool(PlayMode):
"""A video pool. Videos played are played back in random order, and they are kept in the pool."""
def __init__(self, starting_pool=None):
"""A RPA pool. RPAs played are played back in random order, and they are kept in the pool."""
def __init__(self, starting_pool: typing.List[RoyalPCMAudio] = None):
super().__init__()
if starting_pool is None:
starting_pool = []
self.pool = starting_pool
self._pool_copy = []
self.pool: typing.List[RoyalPCMAudio] = starting_pool
self._pool_copy: typing.List[RoyalPCMAudio] = []
def videos_left(self):
return math.inf
async def _generator(self):
while True:
if self.pool:
if not self.pool:
self.now_playing = None
yield None
continue
@ -74,5 +89,11 @@ class Pool(PlayMode):
def add(self, item):
self.pool.append(item)
self._pool_copy.append(self._pool_copy)
self._pool_copy.append(item)
random.shuffle(self._pool_copy)
def delete(self):
for item in self.pool:
item.delete()
self.pool = None
self._pool_copy = None

View file

@ -7,7 +7,7 @@ from .royalpcmfile import RoyalPCMFile
class RoyalPCMAudio(AudioSource):
def __init__(self, rpf: "RoyalPCMFile"):
self.rpf: "RoyalPCMFile" = rpf
self._file = open(rpf.audio_filename, "rb")
self._file = open(self.rpf.audio_filename, "rb")
@staticmethod
def create_from_url(url) -> typing.List["RoyalPCMAudio"]:
@ -19,13 +19,20 @@ class RoyalPCMAudio(AudioSource):
def read(self):
data: bytes = self._file.read(OpusEncoder.FRAME_SIZE)
# If the file was externally closed, it means it was deleted
if self._file.closed:
return b""
if len(data) != OpusEncoder.FRAME_SIZE:
# Close the file as soon as the playback is finished
self._file.close()
# Reopen the file, so it can be reused
self._file = open(self.rpf.audio_filename, "rb")
return b""
return data
def delete(self):
self._file.close()
self.rpf.delete_audio_file()
def __repr__(self):
return f"<RoyalPCMAudio {self.rpf.audio_filename}>"
def __del__(self):
self._file.close()
del self.rpf

View file

@ -28,7 +28,7 @@ class RoyalPCMFile(YtdlFile):
log.info(f"Now downloading {info.webpage_url}")
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}...")
log.info(f"Converting {self.video_filename}...")
# Convert the video to pcm
try:
ffmpeg.input(f"./{self.video_filename}") \
@ -58,6 +58,6 @@ class RoyalPCMFile(YtdlFile):
def audio_filename(self):
return f"./downloads/{safefilename(self.info.title)}-{safefilename(str(int(self._time)))}.pcm"
def __del__(self):
def delete_audio_file(self):
log.info(f"Deleting {self.audio_filename}")
os.remove(self.audio_filename)

View file

@ -147,7 +147,3 @@ class YtdlInfo:
if self.webpage_url:
return self.webpage_url
return self.id
if __name__ == "__main__":
f = YtdlFile.create_from_url("https://www.youtube.com/watch?v=BaW_jenozKc&v=UxxajLWwzqY", "./lovely.mp4")

View file

@ -102,7 +102,7 @@ class DiscordBot(GenericBot):
log.debug(f"Creating music_data for {channel.guild}")
self.music_data[channel.guild] = Playlist()
@staticmethod # Not really static because of the self reference
@staticmethod
async def on_message(message: discord.Message):
text = message.content
# Skip non-text messages
@ -119,6 +119,10 @@ class DiscordBot(GenericBot):
# Call the command
await self.call(command_name, message.channel, parameters, message=message)
async def on_ready(cli):
log.debug("Connection successful, client is ready")
await cli.change_presence(status=discord.Status.online)
def find_guild_by_name(cli, name: str) -> discord.Guild:
"""Find the Guild with the specified name. Case-insensitive.
Will raise a NoneFoundError if no channels are found, or a TooManyFoundError if more than one is found."""
@ -212,10 +216,10 @@ class DiscordBot(GenericBot):
async def advance_music_data(self, guild: discord.Guild):
"""Try to play the next song, while it exists. Otherwise, just return."""
log.debug(f"Starting playback chain")
guild_music_data = self.music_data[guild]
voice_client = self.client.find_voice_client_by_guild(guild)
next_source: RoyalPCMAudio = await guild_music_data.next()
await self.update_activity_with_source_title(next_source)
if next_source is None:
log.debug(f"Ending playback chain")
return
@ -227,3 +231,19 @@ class DiscordBot(GenericBot):
log.debug(f"Starting playback of {next_source}")
voice_client.play(next_source, after=advance)
async def update_activity_with_source_title(self, rpa: typing.Optional[RoyalPCMAudio] = None):
if len(self.music_data) > 1:
# Multiple guilds are using the bot, do not display anything
log.debug(f"Updating current Activity: setting to None, as multiple guilds are using the bot")
await self.client.change_presence(status=discord.Status.online)
return
if rpa is None:
# No songs are playing now
log.debug(f"Updating current Activity: setting to None, as nothing is currently being played")
await self.client.change_presence(status=discord.Status.online)
return
log.debug(f"Updating current Activity: listening to {rpa.rpf.info.title}")
await self.client.change_presence(activity=discord.Activity(name=rpa.rpf.info.title,
type=discord.ActivityType.listening),
status=discord.Status.online)

View file

@ -17,9 +17,10 @@ from .videoinfo import VideoinfoCommand
from .summon import SummonCommand
from .play import PlayCommand
from .skip import SkipCommand
from .playmode import PlaymodeCommand
__all__ = ["NullCommand", "PingCommand", "ShipCommand", "SmecdsCommand", "CiaoruoziCommand", "ColorCommand",
"SyncCommand", "DiarioCommand", "RageCommand", "DateparserCommand", "AuthorCommand", "ReminderCommand",
"KvactiveCommand", "KvCommand", "KvrollCommand", "VideoinfoCommand", "SummonCommand", "PlayCommand",
"SkipCommand"]
"SkipCommand", "PlaymodeCommand"]

View file

@ -2,7 +2,7 @@ import typing
import asyncio
from ..utils import Command, Call, NetworkHandler
from ..network import Message, RequestSuccessful
from ..error import TooManyFoundError
from ..error import TooManyFoundError, NoneFoundError
if typing.TYPE_CHECKING:
from ..bots import DiscordBot
@ -26,7 +26,9 @@ class PlayNH(NetworkHandler):
if message.guild_name:
guild = bot.client.find_guild(message.guild_name)
else:
if len(bot.music_data) != 1:
if len(bot.music_data) == 0:
raise NoneFoundError("No voice clients active")
if len(bot.music_data) > 1:
raise TooManyFoundError("Multiple guilds found")
guild = list(bot.music_data)[0]
# Ensure the guild has a PlayMode before adding the file to it

View file

@ -0,0 +1,59 @@
import typing
import asyncio
from ..utils import Command, Call, NetworkHandler
from ..network import Message, RequestSuccessful
from ..error import NoneFoundError, TooManyFoundError
from ..audio import Playlist, Pool
if typing.TYPE_CHECKING:
from ..bots import DiscordBot
loop = asyncio.get_event_loop()
class PlaymodeMessage(Message):
def __init__(self, mode_name: str, guild_name: typing.Optional[str] = None):
self.mode_name: str = mode_name
self.guild_name: typing.Optional[str] = guild_name
class PlaymodeNH(NetworkHandler):
message_type = PlaymodeMessage
@classmethod
async def discord(cls, bot: "DiscordBot", message: PlaymodeMessage):
"""Handle a play Royalnet request. That is, add audio to a PlayMode."""
# Find the matching guild
if message.guild_name:
guild = bot.client.find_guild(message.guild_name)
else:
if len(bot.music_data) == 0:
raise NoneFoundError("No voice clients active")
if len(bot.music_data) > 1:
raise TooManyFoundError("Multiple guilds found")
guild = list(bot.music_data)[0]
# Delete the previous PlayMode, if it exists
if bot.music_data[guild] is not None:
bot.music_data[guild].delete()
# Create the new PlayMode
if message.mode_name == "playlist":
bot.music_data[guild] = Playlist()
elif message.mode_name == "pool":
bot.music_data[guild] = Pool()
else:
raise ValueError("No such PlayMode")
return RequestSuccessful()
class PlaymodeCommand(Command):
command_name = "playmode"
command_description = "Cambia modalità di riproduzione per la chat vocale."
command_syntax = "[ [guild] ] (mode)"
network_handlers = [PlaymodeNH]
@classmethod
async def common(cls, call: Call):
guild, mode_name = call.args.match(r"(?:\[(.+)])?\s*(\S+)\s*")
await call.net_request(PlaymodeMessage(mode_name, guild), "discord")
await call.reply(f"✅ Richiesto di passare alla modalità di riproduzione [c]{mode_name}[/c].")

View file

@ -21,7 +21,9 @@ class SkipNH(NetworkHandler):
if message.guild_name:
guild = bot.client.find_guild_by_name(message.guild_name)
else:
if len(bot.music_data) != 1:
if len(bot.music_data) == 0:
raise NoneFoundError("No voice clients active")
if len(bot.music_data) > 1:
raise TooManyFoundError("Multiple guilds found")
guild = list(bot.music_data)[0]
# Set the currently playing source as ended
@ -45,4 +47,4 @@ class SkipCommand(Command):
async def common(cls, call: Call):
guild, = call.args.match(r"(?:\[(.+)])?")
await call.net_request(SkipMessage(guild), "discord")
await call.reply(f"✅ Richiesta lo skip della canzone attuale..")
await call.reply(f"✅ Richiesto lo skip della canzone attuale.")

View file

@ -25,8 +25,8 @@ class InvalidConfigError(Exception):
class RoyalnetError(Exception):
"""An error was raised while handling the Royalnet request.
This exception contains the exception that was raised during the handling."""
def __init__(self, exc):
self.exc = exc
def __init__(self, exc: Exception):
self.exc: Exception = exc
class ExternalError(Exception):