1
Fork 0
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:
Steffo 2019-07-31 16:18:19 +02:00
parent d570e09e90
commit a9967918ff
9 changed files with 72 additions and 40 deletions

View file

@ -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"]

View file

@ -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

View 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)

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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]))
}
})

View file

@ -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"