tts-studio 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tts_studio-1.0.0/MANIFEST.in +5 -0
- tts_studio-1.0.0/PKG-INFO +528 -0
- tts_studio-1.0.0/README.md +475 -0
- tts_studio-1.0.0/pyproject.toml +82 -0
- tts_studio-1.0.0/setup.cfg +4 -0
- tts_studio-1.0.0/src/tts_studio/__init__.py +11 -0
- tts_studio-1.0.0/src/tts_studio/__main__.py +8 -0
- tts_studio-1.0.0/src/tts_studio/audio_convert.py +186 -0
- tts_studio-1.0.0/src/tts_studio/cli.py +145 -0
- tts_studio-1.0.0/src/tts_studio/config.py +81 -0
- tts_studio-1.0.0/src/tts_studio/engines/__init__.py +30 -0
- tts_studio-1.0.0/src/tts_studio/engines/base.py +222 -0
- tts_studio-1.0.0/src/tts_studio/engines/catalogs.py +401 -0
- tts_studio-1.0.0/src/tts_studio/engines/kitten_engine.py +384 -0
- tts_studio-1.0.0/src/tts_studio/engines/kokoro_engine.py +296 -0
- tts_studio-1.0.0/src/tts_studio/engines/piper_engine.py +541 -0
- tts_studio-1.0.0/src/tts_studio/engines/pocket_engine.py +313 -0
- tts_studio-1.0.0/src/tts_studio/frontend_dist/assets/index-C084SbHr.js +67 -0
- tts_studio-1.0.0/src/tts_studio/frontend_dist/assets/index-Cl6__h45.css +1 -0
- tts_studio-1.0.0/src/tts_studio/frontend_dist/index.html +24 -0
- tts_studio-1.0.0/src/tts_studio/hardware.py +126 -0
- tts_studio-1.0.0/src/tts_studio/jobs.py +196 -0
- tts_studio-1.0.0/src/tts_studio/logbus.py +153 -0
- tts_studio-1.0.0/src/tts_studio/main.py +139 -0
- tts_studio-1.0.0/src/tts_studio/model_manager.py +403 -0
- tts_studio-1.0.0/src/tts_studio/openai_store.py +296 -0
- tts_studio-1.0.0/src/tts_studio/routers/__init__.py +1 -0
- tts_studio-1.0.0/src/tts_studio/routers/generate.py +194 -0
- tts_studio-1.0.0/src/tts_studio/routers/models.py +110 -0
- tts_studio-1.0.0/src/tts_studio/routers/openai_admin.py +245 -0
- tts_studio-1.0.0/src/tts_studio/routers/openai_api.py +445 -0
- tts_studio-1.0.0/src/tts_studio/routers/system.py +58 -0
- tts_studio-1.0.0/src/tts_studio/routers/voices.py +217 -0
- tts_studio-1.0.0/src/tts_studio/schemas.py +35 -0
- tts_studio-1.0.0/src/tts_studio/storage.py +341 -0
- tts_studio-1.0.0/src/tts_studio.egg-info/PKG-INFO +528 -0
- tts_studio-1.0.0/src/tts_studio.egg-info/SOURCES.txt +39 -0
- tts_studio-1.0.0/src/tts_studio.egg-info/dependency_links.txt +1 -0
- tts_studio-1.0.0/src/tts_studio.egg-info/entry_points.txt +2 -0
- tts_studio-1.0.0/src/tts_studio.egg-info/requires.txt +36 -0
- tts_studio-1.0.0/src/tts_studio.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tts-studio
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Local-first text-to-speech studio: run 'tts-studio' and your browser opens - Kokoro, Kitten, Pocket and Piper in one app, no API keys
|
|
5
|
+
Author: opengptloop
|
|
6
|
+
Project-URL: Homepage, https://github.com/opengptloop/tts-studio
|
|
7
|
+
Project-URL: Repository, https://github.com/opengptloop/tts-studio
|
|
8
|
+
Project-URL: Issues, https://github.com/opengptloop/tts-studio/issues
|
|
9
|
+
Keywords: tts,text-to-speech,speech-synthesis,kokoro,kitten-tts,piper,voice-cloning,audio,local-ai
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Web Environment
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: fastapi>=0.115
|
|
25
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
26
|
+
Requires-Dist: python-multipart>=0.0.9
|
|
27
|
+
Requires-Dist: httpx>=0.27
|
|
28
|
+
Requires-Dist: psutil>=5.9
|
|
29
|
+
Requires-Dist: soundfile>=0.12
|
|
30
|
+
Requires-Dist: numpy>=1.26
|
|
31
|
+
Requires-Dist: huggingface_hub>=0.25
|
|
32
|
+
Provides-Extra: kokoro
|
|
33
|
+
Requires-Dist: kokoro>=0.9.4; extra == "kokoro"
|
|
34
|
+
Requires-Dist: soundfile>=0.12; extra == "kokoro"
|
|
35
|
+
Provides-Extra: kokoro-ja
|
|
36
|
+
Requires-Dist: misaki[ja]; extra == "kokoro-ja"
|
|
37
|
+
Provides-Extra: kokoro-zh
|
|
38
|
+
Requires-Dist: misaki[zh]; extra == "kokoro-zh"
|
|
39
|
+
Provides-Extra: kitten
|
|
40
|
+
Requires-Dist: kittenml>=0.9.4; extra == "kitten"
|
|
41
|
+
Provides-Extra: pocket
|
|
42
|
+
Requires-Dist: pocket-tts>=3.3.0; extra == "pocket"
|
|
43
|
+
Provides-Extra: piper
|
|
44
|
+
Requires-Dist: piper-tts>=1.5; extra == "piper"
|
|
45
|
+
Provides-Extra: all
|
|
46
|
+
Requires-Dist: kokoro>=0.9.4; extra == "all"
|
|
47
|
+
Requires-Dist: soundfile>=0.12; extra == "all"
|
|
48
|
+
Requires-Dist: misaki[ja]; extra == "all"
|
|
49
|
+
Requires-Dist: misaki[zh]; extra == "all"
|
|
50
|
+
Requires-Dist: kittenml>=0.9.4; extra == "all"
|
|
51
|
+
Requires-Dist: pocket-tts>=3.3.0; extra == "all"
|
|
52
|
+
Requires-Dist: piper-tts>=1.5; extra == "all"
|
|
53
|
+
|
|
54
|
+
# TTS Studio
|
|
55
|
+
|
|
56
|
+
A local-first web studio for open-source text-to-speech. Pick an engine, pick
|
|
57
|
+
a voice (or clone one), type your text and get audio — all running on your own
|
|
58
|
+
machine, with no API keys and no data leaving your computer.
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+

|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
## Install & run
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install tts-studio
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Then launch it from any terminal:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
tts-studio
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Your browser opens the studio automatically. Everything — the web UI, the
|
|
77
|
+
studio API and the OpenAI-compatible `/v1` speech API — is served from a
|
|
78
|
+
**single local URL** (`http://127.0.0.1:8000` by default); there is no
|
|
79
|
+
separate backend port to manage or expose. Runtime data (models, voices,
|
|
80
|
+
audio, settings) lives in `~/.tts-studio`.
|
|
81
|
+
|
|
82
|
+
CLI options:
|
|
83
|
+
|
|
84
|
+
| Flag | Default | Description |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `--port` | `8000` | Port to serve on (busy ports are skipped automatically) |
|
|
87
|
+
| `--host` | `127.0.0.1` | Bind address (use `0.0.0.0` to share on your LAN) |
|
|
88
|
+
| `--data-dir` | `~/.tts-studio` | Where models, voices and audio are stored |
|
|
89
|
+
| `--no-browser` | — | Do not open the browser automatically |
|
|
90
|
+
| `--version` | — | Print the version and exit |
|
|
91
|
+
|
|
92
|
+
Engine packages are **not** bundled (they pull in PyTorch and friends), so a
|
|
93
|
+
fresh install is lightweight. Install them from the in-app **Models & Config**
|
|
94
|
+
page, or upfront with extras:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install "tts-studio[piper]" # lightweight start
|
|
98
|
+
pip install "tts-studio[all]" # every engine
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Features
|
|
102
|
+
|
|
103
|
+
- **Four engines in one UI** — Kokoro-82M, KittenTTS 2 + 0.8 ONNX models,
|
|
104
|
+
Pocket TTS and Piper, with per-engine variants and generation options.
|
|
105
|
+
- **Voice management** — 300+ built-in voices, searchable and filterable by
|
|
106
|
+
language, gender and quality; on-demand downloads with progress; custom
|
|
107
|
+
voice uploads (Kokoro tensors, Kitten/Pocket reference clips, Piper ONNX).
|
|
108
|
+
- **Voice cloning** — reference-clip cloning with KittenTTS 2 (Whisper
|
|
109
|
+
auto-transcription when no transcript is given).
|
|
110
|
+
- **Studio workflow** — single or batch (one clip per line) synthesis, live
|
|
111
|
+
job progress, inline previews, result player, downloadable WAV history.
|
|
112
|
+
- **Models & Config page** — install engines with pip in the background,
|
|
113
|
+
load/unload variants, browse the full Piper catalogue, monitor CPU/RAM/disk/GPU.
|
|
114
|
+
- **OpenAI-compatible speech API** — one endpoint, one base URL, zero client
|
|
115
|
+
configuration. Configure the TTS model once in the UI, then send plain
|
|
116
|
+
text from any OpenAI-compatible client (see the full reference below).
|
|
117
|
+
- **Live logs** — backend logs streamed to the browser over WebSocket.
|
|
118
|
+
- **Dark & light theme**, responsive layout, no telemetry.
|
|
119
|
+
|
|
120
|
+
## Engines
|
|
121
|
+
|
|
122
|
+
| Engine | Family | Voices | Languages | Cloning |
|
|
123
|
+
| --- | --- | --- | --- | --- |
|
|
124
|
+
| [Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M) | StyleTTS2 | 54 | 9 | via custom `.pt` tensors |
|
|
125
|
+
| [KittenTTS 2](https://github.com/KittenML/KittenTTS) | 1.7B speech LM | 47 | 10 | yes (reference clip) |
|
|
126
|
+
| KittenTTS 0.8 | ONNX (15–80M) | 8 | English | no |
|
|
127
|
+
| [Pocket TTS](https://github.com/kyutai-labs/pocket-tts) | 100M LM | 26 | 6 | via exported voice states |
|
|
128
|
+
| [Piper](https://github.com/OHF-Voice/piper1-gpl) | VITS/ONNX | 177 | 40+ | — |
|
|
129
|
+
|
|
130
|
+
Models are downloaded on first use and cached locally. Only engines whose
|
|
131
|
+
Python package is installed show as ready; the rest can be installed from the
|
|
132
|
+
Models page.
|
|
133
|
+
|
|
134
|
+
## Requirements
|
|
135
|
+
|
|
136
|
+
- Python 3.10 – 3.12 (Kokoro has no 3.13 wheels yet)
|
|
137
|
+
- Node.js 20+ (only for building/serving the frontend yourself)
|
|
138
|
+
- Optional NVIDIA GPU with CUDA for faster synthesis
|
|
139
|
+
- Optional `ffmpeg` binary — only needed for the `mp3`, `opus` and `aac`
|
|
140
|
+
output formats (`wav`, `flac` and `pcm` always work)
|
|
141
|
+
|
|
142
|
+
## Development (from source)
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
git clone https://github.com/opengptloop/tts-studio.git
|
|
146
|
+
cd tts-studio
|
|
147
|
+
|
|
148
|
+
# Backend: editable install with every engine (or ".[piper]" for a light start)
|
|
149
|
+
python -m venv .venv && source .venv/bin/activate
|
|
150
|
+
pip install -e ".[all]"
|
|
151
|
+
|
|
152
|
+
# Frontend: build the bundle into src/tts_studio/frontend_dist
|
|
153
|
+
cd frontend && npm install && npm run build
|
|
154
|
+
cd ..
|
|
155
|
+
rm -rf src/tts_studio/frontend_dist && cp -r frontend/dist src/tts_studio/frontend_dist
|
|
156
|
+
|
|
157
|
+
# Run the whole app from source (same CLI as the published package)
|
|
158
|
+
./start.sh # or: python -m tts_studio
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
CPU-only tip: install torch first from the CPU wheel index to avoid pulling
|
|
162
|
+
multi-GB CUDA libraries.
|
|
163
|
+
|
|
164
|
+
For frontend development with hot reload:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
cd frontend
|
|
168
|
+
npm run dev # http://localhost:5173, proxies /api, /v1 and /ws to :8000
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
while `tts-studio` (or `./start.sh`) serves the API on :8000.
|
|
172
|
+
|
|
173
|
+
### Use it
|
|
174
|
+
|
|
175
|
+
1. Open the **Models & Config** page and install the engine(s) you want.
|
|
176
|
+
2. Load a model variant (weights download on first load).
|
|
177
|
+
3. Go to the **Studio**, choose an engine/voice, type text and hit Generate.
|
|
178
|
+
4. Watch **Logs** for live backend activity.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## OpenAI-compatible speech API
|
|
183
|
+
|
|
184
|
+
TTS Studio exposes a drop-in, OpenAI-compatible speech endpoint with a
|
|
185
|
+
**single base URL** and **one endpoint**. The flow is intentionally simple:
|
|
186
|
+
|
|
187
|
+
> **The server owns the configuration. The client just sends text.**
|
|
188
|
+
|
|
189
|
+
1. **You** (the administrator) pick the TTS model, voice and defaults **once**
|
|
190
|
+
on the in-app **API** page and hit Save. Only installed engines can be
|
|
191
|
+
selected, and for Piper only downloaded (installed) voices are listed.
|
|
192
|
+
2. **Clients** POST text to `/v1/audio/speech`. They never pick a model, a
|
|
193
|
+
voice, or any other setting.
|
|
194
|
+
3. **Audio** comes back as a streamed binary file in the format you asked for.
|
|
195
|
+
|
|
196
|
+
There are no model mappings and no client-side setup beyond the base URL.
|
|
197
|
+
API keys are **optional**: the endpoint is open by default, and you can lock
|
|
198
|
+
it down with a single toggle if you ever need to.
|
|
199
|
+
|
|
200
|
+
### Base URL
|
|
201
|
+
|
|
202
|
+
Everything lives under one prefix — your server origin plus `/v1`:
|
|
203
|
+
|
|
204
|
+
| Deployment | Base URL |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| Default backend | `http://localhost:8000/v1` |
|
|
207
|
+
| Custom host/port | `http://<host>:<port>/v1` |
|
|
208
|
+
| Behind a reverse proxy | `https://<your-domain>/v1` |
|
|
209
|
+
| Frontend dev server | `http://localhost:5173/v1` (proxied to the backend) |
|
|
210
|
+
|
|
211
|
+
### Authentication — optional by default
|
|
212
|
+
|
|
213
|
+
The API starts in **open mode**: anyone who can reach the server may call it,
|
|
214
|
+
no key required. Keys exist if you want to lock things down or track usage.
|
|
215
|
+
|
|
216
|
+
| Mode | Behaviour |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| **Open** (default, `require_api_key: false`) | No header needed. If a valid key is sent anyway, it still gets usage stats and rate limits; unknown/placeholder keys are tolerated (so OpenAI SDKs that insist on a key keep working). |
|
|
219
|
+
| **Required** (`require_api_key: true`) | A valid key must be sent as `Authorization: Bearer sk-tts-...` (or an `api-key: <key>` header). Missing or wrong keys get `401 invalid_api_key`. |
|
|
220
|
+
|
|
221
|
+
Keys are created on the **API** page (or via the admin endpoints below). They
|
|
222
|
+
look like `sk-tts-<48 hex chars>`, can be named, rate-limited per minute,
|
|
223
|
+
revoked and deleted, and report usage counts.
|
|
224
|
+
|
|
225
|
+
### `POST /v1/audio/speech`
|
|
226
|
+
|
|
227
|
+
The only endpoint clients ever need.
|
|
228
|
+
|
|
229
|
+
#### Request body
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"input": "Today is a wonderful day!",
|
|
234
|
+
"output": "mp3"
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
| Field | Type | Required | Description |
|
|
239
|
+
| --- | --- | --- | --- |
|
|
240
|
+
| `input` | string **or** array of strings | yes | The text to synthesize. An array is joined with newlines into one clip. Maximum length is the server's `max_input_chars` (default 4096). |
|
|
241
|
+
| `output` | string | no | Audio format: `mp3`, `opus`, `aac`, `flac`, `wav`, `pcm`. Falls back to the server's default format (`mp3` unless changed). |
|
|
242
|
+
| `response_format` | string | no | OpenAI-compatible alias for `output`. If both are sent, `output` wins. |
|
|
243
|
+
| `model` | string | — | **Accepted but ignored.** Send it (e.g. `"tts-1"`) only because some SDKs require a value. |
|
|
244
|
+
| `voice` | string | — | **Accepted but ignored.** The server-configured voice always wins. |
|
|
245
|
+
| `speed` | number | — | **Accepted but ignored.** Speed is a server-side setting (0.25 – 4.0). |
|
|
246
|
+
|
|
247
|
+
Unknown extra fields are ignored, exactly like the OpenAI API — existing
|
|
248
|
+
OpenAI-shaped clients work unchanged. That's the whole request: **`input`**
|
|
249
|
+
plus, optionally, **`output`**.
|
|
250
|
+
|
|
251
|
+
#### Response
|
|
252
|
+
|
|
253
|
+
The audio bytes, streamed straight from disk with no intermediate buffering:
|
|
254
|
+
|
|
255
|
+
| Header | Meaning |
|
|
256
|
+
| --- | --- |
|
|
257
|
+
| `Content-Type` | Media type of the chosen format (e.g. `audio/mpeg`) |
|
|
258
|
+
| `Content-Disposition` | `inline; filename="speech_<timestamp>.<ext>"` |
|
|
259
|
+
| `X-Sample-Rate` | Engine sample rate in Hz (e.g. `22050` for Piper) |
|
|
260
|
+
| `X-Model` | Engine that rendered the audio (e.g. `piper`) |
|
|
261
|
+
| `X-Voice` | Voice that rendered the audio |
|
|
262
|
+
|
|
263
|
+
### Output formats
|
|
264
|
+
|
|
265
|
+
| Format | Media type | Rendered by | Notes |
|
|
266
|
+
| --- | --- | --- | --- |
|
|
267
|
+
| `mp3` *(default)* | `audio/mpeg` | ffmpeg (libmp3lame) | 128 kbps |
|
|
268
|
+
| `opus` | `audio/ogg` | ffmpeg (libopus) | 64 kbps, Opus in Ogg container |
|
|
269
|
+
| `aac` | `audio/aac` | ffmpeg (aac) | 128 kbps, ADTS stream |
|
|
270
|
+
| `flac` | `audio/flac` | soundfile | lossless |
|
|
271
|
+
| `wav` | `audio/wav` | soundfile | 16-bit PCM |
|
|
272
|
+
| `pcm` | `audio/pcm` | soundfile | raw 16-bit little-endian samples, **no header**; use `X-Sample-Rate` to play it back correctly |
|
|
273
|
+
|
|
274
|
+
`mp3`, `opus` and `aac` require an `ffmpeg` binary on the host; `wav`, `flac`
|
|
275
|
+
and `pcm` are always available. If ffmpeg is missing, requesting a
|
|
276
|
+
ffmpeg-only format returns `400 format_unavailable` listing what *is*
|
|
277
|
+
available. The engine's native sample rate is used for every format.
|
|
278
|
+
|
|
279
|
+
### Errors
|
|
280
|
+
|
|
281
|
+
Errors always use OpenAI's envelope, so standard SDK error handling works:
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"error": {
|
|
286
|
+
"message": "You must provide an 'input' parameter with the text to synthesize.",
|
|
287
|
+
"type": "invalid_request_error",
|
|
288
|
+
"param": "input",
|
|
289
|
+
"code": "missing_input"
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
| Status | `code` | When |
|
|
295
|
+
| --- | --- | --- |
|
|
296
|
+
| 400 | `missing_input` | `input` is missing or empty |
|
|
297
|
+
| 400 | `input_too_long` | Text exceeds the configured `max_input_chars` |
|
|
298
|
+
| 400 | `invalid_request` | Malformed JSON / body validation failure |
|
|
299
|
+
| 400 | `model_not_configured` | No TTS model saved yet — configure one on the API page |
|
|
300
|
+
| 400 | `voice_not_configured` | No voice saved yet — configure one on the API page |
|
|
301
|
+
| 400 | `model_not_ready` | Configured engine is not installed (install it on the Models page) |
|
|
302
|
+
| 400 | `voice_not_found` | Configured voice is unavailable (e.g. Piper voice deleted) |
|
|
303
|
+
| 400 | `format_unavailable` | Unknown format, or ffmpeg missing for mp3/opus/aac |
|
|
304
|
+
| 401 | `invalid_api_key` | Auth mode is *required* and the key is missing/wrong |
|
|
305
|
+
| 429 | `rate_limit_exceeded` | Per-key sliding-window limit hit (retry after N seconds) |
|
|
306
|
+
| 503 | `model_loading` | Engine is still loading — the load continues in the background; retry in a few seconds |
|
|
307
|
+
| 503 | `engine_busy` | Engine is busy with another request — retry |
|
|
308
|
+
| 500 | `internal_error` | Unexpected synthesis failure |
|
|
309
|
+
|
|
310
|
+
Error `type` is `invalid_request_error` for 400/401, `rate_limit_error` for
|
|
311
|
+
429 and `server_error` for 5xx. FastAPI's default 422 validation shape is
|
|
312
|
+
translated to this envelope for all `/v1` routes.
|
|
313
|
+
|
|
314
|
+
### Cold starts & auto-loading
|
|
315
|
+
|
|
316
|
+
You never have to pre-load the configured engine. The first request after a
|
|
317
|
+
server restart triggers a background engine load; the request waits up to 90
|
|
318
|
+
seconds for it. If loading takes longer, the API answers `503 model_loading`
|
|
319
|
+
and the load keeps running — just retry. Saving the configuration warms the
|
|
320
|
+
engine up immediately, so real clients normally never see a cold start.
|
|
321
|
+
|
|
322
|
+
### Rate limits
|
|
323
|
+
|
|
324
|
+
Rate limits are **per API key** with a 60-second sliding window, configured
|
|
325
|
+
as `rate_limit_per_min` when you create or edit a key (unlimited by default).
|
|
326
|
+
Exceeding it returns `429 rate_limit_exceeded` with a retry-after hint.
|
|
327
|
+
Anonymous requests in open mode are not rate-limited.
|
|
328
|
+
|
|
329
|
+
### Code examples
|
|
330
|
+
|
|
331
|
+
#### cURL — the whole API in one line
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
curl http://localhost:8000/v1/audio/speech \
|
|
335
|
+
-H "Content-Type: application/json" \
|
|
336
|
+
-d '{"input": "Today is a wonderful day!"}' \
|
|
337
|
+
--output speech.mp3
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Ask for a specific format, or send a key when "require API key" is on:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
curl http://localhost:8000/v1/audio/speech \
|
|
344
|
+
-H "Content-Type: application/json" \
|
|
345
|
+
-H "Authorization: Bearer sk-tts-your-key-here" \
|
|
346
|
+
-d '{"input": "Today is a wonderful day!", "output": "wav"}' \
|
|
347
|
+
--output speech.wav
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Python — requests
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
import requests
|
|
354
|
+
|
|
355
|
+
resp = requests.post(
|
|
356
|
+
"http://localhost:8000/v1/audio/speech",
|
|
357
|
+
json={"input": "Today is a wonderful day!", "output": "mp3"},
|
|
358
|
+
# headers={"Authorization": "Bearer sk-tts-..."}, # only if keys are required
|
|
359
|
+
)
|
|
360
|
+
resp.raise_for_status()
|
|
361
|
+
with open("speech.mp3", "wb") as f:
|
|
362
|
+
f.write(resp.content)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### Python — official OpenAI SDK
|
|
366
|
+
|
|
367
|
+
Drop-in compatible: point `base_url` at TTS Studio. SDKs require an API key
|
|
368
|
+
and a model name — send placeholders; the server ignores them.
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
from openai import OpenAI
|
|
372
|
+
from pathlib import Path
|
|
373
|
+
|
|
374
|
+
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
|
|
375
|
+
|
|
376
|
+
with client.audio.speech.with_streaming_response.create(
|
|
377
|
+
model="tts-1", # ignored by TTS Studio
|
|
378
|
+
voice="alloy", # ignored — server voice wins
|
|
379
|
+
input="Today is a wonderful day!",
|
|
380
|
+
response_format="mp3",
|
|
381
|
+
) as response:
|
|
382
|
+
response.stream_to_file("speech.mp3")
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
#### JavaScript / TypeScript
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
const resp = await fetch("http://localhost:8000/v1/audio/speech", {
|
|
389
|
+
method: "POST",
|
|
390
|
+
headers: { "Content-Type": "application/json" },
|
|
391
|
+
body: JSON.stringify({ input: "Today is a wonderful day!", output: "mp3" }),
|
|
392
|
+
});
|
|
393
|
+
if (!resp.ok) throw new Error(`${resp.status}: ${await resp.text()}`);
|
|
394
|
+
|
|
395
|
+
const blob = await resp.blob(); // e.g. plug into an <audio> element
|
|
396
|
+
document.querySelector("audio").src = URL.createObjectURL(blob);
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Or with the official `openai` npm package:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
import OpenAI from "openai";
|
|
403
|
+
import fs from "fs";
|
|
404
|
+
|
|
405
|
+
const client = new OpenAI({ baseURL: "http://localhost:8000/v1", apiKey: "not-needed" });
|
|
406
|
+
const mp3 = await client.audio.speech.create({
|
|
407
|
+
model: "tts-1", // ignored
|
|
408
|
+
voice: "alloy", // ignored
|
|
409
|
+
input: "Today is a wonderful day!",
|
|
410
|
+
response_format: "mp3",
|
|
411
|
+
});
|
|
412
|
+
fs.writeFileSync("speech.mp3", Buffer.from(await mp3.arrayBuffer()));
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### Other OpenAI-compatible apps (SillyTavern, LibreChat, …)
|
|
416
|
+
|
|
417
|
+
Any app that lets you set a custom OpenAI-compatible endpoint works:
|
|
418
|
+
|
|
419
|
+
| Setting | Value |
|
|
420
|
+
| --- | --- |
|
|
421
|
+
| Base URL / API URL | `http://<your-server>:8000/v1` (TTS: append `/audio/speech` if the app asks for the full URL) |
|
|
422
|
+
| API key | your `sk-tts-…` key — or any placeholder (e.g. `not-needed`) while in open mode |
|
|
423
|
+
| Model | any (e.g. `tts-1`) — ignored |
|
|
424
|
+
| Voice | any — the server-configured voice is used |
|
|
425
|
+
|
|
426
|
+
### `GET /v1` — service info
|
|
427
|
+
|
|
428
|
+
A tiny health/info endpoint so clients can verify the base URL:
|
|
429
|
+
|
|
430
|
+
```json
|
|
431
|
+
{
|
|
432
|
+
"status": "ok",
|
|
433
|
+
"service": "TTS Studio",
|
|
434
|
+
"auth_required": false,
|
|
435
|
+
"endpoints": { "speech": "POST /v1/audio/speech" }
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Server-side configuration (the API page)
|
|
440
|
+
|
|
441
|
+
All of the above is configured once in the web UI on the **API** page — no
|
|
442
|
+
config files to hand-edit:
|
|
443
|
+
|
|
444
|
+
1. **TTS model** — a dropdown listing **only installed engines** (uninstalled
|
|
445
|
+
ones are shown disabled with an "install required" hint; install them on
|
|
446
|
+
the Models & Config page first).
|
|
447
|
+
2. **Voice** — the chosen engine's voices; for **Piper only downloaded
|
|
448
|
+
voices** are listed, with a hint linking to the catalogue when none are
|
|
449
|
+
downloaded yet.
|
|
450
|
+
3. **Defaults** — speaking speed (0.25 – 4.0), default output format, and the
|
|
451
|
+
maximum input length in characters (default 4096).
|
|
452
|
+
4. **Save** persists everything to `backend/data/api_config.json` and warms
|
|
453
|
+
the engine up in the background.
|
|
454
|
+
5. **API keys** — create, name, rate-limit, revoke and delete keys, with
|
|
455
|
+
per-key usage counters, plus the **"Require an API key"** toggle.
|
|
456
|
+
|
|
457
|
+
The same operations are available as a local admin API (this is the studio's
|
|
458
|
+
own `/api` surface — it is never exposed through the public `/v1` prefix):
|
|
459
|
+
|
|
460
|
+
| Endpoint | Description |
|
|
461
|
+
| --- | --- |
|
|
462
|
+
| `GET /api/openai/status` | Public-API status: auth mode, active engine/voice, key counts, ffmpeg availability, supported formats |
|
|
463
|
+
| `GET /api/openai/config` | Active configuration + per-engine install/loaded state + available formats |
|
|
464
|
+
| `PUT /api/openai/config` | Save the configuration; validates that the engine is installed and the voice available, then triggers a background warm-up |
|
|
465
|
+
| `PATCH /api/openai/config/auth` | Toggle `require_api_key` without touching anything else |
|
|
466
|
+
| `GET /api/openai/keys` | List keys with usage stats |
|
|
467
|
+
| `POST /api/openai/keys` | Create a key (`{"name": "...", "rate_limit_per_min": 60}`) |
|
|
468
|
+
| `PATCH /api/openai/keys/{id}` | Rename / change rate limit / revoke |
|
|
469
|
+
| `DELETE /api/openai/keys/{id}` | Delete a key permanently |
|
|
470
|
+
|
|
471
|
+
Saved configuration shape (`PUT /api/openai/config` body):
|
|
472
|
+
|
|
473
|
+
| Field | Default | Constraints |
|
|
474
|
+
| --- | --- | --- |
|
|
475
|
+
| `engine` | — (required) | must be an **installed** engine |
|
|
476
|
+
| `variant` | `null` | engine-specific flavour (e.g. `piper`, `kokoro-82m`) |
|
|
477
|
+
| `voice` | — (required) | must be available for the engine (Piper: downloaded ONNX voices only) |
|
|
478
|
+
| `speed` | `1.0` | 0.25 – 4.0 |
|
|
479
|
+
| `response_format` | `mp3` | one of the six formats |
|
|
480
|
+
| `require_api_key` | `false` | open mode when `false` |
|
|
481
|
+
| `max_input_chars` | `4096` | 1 – 100000 |
|
|
482
|
+
|
|
483
|
+
### Storage & compatibility
|
|
484
|
+
|
|
485
|
+
- `backend/data/api_config.json` — the single active configuration.
|
|
486
|
+
- `backend/data/api_keys.json` — API keys with usage stats (stored in plain
|
|
487
|
+
text by design: this is a local-first, single-user app).
|
|
488
|
+
- `backend/data/api_audio/` — temporary render files, deleted right after
|
|
489
|
+
each response is streamed.
|
|
490
|
+
- **Upgrading from the old mapping-based API?** `api_config.json` files with
|
|
491
|
+
the legacy `mappings` schema are migrated automatically: the first enabled
|
|
492
|
+
mapping becomes the active engine/voice, and the old defaults carry over.
|
|
493
|
+
- CORS is open by default (`TTS_CORS_ORIGINS=*`, see `src/tts_studio/config.py`),
|
|
494
|
+
so browser-based clients can call the endpoint directly.
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## Project layout
|
|
499
|
+
|
|
500
|
+
```
|
|
501
|
+
src/tts_studio/ installable Python package (published to PyPI)
|
|
502
|
+
cli.py the `tts-studio` command: one port, browser auto-open
|
|
503
|
+
main.py FastAPI app - serves the API and the packaged web UI
|
|
504
|
+
engines/ kokoro / kitten / pocket / piper adapters + catalogs
|
|
505
|
+
routers/ system, models, voices, generate + OpenAI-compatible endpoints
|
|
506
|
+
frontend_dist/ built web UI, bundled into the wheel at release time
|
|
507
|
+
frontend/ React 19 + Vite single-page app (source)
|
|
508
|
+
src/pages/ Studio, Models & Config, API, Logs
|
|
509
|
+
src/components/ voice picker, modals, toasts, primitives
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Runtime data (models, voices, audio, logs) lives in `~/.tts-studio` — override
|
|
513
|
+
with `TTS_DATA_DIR` or the `--data-dir` flag. Configuration variables are read
|
|
514
|
+
from `TTS_*` environment variables (see `src/tts_studio/config.py`):
|
|
515
|
+
`TTS_HOST`, `TTS_PORT`, `TTS_DATA_DIR`, `TTS_CORS_ORIGINS`,
|
|
516
|
+
`TTS_PIPER_CATALOG_TTL`.
|
|
517
|
+
|
|
518
|
+
## Notes
|
|
519
|
+
|
|
520
|
+
- Voices and weights belong to their upstream projects; licences vary per
|
|
521
|
+
model (Kokoro: Apache-2.0, KittenTTS code: Apache-2.0, Pocket TTS:
|
|
522
|
+
CC-BY-4.0, Piper voices: per-voice). Check the upstream model cards before
|
|
523
|
+
commercial use.
|
|
524
|
+
- Everything is stored and computed locally; the only network calls are model
|
|
525
|
+
and voice downloads from Hugging Face.
|
|
526
|
+
- The speech API is open by default. If you expose it beyond localhost, turn
|
|
527
|
+
on "Require an API key" on the API page (and/or put it behind your proxy's
|
|
528
|
+
auth) so only intended callers can generate audio.
|