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
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:
co-authored by
Claude Fable 5
parent
1fd0c1b3d6
commit
93a506b636
@@ -21,7 +21,7 @@ cubierto.
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Iterable, Optional
|
||||
|
||||
__all__ = [
|
||||
"SpecInvalid",
|
||||
@@ -183,14 +183,23 @@ def _check_meta(meta: Any) -> list[str]:
|
||||
return errors
|
||||
|
||||
|
||||
def _check_audio(audio: Any, total: float) -> list[str]:
|
||||
#: Los presets que existían antes de `GET /audio`. Solo es el default cuando el
|
||||
#: caller no pasa la paleta viva; con ella, un preset nuevo en shortsmith llega
|
||||
#: aquí sin tocar este repo — el mismo pacto que las plantillas.
|
||||
BASELINE_PRESET_NAMES = ("sonar", "none")
|
||||
|
||||
|
||||
def _check_audio(audio: Any, total: float,
|
||||
presets: Optional[Iterable[str]] = None) -> list[str]:
|
||||
if audio is None:
|
||||
return []
|
||||
if not isinstance(audio, dict):
|
||||
return ["audio: se esperaba un objeto"]
|
||||
errors = []
|
||||
if audio.get("preset", "sonar") not in ("sonar", "none"):
|
||||
errors.append(f"audio.preset: {audio.get('preset')!r} no es 'sonar' ni 'none'")
|
||||
known = tuple(presets) if presets else BASELINE_PRESET_NAMES
|
||||
if audio.get("preset", "sonar") not in known:
|
||||
errors.append(f"audio.preset: {audio.get('preset')!r} no existe "
|
||||
f"(los presets son: {', '.join(sorted(known))})")
|
||||
silence = audio.get("silence", [])
|
||||
if not isinstance(silence, list):
|
||||
return errors + ["audio.silence: se esperaba una lista de pares [inicio, fin]"]
|
||||
@@ -223,12 +232,16 @@ def _total_duration(spec: dict) -> float:
|
||||
return total
|
||||
|
||||
|
||||
def validate_spec(spec: Any, templates: dict[str, dict]) -> None:
|
||||
def validate_spec(spec: Any, templates: dict[str, dict],
|
||||
presets: Optional[Iterable[str]] = None) -> None:
|
||||
"""Lanza `SpecInvalid` con TODAS las rutas que fallan.
|
||||
|
||||
Se devuelven todos los errores de golpe a propósito: el bucle de reintento
|
||||
se los da al modelo verbatim y arreglar cinco de una vez sale más barato
|
||||
que cinco vueltas.
|
||||
|
||||
`presets` es la paleta viva de `GET /audio`; sin ella se valida contra la
|
||||
paleta base, que nunca acepta nada que un shortsmith viejo no renderice.
|
||||
"""
|
||||
errors: list[str] = []
|
||||
if not isinstance(spec, dict):
|
||||
@@ -284,7 +297,7 @@ def validate_spec(spec: Any, templates: dict[str, dict]) -> None:
|
||||
if total > MAX_TOTAL_DURATION:
|
||||
errors.append(f"shots: la duración total ({total:.2f}s) pasa del límite "
|
||||
f"de {MAX_TOTAL_DURATION}s")
|
||||
errors.extend(_check_audio(spec.get("audio"), total))
|
||||
errors.extend(_check_audio(spec.get("audio"), total, presets))
|
||||
|
||||
if errors:
|
||||
raise SpecInvalid(errors)
|
||||
|
||||
Reference in New Issue
Block a user