Metadata-Version: 2.4
Name: zeli-tts
Version: 0.1.0
Summary: Python SDK for Zeli TTS — self-hosted, low-latency streaming text-to-speech.
Author: Zeligate
License: MIT
Project-URL: Homepage, https://github.com/zeligate/zeli-tts-v2
Project-URL: Source, https://github.com/zeligate/zeli-tts-v2
Keywords: tts,text-to-speech,streaming,speech,voice,zeli
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.28
Requires-Dist: websocket-client>=1.6
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"

# Zeli TTS — Python SDK

A small, dependency-light client for [Zeli TTS](../../README.md) — self-hosted,
low-latency streaming text-to-speech. Point it at your running server and get
audio back in a couple of lines. The streaming API mirrors the shape of the
ElevenLabs SDK (`text_to_speech.stream(...)` / `.convert(...)` plus `play` /
`save` / `stream` helpers).

> The server streams **raw mono int16 PCM at 24 kHz** over a WebSocket. The SDK
> hides that wire protocol — you work with byte chunks, WAV blobs, and voices.

## Install

```bash
pip install ./sdk/python          # from this repo
# or, once published:
# pip install zeli-tts
```

Runtime deps: `requests` (HTTP) and `websocket-client` (streaming). Live
playback (`play`/`stream`) shells out to `ffplay` (ffmpeg), `mpv`, or `afplay`.

## Quickstart

```python
from zeli_tts import ZeliTTS, play, save, stream

client = ZeliTTS(base_url="http://<host>:8000")   # api_key optional

# 1) Stream — first audio after the first sentence, not the whole passage
audio = client.text_to_speech.stream("Hey there, this is Zeli.", voice="zeli-voice-1")
for chunk in audio:
    ...                # raw 24 kHz mono int16 PCM bytes as they generate

# 2) Stream + play live (needs ffplay or mpv)
stream(client.text_to_speech.stream("Playing as I generate.", voice="zeli-voice-1"))

# 3) One-shot — a finished WAV
wav = client.text_to_speech.convert("Hello world.", voice="zeli-voice-1")
save(wav, "hello.wav")
```

## Client

```python
ZeliTTS(base_url, *, api_key=None, timeout=60.0)
```

- `base_url` — `http://host:8000` or `https://tts.example.com`; `ws(s)://` is
  derived for streaming.
- `api_key` — sent as `Authorization: Bearer …` on every request. The current
  server doesn't enforce auth, so this is a no-op today and ready for when it
  does.

```python
client.capabilities()   # -> Capabilities(engine, engine_label, tags, tag_list, tag_groups, controls)
client.health()         # -> {"status": "ok", "ready": True}
client.is_ready()       # -> bool
```

## Synthesis — `client.text_to_speech`

```python
stream(text, *, voice=None, humanize=False, use_tags=False,
       exaggeration=None, cfg_weight=None, temperature=None,
       speed=None, speaker=None, humanize_system=None, timeout=None) -> AudioStream

convert(text, *, ..., output_format="wav") -> bytes   # "wav" | "pcm"
```

- `stream(...)` returns an **`AudioStream`** — iterate it for PCM `bytes`. After
  iteration starts it exposes `.sample_rate`, `.format`, `.voice`, and `.text`
  (the exact text spoken, after any Humanize rewrite). `.read()` drains it to a
  single blob.
- `convert(...)` drains the same stream and returns a finished **WAV** (default)
  or raw **PCM**.
- You pass one unified set of options; the server applies per-engine defaults,
  clamps ranges, and ignores fields its engine doesn't use — so the same call
  works whether the live engine is **Zeli Turbo** (`exaggeration`, `cfg_weight`,
  `temperature`, `use_tags`) or **CSM** (`temperature`, `speed`). Check
  `client.capabilities()` to adapt a UI.

## Voices — `client.voices`

```python
client.voices.list()                 # -> [Voice(id, label, description, custom), ...]
client.voices.add(name="My voice", file="me.wav", description="", source="upload")  # -> 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.

## Helpers

```python
from zeli_tts import play, save, stream, pcm_to_wav

play(audio)                    # play a finished clip (WAV or PCM/iterable)
stream(audio_stream)           # play chunks live; returns the collected PCM
save(audio, "out.wav")         # write WAV (raw PCM is wrapped when path ends .wav)
pcm_to_wav(pcm, sample_rate=24000)
```

## Errors

All derive from `ZeliTTSError`:

| 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`, `.body`) |
| `GenerationError` | server sent an `error` event mid-synthesis |

## Notes / roadmap

- **Sync** client for v1. An async variant (`websockets` + `httpx`) can follow.
- The SDK targets the server's `/ws/tts` + `/voices` + `/capabilities` contract;
  freeze/version that contract before publishing to PyPI.
