"""Spawn reference bundle child processes (main.py, visualize_result_video.py).""" from __future__ import annotations import os import signal import subprocess import sys from pathlib import Path from loguru import logger from app.algo_host.bundle import load_reference_default_config, resolve_bundle_relative_path from app.algo_host.cjk_font import resolve_cjk_font_path from app.algo_host.transcode import VISUALIZATION_MAX_WIDTH def build_reference_env() -> dict[str, str]: env = os.environ.copy() env["PYTHONFAULTHANDLER"] = "1" env["PYTHONUNBUFFERED"] = "1" return env def build_batch_main_command(*, bundle_dir: Path, config_path: Path) -> list[str]: return [ "uv", "run", "python", "-X", "faulthandler", str(bundle_dir / "main.py"), "--config", str(config_path), ] def build_visualization_command( *, bundle_dir: Path, video_path: Path, result_path: Path, output_video_path: Path, ) -> list[str]: cfg = load_reference_default_config(bundle_dir) weights = cfg.get("weights") if isinstance(cfg.get("weights"), dict) else {} device_cfg = cfg.get("device") if isinstance(cfg.get("device"), dict) else {} hand_raw = str((weights or {}).get("hand") or "weights/hand_detect.pt").strip() hand_model = resolve_bundle_relative_path(bundle_dir, hand_raw) cmd = [ sys.executable, "-X", "faulthandler", str((bundle_dir / "visualize_result_video.py").resolve()), "--video", str(video_path.resolve()), "--result-txt", str(result_path.resolve()), "--hand-model", str(hand_model), "--out-video", str(output_video_path.resolve()), "--device", str(device_cfg.get("type") or "cuda"), "--max-width", str(VISUALIZATION_MAX_WIDTH), ] font_path = resolve_cjk_font_path() if font_path is not None: cmd.extend(["--font-path", str(font_path)]) return cmd def _signal_name(signum: int) -> str: try: return signal.Signals(signum).name except ValueError: return f"signal {signum}" def describe_batch_returncode(returncode: int) -> str: if returncode < 0: signum = -returncode return f"terminated by {_signal_name(signum)} ({signum})" if returncode > 128: wrapped = returncode - 256 if wrapped < 0: signum = -wrapped return f"exit={returncode} (possibly propagated {wrapped}/{_signal_name(signum)})" return f"exit={returncode}" def format_batch_failure( returncode: int, *, stdout: str, stderr: str, work_dir: Path, output_path: Path, ) -> str: chunks: list[str] = [ describe_batch_returncode(returncode), f"work_dir={work_dir}", f"output={output_path}", ] stdout = stdout.strip() stderr = stderr.strip() if stdout: chunks.append(f"stdout:\n{stdout[-3000:]}") if stderr: chunks.append(f"stderr:\n{stderr[-3000:]}") return "\n".join(chunks) def _log_subprocess_output(prefix: str, stdout: str, stderr: str, *, max_lines: int = 40) -> None: for label, text in (("stdout", stdout), ("stderr", stderr)): lines = [ln for ln in (text or "").splitlines() if ln.strip()] if not lines: continue tail = lines[-max_lines:] if len(lines) > max_lines else lines for line in tail: logger.info("{} {}", prefix, line) def run_subprocess( cmd: list[str], *, cwd: Path, work_dir: Path, output_path: Path, log_label: str, ) -> None: proc = subprocess.run( cmd, cwd=str(cwd), check=False, text=True, capture_output=True, env=build_reference_env(), ) if proc.returncode != 0: msg = format_batch_failure( proc.returncode, stdout=proc.stdout or "", stderr=proc.stderr or "", work_dir=work_dir, output_path=output_path, ) raise RuntimeError(f"{log_label} failed {msg}") _log_subprocess_output(log_label, proc.stdout or "", proc.stderr or "") def run_batch_main(*, bundle_dir: Path, config_path: Path, work_dir: Path, output_path: Path) -> None: cmd = build_batch_main_command(bundle_dir=bundle_dir, config_path=config_path) logger.info("reference batch starting: {}", " ".join(cmd)) run_subprocess( cmd, cwd=bundle_dir, work_dir=work_dir, output_path=output_path, log_label="reference batch", ) def run_visualization_script( *, bundle_dir: Path, video_path: Path, result_path: Path, raw_output_video_path: Path, ) -> None: cmd = build_visualization_command( bundle_dir=bundle_dir, video_path=video_path, result_path=result_path, output_video_path=raw_output_video_path, ) logger.info("reference visualization starting: {}", " ".join(cmd)) run_subprocess( cmd, cwd=bundle_dir, work_dir=raw_output_video_path.parent, output_path=raw_output_video_path, log_label="visualize", )