Show streamlink/ffmpeg stderr for debugging, handle startup errors
- stderr from both processes now displayed in console - streamlink checked for early exit before starting ffmpeg - RuntimeError shown to user if stream unavailable Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
41
capture.py
41
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()
|
||||
|
||||
30
main.py
30
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:
|
||||
|
||||
Reference in New Issue
Block a user