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.
Files changed (41) hide show
  1. tts_studio-1.0.0/MANIFEST.in +5 -0
  2. tts_studio-1.0.0/PKG-INFO +528 -0
  3. tts_studio-1.0.0/README.md +475 -0
  4. tts_studio-1.0.0/pyproject.toml +82 -0
  5. tts_studio-1.0.0/setup.cfg +4 -0
  6. tts_studio-1.0.0/src/tts_studio/__init__.py +11 -0
  7. tts_studio-1.0.0/src/tts_studio/__main__.py +8 -0
  8. tts_studio-1.0.0/src/tts_studio/audio_convert.py +186 -0
  9. tts_studio-1.0.0/src/tts_studio/cli.py +145 -0
  10. tts_studio-1.0.0/src/tts_studio/config.py +81 -0
  11. tts_studio-1.0.0/src/tts_studio/engines/__init__.py +30 -0
  12. tts_studio-1.0.0/src/tts_studio/engines/base.py +222 -0
  13. tts_studio-1.0.0/src/tts_studio/engines/catalogs.py +401 -0
  14. tts_studio-1.0.0/src/tts_studio/engines/kitten_engine.py +384 -0
  15. tts_studio-1.0.0/src/tts_studio/engines/kokoro_engine.py +296 -0
  16. tts_studio-1.0.0/src/tts_studio/engines/piper_engine.py +541 -0
  17. tts_studio-1.0.0/src/tts_studio/engines/pocket_engine.py +313 -0
  18. tts_studio-1.0.0/src/tts_studio/frontend_dist/assets/index-C084SbHr.js +67 -0
  19. tts_studio-1.0.0/src/tts_studio/frontend_dist/assets/index-Cl6__h45.css +1 -0
  20. tts_studio-1.0.0/src/tts_studio/frontend_dist/index.html +24 -0
  21. tts_studio-1.0.0/src/tts_studio/hardware.py +126 -0
  22. tts_studio-1.0.0/src/tts_studio/jobs.py +196 -0
  23. tts_studio-1.0.0/src/tts_studio/logbus.py +153 -0
  24. tts_studio-1.0.0/src/tts_studio/main.py +139 -0
  25. tts_studio-1.0.0/src/tts_studio/model_manager.py +403 -0
  26. tts_studio-1.0.0/src/tts_studio/openai_store.py +296 -0
  27. tts_studio-1.0.0/src/tts_studio/routers/__init__.py +1 -0
  28. tts_studio-1.0.0/src/tts_studio/routers/generate.py +194 -0
  29. tts_studio-1.0.0/src/tts_studio/routers/models.py +110 -0
  30. tts_studio-1.0.0/src/tts_studio/routers/openai_admin.py +245 -0
  31. tts_studio-1.0.0/src/tts_studio/routers/openai_api.py +445 -0
  32. tts_studio-1.0.0/src/tts_studio/routers/system.py +58 -0
  33. tts_studio-1.0.0/src/tts_studio/routers/voices.py +217 -0
  34. tts_studio-1.0.0/src/tts_studio/schemas.py +35 -0
  35. tts_studio-1.0.0/src/tts_studio/storage.py +341 -0
  36. tts_studio-1.0.0/src/tts_studio.egg-info/PKG-INFO +528 -0
  37. tts_studio-1.0.0/src/tts_studio.egg-info/SOURCES.txt +39 -0
  38. tts_studio-1.0.0/src/tts_studio.egg-info/dependency_links.txt +1 -0
  39. tts_studio-1.0.0/src/tts_studio.egg-info/entry_points.txt +2 -0
  40. tts_studio-1.0.0/src/tts_studio.egg-info/requires.txt +36 -0
  41. tts_studio-1.0.0/src/tts_studio.egg-info/top_level.txt +1 -0
@@ -0,0 +1,5 @@
1
+ # Ship the built web UI inside both sdists and wheels.
2
+ recursive-include src/tts_studio/frontend_dist *
3
+ # Keep the frontend sources out of distributions; they are rebuilt at release time.
4
+ prune frontend
5
+ global-exclude *.pyc __pycache__ .DS_Store
@@ -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
+ ![stack](https://img.shields.io/badge/backend-FastAPI-009688)
61
+ ![stack](https://img.shields.io/badge/frontend-React%2019-61dafb)
62
+ ![stack](https://img.shields.io/badge/engines-Kokoro%20·%20Kitten%20·%20Pocket%20·%20Piper-7c5cff)
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.