ZeliSpeech Python SDK — self-hosted, low-latency streaming text-to-speech.
Project description
ZeliSpeech — Python SDK
A small, dependency-light client for ZeliSpeech —
self-hosted, low-latency streaming text-to-speech. Point it at your running
server and get audio back in a couple of lines. The SDK talks the same
authenticated /v1 API documented in the
API reference, so
the request/response shapes follow mainstream TTS-API conventions.
Install
pip install zeli-tts
Runtime dep: requests. Live playback (play/stream) shells out to ffplay
(ffmpeg), mpv, or afplay.
Quickstart
from zeli_tts import ZeliSpeech, play, save, stream
client = ZeliSpeech(
base_url="https://voice.zeligate.com",
api_key="sk-zeli-...", # optional if the box runs open
)
# 1) Stream — first audio after the first sentence, not the whole passage
audio = client.text_to_speech.stream(
voice_id="zeli-voice-1",
text="Hey there, this is Zeli.",
output_format="mp3_44100_128",
)
for chunk in audio:
... # audio bytes as they generate
# 2) Stream + play live (needs ffplay or mpv)
stream(client.text_to_speech.stream(voice_id="zeli-voice-1", text="Playing as I generate."))
# 3) One-shot — a finished clip
audio = client.text_to_speech.convert(voice_id="zeli-voice-1", text="Hello world.")
save(audio, "hello.mp3")
Client
ZeliSpeech(base_url, *, api_key=None, timeout=60.0)
base_url—http://host:8000orhttps://voice.zeligate.com; do not include a trailing/v1.api_key— sent asAuthorization: Bearer …on every request. Required only when the box setsZELI_API_KEY; a loopback/embedded box can run open.client.capabilities()/client.health()/client.is_ready()— engine info and readiness (handy while a box is warming up).
Synthesis — client.text_to_speech
stream(voice_id, text, *, model_id="zeli-turbo", voice_settings=None,
output_format="mp3_44100_128", timeout=None) -> AudioStream
convert(voice_id, text, *, ...same...) -> bytes
stream(...)returns anAudioStream— iterate it for audiobytesas they generate;.read()drains it to one blob. It exposes.output_format,.sample_rate,.voice(the resolved voice id), and.request_id.convert(...)returns the finished clip in the requestedoutput_format.output_formataccepts the full menu —mp3_*,wav_*, rawpcm_*,ulaw_8000,alaw_8000,opus_48000_*— see the output formats reference.voice_settings(stability/style/speed/…) maps onto Turbo delivery — see voice settings.
Voices — client.voices
client.voices.list() # -> [Voice(id, label, description, custom), ...]
client.voices.get("zeli-voice-1") # -> Voice (404s on unknown ids)
client.voices.add(name="My voice", file="me.wav", description="") # -> Voice
client.voices.delete("custom-abc123")
add clones a voice zero-shot from a short reference clip (~10–20 s of
clean single-speaker audio; file may be a path, a file object, or bytes).
Cloning and removal apply to the Turbo engine's custom voices, and require a
full API key — ephemeral tokens get 401 insufficient_scope here.
Ephemeral tokens — client.tokens
Never ship a real API key to a browser or mobile app: anyone can read it. The
intended flow is that your backend (which holds a full api_key) mints a
short-lived, synthesis-only token and hands it to the client, which then talks
to the API directly with that token.
tok = client.tokens.create_ephemeral(ttl_seconds=300)
# -> {"token": "zsk_temp_…", "expires_at": 1770000000, "scope": "tts"}
ttl_secondsis clamped to 30–600 (default 300).expires_atis the epoch second the token dies — enforced server-side on every request, so a leaked token is usable for minutes at most.- The returned token has scope
"tts": it can call synthesis and read endpoints (text_to_speech.stream/convert,voices.list/get) but notvoices.add/deleteortokens.create_ephemeral— those return401 insufficient_scope. - Minting requires a full key, and
501 not_supportedis returned on a server without the portal keys table configured.
Backend endpoint example (FastAPI):
from fastapi import FastAPI
from zeli_tts import ZeliSpeech
app = FastAPI()
zeli = ZeliSpeech(base_url="https://voice.zeligate.com", api_key=os.environ["ZELISPEECH_API_KEY"])
@app.post("/api/tts-token")
def tts_token():
return zeli.tokens.create_ephemeral(ttl_seconds=300) # browser uses this directly
More examples
# Delivery control (stability / style / speed map onto Turbo knobs)
clip = client.text_to_speech.convert(
"zeli-voice-1", "Same words, very different delivery!",
voice_settings={"stability": 0.2, "style": 0.9, "speed": 1.1},
)
# Raw PCM for your own audio pipeline (24 kHz mono int16, no container)
pcm = client.text_to_speech.convert("zeli-voice-1", "Raw samples.", output_format="pcm_24000")
# Telephony formats
ulaw = client.text_to_speech.convert("zeli-voice-1", "For the phone line.", output_format="ulaw_8000")
# Stream straight to a file as it generates
with open("out.mp3", "wb") as f:
for chunk in client.text_to_speech.stream("zeli-voice-1", "Written as it speaks."):
f.write(chunk)
# Clone a voice, use it, remove it
voice = client.voices.add(name="My narrator", file="reference.wav")
client.text_to_speech.convert(voice.id, "Cloned voice speaking.")
client.voices.delete(voice.id)
# Play inline in a Jupyter notebook
from IPython.display import Audio
Audio(data=bytes(client.text_to_speech.convert("zeli-voice-1", "Hello, notebook!")))
# Robust error handling
from zeli_tts import APIError, ConnectionError
try:
client.text_to_speech.convert("zeli-voice-1", "…")
except APIError as e:
if e.status == "invalid_api_key":
... # rotate/refresh the key
elif e.status_code == 503:
... # model still loading — retry shortly
except ConnectionError:
... # box unreachable / stopped
Helpers
from zeli_tts import play, save, stream, pcm_to_wav
play(audio) # play a finished clip (any output format)
stream(audio_stream) # play chunks live; returns the collected bytes
save(audio, "out.mp3") # write bytes to a file (pcm_* wrapped when .wav)
pcm_to_wav(pcm, sample_rate=24000)
Errors
All derive from ZeliSpeechError:
| Exception | When |
|---|---|
ConfigurationError |
bad base_url, or a missing dep/player |
ConnectionError |
server unreachable (refused / DNS / timeout) |
APIError |
non-2xx from an HTTP endpoint (.status_code, .status, .body) |
GenerationError |
the stream broke mid-synthesis |
Auth failures are APIErrors whose .status tells you why:
.status_code |
.status |
When |
|---|---|---|
| 401 | missing_api_key |
auth required but no key sent |
| 401 | invalid_api_key |
unknown, revoked, or expired key/token |
| 401 | insufficient_scope |
an ephemeral token hit a management route |
| 501 | not_supported |
minting on a server without the keys table |
Migrating from 0.1.x
0.2.0 moves the SDK onto the authenticated /v1 API and adopts the ZeliSpeech
naming. ZeliTTS / ZeliTTSError remain as aliases, but the synthesis calls
changed: stream(text, voice=...) → stream(voice_id, text), audio now
arrives in output_format (mp3 by default) instead of raw PCM, and the
engine-knob kwargs (exaggeration, cfg_weight, …) are replaced by
voice_settings.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zeli_tts-0.3.1.tar.gz.
File metadata
- Download URL: zeli_tts-0.3.1.tar.gz
- Upload date:
- Size: 18.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5cda34278bfaccefafe5bf88344ffd7e750683e0d38ebb0f1b39ee28c9821a9e
|
|
| MD5 |
94565c33142277eabf3ae8907626769d
|
|
| BLAKE2b-256 |
d11c5f06c6e19531df93ee975375fd29e4fa43f8175654dd380766a9e0407de6
|
File details
Details for the file zeli_tts-0.3.1-py3-none-any.whl.
File metadata
- Download URL: zeli_tts-0.3.1-py3-none-any.whl
- Upload date:
- Size: 16.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e7180e8133f3db3be7f5184109247e2c560bfd49e8200135c09e36a0c414329
|
|
| MD5 |
1a3731e7ac98d98ad50b27e1788e7073
|
|
| BLAKE2b-256 |
58b09c6a930577cf2ff157716112d53ffc9ceb7002b240c620d78785fb14f0dc
|