Paste a chapter. StudioCast reads it, directs it into a multi-voice scene, then listens back to its own render and holds any line that came out flat.
Live demo · Architecture · License
Story Analysis → The Director → Audio QC, with a Producer release decision on top.
Built for the Pocket FM "Zero to One" hackathon at IIM Bangalore. Theme: Creator Tools & Copilots.
Generating audio drama is close to solved. Knowing whether the render is good is not.
A text model can direct a scene and a voice model can perform it, but nothing in that chain ever listens to the result. A line directed "furious" that comes out flat ships anyway, and the first listener is the one who finds it.
StudioCast closes that loop. It re-transcribes its own render, reads the delivered emotion two independent ways, measures pacing and loudness, and holds any line whose delivery does not match its direction. Above that, a Producer layer weighs each held line against what the beat is carrying and returns publish, hold or regenerate with its reasoning.
Generation, verification, regeneration: a loop, not a one-shot.
Needs python3.12, uv, node and pnpm. ffmpeg is not a manual step; a system build is used when
present and a static build ships in the hosted pip extra otherwise.
# API (FastAPI, Python 3.12, uv)
cd api && uv run uvicorn main:app --reload --port 8000
# Web (Next.js 16, pnpm)
cd web && pnpm install && pnpm dev # http://localhost:3000/studio
# Demo audio is gitignored, so rebuild it on a fresh clone
python3 api/scripts/gen_renders.pyTwo run modes and no third, chosen by one variable, MODE=demo|live. demo is the default:
cached scenes stream the full three-stage path with zero external calls, paced to arrive the way the
live pipeline does rather than dumping whole. live calls every real seam and reads no fixture; a
seam that fails says so instead of quietly serving a draft. Nothing in the demo path requires a key,
and the header carries the mode it is running in.
cd api && uv run pytest # 82 tests
cd api && uv run python scripts/preflight.py # the pre-demo gate
cd web && pnpm lint && pnpm buildpreflight is authoritative. It runs all three stages in-process with no network and checks the demo
assets, the honesty guardrails, the published eval figures and the data layer. DEMO READY with exit
0 is the bar. Unconfigured live seams report ○ and never fail, because the demo is designed to run
offline.
Every component is a named agent with a role, and the console lights each one as its stage runs.
| Stage | Agents |
|---|---|
| Story Analysis | Story Analyst |
| The Director | Showrunner, Casting Director, SFX Spotter |
| Audio QC | Transcript Auditor, Emotion Auditor, Pacing Auditor, Voice Auditor |
| Release | Executive Producer |
Rubric-anchored critique with rationale and citations. An emotional-arc classifier over Reagan's six shapes (control-point Pearson fit) with a self-consistency stability band showing how far valence can drift before the label flips. Second-episode hook meter, foreshadowing tracker (setup to payoff, plus open threads), and one-click beat rewrite.
Multi-voice casting with a stated rationale per character. Voice-lock persists and reuses the cast
across chapters of a project. Per-line emotion direction, SFX and music cue spotting, mood to a generated
music-bed match, then a render mixed with ffmpeg: the bed ducked under dialogue by sidechain
compression, SFX overlaid. The renderer is a ladder, OpenAI gpt-4o-mini-tts first and a local model
beneath it, so a dead key costs quality rather than taking the stage down.
The differentiator. Transcript diff with per-line WER, then dual-path emotion: a non-LLM acoustic
model and an audio-LLM, where a line is flagged only when both disagree with the direction.
Neither is trusted alone. Plus pacing, an LUFS loudness badge, voice separation between characters,
and an IPA guide for proper nouns. Click a flag, regenerate that one line, and the verdict flips live.
Behind QC_REPORT_CARD the findings also read as a five-row chapter report card (Emotion,
Pronunciation, Continuity, Pacing, Loudness), where a row with no measurement behind it says so rather
than reporting clean.
Above the rule sits a decision layer. The rule is fixed: any flag means hold. It cannot weigh a defect against what the beat is worth, and a flat delivery on the cliffhanger is a different problem from a flat delivery on "he closed the door".
So the Producer takes the same signals the rule saw, plus the story analysis read on what each beat is carrying, and returns publish / hold / regenerate with its reasoning. When it lands somewhere the rule would not, it says so:
"The rule says hold, and I am overruling it. That line sits on a transitional beat, where a flat reading costs less than delaying the episode."
Both verdicts are always returned, so a reviewer can disagree with either.
Nine chapters ship ready to run, drawn from four public-domain books: Conan Doyle, plus Bram Stoker's Dracula, Mary Shelley's Frankenstein and Jane Austen's Pride and Prejudice. Three books beyond the detective means three different arc shapes for the classifier to name and an audience panel with something to disagree about.
Every cached scene is captured pipeline output, frozen. scripts/capture_live.py runs the real
three-stage path over the chapters in api/scenes.py and writes the result to the manifest one scene
at a time; nothing in it is hand-authored. /studio reads the picker from GET /scenes, so the
button text and the fixture cannot drift apart. The renders the manifest names are tracked, because
they are captured model output that CI cannot reproduce.
Running a chapter live a second time replays the stored result instead of calling and billing again. The replay is labelled with its age and re-measures nothing; Fresh run forces the real call.
Two of them exist to show the same defect resolving two ways, and the difference comes from the analysed beats rather than from the fixtures.
| Scene | Flagged line sits on | Rule | Producer |
|---|---|---|---|
| The Speckled Band (hero) | the cliffhanger, tension 0.95 | hold | regenerate, upholding the hold |
| The Red-Headed League | a transitional beat, tension 0.15 | hold | publish, overruled_rule |
preflight fails if the two ever resolve the same way.
These are enforced by tests and by preflight, not just stated. They are the reason the QC claim is
worth anything.
- Never claim a measurement that wasn't taken. Every emotion reading records which rung produced it, and a text-derived reading is never presented as if it heard the audio. Every measurement field is nullable, and absence survives all the way to the UI.
- The planted flaw is real. The flagged line is acoustically flat in the samples, and
preflightmeasures it. There is no "always flag this line" detector. - The Producer reads measurements, never audio. The audio critics stay the acoustic model and the audio-LLM. Its confidence is labelled self-reported and uncalibrated, and it never silently replaces the rule's verdict.
- Two run modes and no third, labelled on every stage:
demoserves the cached scenes with no network,livecalls every real seam. A live stage that cannot reach its model fails and says so rather than quietly serving a draft. A deterministic decision is labelledpolicy, never a model. - Classifier numbers are reported as agreement, never as absolute quality. No churn, pay or
conversion claims appear anywhere in shipped copy;
preflightgreps for them.
Shipped classifiers scored against a hand-labeled public-domain set (api/evals/):
| Classifier | Accuracy | Cohen's κ | Macro-F1 |
|---|---|---|---|
| Emotion (text) | 0.87 | 0.84 | 0.88 |
| Story arc (Reagan shapes) | 0.86 | 0.83 | 0.78 |
κ > 0.8 is "almost perfect" agreement (Landis and Koch), with visible misclassifications left in. Emotion labels carry the acted-versus-natural caveat. Story signals such as cliffhanger strength and hook are shown as relative bands with rationale and citations, never as validated metrics.
| Route | What it is |
|---|---|
/ |
Landing |
/audience |
What the listener panel is, read off the running build |
/report-card |
What the five chapter rows measure, read off the running build |
/studio |
Source picker: the chapters this build ships with |
/studio/run |
The three-stage console, the demo path |
/scene/[id] |
Share page: listener player, generated PNG mood cover, OG audio card |
/explore |
Feed of published scenes that have audio |
This build is public demo only. There is no sign in, no sign up and no account. Every route
above is open to everyone and every demo endpoint is reachable without a token. The creator layer
(dashboard, series library, saved chapters) has been removed from the web app, and proxy.ts
returns 404 for /dashboard, /series, /sign-in and /sign-up so nothing survives in a stale
link or a bookmark.
| Path | What |
|---|---|
api/ |
FastAPI, Python 3.12, uv. Flat module layout: pipeline.py orchestrates, producer.py decides, qc_live.py / ser.py / prosody.py / asr.py measure, db.py fronts three backends. |
web/ |
Next.js 16, pnpm, Tailwind. app/studio is the demo surface. |
api/cache/ |
The offline demo. manifest.json is committed; renders are gitignored and rebuilt by scripts/gen_renders.py. |
api/evals/ |
The hand-labeled sets behind the table above. |
api/databricks/ |
Workspace-side SQL: Delta ingest, catalog views, CI grants. |
localdocs/ |
Working notes and ops records. Gitignored. |
Innovation. The only stage here that hears its own output, with a Producer above it that makes a judgement rather than matching a threshold, sometimes overruling the rule and always saying why.
Technical execution. Strict-schema structured outputs on every stage; SSE streaming with guaranteed failure propagation, job recovery and terminal replay; three storage backends behind one interface, verified value-identical; an append-only QC-event flywheel exported as a fact table.
Use of AI. Each model justified and attributed exactly. Audio QC is deliberately not one model: an audio-LLM paired with a non-LLM acoustic model, agreeing before anything is flagged. The audio judge has earned that distrust, since asked twice about the same clip it can answer differently. It is one opinion, not a measurement.
Real-world impact. Sits after the authoring suite and before publish, which is the verification gap in the stack. Every flagged line and every human accept or reject becomes an append-only training triple, so the QC data compounds.
Working demo. Three beats in 90 seconds, with a cached offline path underneath so a dead network cannot take it down.
- ARCHITECTURE.md for the system design: pipeline internals, the degradation ladders, the data layer, deployment topology and CI.
- CLAUDE.md for the repo working agreement.
Proprietary. All rights reserved. See LICENSE. This source is published for review and evaluation only. It is not open source, and no permission is granted to use, copy, modify or redistribute it. For any other use, ask first.