Ramblings of an aging IT geek
← Ramblings of an aging IT geek
colophon

ElevenLabs Voices and Pronunciation Dictionaries

colophon now offers ElevenLabs alongside MiniMax for spoken posts, plus pronunciation dictionaries that fix the words a technical blog mispronounces, on either provider.

Listen to this post
A ribbon microphone beside an open dictionary of phonetic symbols under a pool of warm light

Every post on this blog has a spoken reading, generated text-to-speech (TTS), sitting under the title for anyone who'd rather listen. It's been driven by MiniMax since I added it. Two things have changed recently, and I think they're worth writing down. A caveat first: this is all new and still moving, so treat the specifics as a snapshot that may change.

The short version: there's now a second voice provider, ElevenLabs, and pronunciation dictionaries that work across both. Plus a quiet format change underneath.

Pronunciation dictionaries

The honest problem with generated speech is that it mispronounces exactly the words a technical blog uses most. A voice trained mostly on American English says "raowter" for router, gives cache an extra syllable, and has its own ideas about aluminium. Re-recording isn't an option when the audio is generated, so the fix is to tell the voice how to say specific words.

colophon now takes a pronunciation dictionary, and it applies to whichever provider you're on. The cheapest version is the bundled British one:

generation:
  speech:
    pronunciation_dict: en_GB     # the dictionary shipped with colophon

Or point it at your own file, relative to the site root:

generation:
  speech:
    pronunciation_dict: pronunciation.yaml

The format is small on purpose. Each entry is a word plus one of two ways to fix it:

pronunciations:
  - word: router
    ipa: ˈruːtə        # IPA: precise; rendered to each provider's phoneme mechanism
  - word: nginx
    say: engine x      # respelling: substituted as plain text, works on any provider

The two knobs are worth understanding:

  • ipa is the International Phonetic Alphabet (IPA) transcription, precise and unambiguous. colophon renders it to each provider's own phoneme mechanism (more on the ElevenLabs side of that below).
  • say is a plain respelling, substituted as ordinary text before synthesis. It's less exact but completely portable, any voice can read "engine x", no phoneme support required.

One piece of hard-won advice that's baked into the bundled dictionary: keep it small. Overriding a word the voice already says correctly almost always makes it worse. Add a word only after you've heard it get one wrong, and audition the fix before you keep it.

A second voice: ElevenLabs

MiniMax is cheap and fine, but ElevenLabs is the one people reach for when they want a specific, higher-fidelity voice, including a clone of their own. colophon now has an elevenlabs speech driver alongside the MiniMax one. Switching is a config change:

generation:
  speech:
    provider: elevenlabs
    model: eleven_multilingual_v2          # the default; override if you like
    voice: "<your ElevenLabs voice id>"    # a premade voice, or your own cloned one
    api_key: "{env:ELEVENLABS_API_KEY}"

As with every secret in colophon, the key only ever arrives through the environment, never written into the config. It's read from ELEVENLABS_API_KEY (or COLOPHON_ELEVENLABS_API_KEY), so it lives in your keyring or CI secrets exactly like the deploy tokens.

The API key permissions

ElevenLabs lets you scope an API key to specific capabilities, and colophon needs:

  • Text to Speech: to generate the readings. This is the only permission you need if you're not using a pronunciation dictionary.
  • Pronunciation Dictionaries (read and write): only if you set pronunciation_dict. ElevenLabs doesn't take IPA inline; it applies pronunciation through an uploaded, versioned dictionary tied to your account. So colophon syncs your ipa entries up to ElevenLabs (it lists, creates, and adds or removes rules on a dictionary it manages for you), then references that dictionary when it synthesises. That sync is why the key needs write access to dictionaries, not just speech.

So: a plain ElevenLabs reading needs Text to Speech alone; add Pronunciation Dictionaries (read/write) the moment you want your ipa overrides to take effect. The say respellings, being plain text substitution, need no extra permission on either provider.

The quiet change: WAV now, Opus later

Underneath both providers, the audio format changed. colophon no longer generates MP3; both drivers now return raw PCM (16 kHz mono) which colophon wraps as a WAV file. Two nice consequences fall out of that. The waveform you see under each reading is computed from that same PCM, so there's no second render just to draw it (the old MiniMax path did a separate call for the waveform, which doubled the token spend, that's gone). And WAV is trivially decodable, so the peaks come for free.

The obvious objection is size: WAV is uncompressed. That's the next piece of work, an Opus (SILK) encoder to shrink the readings without a heavyweight dependency. The plumbing already half-knows about it (.opus is a recognised type), it just isn't wired to an encoder yet. For now the trade is deliberate: simple, decoder-free, accurate waveforms, and pay the bytes until the encoder lands.

Putting it together

So the current shape of a reading: pick a provider (MiniMax or ElevenLabs), optionally give it your voice, and hand it a small pronunciation dictionary so it stops saying "raowter". The dictionary is provider-agnostic; the voice and the key are provider-specific; the output is a WAV and a waveform.

All of this is recent and still settling, so the field names and defaults may move. If you're wiring it up from this post, sanity-check against your own colophon.yaml and the current build.