most things work, only skipping song needs work
This commit is contained in:
parent
e51acd075a
commit
63555dde87
6 changed files with 100 additions and 49 deletions
|
@ -123,6 +123,7 @@ class Client:
|
|||
self.sources = configure_sources(config["sources"])
|
||||
self.state = State()
|
||||
self.currentLock = asyncio.Semaphore(0)
|
||||
print("blubb")
|
||||
self.player = Player()
|
||||
self.register_handlers()
|
||||
|
||||
|
@ -270,17 +271,20 @@ class Client:
|
|||
:rtype: None
|
||||
"""
|
||||
entry: Entry = Entry(**data)
|
||||
source = self.sources[entry.source]
|
||||
print(
|
||||
f"Playing: {entry.artist} - {entry.title} [{entry.album}] "
|
||||
f"({entry.source}) for {entry.performer}"
|
||||
)
|
||||
try:
|
||||
self.state.current_source = self.sources[entry.source]
|
||||
# self.state.current_source = self.sources[entry.source]
|
||||
if self.state.config["preview_duration"] > 0:
|
||||
await self.preview(entry)
|
||||
await self.sources[entry.source].play(
|
||||
entry, self.player, self.state.config["mpv_options"]
|
||||
)
|
||||
video, audio = await source.ensure_playable(entry)
|
||||
await self.player.play(video, audio, source.extra_mpv_options)
|
||||
# await self.sources[entry.source].play(
|
||||
# entry, self.player, self.state.config["mpv_options"]
|
||||
# )
|
||||
except Exception: # pylint: disable=broad-except
|
||||
print_exc()
|
||||
self.state.current_source = None
|
||||
|
@ -421,9 +425,8 @@ class Client:
|
|||
:rtype: None
|
||||
"""
|
||||
engineio.async_client.async_signal_handler()
|
||||
if self.state.current_source is not None:
|
||||
if self.state.current_source.player is not None: # TODO old player
|
||||
self.state.current_source.player.kill()
|
||||
if self.player is not None:
|
||||
self.player.mpv.terminate()
|
||||
|
||||
async def start_client(self, config: dict[str, Any]) -> None:
|
||||
"""
|
||||
|
@ -469,9 +472,8 @@ class Client:
|
|||
except asyncio.CancelledError:
|
||||
pass
|
||||
finally:
|
||||
if self.state.current_source is not None:
|
||||
if self.state.current_source.player is not None: # TODO old player
|
||||
self.state.current_source.player.kill()
|
||||
if self.player is not None:
|
||||
self.player.mpv.terminate()
|
||||
|
||||
|
||||
def create_async_and_start_client(
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import asyncio
|
||||
import locale
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Iterable, Optional
|
||||
from typing import Iterable, Optional, cast
|
||||
from qrcode.main import QRCode
|
||||
import mpv
|
||||
import os
|
||||
from PIL.Image import Image
|
||||
|
||||
|
||||
from .entry import Entry
|
||||
|
@ -14,33 +13,11 @@ __dirname__ = os.path.dirname(__file__)
|
|||
|
||||
|
||||
class Player:
|
||||
def osd_size_handler(self, *args):
|
||||
if self.qr_overlay:
|
||||
self.mpv.remove_overlay(self.qr_overlay.overlay_id)
|
||||
|
||||
osd_width: int = self.mpv.osd_width
|
||||
osd_height: int = self.mpv.osd_height
|
||||
|
||||
x_pos = osd_width - self.qr.width - 10
|
||||
y_pos = osd_height - self.qr.height - 10
|
||||
|
||||
print(osd_width, osd_height)
|
||||
print(x_pos, y_pos)
|
||||
|
||||
self.qr_overlay = self.mpv.create_image_overlay(self.qr, pos=(x_pos, y_pos))
|
||||
|
||||
def event_handler(self, event):
|
||||
devent = event.as_dict()
|
||||
if devent["event"] == b"file-loaded":
|
||||
print(self.audio)
|
||||
if self.audio:
|
||||
self.mpv.audio_add(self.audio)
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
locale.setlocale(locale.LC_ALL, "C")
|
||||
self.mpv = mpv.MPV(ytdl=True, input_default_bindings=True, input_vo_keyboard=True, osc=True)
|
||||
self.mpv.keep_open = "yes"
|
||||
self.audio = None
|
||||
self.qr_overlay = None
|
||||
self.qr_overlay: Optional[mpv.ImageOverlay] = None
|
||||
qr = QRCode(box_size=5, border=1)
|
||||
qr.add_data("https://syng.rocks/")
|
||||
qr.make()
|
||||
|
@ -50,15 +27,26 @@ class Player:
|
|||
f"{__dirname__}/static/background.png",
|
||||
)
|
||||
|
||||
self.mpv.register_event_callback(self.event_handler)
|
||||
self.default_options = {
|
||||
"scale": "bilinear",
|
||||
}
|
||||
|
||||
self.mpv.observe_property("osd-width", self.osd_size_handler)
|
||||
self.mpv.observe_property("osd-height", self.osd_size_handler)
|
||||
|
||||
# def play_entry(self, entry: Entry, video: str, audio: Optional[str] = None):
|
||||
# self.queue_next(entry)
|
||||
# self.play(video, audio)
|
||||
def osd_size_handler(self, attribute: str, value: int) -> None:
|
||||
if self.qr_overlay:
|
||||
self.mpv.remove_overlay(self.qr_overlay.overlay_id)
|
||||
|
||||
async def queue_next(self, entry: Entry):
|
||||
osd_width: int = cast(int, self.mpv.osd_width)
|
||||
osd_height: int = cast(int, self.mpv.osd_height)
|
||||
|
||||
x_pos = osd_width - self.qr.width - 10
|
||||
y_pos = osd_height - self.qr.height - 10
|
||||
|
||||
self.qr_overlay = self.mpv.create_image_overlay(self.qr, pos=(x_pos, y_pos))
|
||||
|
||||
async def queue_next(self, entry: Entry) -> None:
|
||||
loop = asyncio.get_running_loop()
|
||||
self.play_image(f"{__dirname__}/static/background20perc.png", 3)
|
||||
|
||||
|
@ -79,21 +67,39 @@ class Player:
|
|||
|
||||
await loop.run_in_executor(None, self.mpv.wait_for_property, "eof-reached")
|
||||
|
||||
def play_image(self, image: str, duration: int):
|
||||
def play_image(self, image: str, duration: int) -> None:
|
||||
for property, value in self.default_options.items():
|
||||
self.mpv[property] = value
|
||||
self.mpv.image_display_duration = duration
|
||||
self.mpv.keep_open = "yes"
|
||||
self.mpv.play(image)
|
||||
self.mpv.pause = False
|
||||
|
||||
async def play(self, video: str, audio: Optional[str] = None):
|
||||
async def play(
|
||||
self,
|
||||
video: str,
|
||||
audio: Optional[str] = None,
|
||||
override_options: Optional[dict[str, str]] = None,
|
||||
) -> None:
|
||||
if override_options is None:
|
||||
override_options = {}
|
||||
for property, value in self.default_options.items():
|
||||
self.mpv[property] = value
|
||||
|
||||
for property, value in override_options.items():
|
||||
self.mpv[property] = value
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
self.audio = audio
|
||||
self.mpv.pause = True
|
||||
self.mpv.play(video)
|
||||
await loop.run_in_executor(None, self.mpv.wait_for_event, "file-loaded")
|
||||
if audio:
|
||||
self.mpv.audio_add(audio)
|
||||
self.mpv.pause = False
|
||||
await loop.run_in_executor(None, self.mpv.wait_for_property, "eof-reached")
|
||||
self.mpv.play(f"{__dirname__}/static/background.png")
|
||||
|
||||
def skip_current(self):
|
||||
def skip_current(self) -> None:
|
||||
self.mpv.playlist_append(
|
||||
f"{__dirname__}/static/background.png",
|
||||
)
|
||||
|
|
|
@ -40,6 +40,7 @@ class FileBasedSource(Source):
|
|||
|
||||
self.extensions: list[str] = config["extensions"] if "extensions" in config else ["mp3+cdg"]
|
||||
self.extra_mpv_arguments = ["--scale=oversample"]
|
||||
self.extra_mpv_options = {"scale": "oversample"}
|
||||
|
||||
def has_correct_extension(self, path: Optional[str]) -> bool:
|
||||
"""
|
||||
|
|
|
@ -130,6 +130,7 @@ class Source(ABC):
|
|||
# self.player: Optional[asyncio.subprocess.Process] = None
|
||||
self._index: list[str] = config["index"] if "index" in config else []
|
||||
self.extra_mpv_arguments: list[str] = []
|
||||
self.extra_mpv_options: dict[str, str] = {}
|
||||
self._skip_next = False
|
||||
|
||||
@staticmethod
|
||||
|
@ -341,7 +342,7 @@ class Source(ABC):
|
|||
# if self.player is not None:
|
||||
# self.player.kill()
|
||||
|
||||
async def ensure_playable(self, entry: Entry) -> None:
|
||||
async def ensure_playable(self, entry: Entry) -> tuple[str, Optional[str]]:
|
||||
"""
|
||||
Guaranties that the given entry can be played.
|
||||
|
||||
|
@ -352,7 +353,9 @@ class Source(ABC):
|
|||
:rtype: None
|
||||
"""
|
||||
await self.buffer(entry)
|
||||
await self.downloaded_files[entry.ident].ready.wait()
|
||||
dlfilesentry = self.downloaded_files[entry.ident]
|
||||
await dlfilesentry.ready.wait()
|
||||
return dlfilesentry.video, dlfilesentry.audio
|
||||
|
||||
async def get_missing_metadata(self, _entry: Entry) -> dict[str, Any]:
|
||||
"""
|
||||
|
|
|
@ -221,6 +221,7 @@ class YoutubeSource(Source):
|
|||
self.formatstring = (
|
||||
f"bestvideo[height<={self.max_res}]+" f"bestaudio/best[height<={self.max_res}]"
|
||||
)
|
||||
self.extra_mpv_options = {"ytdl-format": self.formatstring}
|
||||
self._yt_dlp = YoutubeDL(
|
||||
params={
|
||||
"paths": {"home": self.tmp_dir},
|
||||
|
|
38
typings/mpv.pyi
Normal file
38
typings/mpv.pyi
Normal file
|
@ -0,0 +1,38 @@
|
|||
from typing import Any, Callable, Iterable, Protocol
|
||||
|
||||
from PIL.Image import Image
|
||||
|
||||
class Unregisterable(Protocol):
|
||||
def unregister(self) -> None: ...
|
||||
|
||||
class ImageOverlay:
|
||||
overlay_id: int
|
||||
def remove(self) -> None: ...
|
||||
|
||||
class MPV:
|
||||
pause: bool
|
||||
keep_open: str
|
||||
image_display_duration: int
|
||||
sub_pos: int
|
||||
osd_width: str
|
||||
osd_height: str
|
||||
|
||||
def __init__(
|
||||
self, ytdl: bool, input_default_bindings: bool, input_vo_keyboard: bool, osc: bool
|
||||
) -> None: ...
|
||||
def terminate(self) -> None: ...
|
||||
def play(self, file: str) -> None: ...
|
||||
def playlist_append(self, file: str) -> None: ...
|
||||
def wait_for_property(self, property: str) -> None: ...
|
||||
def playlist_next(self) -> None: ...
|
||||
def audio_add(self, file: str) -> None: ...
|
||||
def wait_for_event(self, event: str) -> None: ...
|
||||
def python_stream(
|
||||
self, stream_name: str
|
||||
) -> Callable[[Callable[[], Iterable[bytes]]], Unregisterable]: ...
|
||||
def sub_add(self, file: str) -> None: ...
|
||||
def create_image_overlay(self, image: Image, pos: tuple[int, int]) -> ImageOverlay: ...
|
||||
def remove_overlay(self, overlay_id: int) -> None: ...
|
||||
def observe_property(self, property: str, callback: Callable[[str, Any], None]) -> None: ...
|
||||
def __setitem__(self, key: str, value: str) -> None: ...
|
||||
def __getitem__(self, key: str) -> str: ...
|
Loading…
Add table
Reference in a new issue