From 15cc8f8147f8e013a44f7f3f892096e9f8681e35 Mon Sep 17 00:00:00 2001 From: Christoph Stahl Date: Wed, 9 Oct 2024 18:05:29 +0200 Subject: [PATCH] Working in a buggy state... but more or less working --- syng/client.py | 28 +++++++--------------------- syng/player_libmpv.py | 25 +++++++++++++++++-------- syng/sources/source.py | 18 +++++++++++------- syng/sources/youtube.py | 26 ++++++++++++++------------ 4 files changed, 49 insertions(+), 48 deletions(-) diff --git a/syng/client.py b/syng/client.py index f3a6e42..7610cd9 100644 --- a/syng/client.py +++ b/syng/client.py @@ -37,6 +37,8 @@ import engineio from PIL import Image from yaml import load, Loader +from syng.player_libmpv import Player + from . import jsonencoder from .entry import Entry from .sources import configure_sources, Source @@ -121,6 +123,7 @@ class Client: self.sources = configure_sources(config["sources"]) self.state = State() self.currentLock = asyncio.Semaphore(0) + self.player = Player() self.register_handlers() def register_handlers(self) -> None: @@ -244,26 +247,7 @@ class Client: :type entry: :py:class:`Entry` :rtype: None """ - background = Image.new("RGB", (1280, 720)) - subtitle: str = f"""1 -00:00:00,00 --> 00:05:00,00 -{entry.artist} - {entry.title} -{entry.performer}""" - with tempfile.NamedTemporaryFile() as tmpfile: - background.save(tmpfile, "png") - process = await asyncio.create_subprocess_exec( - "mpv", - tmpfile.name, - f"--image-display-duration={self.state.config['preview_duration']}", - "--sub-pos=50", - "--sub-file=-", - "--fullscreen", - self.state.config["mpv_options"], - stdin=asyncio.subprocess.PIPE, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - await process.communicate(subtitle.encode()) + self.player.queue_next(entry) async def handle_play(self, data: dict[str, Any]) -> None: """ @@ -293,7 +277,9 @@ class Client: 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.state.config["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 diff --git a/syng/player_libmpv.py b/syng/player_libmpv.py index bc80080..b86a2f8 100644 --- a/syng/player_libmpv.py +++ b/syng/player_libmpv.py @@ -1,5 +1,6 @@ +import sys import tempfile -from typing import Optional +from typing import Iterable, Optional from qrcode.main import QRCode import mpv import os @@ -64,16 +65,23 @@ class Player: def queue_next(self, entry: Entry): self.play_image(f"{__dirname__}/static/background20perc.png", 3) - subtitle: str = f"""1 + + frame = sys._getframe() + stream_name = f"__python_mpv_play_generator_{hash(frame)}" + + @self.mpv.python_stream(stream_name) + def preview() -> Iterable[bytes]: + subtitle: str = f"""1 00:00:00,00 --> 00:05:00,00 {entry.artist} - {entry.title} {entry.performer}""" - with tempfile.NamedTemporaryFile(delete=False) as tmpfile: - print(tmpfile.name) - tmpfile.write(subtitle.encode()) - tmpfile.flush() - self.mpv.sub_pos = 50 - self.mpv.sub_add(tmpfile.name) + yield subtitle.encode() + preview.unregister() + + self.mpv.sub_pos = 50 + self.mpv.sub_add(f"python://{stream_name}") + + self.mpv.wait_for_property("eof-reached") def play_image(self, image: str, duration: int): self.mpv.image_display_duration = duration @@ -86,3 +94,4 @@ class Player: self.mpv.pause = True self.mpv.play(video) self.mpv.pause = False + self.mpv.wait_for_property("eof-reached") diff --git a/syng/sources/source.py b/syng/sources/source.py index c7c04e7..b65e15e 100644 --- a/syng/sources/source.py +++ b/syng/sources/source.py @@ -21,6 +21,8 @@ from typing import Tuple from typing import Type from abc import ABC, abstractmethod +from syng.player_libmpv import Player + from ..log import logger from ..entry import Entry @@ -271,7 +273,7 @@ class Source(ABC): self.downloaded_files[entry.ident].ready.set() - async def play(self, entry: Entry, mpv_options: str) -> None: + async def play(self, entry: Entry, player: Player, mpv_options: str) -> None: """ Play the entry. @@ -302,13 +304,15 @@ class Source(ABC): else self.extra_mpv_arguments ) - self.player = await self.play_mpv( - self.downloaded_files[entry.ident].video, - self.downloaded_files[entry.ident].audio, - *extra_options, + # self.player = await self.play_mpv( + # self.downloaded_files[entry.ident].video, + # self.downloaded_files[entry.ident].audio, + # *extra_options, + # ) + player.play( + self.downloaded_files[entry.ident].video, self.downloaded_files[entry.ident].audio ) - await self.player.communicate() - await self.player.wait() + # await self.player.wait() self.player = None if self._skip_next: self._skip_next = False diff --git a/syng/sources/youtube.py b/syng/sources/youtube.py index a061b9b..3df32e0 100644 --- a/syng/sources/youtube.py +++ b/syng/sources/youtube.py @@ -18,6 +18,8 @@ from yt_dlp import YoutubeDL from yt_dlp.utils import DownloadError from platformdirs import user_cache_dir +from syng.player_libmpv import Player + from ..entry import Entry from ..result import Result @@ -236,7 +238,7 @@ class YoutubeSource(Source): """ return {"channels": self.channels} - async def play(self, entry: Entry, mpv_options: str) -> None: + async def play(self, entry: Entry, player: Player, mpv_options: str) -> None: """ Play the given entry. @@ -252,18 +254,18 @@ class YoutubeSource(Source): :rtype: None """ if self.start_streaming and not self.downloaded_files[entry.ident].complete: - self.player = await self.play_mpv( - entry.ident, - None, - "--script-opts=ytdl_hook-ytdl_path=yt-dlp,ytdl_hook-exclude='%.pls$'", - f"--ytdl-format={self.formatstring}", - "--fullscreen", - mpv_options, - ) - await self.player.communicate() - await self.player.wait() + # self.player = await self.play_mpv( + # entry.ident, + # None, + # "--script-opts=ytdl_hook-ytdl_path=yt-dlp,ytdl_hook-exclude='%.pls$'", + # f"--ytdl-format={self.formatstring}", + # "--fullscreen", + # mpv_options, + # ) + # await self.player.wait() + player.play(entry.ident) else: - await super().play(entry, mpv_options) + await super().play(entry, player, mpv_options) async def get_entry( self,