Add operator user agent and video recorder
This commit is contained in:
128
scripts/record_usage_video.py
Executable file
128
scripts/record_usage_video.py
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import signal
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_OUTPUT = ROOT.parent / "使用视频录制" / "seg_data_server_net_usage.mp4"
|
||||
PAGES = ["overview", "datasets", "training", "inference", "results", "system", "agents"]
|
||||
|
||||
|
||||
def _tool(*names: str) -> str:
|
||||
for name in names:
|
||||
found = shutil.which(name)
|
||||
if found:
|
||||
return found
|
||||
raise SystemExit(f"missing required tool: {'/'.join(names)}")
|
||||
|
||||
|
||||
def _run_quiet(command: list[str], timeout: int) -> None:
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True,
|
||||
)
|
||||
try:
|
||||
exit_code = process.wait(timeout=timeout)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
try:
|
||||
os.killpg(process.pid, signal.SIGTERM)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
try:
|
||||
os.killpg(process.pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
process.wait()
|
||||
raise TimeoutError(f"command timed out: {' '.join(command[:4])}") from exc
|
||||
if exit_code != 0:
|
||||
raise subprocess.CalledProcessError(exit_code, command)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Record the Seg Data Server Net UI as a page walkthrough video.")
|
||||
parser.add_argument("--base-url", default="http://127.0.0.1:5173", help="running frontend URL")
|
||||
parser.add_argument("--output", default=str(DEFAULT_OUTPUT), help="target mp4 path")
|
||||
parser.add_argument("--seconds", type=int, default=4, help="seconds to hold each page")
|
||||
parser.add_argument("--width", type=int, default=1440)
|
||||
parser.add_argument("--height", type=int, default=1000)
|
||||
parser.add_argument("--wait-ms", type=int, default=3500, help="virtual browser wait per page before screenshot")
|
||||
parser.add_argument("--page-timeout", type=int, default=25, help="seconds before a browser screenshot is killed")
|
||||
args = parser.parse_args()
|
||||
|
||||
chrome = _tool("google-chrome", "chromium", "chromium-browser")
|
||||
ffmpeg = _tool("ffmpeg")
|
||||
output = Path(args.output).expanduser().resolve()
|
||||
frames = output.parent / "frames"
|
||||
frames.mkdir(parents=True, exist_ok=True)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for page in PAGES:
|
||||
screenshot = frames / f"{page}.png"
|
||||
user_data_dir = Path(tempfile.mkdtemp(prefix=f"seg-chrome-{page}-"))
|
||||
command = [
|
||||
chrome,
|
||||
"--headless=new",
|
||||
"--disable-gpu",
|
||||
"--no-sandbox",
|
||||
"--disable-background-networking",
|
||||
"--disable-extensions",
|
||||
"--disable-sync",
|
||||
"--disable-crash-reporter",
|
||||
"--disable-features=OptimizationGuideModelDownloading,MediaRouter",
|
||||
f"--user-data-dir={user_data_dir}",
|
||||
f"--window-size={args.width},{args.height}",
|
||||
"--run-all-compositor-stages-before-draw",
|
||||
f"--virtual-time-budget={args.wait_ms}",
|
||||
f"--screenshot={screenshot}",
|
||||
f"{args.base_url.rstrip('/')}/#{page}",
|
||||
]
|
||||
try:
|
||||
_run_quiet(command, timeout=args.page_timeout)
|
||||
except TimeoutError:
|
||||
if not screenshot.exists() or screenshot.stat().st_size == 0:
|
||||
raise
|
||||
|
||||
fd, concat_name = tempfile.mkstemp(prefix="seg-usage-", suffix=".txt")
|
||||
os.close(fd)
|
||||
concat = Path(concat_name)
|
||||
with concat.open("w", encoding="utf-8") as handle:
|
||||
for page in PAGES:
|
||||
handle.write(f"file '{(frames / f'{page}.png').resolve()}'\n")
|
||||
handle.write(f"duration {args.seconds}\n")
|
||||
handle.write(f"file '{(frames / f'{PAGES[-1]}.png').resolve()}'\n")
|
||||
|
||||
_run_quiet(
|
||||
[
|
||||
ffmpeg,
|
||||
"-y",
|
||||
"-f",
|
||||
"concat",
|
||||
"-safe",
|
||||
"0",
|
||||
"-i",
|
||||
str(concat),
|
||||
"-vf",
|
||||
"fps=30,format=yuv420p",
|
||||
"-movflags",
|
||||
"+faststart",
|
||||
str(output),
|
||||
],
|
||||
timeout=120,
|
||||
)
|
||||
print(output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -10,6 +10,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "backend"))
|
||||
|
||||
from app.agents.evaluation_agent import evaluate_project # noqa: E402
|
||||
from app.agents.user_agent import run_user_agent # noqa: E402
|
||||
from app.agents.validation_agent import validate_project # noqa: E402
|
||||
|
||||
|
||||
@@ -20,6 +21,7 @@ def main() -> None:
|
||||
parser.add_argument("--acceptance", action="store_true", help="run the lightweight live acceptance smoke")
|
||||
parser.add_argument("--real", action="store_true", help="run real workspace data acceptance through the live backend")
|
||||
parser.add_argument("--real-train", action="store_true", help="run a short real workspace YOLO train/predict/heatmap acceptance")
|
||||
parser.add_argument("--user", action="store_true", help="run the operator-style user agent on synthetic open data")
|
||||
parser.add_argument("--no-deep", action="store_true", help="skip synthetic deep training acceptance")
|
||||
parser.add_argument("--out", default="var/agent_reports/latest.json")
|
||||
args = parser.parse_args()
|
||||
@@ -34,11 +36,13 @@ def main() -> None:
|
||||
run_deep=not args.no_deep,
|
||||
),
|
||||
}
|
||||
if args.user:
|
||||
report["user"] = run_user_agent()
|
||||
out = ROOT / args.out
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
if not report["evaluation"]["passed"] or not report["validation"]["passed"]:
|
||||
if not report["evaluation"]["passed"] or not report["validation"]["passed"] or (args.user and not report["user"]["passed"]):
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user