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

Managed to get Play working

This commit is contained in:
Steffo 2019-12-01 23:53:50 +01:00
parent 8ff0731aa5
commit 8d08d7e010
13 changed files with 106 additions and 53 deletions

View file

@ -27,6 +27,16 @@ class YtdlDiscord:
self.pcm_filename: typing.Optional[str] = None
self.lock: MultiLock = MultiLock()
def __repr__(self):
if not self.ytdl_file.has_info:
return f"<{self.__class__.__qualname__} without info>"
elif not self.ytdl_file.is_downloaded:
return f"<{self.__class__.__qualname__} not downloaded>"
elif not self.is_converted:
return f"<{self.__class__.__qualname__} at '{self.ytdl_file.filename}' not converted>"
else:
return f"<{self.__class__.__qualname__} at '{self.pcm_filename}'>"
@property
def is_converted(self):
"""Has the file been converted?"""

View file

@ -1,8 +1,9 @@
import os
import logging
import re
from contextlib import asynccontextmanager
from typing import Optional, List, Dict, Any
from royalnet.utils import asyncify, MultiLock
from typing import *
from royalnet.utils import *
from asyncio import AbstractEventLoop, get_event_loop
from .ytdlinfo import YtdlInfo
from .errors import NotFoundError, MultipleFilesError
@ -23,7 +24,7 @@ class YtdlFile:
"quiet": not __debug__, # Do not print messages to stdout.
"noplaylist": True, # Download single video instead of a playlist if in doubt.
"no_warnings": not __debug__, # Do not print out anything for warnings.
"outtmpl": "%(epoch)s-%(title)s-%(id)s.%(ext)s", # Use the default outtmpl.
"outtmpl": "./downloads/%(epoch)s-%(title)s-%(id)s.%(ext)s", # Use the default outtmpl.
"ignoreerrors": True # Ignore unavailable videos
}
@ -46,6 +47,14 @@ class YtdlFile:
loop = get_event_loop()
self._loop = loop
def __repr__(self):
if not self.has_info:
return f"<{self.__class__.__qualname__} without info>"
elif not self.is_downloaded:
return f"<{self.__class__.__qualname__} not downloaded>"
else:
return f"<{self.__class__.__qualname__} at '{self.filename}'>"
@property
def has_info(self) -> bool:
"""Does the :class:`YtdlFile` have info available?"""
@ -77,9 +86,13 @@ class YtdlFile:
filename = ytdl.prepare_filename(self.info.__dict__)
with YoutubeDL({**self.ytdl_args, "outtmpl": filename}) as ytdl:
ytdl.download([self.info.webpage_url])
# "WARNING: Requested formats are incompatible for merge and will be merged into mkv."
if not os.path.exists(filename):
filename = re.sub(r"\.[^.]+$", ".mkv", filename)
self.filename = filename
await self.retrieve_info()
if not self.is_downloaded:
async with self.lock.exclusive():
log.debug(f"Downloading with youtube-dl: {self}")
await asyncify(download, loop=self._loop)

View file

@ -21,7 +21,7 @@ class YtdlInfo:
"quiet": True, # Do not print messages to stdout.
"noplaylist": True, # Download single video instead of a playlist if in doubt.
"no_warnings": True, # Do not print out anything for warnings.
"outtmpl": "%(title)s-%(id)s.%(ext)s", # Use the default outtmpl.
"outtmpl": "./downloads/%(epoch)s-%(title)s-%(id)s.%(ext)s", # Use the default outtmpl.
"ignoreerrors": True # Ignore unavailable videos
}

View file

@ -18,6 +18,16 @@ class YtdlMp3:
self.mp3_filename: typing.Optional[str] = None
self.lock: MultiLock = MultiLock()
def __repr__(self):
if not self.ytdl_file.has_info:
return f"<{self.__class__.__qualname__} without info>"
elif not self.ytdl_file.is_downloaded:
return f"<{self.__class__.__qualname__} not downloaded>"
elif not self.is_converted:
return f"<{self.__class__.__qualname__} at '{self.ytdl_file.filename}' not converted>"
else:
return f"<{self.__class__.__qualname__} at '{self.mp3_filename}'>"
@property
def is_converted(self):
"""Has the file been converted?"""

View file

@ -129,14 +129,20 @@ class Link:
@requires_identification
async def send(self, package: Package):
"""Send a package to the :class:`Server`."""
await self.websocket.send(package.to_json_bytes())
log.debug(f"Trying to send package: {package}")
try:
jbytes = package.to_json_bytes()
except TypeError as e:
log.fatal(f"Could not send package: {' '.join(e.args)}")
raise
await self.websocket.send(jbytes)
log.debug(f"Sent package: {package}")
@requires_identification
async def broadcast(self, destination: str, broadcast: Broadcast) -> None:
package = Package(broadcast.to_dict(), source=self.nid, destination=destination)
await self.send(package)
log.debug(f"Sent broadcast: {broadcast}")
log.debug(f"Sent broadcast to {destination}: {broadcast}")
@requires_identification
async def request(self, destination: str, request: Request) -> Response:

View file

@ -229,9 +229,16 @@ class DiscordSerf(Serf):
return None
else:
# Give priority to channels with the most people
def people_count(c: discord.VoiceChannel):
def people_count(c: "discord.VoiceChannel"):
return len(c.members)
channels.sort(key=people_count, reverse=True)
return channels[0]
def find_voice_player(self, guild: "discord.Guild") -> Optional[VoicePlayer]:
for voice_player in self.voice_players:
if voice_player.voice_client.guild == guild:
return voice_player
else:
return None

View file

@ -1,4 +1,5 @@
import asyncio
import threading
import logging
from typing import Optional
from .errors import *
@ -19,6 +20,8 @@ class VoicePlayer:
self.loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
else:
self.loop = loop
# FIXME: this looks like spaghetti
self._playback_ended_event: threading.Event = threading.Event()
async def connect(self, channel: "discord.VoiceChannel") -> "discord.VoiceClient":
"""Connect the :class:`VoicePlayer` to a :class:`discord.VoiceChannel`, creating a :class:`discord.VoiceClient`
@ -92,12 +95,20 @@ class VoicePlayer:
return
log.debug(f"Next: {next_source}")
self.voice_client.play(next_source, after=self._playback_ended)
self.loop.create_task(self._playback_check())
def _playback_ended(self, error: Exception = None):
"""An helper method that is called when the :attr:`.voice_client._player` has finished playing."""
async def _playback_check(self):
# FIXME: quite spaghetti
while True:
if self._playback_ended_event.is_set():
self._playback_ended_event.clear()
await self.start()
break
await asyncio.sleep(1)
def _playback_ended(self, error=None):
if error is not None:
# TODO: capture exception with Sentry
log.error(f"Error during playback: {error}")
# TODO: catch with Sentry
log.error(error)
return
# Create a new task to create
self.loop.create_task(self.start())
self._playback_ended_event.set()

View file

@ -331,7 +331,9 @@ class Serf:
except ImportError:
log.info("Sentry: not installed")
serf = cls(loop=aio.get_event_loop(), **kwargs)
loop = aio.get_event_loop()
serf = cls(loop=loop, **kwargs)
try:
serf.loop.run_until_complete(serf.run())

View file

@ -1,5 +1,4 @@
from .asyncify import asyncify
from .safeformat import safeformat
from .sleep_until import sleep_until
from .formatters import andformat, underscorize, ytdldateformat, numberemojiformat, ordinalformat
from .urluuid import to_urluuid, from_urluuid
@ -10,7 +9,6 @@ from .log import init_logging
__all__ = [
"asyncify",
"safeformat",
"sleep_until",
"andformat",
"underscorize",

View file

@ -6,18 +6,24 @@ try:
except ImportError:
coloredlogs = None
log_format = "{asctime}\t| {processName}\t| {name}\t| {message}"
l: logging.Logger = logging.getLogger(__name__)
def init_logging(logging_cfg: Dict[str, Any]):
royalnet_log: logging.Logger = logging.getLogger("royalnet")
royalnet_log.setLevel(logging_cfg["log_level"])
loggers_cfg = logging_cfg["Loggers"]
for logger_name in loggers_cfg:
if logger_name == "root":
log: logging.Logger = logging.root
else:
log: logging.Logger = logging.getLogger(logger_name)
log.setLevel(loggers_cfg[logger_name])
stream_handler = logging.StreamHandler()
if coloredlogs is not None:
stream_handler.formatter = coloredlogs.ColoredFormatter(log_format, style="{")
stream_handler.formatter = coloredlogs.ColoredFormatter(logging_cfg["log_format"], style="{")
else:
stream_handler.formatter = logging.Formatter(log_format, style="{")
if len(royalnet_log.handlers) < 1:
royalnet_log.addHandler(stream_handler)
royalnet_log.debug("Logging: ready")
stream_handler.formatter = logging.Formatter(logging_cfg["log_format"], style="{")
if len(logging.root.handlers) < 1:
logging.root.addHandler(stream_handler)
l.debug("Logging: ready")

View file

@ -46,11 +46,11 @@ class MultiLock:
log.debug(f"Waiting for normal lock end: {self}")
await self._normal_event.wait()
try:
log.debug("Acquiring exclusive lock: {self}")
log.debug(f"Acquiring exclusive lock: {self}")
self._exclusive_event.clear()
yield
finally:
log.debug("Releasing exclusive lock: {self}")
log.debug(f"Releasing exclusive lock: {self}")
self._exclusive_event.set()
def __repr__(self):

View file

@ -1,15 +0,0 @@
class SafeDict(dict):
def __missing__(self, key):
return "{" + key + "}"
def safeformat(string: str, **words: str) -> str:
""":py:func:`str.format` something, but ignore missing keys instead of raising an error.
Parameters:
string: The base string to be formatted.
words: The words to format the string with.
Returns:
The formatted string."""
return string.format_map(SafeDict(**words))

View file

@ -82,10 +82,15 @@ enabled = true
token = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
[Logging]
# Print to stderr all logging events of an equal or greater level than this
# Possible values are "DEBUG", "INFO", "WARNING", "ERROR", "FATAL"
log_level = "INFO"
# Optional: install the `coloredlogs` extra for colored output!
# TODO: document this
log_format = "{asctime}\t| {processName}\t| {name}\t| {message}"
[Logging.Loggers]
root = "ERROR"
"royalnet" = "INFO"
# "royalnet.commands" = "DEBUG"
# "websockets.protocol" = "ERROR"
# ...
[Sentry]
# Connect Royalnet to a https://sentry.io/ project for error logging