tts-voice-creator-clone-and.../routes/rehearsals_db.py
mARTin-B78 ea50267c30 Add unified Studio casting workflow and fix voice/casting pipeline bugs
Introduces the new Studio section (Source -> Characters -> Voices ->
Perform & Export) that reuses the existing Read Aloud/Library/Script
Rehearsal code via DOM reparenting instead of duplicating it, and rolls up
a long tail of bugs found while producing a real audiobook through it:
umlaut-eating name sanitizers, a voice picker that mispositioned itself and
capped results at 60, PDF pagination silently breaking on trimmed \f
markers, a race letting stale audio keep playing after a new line was
clicked, an alias-overlap bug that could silently redirect a voice/image
save onto the wrong character, voice design failing outright during brief
TTS backend restarts instead of retrying, sparse cast entries defaulting to
English/wrong gender, and a reassigned voice never reaching an already-open
Stage session or invalidating its cached audio. Also adds a persistent
per-line audio cache, audiobook export browsing/download, and an inline
voice-design prompt editor. Full details in CHANGELOG.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-26 02:03:56 +02:00

77 lines
2.3 KiB
Python

"""Rehearsal library REST API — backed by SQLite via core.database.
Mirrors the IndexedDB API in rehearser.js:
GET /api/rehearsals → { rehearsals: [...] }
GET /api/rehearsals/{id} → record | 404
PUT /api/rehearsals/{id} → full replace (id must exist)
POST /api/rehearsals → insert (no id), returns { id }
DELETE /api/rehearsals/{id}{ ok: true }
POST /api/rehearsals/migrate → bulk import from JS IndexedDB dump
"""
from __future__ import annotations
from fastapi import APIRouter, HTTPException, Request
from core.database import reh_get_all, reh_get, reh_put, reh_delete, reh_compact_titles
router = APIRouter()
@router.get("/api/rehearsals")
async def rehearsals_list():
reh_compact_titles()
return {"rehearsals": reh_get_all()}
@router.get("/api/rehearsals/{reh_id:int}")
async def rehearsals_get(reh_id: int):
rec = reh_get(reh_id)
if rec is None:
raise HTTPException(404, "Rehearsal not found")
return rec
@router.post("/api/rehearsals")
async def rehearsals_add(request: Request):
try:
body = await request.json()
except Exception:
raise HTTPException(400, "Invalid JSON")
body.pop("id", None)
saved = reh_put(body)
return {"id": saved["id"]}
@router.put("/api/rehearsals/{reh_id:int}")
async def rehearsals_put(reh_id: int, request: Request):
if reh_get(reh_id) is None:
raise HTTPException(404, "Rehearsal not found")
try:
body = await request.json()
except Exception:
raise HTTPException(400, "Invalid JSON")
body["id"] = reh_id
reh_put(body)
return {"ok": True}
@router.delete("/api/rehearsals/{reh_id:int}")
async def rehearsals_delete(reh_id: int):
reh_delete(reh_id)
return {"ok": True}
@router.post("/api/rehearsals/migrate")
async def rehearsals_migrate(request: Request):
"""Accept a batch of rehearsal records from the browser's IndexedDB dump."""
try:
body = await request.json()
except Exception:
raise HTTPException(400, "Invalid JSON")
records = body if isinstance(body, list) else body.get("rehearsals", [])
ids = []
for rec in records:
saved = reh_put(rec)
ids.append(saved["id"])
return {"imported": len(ids), "ids": ids}