Metadata-Version: 2.1
Name: convozen
Version: 0.3.9
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) or `"rawi-v1"` (Arabic) |
| `speed` | `float` | `1.0` | Speech speed. `0.5` = half speed, `2.0` = double speed |
| `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")
```

### 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.credits.tts.balance)
print(info.credits.stt.balance)
```

---

## 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}")
```
