mirror of
https://github.com/RYGhub/royalnet.git
synced 2024-11-23 11:34:18 +00:00
rework is finally finished
This commit is contained in:
parent
d570e09e90
commit
a9967918ff
9 changed files with 72 additions and 40 deletions
|
@ -1,8 +1,9 @@
|
|||
"""Video and audio downloading related classes, mainly used for Discord voice bots."""
|
||||
|
||||
from .playmodes import PlayMode, Playlist, Pool
|
||||
from .ytdlfile import YtdlFile, YtdlInfo
|
||||
from .royalpcmfile import RoyalPCMFile
|
||||
from .royalpcmaudio import RoyalPCMAudio
|
||||
from . import playmodes
|
||||
from .ytdlinfo import YtdlInfo
|
||||
from .ytdlfile import YtdlFile
|
||||
from .fileaudiosource import FileAudioSource
|
||||
from .ytdldiscord import YtdlDiscord
|
||||
|
||||
__all__ = ["PlayMode", "Playlist", "Pool", "YtdlFile", "YtdlInfo", "RoyalPCMFile", "RoyalPCMAudio"]
|
||||
__all__ = ["playmodes", "YtdlInfo", "YtdlFile", "FileAudioSource", "YtdlDiscord"]
|
||||
|
|
|
@ -7,7 +7,7 @@ class FileAudioSource(discord.AudioSource):
|
|||
The stream should be in the usual PCM encoding.
|
||||
|
||||
Warning:
|
||||
This AudioSource will consume (and close) the passed stream"""
|
||||
This AudioSource will consume (and close) the passed stream."""
|
||||
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
|
@ -29,10 +29,10 @@ class FileAudioSource(discord.AudioSource):
|
|||
"""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""
|
||||
data: bytes = self.file.read(discord.opus.Encoder.FRAME_SIZE)
|
||||
# If there is no more data to be streamed
|
||||
if len(data) != discord.opus.Encoder.FRAME_SIZE:
|
||||
# Close the file
|
||||
|
|
|
@ -83,9 +83,10 @@ class Playlist(PlayMode):
|
|||
next_video = self.list.pop(0)
|
||||
except IndexError:
|
||||
self.now_playing = None
|
||||
yield None
|
||||
else:
|
||||
self.now_playing = next_video
|
||||
yield self.now_playing
|
||||
yield next_video.spawn_audiosource()
|
||||
if self.now_playing is not None:
|
||||
self.now_playing.delete()
|
||||
|
||||
|
@ -130,7 +131,7 @@ class Pool(PlayMode):
|
|||
while self._pool_copy:
|
||||
next_video = self._pool_copy.pop(0)
|
||||
self.now_playing = next_video
|
||||
yield next_video
|
||||
yield next_video.spawn_audiosource()
|
||||
|
||||
def add(self, item) -> None:
|
||||
self.pool.append(item)
|
||||
|
|
|
@ -3,6 +3,7 @@ import discord
|
|||
import re
|
||||
import ffmpeg
|
||||
import os
|
||||
from .ytdlinfo import YtdlInfo
|
||||
from .ytdlfile import YtdlFile
|
||||
from .fileaudiosource import FileAudioSource
|
||||
|
||||
|
@ -11,6 +12,7 @@ class YtdlDiscord:
|
|||
def __init__(self, ytdl_file: YtdlFile):
|
||||
self.ytdl_file: YtdlFile = ytdl_file
|
||||
self.pcm_filename: typing.Optional[str] = None
|
||||
self._fas_spawned: typing.List[FileAudioSource] = []
|
||||
|
||||
def pcm_available(self):
|
||||
return self.pcm_filename is not None and os.path.exists(self.pcm_filename)
|
||||
|
@ -35,14 +37,40 @@ class YtdlDiscord:
|
|||
if not self.pcm_available():
|
||||
self.convert_to_pcm()
|
||||
|
||||
def to_audiosource(self) -> discord.AudioSource:
|
||||
def spawn_audiosource(self) -> discord.AudioSource:
|
||||
if not self.pcm_available():
|
||||
raise FileNotFoundError("File hasn't been converted to PCM yet")
|
||||
stream = open(self.pcm_filename, "rb")
|
||||
return FileAudioSource(stream)
|
||||
source = FileAudioSource(stream)
|
||||
# FIXME: it's a intentional memory leak
|
||||
self._fas_spawned.append(source)
|
||||
return source
|
||||
|
||||
def delete(self) -> None:
|
||||
if self.pcm_available():
|
||||
for source in self._fas_spawned:
|
||||
if not source.file.closed:
|
||||
source.file.close()
|
||||
os.remove(self.pcm_filename)
|
||||
self.pcm_filename = None
|
||||
self.ytdl_file.delete()
|
||||
|
||||
@classmethod
|
||||
def create_from_url(cls, url, **ytdl_args) -> typing.List["YtdlDiscord"]:
|
||||
files = YtdlFile.download_from_url(url, **ytdl_args)
|
||||
dfiles = []
|
||||
for file in files:
|
||||
dfile = YtdlDiscord(file)
|
||||
dfiles.append(dfile)
|
||||
return dfiles
|
||||
|
||||
@classmethod
|
||||
def create_and_ready_from_url(cls, url, **ytdl_args) -> typing.List["YtdlDiscord"]:
|
||||
dfiles = cls.create_from_url(url, **ytdl_args)
|
||||
for dfile in dfiles:
|
||||
dfile.ready_up()
|
||||
return dfiles
|
||||
|
||||
@property
|
||||
def info(self) -> typing.Optional[YtdlInfo]:
|
||||
return self.ytdl_file.info
|
||||
|
|
|
@ -8,7 +8,7 @@ from ..utils import asyncify, Call, Command, discord_escape
|
|||
from ..error import UnregisteredError, NoneFoundError, TooManyFoundError, InvalidConfigError, RoyalnetResponseError
|
||||
from ..network import RoyalnetConfig, Request, ResponseSuccess, ResponseError
|
||||
from ..database import DatabaseConfig
|
||||
from ..audio import PlayMode, Playlist, RoyalPCMAudio
|
||||
from ..audio import playmodes, YtdlDiscord
|
||||
|
||||
log = _logging.getLogger(__name__)
|
||||
|
||||
|
@ -31,7 +31,7 @@ class DiscordBot(GenericBot):
|
|||
def _init_voice(self):
|
||||
"""Initialize the variables needed for the connection to voice chat."""
|
||||
log.debug(f"Creating music_data dict")
|
||||
self.music_data: typing.Dict[discord.Guild, PlayMode] = {}
|
||||
self.music_data: typing.Dict[discord.Guild, playmodes.PlayMode] = {}
|
||||
|
||||
def _call_factory(self) -> typing.Type[Call]:
|
||||
log.debug(f"Creating DiscordCall")
|
||||
|
@ -100,7 +100,7 @@ class DiscordBot(GenericBot):
|
|||
# Create a music_data entry, if it doesn't exist; default is a Playlist
|
||||
if not self.music_data.get(channel.guild):
|
||||
log.debug(f"Creating music_data for {channel.guild}")
|
||||
self.music_data[channel.guild] = Playlist()
|
||||
self.music_data[channel.guild] = playmodes.Playlist()
|
||||
|
||||
@staticmethod
|
||||
async def on_message(message: discord.Message):
|
||||
|
@ -216,12 +216,12 @@ class DiscordBot(GenericBot):
|
|||
await self.client.connect()
|
||||
# TODO: how to stop?
|
||||
|
||||
async def add_to_music_data(self, audio_sources: typing.List[RoyalPCMAudio], guild: discord.Guild):
|
||||
"""Add a file to the corresponding music_data object."""
|
||||
async def add_to_music_data(self, dfiles: typing.List[YtdlDiscord], guild: discord.Guild):
|
||||
"""Add a list of :py:class:`royalnet.audio.YtdlDiscord` to the corresponding music_data object."""
|
||||
guild_music_data = self.music_data[guild]
|
||||
for audio_source in audio_sources:
|
||||
log.debug(f"Adding {audio_source} to music_data")
|
||||
guild_music_data.add(audio_source)
|
||||
for dfile in dfiles:
|
||||
log.debug(f"Adding {dfile} to music_data")
|
||||
guild_music_data.add(dfile)
|
||||
if guild_music_data.now_playing is None:
|
||||
await self.advance_music_data(guild)
|
||||
|
||||
|
@ -229,7 +229,7 @@ class DiscordBot(GenericBot):
|
|||
"""Try to play the next song, while it exists. Otherwise, just return."""
|
||||
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()
|
||||
next_source: discord.AudioSource = await guild_music_data.next()
|
||||
await self.update_activity_with_source_title()
|
||||
if next_source is None:
|
||||
log.debug(f"Ending playback chain")
|
||||
|
@ -252,16 +252,14 @@ class DiscordBot(GenericBot):
|
|||
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
|
||||
# FIXME: PyCharm faulty inspection?
|
||||
# noinspection PyUnresolvedReferences
|
||||
play_mode: PlayMode = list(self.music_data.items())[0][1]
|
||||
play_mode: playmodes.PlayMode = self.music_data[list(self.music_data)[0]]
|
||||
now_playing = play_mode.now_playing
|
||||
if now_playing 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 {now_playing.rpf.info.title}")
|
||||
await self.client.change_presence(activity=discord.Activity(name=now_playing.rpf.info.title,
|
||||
log.debug(f"Updating current Activity: listening to {now_playing.info.title}")
|
||||
await self.client.change_presence(activity=discord.Activity(name=now_playing.info.title,
|
||||
type=discord.ActivityType.listening),
|
||||
status=discord.Status.online)
|
||||
|
|
|
@ -6,11 +6,17 @@ import pickle
|
|||
from ..utils import Command, Call, NetworkHandler, asyncify
|
||||
from ..network import Request, ResponseSuccess
|
||||
from ..error import TooManyFoundError, NoneFoundError
|
||||
from ..audio import RoyalPCMAudio
|
||||
from ..audio import YtdlDiscord
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..bots import DiscordBot
|
||||
|
||||
|
||||
ytdl_args = {
|
||||
"format": "bestaudio",
|
||||
"outtmpl": f"./downloads/%(autonumber)s_%(title)s.%(ext)s"
|
||||
}
|
||||
|
||||
|
||||
class PlayNH(NetworkHandler):
|
||||
message_type = "music_play"
|
||||
|
||||
|
@ -32,16 +38,16 @@ class PlayNH(NetworkHandler):
|
|||
raise Exception("No music_data for this guild")
|
||||
# Start downloading
|
||||
if data["url"].startswith("http://") or data["url"].startswith("https://"):
|
||||
audio_sources: typing.List[RoyalPCMAudio] = await asyncify(RoyalPCMAudio.create_from_url, data["url"])
|
||||
dfiles: typing.List[YtdlDiscord] = await asyncify(YtdlDiscord.create_and_ready_from_url, data["url"], **ytdl_args)
|
||||
else:
|
||||
audio_sources = await asyncify(RoyalPCMAudio.create_from_ytsearch, data["url"])
|
||||
await bot.add_to_music_data(audio_sources, guild)
|
||||
dfiles = await asyncify(YtdlDiscord.create_and_ready_from_url, f"ytsearch:{data['url']}", **ytdl_args)
|
||||
await bot.add_to_music_data(dfiles, guild)
|
||||
# Create response dictionary
|
||||
response = {
|
||||
"videos": [{
|
||||
"title": source.rpf.info.title,
|
||||
"discord_embed_pickle": str(pickle.dumps(source.rpf.info.to_discord_embed()))
|
||||
} for source in audio_sources]
|
||||
"title": dfile.info.title,
|
||||
"discord_embed_pickle": str(pickle.dumps(dfile.info.to_discord_embed()))
|
||||
} for dfile in dfiles]
|
||||
}
|
||||
return ResponseSuccess(response)
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import typing
|
||||
import asyncio
|
||||
from ..utils import Command, Call, NetworkHandler
|
||||
from ..network import Request, ResponseSuccess
|
||||
from ..error import NoneFoundError, TooManyFoundError, CurrentlyDisabledError
|
||||
from ..audio import Playlist, Pool
|
||||
from ..error import NoneFoundError, TooManyFoundError
|
||||
from ..audio.playmodes import Playlist, Pool
|
||||
if typing.TYPE_CHECKING:
|
||||
from ..bots import DiscordBot
|
||||
|
||||
|
@ -30,8 +29,7 @@ class PlaymodeNH(NetworkHandler):
|
|||
if data["mode_name"] == "playlist":
|
||||
bot.music_data[guild] = Playlist()
|
||||
elif data["mode_name"] == "pool":
|
||||
# bot.music_data[guild] = Pool()
|
||||
raise CurrentlyDisabledError("Bug: https://github.com/royal-games/royalnet/issues/61")
|
||||
bot.music_data[guild] = Pool()
|
||||
else:
|
||||
raise ValueError("No such PlayMode")
|
||||
return ResponseSuccess()
|
||||
|
|
|
@ -37,8 +37,8 @@ class QueueNH(NetworkHandler):
|
|||
"type": playmode.__class__.__name__,
|
||||
"queue":
|
||||
{
|
||||
"strings": [str(element.rpf.info) for element in queue],
|
||||
"pickled_embeds": str(pickle.dumps([element.rpf.info.to_discord_embed() for element in queue]))
|
||||
"strings": [str(dfile.info) for dfile in queue],
|
||||
"pickled_embeds": str(pickle.dumps([dfile.info.to_discord_embed() for dfile in queue]))
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class VideoinfoCommand(Command):
|
|||
@classmethod
|
||||
async def common(cls, call: Call):
|
||||
url = call.args[0]
|
||||
info_list = await asyncify(YtdlInfo.create_from_url, url)
|
||||
info_list = await asyncify(YtdlInfo.retrieve_for_url, url)
|
||||
for info in info_list:
|
||||
info_dict = info.__dict__
|
||||
message = f"🔍 Dati di [b]{info}[/b]:\n"
|
||||
|
|
Loading…
Reference in a new issue