[laneC] /rhubarb viseme endpoint (ffmpeg-style 503 gating)
GET /rhubarb?path= -> rhubarb -f json -> mouthCues for Lane A's viseme lane; 503 when the binary is absent (not installed on ultra yet). Path-guarded. test_server.py: +503 gating assert (9 groups green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a15612eed0
commit
fb59601cc3
@ -45,6 +45,7 @@ MEDIA_TYPES = {
|
|||||||
SCENES.mkdir(parents=True, exist_ok=True)
|
SCENES.mkdir(parents=True, exist_ok=True)
|
||||||
RENDERS.mkdir(parents=True, exist_ok=True)
|
RENDERS.mkdir(parents=True, exist_ok=True)
|
||||||
HAVE_FFMPEG = shutil.which("ffmpeg") is not None
|
HAVE_FFMPEG = shutil.which("ffmpeg") is not None
|
||||||
|
HAVE_RHUBARB = shutil.which("rhubarb") is not None
|
||||||
|
|
||||||
# MODELBEAST proxy (M4) — off unless SCENEGOD_MB=1. Token read from disk at
|
# MODELBEAST proxy (M4) — off unless SCENEGOD_MB=1. Token read from disk at
|
||||||
# request time, never logged. Contract mirrors MESHGOD meshgod/ops.py.
|
# request time, never logged. Contract mirrors MESHGOD meshgod/ops.py.
|
||||||
@ -347,6 +348,23 @@ def render_out(rid: str):
|
|||||||
return FileResponse(out, media_type="video/mp4")
|
return FileResponse(out, media_type="video/mp4")
|
||||||
|
|
||||||
|
|
||||||
|
# ---- viseme lip-sync (M4) — feeds Lane A's viseme lane ----
|
||||||
|
@app.get("/rhubarb")
|
||||||
|
def rhubarb(path: str):
|
||||||
|
"""Rhubarb Lip Sync on an asset audio file -> {metadata, mouthCues:[...]}.
|
||||||
|
503 if rhubarb absent (same pattern as ffmpeg). ponytail: synchronous —
|
||||||
|
VO clips are short; make it a render-style session if that ever bites."""
|
||||||
|
if not HAVE_RHUBARB:
|
||||||
|
raise HTTPException(503, "rhubarb not installed (see github.com/DanielSWolf/rhubarb-lip-sync)")
|
||||||
|
f = safe_under(ASSETS, path)
|
||||||
|
if not f.is_file():
|
||||||
|
raise HTTPException(404, "not found")
|
||||||
|
p = subprocess.run(["rhubarb", "-f", "json", str(f)], capture_output=True, text=True)
|
||||||
|
if p.returncode != 0:
|
||||||
|
raise HTTPException(500, f"rhubarb failed: {p.stderr[-500:]}")
|
||||||
|
return json.loads(p.stdout)
|
||||||
|
|
||||||
|
|
||||||
# ---- MODELBEAST proxy (M4, gated) ----
|
# ---- MODELBEAST proxy (M4, gated) ----
|
||||||
def _mb_post(path: str, data: bytes, content_type: str) -> dict:
|
def _mb_post(path: str, data: bytes, content_type: str) -> dict:
|
||||||
req = urllib.request.Request(MB_HOST + path, data=data, method="POST",
|
req = urllib.request.Request(MB_HOST + path, data=data, method="POST",
|
||||||
|
|||||||
@ -176,6 +176,11 @@ def main():
|
|||||||
else:
|
else:
|
||||||
print("(render test skipped — ffmpeg/ffprobe not found)")
|
print("(render test skipped — ffmpeg/ffprobe not found)")
|
||||||
|
|
||||||
|
# --- rhubarb: 503 when the binary is absent (happy path needs rhubarb) ---
|
||||||
|
if not shutil.which("rhubarb"):
|
||||||
|
assert req("GET", "/rhubarb?path=audio/x.wav")[0] == 503
|
||||||
|
n += 1
|
||||||
|
|
||||||
# --- MODELBEAST proxy: gating + two-step contract (mock MB, no farm jobs) ---
|
# --- MODELBEAST proxy: gating + two-step contract (mock MB, no farm jobs) ---
|
||||||
assert req("POST", "/mb/submit", json.dumps({"operator": "tts"}))[0] == 503 # gated off
|
assert req("POST", "/mb/submit", json.dumps({"operator": "tts"}))[0] == 503 # gated off
|
||||||
n += 1
|
n += 1
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user