Scriptorium / tests /test_synthesis_service.py
mattkevan's picture
add modal endpoint and magpietts
cd0ff97
Raw
History Blame Contribute Delete
5.21 kB
from pathlib import Path
import pytest
from backend.synthesis_service import SynthesisService
from backend.types import VoiceConfig
class FakeSynthesizer:
def __init__(self, model: str) -> None:
self.model = model
self.calls = []
def synthesize(self, *, text: str, output_path: Path, voice_config: VoiceConfig, **kwargs) -> dict:
self.calls.append(
{
"text": text,
"output_path": output_path,
"voice_config": voice_config,
**kwargs,
}
)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_bytes(b"RIFFfakewave")
return {
"duration_seconds": 4,
"sample_rate": 24000,
"backend": "local",
"model": self.model,
}
class FakeModalClient:
def __init__(self) -> None:
self.preview_calls = []
self.submit_calls = []
self.render_calls = []
self.cancelled = []
def generate_preview(self, **kwargs) -> dict:
self.preview_calls.append(kwargs)
output_path = kwargs["output_path"]
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_bytes(b"RIFFmodalpreview")
return {
"duration_seconds": 5,
"sample_rate": 24000,
"backend": "modal",
"model": kwargs["voice_config"].model,
}
def submit_render(self, **kwargs) -> str:
self.submit_calls.append(kwargs)
return "job-123"
def render(self, **kwargs):
self.render_calls.append(kwargs)
output_path = kwargs["render_dir"] / "001-c1.wav"
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_bytes(b"RIFFmodalrender")
yield {
"type": "started",
"session_id": kwargs["session_id"],
"total_chapters": 1,
"book_title": "Book",
"backend": "modal",
"model": kwargs["voice_config"].model,
}
yield {
"type": "chapter_done",
"session_id": kwargs["session_id"],
"chapter_id": "c1",
"duration_seconds": 7,
"overall_progress": 1.0,
"output_path": str(output_path),
"backend": "modal",
"model": kwargs["voice_config"].model,
}
yield {
"type": "completed",
"session_id": kwargs["session_id"],
"outputs": [str(output_path)],
"backend": "modal",
"model": kwargs["voice_config"].model,
}
def cancel(self, session_id: str) -> dict:
self.cancelled.append(session_id)
return {"type": "cancelled", "session_id": session_id, "backend": "modal"}
def test_preview_routes_to_model_specific_local_adapter(tmp_path: Path) -> None:
omnivoice = FakeSynthesizer("omnivoice")
magpie = FakeSynthesizer("magpie")
service = SynthesisService(
session_root=tmp_path,
local_synthesizers={"omnivoice": omnivoice, "magpie": magpie},
modal_client=FakeModalClient(),
)
result = service.generate_preview(
text="Hello from Magpie.",
output_path=tmp_path / "previews" / "preview.wav",
voice_config=VoiceConfig(model="magpie", backend="local", speaker="Sofia", language="en"),
diffusion_steps=32,
speed=1.0,
)
assert result["backend"] == "local"
assert result["model"] == "magpie"
assert len(magpie.calls) == 1
assert magpie.calls[0]["voice_config"].speaker == "Sofia"
assert not omnivoice.calls
def test_render_routes_modal_jobs_and_preserves_event_shape(tmp_path: Path) -> None:
service = SynthesisService(
session_root=tmp_path,
local_synthesizers={"omnivoice": FakeSynthesizer("omnivoice"), "magpie": FakeSynthesizer("magpie")},
modal_client=FakeModalClient(),
)
events = list(
service.render(
session_id="session-a",
book={"title": "Book"},
chapters=[{"id": "c1", "title": "One", "text": "hello world", "included": True}],
voice_config={"model": "magpie", "backend": "modal", "speaker": "Sofia", "language": "en"},
diffusion_steps=32,
speed=1.0,
)
)
assert [event["type"] for event in events] == ["started", "chapter_done", "completed"]
assert all(event["backend"] == "modal" for event in events)
assert all(event["model"] == "magpie" for event in events)
assert Path(events[1]["output_path"]).exists()
def test_pause_and_resume_are_rejected_for_modal_jobs(tmp_path: Path) -> None:
service = SynthesisService(
session_root=tmp_path,
local_synthesizers={"omnivoice": FakeSynthesizer("omnivoice"), "magpie": FakeSynthesizer("magpie")},
modal_client=FakeModalClient(),
)
service._active_backends["session-a"] = "modal"
with pytest.raises(ValueError, match="Pause is only supported for local renders"):
service.pause("session-a")
with pytest.raises(ValueError, match="Resume is only supported for local renders"):
service.resume("session-a")