Case Study All Work

Alias TTS

A self-hosted text-to-speech server that speaks two API dialects — ElevenLabs and OpenAI — so existing integrations switch over with a base URL and a key, then adds the parts a publishing workflow actually needs: pronunciation control, automated quality checks, and a provenance receipt for every approved file.


Role: Solo developer — architecture, API design, frontend, backend, deployment

  • Timeline: ~6 weeks, 2026 — in active development
  • Stack: Laravel, Replicate (Chatterbox, Chatterbox Turbo, Qwen3 TTS), Whisper, ffmpeg, Docker, DDEV, S3-compatible storage
  • Links: alias.morton.dev (currently only in private beta, but you can view the public pages)

The Problem

I built Bespoken, a Craft CMS plugin that adds narration to articles, on top of the ElevenLabs API. The quality is excellent. The cost is not — per-character pricing turns a site with an archive into a bill that grows every time you regenerate a page, and regeneration is exactly what you do when the audio comes out wrong.

Open models fixed the cost problem and introduced a different one. Chatterbox and its peers are good enough to publish, but they arrive as raw inference: no chunking for long text, no way to make a model stop saying “D-dev,” no way to catch the take that trails off into a half-second of noise, and no accounting for what you actually shipped. Every one of those gaps is the difference between a demo and something you’d put on a client’s site.

So the real question wasn’t “can I generate speech cheaply.” It was: what has to exist between a text field and a published audio file before I’d trust it without listening to every second myself?

Constraints

  • No migration cost. Anything I built had to work with integrations that already existed. If adopting it meant rewriting a client, nobody would adopt it — including me.
  • No GPUs of my own. I wasn’t going to buy or babysit inference hardware. The server had to orchestrate someone else’s, and the model layer had to be swappable, because the best open model in six months won’t be the best one today.
  • Long text is the normal case, not the edge case. Articles run thousands of words. That means chunking, stitching, and an async job API — not a request that either returns audio or times out.
  • Quality had to be checkable by machine. A human listening to every generated file doesn’t scale past one person. If the system couldn’t flag its own bad takes, the whole thing was a toy.

Approach

Two dialects, one server

Alias TTS implements both the ElevenLabs endpoint (POST /v1/text-to-speech/{voice_id}) and the OpenAI endpoint (POST /v1/audio/speech). For anything already wired to either service, adoption is a base URL and a key — no client rewrite. The Bespoken plugin speaks the ElevenLabs dialect and worked against Alias with no changes at all, which was the first real proof the approach held.

Inference as a driver, not a dependency

The server doesn’t run models. It delegates to Replicate behind a pluggable driver — Chatterbox, Chatterbox Turbo, and Qwen3 TTS today, chosen per voice — and each account brings its own token and credit. Alias handles what sits around inference: auth, caching, sentence-aware chunking, ffmpeg stitching, storage to local disk or S3, an async job API for long text, and per-key rate limits. When a better model ships, it’s a driver, not a rewrite.

Teaching it to say the words

The failure that actually ruins narration is mispronunciation, and it’s always the same words — product names, acronyms, the technical vocabulary a general model has never heard. Alias runs an opt-in LLM pre-processor against a per-account pronunciation dictionary before anything reaches the model, and exposes that dictionary at GET /v1/pronunciations so clients can read it. It’s the least glamorous part of the system and the one that most determines whether the output is usable.

Catching bad takes automatically

A local Whisper sidecar transcribes generated audio and compares it back against the source, flagging truncation, trailing noise, dropped tails, bad pauses, and boundary artifacts. Flagged chunks are re-rolled or trimmed automatically. This is what makes the system trustworthy without a person in the loop for every file.

A workbench for the takes that still need a human

Studio exposes the seams. A chunk inspector shows the text split, a cost estimate before generation, and per-chunk regeneration so a single bad sentence doesn’t mean re-rendering a whole article. Seam preview plays the joins, because stitched audio fails at the boundaries or not at all.

Approve and seal

Approving audio records a SHA–256 fingerprint of the exact file and produces a downloadable receipt. A public /verify page fingerprints a file locally in the browser and confirms whether it’s the approved take. As synthetic voice gets easier to produce, being able to prove which file you signed off on stops being a nice-to-have.

Shipping it

One Docker image, DDEV for local development, and a tts:doctor command that checks the environment and tells you what’s misconfigured before you find out through a failed job. A companion runner, Genblaze, archives takes and provenance manifests to Backblaze B2.

Outcome

Alias TTS runs the narration behind Bespoken, and the drop-in compatibility turned out to be the most useful property in the build — not because it saved effort, but because it let me test the whole system against a real client on day one.

Reflection

The API surface can’t be the moat. An OpenAI-compatible endpoint is a door that swings both ways — the same base URL change that brings someone in lets them leave, and that shape is now close to a commodity. Recognizing that early changed what I spent time on.

The value accumulates in the parts that aren’t in anyone’s spec: the pronunciation dictionary, the QA heuristics, the seam handling. That work compounds, it’s specific to real published output, and it’s the hardest thing for a fresh implementation to reproduce. A wrapper around an open model is a weekend. Knowing which artifacts a model produces that you can’t publish, and building the machinery that catches them, is the actual project.

This one also came directly out of using my own tool and being annoyed by it — which remains the most reliable product research I know of.