Text to Speech skill for Codex

Natural narration with names pronounced right. Codex normalizes text for speech and applies a stored pronunciation dictionary before synthesis, then picks OpenAI voices with style instructions or SSML providers when pauses and phonemes need exact control. Long scripts are chunked at sentences and loudness-normalized once, chat apps stream sentence by sentence, and cloned voices require documented consent.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- text-to-speech

Installs to ~/.agents/skills/text-to-speech/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $text-to-speech, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Generates speech audio with text-to-speech APIs - OpenAI gpt-4o-mini-tts with voices and style instructions, ElevenLabs, Azure, Google Cloud and Amazon Polly with SSML for pauses, pronunciation and emphasis - plus text normalization, chunking long scripts, streaming playback for low latency, output formats and loudness, caching, cost control, and voice-cloning consent and AI-voice disclosure. Use when asked to turn text into audio, narrate an article or course, add a voice to an app, stream TTS for a chatbot, fix mispronounced names, or batch-produce audio files.

Use it when

  • Articles, courses or scripts must become audio files
  • An app or chatbot needs spoken output with low latency
  • Pronunciation, pacing or tone of synthesized speech is wrong
  • You are choosing between TTS providers or voices

Use something else when

How to check the result

Spot-listen to the first, middle and last chunks plus every dictionary term, and confirm format, sample rate and loudness with ffprobe and ebur128.

The mistake it prevents

Sending raw markdown, symbols and unexpanded numbers to the voice, which then reads asterisks, URLs and years incorrectly.

~/.agents/skills/text-to-speech/SKILL.md

--- name: text-to-speech description: Generates speech audio with text-to-speech APIs - OpenAI gpt-4o-mini-tts with voices and style instructions, ElevenLabs, Azure, Google Cloud and… license: MIT ---

Done well means: the text is normalized for speech before synthesis, pronunciation of names and terms is pinned, long content is chunked at sentence boundaries without audible seams, streaming starts playing within a second for interactive use, and listeners are told the voice is AI-generated.

Choose the provider

NeedDefault
Natural voice with tone controlled in plain languageOpenAI gpt-4o-mini-tts with instructions
Precise control: pauses, phonemes, say-as dates/numbersAzure, Google Cloud or Amazon Polly with SSML
Widest voice library, cloning, many languagesElevenLabs
Offline or on-deviceOpen-weights TTS (Piper, Kokoro and similar) run locally; check each model’s license
Two-way spoken conversationNot TTS alone: $openai-realtime-voice

Read AGENTS.md for the provider already in use and where API keys live. Every hosted API needs network; in a Codex sandbox ask before calling it, and estimate cost for batch jobs (characters x price per million) before starting them.

Prepare the text

TTS reads exactly what it is given. Normalize first:

- [ ] Expand abbreviations and symbols: "Dr." -> "Doctor", "&" -> "and", "%" -> "percent"
- [ ] Decide how numbers, dates, currencies, times and versions are spoken ("v2.3.1" -> "version two point three point one")
- [ ] Remove markdown, URLs, code blocks, emojis and footnote markers
- [ ] Spell out or phonetically respell brand names the voice gets wrong
- [ ] Split into sentences; keep paragraphs as natural pause points

Keep a pronunciation dictionary ({"Nginx": "engine x", "Kubernetes": "koo-ber-net-eez"}) in the repo and apply it before every request, so fixes persist across runs.

OpenAI TTS

from openai import OpenAI
client = OpenAI()

with client.audio.speech.with_streaming_response.create(
    model="gpt-4o-mini-tts",
    voice="coral",
    input="Your order has shipped and should arrive on Thursday.",
    instructions="Warm, upbeat customer-support tone. Medium pace. Clear pauses between sentences.",
    response_format="mp3",
) as resp:
    resp.stream_to_file("shipped.mp3")
  • Voices include alloy, ash, ballad, coral, echo, fable, nova, onyx, sage, shimmer and verse (newer voices may be added; list the current set from the docs). Pick by listening to the same paragraph in 3-4 voices.
  • instructions steers tone, pace, accent and emotion; it only works on the gpt-4o TTS models, not on tts-1 / tts-1-hd.
  • Input limit is about 4,096 characters per request; chunk longer text.
  • Formats: mp3 (default, broad support), opus (streaming, small), aac, flac, wav and pcm (raw 24 kHz 16-bit mono, lowest latency to play).
  • OpenAI’s usage policies require telling end users the voice is AI-generated, not human.

SSML providers

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
  <voice name="en-US-JennyNeural">
    Your appointment is on <say-as interpret-as="date" format="mdy">11/04/2026</say-as>
    at <say-as interpret-as="time" format="hms12">3:30pm</say-as>.
    <break time="400ms"/>
    Ask for <phoneme alphabet="ipa" ph="ˈʃɪvɔn">Siobhan</phoneme> at the front desk.
    <prosody rate="-10%">Please arrive ten minutes early.</prosody>
  </voice>
</speak>
  • Element support differs per provider and per voice (neural voices ignore some prosody settings). Test each tag with the exact voice you will ship.
  • Escape &, <, > in user text before inserting it into SSML, or synthesis fails.
  • Azure also offers express-as styles via its mstts namespace; Polly neural voices support a subset of SSML; check the provider’s tag list.

Long-form audio

  1. Chunk at sentence or paragraph boundaries, under the provider limit (about 3,000-4,000 characters).
  2. Use the same voice, model, instructions and format for every chunk; include a short overlap of context only if the provider supports a previous-text parameter (ElevenLabs does).
  3. Concatenate with a small natural pause and normalize loudness once at the end:
printf "file '%s'\n" chunk_*.wav > list.txt
ffmpeg -f concat -safe 0 -i list.txt -af "loudnorm=I=-16:TP=-1.5:LRA=11" -c:a libmp3lame -b:a 128k episode.mp3

-16 LUFS integrated suits podcasts and web audio; use the platform’s own target if it publishes one. Generate chunks as wav or pcm and encode once; concatenating mp3 files adds gaps and re-encoding loss.

Streaming for interactive apps

  • Start synthesis per sentence as the LLM streams text: buffer tokens until ., ?, ! or a newline, then send that sentence to TTS while the next one generates.
  • Use pcm or opus for the lowest time to first audio; play chunks as they arrive.
  • Measure time to first audio byte and end-to-end delay from user message to first sound; under about 1 second feels responsive.
  • Cancel in-flight TTS when the user interrupts, or stale audio keeps playing.

Caching and cost

  • Cache by hash of (provider, model, voice, instructions, normalized text, format); static prompts like menu lines should synthesize once.
  • Batch narration costs scale with characters: report the estimate and get approval before a large run.
  • Store generated audio with its source text and settings so a fix re-renders only the changed chunk.
  • Clone or design a voice resembling a real person only with that person’s documented consent; never imitate public figures or private individuals without it.
  • Disclose AI-generated audio in the product (UI label, intro line, or metadata).
  • Do not publish generated audio publicly without the user’s confirmation.

Gotchas

  • Numbers read wrongly (“1984” as a year vs a quantity) unless normalized or marked with SSML say-as.
  • Very short inputs (one word) can sound clipped or odd; pad with context or join short lines.
  • Different chunks with slightly different instructions produce audible tone jumps.
  • Browser autoplay policies block audio until a user gesture; start playback from a click.
  • Sample-rate mismatches when concatenating (24 kHz vs 44.1 kHz) cause pitch changes; resample everything to one rate before joining.

Verification

  • Listen to the first, a middle and the last chunk of every batch, plus every line containing a dictionary term.
  • Check duration is plausible (about 150 words per minute for narration).
  • ffprobe the output for codec, sample rate and duration; measure loudness with ffmpeg -i out.mp3 -af ebur128 -f null -.
  • For streaming, log time to first audio byte over 20 requests and report p50/p95.

Report template

**Content:** 14 lessons, 61k characters | **Provider:** OpenAI gpt-4o-mini-tts, voice "sage"
**Instructions:** "Calm instructor, moderate pace" (identical for all chunks)
**Pronunciation dictionary:** 23 entries, all spot-checked
**Output:** 14 mp3 files, 128 kbps, -16 LUFS, total 6h12m; est. cost confirmed before run
**Disclosure:** "This narration is AI-generated" added to each lesson intro

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Text to Speech"
  short_description: "Natural narration with names pronounced right"
  default_prompt: "$text-to-speech Narrate these 14 course lessons as MP3s with a calm instructor voice"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem