diff --git a/capture.py b/capture.py index f7f0f20..82c7131 100644 --- a/capture.py +++ b/capture.py @@ -1,6 +1,10 @@ import asyncio from collections.abc import AsyncIterator +from rich.console import Console + +console = Console() + async def _pipe_stream(source: asyncio.StreamReader, dest: asyncio.StreamWriter): """Forward data from streamlink stdout to ffmpeg stdin.""" @@ -17,6 +21,17 @@ async def _pipe_stream(source: asyncio.StreamReader, dest: asyncio.StreamWriter) dest.close() +async def _log_stderr(proc_name: str, stderr: asyncio.StreamReader): + """Read and display stderr from a subprocess.""" + while True: + line = await stderr.readline() + if not line: + break + text = line.decode("utf-8", errors="replace").rstrip() + if text: + console.print(f"[dim red][{proc_name}] {text}[/dim red]") + + async def capture_frames( channel: str, quality: str, interval: int ) -> AsyncIterator[bytes]: @@ -33,6 +48,7 @@ async def capture_frames( ffmpeg_cmd = [ "ffmpeg", + "-loglevel", "warning", "-i", "pipe:0", "-vf", f"fps=1/{interval}", "-f", "image2pipe", @@ -41,24 +57,43 @@ async def capture_frames( "pipe:1", ] + console.print("[dim]Starting streamlink...[/dim]") streamlink_proc = await asyncio.create_subprocess_exec( *streamlink_cmd, stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.DEVNULL, + stderr=asyncio.subprocess.PIPE, ) + # Wait a moment and check if streamlink started OK + await asyncio.sleep(2) + if streamlink_proc.returncode is not None: + stderr_out = await streamlink_proc.stderr.read() + raise RuntimeError( + f"streamlink exited with code {streamlink_proc.returncode}: " + f"{stderr_out.decode('utf-8', errors='replace')}" + ) + + console.print("[dim]Starting ffmpeg...[/dim]") ffmpeg_proc = await asyncio.create_subprocess_exec( *ffmpeg_cmd, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.DEVNULL, + stderr=asyncio.subprocess.PIPE, ) + # Log stderr from both processes + stderr_tasks = [ + asyncio.create_task(_log_stderr("streamlink", streamlink_proc.stderr)), + asyncio.create_task(_log_stderr("ffmpeg", ffmpeg_proc.stderr)), + ] + # Forward streamlink → ffmpeg in background pipe_task = asyncio.create_task( _pipe_stream(streamlink_proc.stdout, ffmpeg_proc.stdin) ) + console.print("[dim]Pipeline running, waiting for first frame...[/dim]") + try: buf = b"" while True: @@ -82,6 +117,8 @@ async def capture_frames( yield frame finally: pipe_task.cancel() + for t in stderr_tasks: + t.cancel() for proc in (ffmpeg_proc, streamlink_proc): try: proc.terminate() diff --git a/main.py b/main.py index 1efb33a..4b172c2 100644 --- a/main.py +++ b/main.py @@ -28,20 +28,26 @@ async def run(config) -> None: frame_number = 0 - async for frame_data in capture_frames( - config.channel, config.quality, config.interval - ): - frame_number += 1 - console.print(f"[dim]Captured frame #{frame_number}, analyzing...[/dim]") + try: + async for frame_data in capture_frames( + config.channel, config.quality, config.interval + ): + frame_number += 1 + console.print(f"[dim]Captured frame #{frame_number}, analyzing...[/dim]") - try: - description = await analyzer.analyze_frame(frame_data) - except Exception as e: - console.print(f"[bold red]Analysis error:[/bold red] {e}") - continue + try: + description = await analyzer.analyze_frame(frame_data) + except Exception as e: + console.print(f"[bold red]Analysis error:[/bold red] {e}") + continue - print_description(description, frame_number) - await log_description(config.log_file, description, frame_number) + print_description(description, frame_number) + await log_description(config.log_file, description, frame_number) + except RuntimeError as e: + console.print(f"[bold red]Error:[/bold red] {e}") + finally: + if frame_number == 0: + console.print("[bold yellow]No frames were captured.[/bold yellow]") def main() -> None: