modelbeast/server/operators/lipsync_local/run.py
type-two b7e113ca5b wan/lipsync: scratch dirs in /tmp so they don't register as assets
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:57:54 +10:00

49 lines
1.5 KiB
Python

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:
import tempfile
dialog_file = Path(tempfile.mkdtemp(prefix="rhubarb_")) / "dialog.txt" # outside outdir: don't register as asset
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")