import argparse import json import subprocess import sys import time from pathlib import Path ap = argparse.ArgumentParser() ap.add_argument("--input", action="append", default=[]) ap.add_argument("--outdir", required=True) ap.add_argument("--params", default="{}") a = ap.parse_args() p = json.loads(a.params) if not a.input: print("ERROR: needs one WAV/OGG input") sys.exit(1) root = Path(__file__).resolve().parents[3] rhubarb = root / "vendor" / "rhubarb" / "rhubarb" if not rhubarb.exists(): print("ERROR: rhubarb not installed — run scripts/install_rhubarb.sh") sys.exit(1) outdir = Path(a.outdir) out_json = outdir / (Path(a.input[0]).stem + ".visemes.json") cmd = [str(rhubarb), "-f", "json", "-o", str(out_json), a.input[0]] if p.get("shapes", "extended") == "basic": cmd += ["--extendedShapes", ""] dialog = (p.get("dialog") or "").strip() if dialog: tmp = outdir / "work" # subdir: only top-level outdir files register as assets tmp.mkdir(exist_ok=True) dialog_file = tmp / "dialog.txt" dialog_file.write_text(dialog + "\n") cmd += ["--dialogFile", str(dialog_file)] t0 = time.time() r = subprocess.run(cmd, capture_output=True, text=True) if r.stderr: print(r.stderr[-1500:], file=sys.stderr) if r.returncode != 0 or not out_json.exists(): print("ERROR: rhubarb failed") sys.exit(1) cues = json.loads(out_json.read_text())["mouthCues"] print(f"done in {time.time() - t0:.1f}s: {len(cues)} mouth cues, " f"{cues[-1]['end'] if cues else 0:.2f}s")