Clone any voice from a short audio sample and synthesize speech in it, using Coqui TTS (XTTS v2). Comes with a web UI, a REST API, and a CLI client.
Three ways to use it:
- Gradio web UI — drag in a reference clip, type text, hear the result in the browser.
- REST API —
POST /api/speakreturns a WAV;POST /api/playplays it on the server's speakers. - CLI —
speak.pycalls the API and plays the audio locally.
- Python ≥ 3.11
- uv for dependency management
- An NVIDIA GPU is strongly recommended (falls back to CPU, but generation is slow)
- The first run downloads the XTTS v2 model (~1.8 GB)
uv syncPut reference voice clips in the voices/ folder (.wav, .mp3, or .m4a).
The API uses voices/default.mp3 when no voice is specified, so add one — or
pass a voice filename on each request.
# Web UI + API together (default), on http://localhost:7860
uv run python app.py
# API only — no Gradio UI mounted (/ returns 404)
uv run python app.py --api-only
# Faster generation: use YourTTS instead of XTTS v2
# (~2.5s/clip vs ~4-5s, but lower cloning quality). Combine with --api-only.
uv run python app.py --fastThe model is preloaded at startup, so the first request is not slow. Wait
for Ready. in the console before sending requests.
On Windows, avoid running other
uvcommands while the server is running — the live process holds a file lock and a concurrentuvsync can corrupt the environment. Start the server, then use a separate shell for requests.
Both endpoints accept the same JSON body:
| Field | Type | Default | Notes |
|---|---|---|---|
text |
string | — | Required. The text to speak. |
voice |
string | null |
Filename in voices/. Omit to use voices/default.mp3. |
language |
string | "en" |
en, es, fr, de, it, pt, pl, tr, ru, nl, cs, ar, zh-cn, ja, ko, hi. |
speed |
float | 1.0 |
0.5–2.0. XTTS v2 only (ignored under --fast). |
temperature |
float | 0.75 |
0.1–1.5; higher = more expressive. XTTS v2 only. |
A request for a voice that isn't in voices/ returns HTTP 400 with the
list of available voices.
Renders the audio and returns it as a audio/wav response.
curl -X POST localhost:7860/api/speak \
-H "Content-Type: application/json" \
-o out.wav \
-d '{"text":"Hello there","speed":1.0}'Renders the audio, queues it for playback on the machine running the server, and returns immediately — it does not wait for playback to finish:
curl -X POST localhost:7860/api/play \
-H "Content-Type: application/json" \
-d '{"text":"Hello there","voice":"beckinsale 1.wav"}'Response: {"status":"queued","queue_position":1}
Rendering happens concurrently, but playback is serialized: clips play one at a time, in FIFO order. A second request returns right away even if the first clip is still playing — its audio simply waits its turn.
Quoting note: the examples above use Bash (single-quoted JSON). In PowerShell,
curlis an alias forInvoke-WebRequest; usecurl.exeand escape the inner quotes, or use a here-string.
speak.py POSTs to /api/speak and plays the returned audio on your local
speakers:
uv run python speak.py "Hello, this is a test of voice cloning."
uv run python speak.py "Hola mundo" --language es --speed 0.9
uv run python speak.py "Greetings" --voice "beckinsale 1.wav"
uv run python speak.py "Save instead of play" --output out.wavRun uv run python speak.py --help for all options.
| File | Responsibility |
|---|---|
app.py |
FastAPI app (/api/speak, /api/play) + Gradio UI + launch flags. |
playback.py |
In-process FIFO queue and the daemon worker that plays one clip at a time. |
audio.py |
Lightweight play_wav helper (no TTS/torch imports). |
speak.py |
CLI client for the API. |
voices/ |
Reference voice clips. |
uv run pytestCovers the audio helper, the playback queue's serialization and error handling,
voice resolution, and the /api/play endpoint. The TTS model and audio hardware
are mocked, so the suite runs without a GPU or speakers.