feat(short): la paleta de audio viva — GET /audio en el contrato, el prompt y el bucle de edición
Build & Deploy ResearchOwl / build-and-push (push) Successful in 9s

shortsmith ya compone la banda sonora (sonar); ahora publica una paleta
(pulse, static) y este repo la consume en vivo: el prompt la ofrece con sus
notas de mood, validate_spec la usa como fuente de verdad para audio.preset,
y editar el preset en /short_spec es la manera gratis de escucharlas. Sin
/audio (404 o caída) todo cae a la paleta base y nada deja de renderizar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
ChemaVX
2026-08-06 14:41:27 +00:00
co-authored by Claude Fable 5
parent 1fd0c1b3d6
commit 93a506b636
11 changed files with 259 additions and 34 deletions
+58
View File
@@ -59,6 +59,14 @@ class FakeProcessor:
return self.chunks
PRESETS = {
"sonar": "low drone — the case-file mood",
"pulse": "sub-bass heartbeat — debunks",
"static": "shortwave static — document drops",
"none": "digital silence",
}
class FakeClient:
"""shortsmith de mentira. `fail_at` decide dónde se rompe."""
@@ -73,6 +81,11 @@ class FakeClient:
raise ShortsmithUnavailable("no hay nadie al otro lado")
return TEMPLATES
async def audio_presets(self, refresh=False):
if self.fail_at == "audio":
raise ShortsmithUnavailable("sin /audio")
return dict(PRESETS)
async def render(self, spec):
if self.fail_at == "render":
raise ShortsmithUnavailable("conexión rechazada")
@@ -450,3 +463,48 @@ async def test_rerender_respects_the_kill_switch(tmp_path, monkeypatch):
with pytest.raises(ShortsDisabled):
await p.rerender(153, json.loads(json.dumps(SPEC)))
# --- la paleta de audio (GET /audio) ----------------------------------------
@pytest.mark.asyncio
async def test_an_edited_preset_from_the_live_palette_renders(tmp_path, monkeypatch):
"""El retoque para el que existe el bucle de edición: cambiar la banda
sonora a "pulse" sin pagar otra generación."""
edited = json.loads(json.dumps(SPEC))
edited["audio"] = {"preset": "pulse"}
client = FakeClient()
p = producer(tmp_path, monkeypatch, client=client, llm=_llm_prohibido)
result = await p.rerender(153, edited)
assert result.has_video
assert client.rendered["audio"]["preset"] == "pulse"
@pytest.mark.asyncio
async def test_a_preset_the_renderer_does_not_know_is_rejected_with_the_palette(
tmp_path, monkeypatch):
edited = json.loads(json.dumps(SPEC))
edited["audio"] = {"preset": "vaporwave"}
client = FakeClient()
p = producer(tmp_path, monkeypatch, client=client, llm=_llm_prohibido)
result = await p.rerender(153, edited)
assert not result.has_video
assert "audio.preset" in result.failure and "pulse" in result.failure
assert client.rendered is None
@pytest.mark.asyncio
async def test_a_dead_audio_endpoint_never_blocks_a_sonar_render(tmp_path, monkeypatch):
"""La paleta mejora el prompt, no lo define: sin /audio se cae a la base y
un spec con sonar renderiza igual."""
p = producer(tmp_path, monkeypatch, client=FakeClient(fail_at="audio"),
llm=_llm_prohibido)
result = await p.rerender(153, json.loads(json.dumps(SPEC)))
assert result.has_video