diff options
Diffstat (limited to 'packages/meshbay-node/src')
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/media_probe.py | 17 | ||||
| -rw-r--r-- | packages/meshbay-node/src/meshbay_node/transport/webrtc/media_tools.py | 7 |
2 files changed, 23 insertions, 1 deletions
diff --git a/packages/meshbay-node/src/meshbay_node/media_probe.py b/packages/meshbay-node/src/meshbay_node/media_probe.py index a6267d9..e9090ea 100644 --- a/packages/meshbay-node/src/meshbay_node/media_probe.py +++ b/packages/meshbay-node/src/meshbay_node/media_probe.py @@ -10,6 +10,12 @@ import asyncio import json from dataclasses import dataclass, field +# How long ffprobe may take over one file's headers. The file is a member's +# upload as often as the operator's own: one that keeps ffprobe busy must not +# keep the stream request, the subtitle request or the enrichment slot that +# asked for it — the same bound the index-time enrichment already put around it. +FFPROBE_TIMEOUT_SECS = 30 + _H264_PROFILES = {"Baseline": "42", "Main": "4d", "High": "64", "High 10": "6e"} # Source video codecs whose MSE codec string is real but which no mainstream @@ -158,7 +164,16 @@ async def probe_video(path: str) -> VideoProbe: "-of", "json", path, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - stdout, _ = await proc.communicate() + try: + stdout, _ = await asyncio.wait_for(proc.communicate(), FFPROBE_TIMEOUT_SECS) + except (TimeoutError, asyncio.CancelledError) as e: + # Killed, not abandoned: a cancelled wait leaves the process running, + # and a caller's own timeout (enrich.py) cancels exactly this wait. + proc.kill() + await proc.wait() + if isinstance(e, asyncio.CancelledError): + raise + raise RuntimeError(f"ffprobe timed out after {FFPROBE_TIMEOUT_SECS}s") from None info = json.loads(stdout) duration = float(info.get("format", {}).get("duration", 0)) diff --git a/packages/meshbay-node/src/meshbay_node/transport/webrtc/media_tools.py b/packages/meshbay-node/src/meshbay_node/transport/webrtc/media_tools.py index 7ecb0a5..22c1690 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/webrtc/media_tools.py +++ b/packages/meshbay-node/src/meshbay_node/transport/webrtc/media_tools.py @@ -165,6 +165,7 @@ async def _seek_lands_at(file_path: Path, t: float, map_args: list[str]) -> floa fd, tmp_name = tempfile.mkstemp(suffix=".mp4") os.close(fd) tmp_path = Path(tmp_name) + proc = probe = None try: proc = await asyncio.create_subprocess_exec( platform.ffmpeg_cmd(), "-hide_banner", "-loglevel", "error", "-y", @@ -189,6 +190,12 @@ async def _seek_lands_at(file_path: Path, t: float, map_args: list[str]) -> floa log.warning("stream: seek probe failed at %.1fs: %r", t, e) return None finally: + # A timed-out wait leaves its process running; it is stopped here, not + # left to finish a seek nobody is waiting for. + for p in (proc, probe): + if p is not None and p.returncode is None: + p.kill() + await p.wait() await _discard_scratch(tmp_path) text = stdout.decode(errors="replace").strip().rstrip(",") try: |