VGen2 / scripts /compare /summarize_compare.py
ouzhang57's picture
Upload folder using huggingface_hub (part 4)
ce3c376 verified
Raw
History Blame Contribute Delete
6.62 kB
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import shutil
import subprocess
from pathlib import Path
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Summarize a prepared or completed poetry compare run.")
parser.add_argument("run_root", nargs="?", type=Path, help="Compare run directory. Defaults to the latest run under outputs/compare_poetry.")
parser.add_argument("--compare-root", type=Path, default=Path(__file__).resolve().parents[2] / "outputs" / "compare_poetry")
parser.add_argument("--json", action="store_true", help="Print JSON instead of a text table.")
parser.add_argument("--ffprobe-timeout", type=float, default=8.0)
return parser.parse_args()
def find_latest_run(compare_root: Path) -> Path:
candidates = [path for path in compare_root.iterdir() if path.is_dir()]
if not candidates:
raise FileNotFoundError(f"No compare runs found under {compare_root}")
return max(candidates, key=lambda path: path.stat().st_mtime)
def load_json(path: Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))
def human_size(num_bytes: int | None) -> str:
if num_bytes is None:
return "-"
value = float(num_bytes)
for unit in ["B", "KB", "MB", "GB", "TB"]:
if value < 1024.0 or unit == "TB":
return f"{value:.1f}{unit}"
value /= 1024.0
return f"{num_bytes}B"
def probe_duration(path: Path, timeout: float) -> float | None:
if not path.is_file() or shutil.which("ffprobe") is None:
return None
command = [
"ffprobe",
"-v",
"error",
"-show_entries",
"format=duration",
"-of",
"default=noprint_wrappers=1:nokey=1",
str(path),
]
try:
result = subprocess.run(command, capture_output=True, text=True, check=True, timeout=timeout)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError):
return None
text = result.stdout.strip()
if not text:
return None
try:
return float(text)
except ValueError:
return None
def collect_row(run_dir: Path, ffprobe_timeout: float) -> dict:
spec_path = run_dir / "spec.json"
spec = load_json(spec_path) if spec_path.is_file() else {}
planner = str(spec.get("planner") or run_dir.name.split("__", 1)[0])
backend = str(spec.get("backend") or run_dir.name.split("__", 1)[-1])
shared_plan_json = Path(spec.get("shared_plan_json")) if spec.get("shared_plan_json") else None
plan_path = run_dir / "plan.json"
final_video = run_dir / "final_poetry_teaching.mp4"
subtitled_video = run_dir / "final_poetry_teaching_subtitled.mp4"
launch_path = run_dir / "launch.sh"
segment_videos = sorted((run_dir / "videos").glob("segment_*.mp4"))
plan_segments = None
if plan_path.is_file():
try:
plan_segments = len(load_json(plan_path).get("segments", []))
except Exception:
plan_segments = None
if final_video.is_file():
status = "done"
elif plan_path.is_file() or segment_videos:
status = "partial"
elif launch_path.is_file():
status = "prepared"
else:
status = "missing"
return {
"planner": planner,
"image_backend": str(spec.get("image_backend") or "-"),
"backend": backend,
"status": status,
"shared_plan_ready": bool(shared_plan_json and shared_plan_json.is_file()),
"plan_ready": plan_path.is_file(),
"plan_segments": plan_segments,
"segment_videos": len(segment_videos),
"final_video": str(final_video) if final_video.is_file() else None,
"final_video_size_bytes": final_video.stat().st_size if final_video.is_file() else None,
"final_video_duration_seconds": probe_duration(final_video, ffprobe_timeout),
"subtitled_video": str(subtitled_video) if subtitled_video.is_file() else None,
"run_dir": str(run_dir),
}
def print_table(rows: list[dict], run_root: Path, manifest: dict | None) -> None:
print(f"Compare run: {run_root}")
if manifest:
print(f"Experiment JSON: {manifest.get('experiment_json', '-')}")
print(f"Requested backends: {', '.join(manifest.get('backends_requested', []))}")
print(f"Planner variants: {', '.join(manifest.get('planner_variants', []))}")
print(f"Image backend: {manifest.get('image_backend', '-')}")
print(f"Persistent video backends: {manifest.get('persistent_video_backends')}")
print(f"Dry run: {manifest.get('dry_run')}")
headers = ["planner", "backend", "image", "status", "shared_plan", "plan", "segments", "duration", "size"]
table = []
for row in rows:
duration = row["final_video_duration_seconds"]
duration_text = f"{duration:.1f}s" if duration is not None else "-"
table.append([
row["planner"],
row["backend"],
row["image_backend"],
row["status"],
"yes" if row["shared_plan_ready"] else "no",
"yes" if row["plan_ready"] else "no",
str(row["segment_videos"]),
duration_text,
human_size(row["final_video_size_bytes"]),
])
widths = [len(header) for header in headers]
for line in table:
for idx, cell in enumerate(line):
widths[idx] = max(widths[idx], len(cell))
header_line = " ".join(header.ljust(widths[idx]) for idx, header in enumerate(headers))
print(header_line)
print(" ".join("-" * width for width in widths))
for line, row in zip(table, rows):
print(" ".join(cell.ljust(widths[idx]) for idx, cell in enumerate(line)))
if row["final_video"]:
print(f" final: {row['final_video']}")
else:
print(f" run: {row['run_dir']}")
def main() -> None:
args = parse_args()
run_root = args.run_root.resolve() if args.run_root else find_latest_run(args.compare_root.resolve())
manifest_path = run_root / "manifest.json"
manifest = load_json(manifest_path) if manifest_path.is_file() else None
rows = []
for run_dir in sorted(path for path in run_root.iterdir() if path.is_dir() and path.name != "shared_plan"):
rows.append(collect_row(run_dir, args.ffprobe_timeout))
if args.json:
print(json.dumps({"run_root": str(run_root), "manifest": manifest, "rows": rows}, ensure_ascii=False, indent=2))
else:
print_table(rows, run_root, manifest)
if __name__ == "__main__":
main()