Spaces:
Running
Running
Download _build/build_media.py from RSIGame/rsigame-page: direct link, hf CLI and curl.
- Browser
- Download file 9.2 kB
-
https://huggingface.co/spaces/RSIGame/rsigame-page/resolve/main/_build/build_media.py
- Command line
-
hf download hf://spaces/RSIGame/rsigame-page/_build/build_media.py
-
curl -L -o build_media.py https://huggingface.co/spaces/RSIGame/rsigame-page/resolve/main/_build/build_media.py
9.2 kB
| """Build the page's media + data from the r0 baseline scoring runs. | |
| For every (arm, task) it writes, under MEDIA_OUT/baseline/<arm>/<task>/: | |
| highlight.mp4 ~10 s montage of the most active window of up to 4 demos (640x360, no audio) | |
| poster.webp the busiest frame, for the card before the video loads | |
| demos/<id>.mp4 every pass-1 demo, remuxed with faststart so it streams | |
| and into the page repo: | |
| static/data/games.json one row per game (what the gallery needs) | |
| static/data/details/<arm>/<task>.json per-requirement scores + judge rationales (loaded on open) | |
| Scores come from pass 1 of r0_scores (judge qwen38-27b); reward/M/D/V/A are the CSV means. | |
| python _build/build_media.py --arms godot_gpt phaser_gpt [--tasks a b] [--jobs 16] [--data-only] | |
| """ | |
| import argparse, csv, json, re, subprocess, sys | |
| from concurrent.futures import ProcessPoolExecutor | |
| from pathlib import Path | |
| import numpy as np | |
| from PIL import Image | |
| SCORES = Path("/storage/admin/wenyi/r0_scores") | |
| MEDIA_OUT = Path("/storage/admin/wenyi/evogame-page-media") | |
| PAGE = Path(__file__).resolve().parents[1] | |
| TASKS = {"godot": Path("/home/admin/wenyi/gamecraft-bench/tasks"), | |
| "phaser": Path("/home/admin/wenyi/gamecraft-bench-web/tasks-web")} | |
| ARMS = { | |
| "godot_gpt": {"engine": "Godot 4", "model": "GPT-5.5", "harness": "Codex CLI"}, | |
| "phaser_gpt": {"engine": "Phaser", "model": "GPT-5.5", "harness": "OpenGame"}, | |
| "godot_qwen": {"engine": "Godot 4", "model": "Qwen3.8-27B", "harness": ""}, | |
| "phaser_qwen": {"engine": "Phaser", "model": "Qwen3.8-27B", "harness": ""}, | |
| "godot_kimi": {"engine": "Godot 4", "model": "Kimi-K2.6", "harness": ""}, | |
| "godot_glm": {"engine": "Godot 4", "model": "GLM-5.3-Flash", "harness": ""}, | |
| } | |
| CATS = {"M": "Core Mechanics", "D": "Content Depth", "V": "Functional Visuals", "A": "Presentation & Art"} | |
| SEG_S, MAX_SEGS, FRAME_STEP_S = 2.5, 4, 0.5 | |
| def run(cmd): | |
| subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) | |
| def instruction_meta(engine_key, task): | |
| md = (TASKS[engine_key] / task / "instruction.md").read_text() | |
| title = re.search(r"^# (.+)$", md, re.M).group(1).strip() | |
| m = re.search(r"## Core Vision\s+(.+?)(?:\n## |\Z)", md, re.S) | |
| vision = re.sub(r"\s+", " ", m.group(1)).strip() if m else "" | |
| toml = (TASKS[engine_key] / task / "task.toml").read_text() | |
| d = re.search(r'^description\s*=\s*"(.*)"', toml, re.M) | |
| return title, (d.group(1) if d else ""), vision | |
| def padded_from(task_dir): | |
| """demo_id -> first padded frame index (frozen tail we should not show).""" | |
| p = task_dir / "tail_padding.json" | |
| if not p.exists(): | |
| return {} | |
| return {x["demo"]: x["from"] for x in json.loads(p.read_text()).get("padded", [])} | |
| def activity(frames): | |
| """Per-step visual change between consecutive 0.5 s frames.""" | |
| thumbs = [np.asarray(Image.open(f).convert("L").resize((64, 36)), dtype=np.float32) for f in frames] | |
| return [float(np.abs(a - b).mean()) for a, b in zip(thumbs, thumbs[1:])] | |
| def best_window(demo): | |
| """(start_s, score, busiest_frame) of the most active SEG_S window in the unpadded part.""" | |
| frames = sorted((demo["dir"] / "frames").glob("frame_*.png")) | |
| usable = demo["dur"] - 0.3 | |
| if demo["pad_from_frame"] is not None: | |
| usable = min(usable, demo["pad_from_frame"] / 30.0) | |
| n = int(usable / FRAME_STEP_S) | |
| frames = frames[: max(n, 2)] | |
| if len(frames) < 2: | |
| return 0.0, 0.0, frames[0] if frames else None | |
| act = activity(frames) | |
| w = max(1, int(SEG_S / FRAME_STEP_S)) | |
| sums = [sum(act[i:i + w]) for i in range(max(1, len(act) - w + 1))] | |
| i = int(np.argmax(sums)) | |
| start = min(i * FRAME_STEP_S, max(0.0, usable - SEG_S)) | |
| busiest = frames[i + 1 + int(np.argmax(act[i:i + w]))] if act[i:i + w] else frames[i] | |
| return start, sums[i], busiest | |
| def build_game(arm, task, data_only=False): | |
| tdir = SCORES / arm / task | |
| p1 = tdir / "p1" | |
| out = MEDIA_OUT / "baseline" / arm / task | |
| pads = padded_from(tdir) | |
| demos = [] | |
| if (p1 / "demos").is_dir(): | |
| for d in sorted((p1 / "demos").iterdir()): | |
| mp4 = d / f"{d.name}.mp4" | |
| if not mp4.exists(): | |
| continue | |
| dur = float(subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration", | |
| "-of", "csv=p=0", str(mp4)], capture_output=True, text=True).stdout or 0) | |
| demos.append({"id": d.name, "dir": d, "mp4": mp4, "dur": dur, "pad_from_frame": pads.get(d.name)}) | |
| has_media = bool(demos) | |
| if has_media and not (data_only and (out / "highlight.mp4").exists()): | |
| (out / "demos").mkdir(parents=True, exist_ok=True) | |
| for d in demos: | |
| run(["ffmpeg", "-y", "-i", str(d["mp4"]), "-c", "copy", "-an", "-movflags", "+faststart", | |
| str(out / "demos" / f"{d['id']}.mp4")]) | |
| wins = [(d, *best_window(d)) for d in demos] | |
| # the MAX_SEGS most active demos, shown in their original order | |
| picked = sorted(sorted(wins, key=lambda x: -x[2])[:MAX_SEGS], key=lambda x: x[0]["id"]) | |
| cmd, fl = ["ffmpeg", "-y"], [] | |
| for k, (d, start, _, _) in enumerate(picked): | |
| cmd += ["-ss", f"{start:.2f}", "-t", str(SEG_S), "-i", str(d["mp4"])] | |
| fl.append(f"[{k}:v]scale=640:360:force_original_aspect_ratio=decrease,pad=640:360:(ow-iw)/2:(oh-ih)/2," | |
| f"fps=24,setsar=1,format=yuv420p[v{k}]") | |
| fl.append("".join(f"[v{k}]" for k in range(len(picked))) + f"concat=n={len(picked)}:v=1:a=0[out]") | |
| cmd += ["-filter_complex", ";".join(fl), "-map", "[out]", "-c:v", "libx264", "-preset", "slow", | |
| "-crf", "30", "-movflags", "+faststart", "-an", str(out / "highlight.mp4")] | |
| run(cmd) | |
| busiest = max(wins, key=lambda x: x[2])[3] | |
| Image.open(busiest).convert("RGB").resize((640, 360)).save(out / "poster.webp", "WEBP", quality=72) | |
| return arm, task, [{"id": d["id"], "dur": round(d["dur"], 1)} for d in demos], has_media | |
| def details(arm, task): | |
| p1 = SCORES / arm / task / "p1" | |
| bpath = p1 / "breakdown.json" | |
| if not bpath.exists(): | |
| return None | |
| b = json.loads(bpath.read_text()) | |
| rationale = {} | |
| jl = p1 / "judge_log.json" | |
| if jl.exists(): | |
| for r in json.loads(jl.read_text()): | |
| rationale.setdefault(r["requirement_id"], {})[r["demo_id"]] = r.get("rationale", "") | |
| return { | |
| "reward_p1": b.get("reward"), "build_ok": b.get("build_ok"), "formula": b.get("formula"), | |
| "judge": b.get("judge", {}).get("model"), | |
| "requirements": [{ | |
| "id": r["id"], "cat": CATS.get(r["id"][0], ""), "description": r["description"], | |
| "score": r.get("aggregated"), "agg": r.get("agg"), | |
| "per_demo": r.get("per_demo", {}), "rationale": rationale.get(r["id"], {}), | |
| } for r in b.get("requirements", [])], | |
| } | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--arms", nargs="+", default=["godot_gpt", "phaser_gpt"]) | |
| ap.add_argument("--tasks", nargs="*") | |
| ap.add_argument("--jobs", type=int, default=16) | |
| ap.add_argument("--data-only", action="store_true", help="skip media that already exists") | |
| a = ap.parse_args() | |
| rows = {} | |
| for arm in a.arms: | |
| for r in csv.DictReader(open(SCORES / f"{arm}.csv")): | |
| if not a.tasks or r["task"] in a.tasks: | |
| rows[(arm, r["task"])] = r | |
| media = {} | |
| with ProcessPoolExecutor(a.jobs) as ex: | |
| futs = [ex.submit(build_game, arm, task, a.data_only) for arm, task in rows] | |
| for f in futs: | |
| try: | |
| arm, task, demos, has_media = f.result() | |
| media[(arm, task)] = (demos, has_media) | |
| print("ok", arm, task, len(demos), flush=True) | |
| except subprocess.CalledProcessError as e: | |
| print("FAIL", e.cmd[-1], e.stderr.decode()[-400:], file=sys.stderr, flush=True) | |
| data = PAGE / "static" / "data" | |
| games = [] | |
| for (arm, task), r in rows.items(): | |
| if (arm, task) not in media: | |
| continue | |
| engine_key = arm.split("_")[0] | |
| title, blurb, vision = instruction_meta(engine_key, task) | |
| demos, has_media = media[(arm, task)] | |
| det = details(arm, task) | |
| if det: | |
| (data / "details" / arm).mkdir(parents=True, exist_ok=True) | |
| (data / "details" / arm / f"{task}.json").write_text(json.dumps(det, ensure_ascii=False)) | |
| f = lambda k: round(float(r[k]), 4) | |
| games.append({ | |
| "arm": arm, "task": task, **ARMS[arm], "genre": task.split("-")[0], | |
| "title": title, "blurb": blurb, "vision": vision, | |
| "reward": f("reward"), "build": f("BUILD"), "M": f("M"), "D": f("D"), "V": f("V"), "A": f("A"), | |
| "media": f"baseline/{arm}/{task}" if has_media else None, "demos": demos, | |
| "repaired": None, | |
| }) | |
| games.sort(key=lambda g: (g["arm"], -g["reward"])) | |
| data.mkdir(parents=True, exist_ok=True) | |
| (data / "games.json").write_text(json.dumps(games, ensure_ascii=False, indent=0)) | |
| print(f"wrote {len(games)} games") | |
| if __name__ == "__main__": | |
| main() | |