Metadata-Version: 2.1
Name: convozen
Version: 0.4.1
Summary: Python SDK for ConvoZen TTS and STT services
Author: Convozen
Keywords: tts,stt,speech,voice,convozen
Classifier: License :: OSI Approved :: MIT License
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests<3.0,>=2.28.0
Provides-Extra: async
Requires-Dist: httpx<1.0,>=0.24.0; extra == "async"
Requires-Dist: httpcore[asyncio]; extra == "async"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: responses; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: anyio[trio]; extra == "dev"
Requires-Dist: respx; extra == "dev"
Requires-Dist: httpx<1.0,>=0.24.0; extra == "dev"

# ConvoZen Voice SDK

Text-to-Speech (TTS) and Speech-to-Text (STT) for Python.

> One API key for both TTS and STT.

## Install

```bash
pip install convozen
```

Requires Python 3.9+

---

## TTS — Convert Text to Speech

Copy and run:

```python
import convozen

client = convozen.Client(api_key="your-api-key")

audio = client.tts.synthesize("नमस्ते… मुझे आपके सर्विस के बारे में थोड़ी जानकारी चाहिए।", language="hi")

with open("output.wav", "wb") as f:
    f.write(audio)
```

With options:

```python
import convozen

client = convozen.Client(api_key="your-api-key")

audio = client.tts.synthesize(
    text="Welcome to Playground.",
    language="en",          # default: "en"
    voice="roohi",          # default: "roohi"
    model="ragini-v1",      # default: "ragini-v1"
    speed=1.2,              # default: 1.0
)

with open("output.wav", "wb") as f:
    f.write(audio)
```

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `text` | `str` | *required* | Text to convert to speech |
| `language` | `str` | `"en"` | Language code (see supported languages below) |
| `voice` | `str` | `"roohi"` | Voice ID for synthesis (see available voices below) |
| `model` | `str` | `"ragini-v1"` | TTS model: `"ragini-v1"` (Indian languages), `"ragini-lite"` / `"ragini_lite"` (lightweight Hindi), or `"rawi-v1"` (Arabic) |
| `speed` | `float` | `1.0` | Speech speed. `0.5` = half speed, `2.0` = double speed. Not applied for `ragini_lite`. |
| `sample_rate` | `int` | model-dependent | Output sample rate in Hz. Defaults to `22050` for `ragini_lite` and `24000` for other models |
| `stream` | `bool` | `False` | If `True`, returns audio chunks as they are generated |
| `format` | `str` | `"wav"` | Audio output format. Currently only `wav` is supported |

### Streaming

```python
import convozen

client = convozen.Client(api_key="your-api-key")

for chunk in client.tts.synthesize("Long text here...", language="en", stream=True):
    audio_player.write(chunk)
```

### Available Voices

| Voice | ID | Default |
|-------|----|---------|
| Roohi | `roohi` | Yes |
| Amaya | `amaya` | |
| Kiyansh | `kiyansh` | |
| Neeraj | `neeraj` | |
| Manya | `manya` | |
| Nidhi | `nidhi` | |
| Ira | `ira` | |
| Trisha | `trisha` | |
| Charvi | `charvi` | |

```python
audio = client.tts.synthesize("Hello", language="en", voice="roohi")
```

### Ragini Lite

`ragini_lite` is the lightweight Ragini backend — a Piper/VITS Hindi voice served
at 22.05 kHz. Use it when you want a smaller, faster Hindi voice.

```python
audio = client.tts.synthesize(
    "नमस्ते, आप कैसे हैं?",
    language="hi",
    model="ragini-lite",   # "ragini-lite" and "ragini_lite" both work
)
```

Both `"ragini-lite"` (hyphen, matching `ragini-v1`) and `"ragini_lite"` (underscore)
are accepted — the SDK normalizes them to the same model.

#### Supported parameters

| Parameter | Value for `ragini_lite` | Notes |
|-----------|-------------------------|-------|
| `text` | any non-empty string | Required |
| `model` | `"ragini-lite"` or `"ragini_lite"` | Both forms accepted |
| `language` | `"hi"`, `"en"` | Hindi voice |
| `sample_rate` | int, **defaults to `22050`** | When omitted, the SDK sends `22050` (vs `24000` for other models) |
| `stream` | `True` / `False` | Chunked streaming supported |
| `format` | `"wav"` | Only `wav` is supported |

#### Not supported / no effect

| Parameter | Behavior on `ragini_lite` |
|-----------|---------------------------|
| `voice` | **Single voice only.** Defaults to `roohi`; any other voice ID falls back to `roohi`. |
| `speed` | **Not applied.** Accepted for API compatibility but has no effect on the output. |

### Rawi — Arabic TTS

Use `model="rawi-v1"` for Arabic dialect synthesis:

```python
import convozen

client = convozen.Client(api_key="your-api-key")

audio = client.tts.synthesize(
    text="Welcome to Playground.",
    language="jo",          # Jordanian dialect
    voice="hadi",
    model="rawi-v1",
    speed=1.2,
)

with open("output.wav", "wb") as f:
    f.write(audio)
```

**Rawi languages:** `en`, `ar`, `ae`, `eg`, `jo`, `ms`, `hz`, `nj`

**Rawi voices:** `ahmad`, `hadi`, `karim`, `mohammad`, `rafoush`, `ghaida`, `hana`, `heba`, `layan`, `salma`

---

## STT — Convert Speech to Text

Copy and run:

```python
import convozen

client = convozen.Client(api_key="your-api-key")

result = client.stt.transcribe("recording.wav")
print(result.text)    # "hello how can I help you"
print(result.score)   # -2.45  (confidence — closer to 0 is better)
```

By default this transcribes **mono, single-speaker** audio and returns an `STTResponse`.

### Alif — Arabic STT

Use `model="alif-v1"` for Arabic speech recognition:

```python
import convozen

client = convozen.Client(api_key="your-api-key")

result = client.stt.transcribe(
    "arabic_recording.wav",
    model="alif-v1",
)
print(result.text)    # Arabic transcript
print(result.score)   # confidence — closer to 0 is better
```

**Notes for `alif-v1`:**
- Optimized for Arabic audio (all dialects supported by the model)
- Plain transcription only — use `akshara-pro` for diarization, stereo, or Indian-language STT
- `lang_tags` are not required (the model does not use language-penalty hints)

---

### Word-level timestamps

Timestamps are **opt-in** — pass `word_time_stamps=True`:

```python
result = client.stt.transcribe("recording.wav", word_time_stamps=True)

for w in result.word_timestamps:
    print(f"{w.word}  {w.start_s:.2f}s – {w.end_s:.2f}s")
```

Works on both the transcript and diarization paths (per-turn timestamps on `DiarizeTurn.word_timestamps`).

---

### Speaker Diarization (mono)

For a **mono** recording with multiple speakers, set `diarize=True` to get a per-speaker breakdown (`DiarizeResponse`):

```python
result = client.stt.transcribe("call.wav", diarize=True)

for turn in result.turns:
    print(f"[{turn.speaker_id}]  {turn.start_sec:.1f}s – {turn.end_sec:.1f}s")
    print(f"  {turn.transcript}")

# [s0]  0.0s – 3.2s
#   Hello, this is support. How can I help you?
# [s1]  4.0s – 7.8s
#   Hi, I'd like to reschedule my appointment.
```

If you know how many speakers to expect, pass `n_speakers` (an integer **2–4**) as a hint. Leave it as `None` (default) to auto-detect:

```python
result = client.stt.transcribe("interview.wav", diarize=True, n_speakers=2)
```

Response fields (`DiarizeResponse`):

| Field | Type | Description |
|-------|------|-------------|
| `turns` | `list[DiarizeTurn]` | Speaker turns in order |
| `num_speakers` | `int` | Number of distinct speakers detected |
| `total_duration_sec` | `float` | Total audio duration in seconds |

Each `DiarizeTurn` has `speaker_id`, `start_sec`, `end_sec`, `transcript`, and `word_timestamps` (populated when `word_time_stamps=True`).

---

### Stereo audio

If each speaker is on a **separate channel** (e.g. a 2-channel call recording), set `audio_channels="stereo"`. Each channel is treated as one speaker, and you get a `DiarizeResponse`:

```python
result = client.stt.transcribe("stereo_call.wav", audio_channels="stereo")
```

> For stereo, keep `diarize=False` (the channels already *are* the speakers) and leave `n_speakers=None`. The declared `audio_channels` **must match the file** — a mono file declared `"stereo"` (or vice-versa) is rejected with a `400`.

---

### Denoising

For noisy multi-speaker audio, enable `denoise=True`. It applies on the **diarization path** (`diarize=True` or `audio_channels="stereo"`):

```python
result = client.stt.transcribe("noisy_call.wav", diarize=True, denoise=True)
```

---

### Specify Languages

If you know which languages are spoken in the audio, pass them in `lang_tags`. This improves accuracy — especially for multilingual audio like Hindi + English call recordings:

```python
result = client.stt.transcribe(
    "recording.wav",
    lang_tags=["hi", "en"],
)
print(result.text)  # "हां मुझे appointment reschedule करना है"
```

---

### Keyword Boosting

Have domain-specific words the model keeps getting wrong? Pass them in `keywords` and the model will bias towards recognizing them:

```python
# Without keyword boosting: "can you tell me about conversion"
# With keyword boosting:    "can you tell me about convozen"

result = client.stt.transcribe(
    "recording.wav",
    keywords=["convozen", "akshara"],
)
```

---

### Choose a Model

Use `model` to select which ASR model processes the audio:

```python
result = client.stt.transcribe("recording.wav", model="akshara-pro")   # default
result = client.stt.transcribe("recording.wav", model="akshara-base")
result = client.stt.transcribe("arabic.wav", model="alif-v1")          # Arabic STT
```

| Model | Best for |
|-------|----------|
| `akshara-pro` | Maximum accuracy for Indian languages (default) |
| `akshara-base` | Lighter / faster Indian-language STT |
| `alif-v1` | Arabic speech-to-text |

---

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `audio` | `str \| os.PathLike` | *required* | Path to the audio file |
| `model` | `str` | `"akshara-pro"` | ASR model: `"akshara-pro"`, `"akshara-base"`, or `"alif-v1"` (Arabic) |
| `audio_channels` | `str` | `"mono"` | `"mono"` or `"stereo"`. Must match the actual file |
| `diarize` | `bool` | `False` | `True` to return per-speaker turns (mono only) |
| `n_speakers` | `int` | `None` | Expected speaker count (**2–4**) for mono diarization. `None` → auto-detect |
| `denoise` | `bool` | `False` | Apply denoising on the diarization path — helps on noisy audio |
| `lang_tags` | `list[str]` | `None` | Languages spoken in the audio — improves accuracy when known |
| `word_time_stamps` | `bool` | `False` | `True` to return per-word timestamps |
| `blank_penalty` | `float` | `None` | Penalizes silence/blank tokens; increase to reduce empty gaps. `None` → derived from `lang_tags` |
| `keywords` | `list[str]` | `None` | Domain words the model should bias towards recognizing |

### When to use what

| `audio_channels` | `diarize` | `n_speakers` | Result |
|------------------|-----------|--------------|--------|
| `"mono"` | `False` | `None` | Plain transcript → `STTResponse` |
| `"mono"` | `True` | `None` | Auto speaker count → `DiarizeResponse` |
| `"mono"` | `True` | `2`–`4` | Hinted speaker count → `DiarizeResponse` |
| `"stereo"` | `False` | `None` | Channel-split diarization → `DiarizeResponse` |

Anything outside these rows raises a `ValueError` (e.g. `n_speakers` without `diarize`, `n_speakers` outside 2–4, `diarize=True` with `"stereo"`).

---

## Supported Languages

| Code | Language | TTS | STT |
|------|----------|:---:|:---:|
| `en` | English | Yes | Yes |
| `hi` | Hindi | Yes | Yes |
| `ta` | Tamil | Yes | Yes |
| `te` | Telugu | Yes | Yes |
| `kn` | Kannada | Yes | Yes |
| `ar` | Arabic | Yes (`rawi-v1`) | Yes (`alif-v1`) |
| `ae` | Emirati Arabic | Yes (`rawi-v1`) | Yes (`alif-v1`) |
| `eg` | Egyptian Arabic | Yes (`rawi-v1`) | Yes (`alif-v1`) |
| `jo` | Jordanian Arabic | Yes (`rawi-v1`) | Yes (`alif-v1`) |
| `ms` | Modern Standard Arabic | Yes (`rawi-v1`) | Yes (`alif-v1`) |
| `hz` | Hijazi Arabic | Yes (`rawi-v1`) | Yes (`alif-v1`) |
| `nj` | Najdi Arabic | Yes (`rawi-v1`) | Yes (`alif-v1`) |
| `mr` | Marathi | — | Yes |
| `bn` | Bengali | — | Yes |
| `gu` | Gujarati | — | Yes |
| `ml` | Malayalam | — | Yes |

---

## Check Credits

```python
import convozen

client = convozen.Client(api_key="your-api-key")
info = client.account.info()

print(info.global_balance)      # credits remaining (shared across TTS and STT)
print(info.plan)                # e.g. "pro"
print(info.allowed_services)    # e.g. ["tts", "stt"]
print(info.tts_lifetime_used)   # credits ever spent on TTS
print(info.stt_lifetime_used)   # credits ever spent on STT

print(info.rate_limit.limit)             # requests allowed per window
print(info.rate_limit.remaining)         # requests left in the current window
print(info.rate_limit.reset_in_seconds)  # seconds until the window resets
print(info.rate_limit.window_seconds)    # length of the window
```

### `AccountInfo`

| Field | Type | Description |
|-------|------|-------------|
| `account_id` | `str` | Account identifier |
| `org_id` | `str` | Organization identifier |
| `plan` | `str` | Plan tier (e.g. `"pro"`, `"free"`) |
| `global_balance` | `int` | Credits remaining, shared across TTS and STT |
| `allowed_services` | `List[str]` | Services enabled for the key, e.g. `["tts", "stt"]` |
| `tts_lifetime_used` | `int` | Credits ever spent on TTS |
| `stt_lifetime_used` | `int` | Credits ever spent on STT |
| `rate_limit` | `RateLimitInfo` | `limit`, `remaining`, `reset_in_seconds`, `window_seconds` |

> **Changed:** credits are a single `global_balance` shared by TTS and STT. The
> old `info.credits.tts.balance` / `info.credits.stt.balance` accessors (and the
> `Credits` / `CreditBalance` types) have been removed — they described a schema
> the server no longer returns and silently reported `0`.

---

## Error Handling

```python
import convozen
from convozen import AuthenticationError, RateLimitError, APIError

client = convozen.Client(api_key="your-api-key")

try:
    audio = client.tts.synthesize("Hello")
except AuthenticationError:
    print("Invalid API key")
except RateLimitError:
    print("Too many requests")
except APIError as e:
    print(f"Server error: {e}")
```

---

## Standalone Clients

```python
from convozen import TTS, STT

tts = TTS(api_key="your-api-key")
audio = tts.synthesize("Hello", language="hi")

stt = STT(api_key="your-api-key")

# Flat transcript
result = stt.transcribe("recording.wav")
print(result.text)

# Per-speaker diarization (mono, multiple speakers)
result = stt.transcribe("call.wav", diarize=True)
for turn in result.turns:
    print(f"[{turn.speaker_id}] {turn.start_sec:.2f}s – {turn.end_sec:.2f}s : {turn.transcript}")
```
