Metadata-Version: 2.5
Name: cairo-tts
Version: 0.1.0
Summary: Cairo-TTS: fast Egyptian Arabic text-to-speech with English code-switching, streaming and zero-shot voice cloning (CPU or GPU).
Project-URL: Homepage, https://github.com/MohammedAly22/Cairo-TTS
Project-URL: Model, https://huggingface.co/mohammedaly22/Cairo-TTS
Project-URL: Demo, https://huggingface.co/spaces/mohammedaly22/Cairo-TTS
Project-URL: Issues, https://github.com/MohammedAly22/Cairo-TTS/issues
Author: Mohammed Aly
License-Expression: MIT
License-File: LICENSE
License-File: NOTICE.md
Keywords: arabic,egyptian,pocket-tts,speech-synthesis,text-to-speech,tts,voice-cloning
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Natural Language :: Arabic
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: <3.15,>=3.10
Requires-Dist: huggingface-hub>=0.24
Requires-Dist: numpy
Requires-Dist: pocket-tts==3.3.0
Requires-Dist: pyyaml
Requires-Dist: scipy
Requires-Dist: soundfile
Requires-Dist: torch>=2.4
Provides-Extra: dev
Requires-Dist: fastapi>=0.110; extra == 'dev'
Requires-Dist: httpx; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: python-multipart>=0.0.9; extra == 'dev'
Requires-Dist: uvicorn[standard]>=0.29; extra == 'dev'
Provides-Extra: ui
Requires-Dist: fastapi>=0.110; extra == 'ui'
Requires-Dist: python-multipart>=0.0.9; extra == 'ui'
Requires-Dist: uvicorn[standard]>=0.29; extra == 'ui'
Description-Content-Type: text/markdown

<p align="center">
  <img src="https://raw.githubusercontent.com/MohammedAly22/Cairo-TTS/main/assets/banner.png" alt="Cairo-TTS" width="100%">
</p>

<h1 align="center">Cairo-TTS · كايرو</h1>

<p align="center">
  <b>Fast Egyptian Arabic text-to-speech with English code-switching, live streaming and zero-shot voice cloning.</b><br>
  <sub>It runs on CPU or GPU, starts speaking in under 100 ms on a GPU, and includes 20 preset voices and a web studio.</sub>
</p>

<p align="center">
  <a href="https://huggingface.co/mohammedaly22/Cairo-TTS"><img src="https://img.shields.io/badge/🤗%20Model-Cairo--TTS-FDB813" alt="Model"></a>
  <a href="https://huggingface.co/spaces/mohammedaly22/Cairo-TTS"><img src="https://img.shields.io/badge/🤗%20Space-Live%20demo-F59E0B" alt="Space"></a>
  <a href="https://colab.research.google.com/github/MohammedAly22/Cairo-TTS/blob/main/notebooks/01_quickstart.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open in Colab"></a>
  <a href="https://pypi.org/project/cairo-tts/"><img src="https://img.shields.io/pypi/v/cairo-tts?color=F97316&label=pypi" alt="PyPI"></a>
  <img src="https://img.shields.io/badge/python-3.10–3.14-1E3A5F" alt="Python">
  <a href="LICENSE"><img src="https://img.shields.io/badge/code-MIT-23466E" alt="MIT"></a>
</p>

<p align="center">
  <a href="#-install">Install</a> ·
  <a href="#-python">Python</a> ·
  <a href="#-web-studio">Web studio</a> ·
  <a href="#-colab-notebooks">Colab</a> ·
  <a href="#-hugging-face-space">Space</a> ·
  <a href="#️-pronunciation-control">Pronunciation</a> ·
  <a href="#-text-preprocessing">Preprocessing</a> ·
  <a href="#️-parameters">Parameters</a>
</p>

---

```python
from cairo_tts import CairoTTS

tts = CairoTTS.from_pretrained()          # downloads mohammedaly22/Cairo-TTS, runs on CPU
tts.save("ال meeting بكره الصبح بدري, ف يا ريت متتأخرش.", "out.wav", voice="malak")
```

## ✨ Features

| | |
|---|---|
| 🗣️ **Egyptian Arabic** | Trained on ~747 h of Egyptian podcasts collected with the [EgySpeech](https://github.com/MohammedAly22/EgySpeech) pipeline. It reads everyday عامية naturally. |
| 🔀 **Code-switching** | English words inside Arabic sentences: «ال deadline بتاع ال project». |
| 🎙️ **Zero-shot cloning** | Clone any voice from a ~10 s recording, or use one of the **20 preset voices** (10 male, 10 female). |
| ⚡ **Streaming** | Audio comes out in 80 ms frames while it is generated: ~80 ms to first audio on a GPU. |
| 💻 **CPU or GPU** | ~100M parameters, faster than real time on a laptop CPU, with optional int8. |
| 🔢 **Egyptian text frontend** | Numbers, dates, times, money, %, phone numbers, emails, URLs, hashtags and acronyms are read the Egyptian way. |
| ✍️ **Pronunciation control** | Diacritics override the model's reading; `ةْ` gives a pronounced ta marbuta, and `~` the Egyptian reading of a letter. |
| 🎛️ **Web studio** | An RTL Arabic UI with generate and stream modes, voices, cloning, parameters, live waveform and metrics, and light/dark themes. |

## 📦 Install

```bash
pip install cairo-tts            # Python API + CLI
pip install "cairo-tts[ui]"      # + web studio (FastAPI)
```

From source:

```bash
git clone https://github.com/MohammedAly22/Cairo-TTS && cd Cairo-TTS
pip install -e ".[ui]"
```

* **GPU**: install the CUDA build of PyTorch first ([pytorch.org](https://pytorch.org/get-started/locally/)). Cairo-TTS runs on the CPU by default; pass `device="cuda"` (or `--device cuda`) to use the GPU, or `device="auto"` to pick it when available.
* **CPU only**: `pip install torch --index-url https://download.pytorch.org/whl/cpu` keeps the install small.

## 🐍 Python

```python
from cairo_tts import CairoTTS

tts = CairoTTS.from_pretrained(
    "mohammedaly22/Cairo-TTS",   # or a local folder
    device="cpu",                # "cpu" (default) | "cuda" | "cuda:1" | "auto"
    max_prompt_sec=10,           # seconds of reference audio used for cloning
    quantize=False,              # int8 weights on CPU
    num_threads=None,            # CPU threads
)

# 1) generate: a float32 numpy array at 24 kHz
audio = tts.generate("النهارده الجو حلو اوي, ايه رأيك ننزل نتمشى شوية؟")

# 2) save straight to a file, with a preset voice
tts.save("عارف ايه اكتر حاجة بتخوفني.", "ahmed.wav", voice="ahmed")

# 3) clone any voice (zero-shot) from a short, clean recording
tts.save("ده صوتي انا, بس الموديل هو اللي بيتكلم.", "me.wav", voice="my_voice.wav")
tts.export_voice("my_voice.wav", "my_voice.safetensors")    # instant to reload: voice="my_voice.safetensors"

# 4) stream: chunks arrive while audio is generated
for chunk in tts.stream("حاسس اني واقف مكاني. كل يوم نفس التاسكات, نفس ال meetings.", voice="sara"):
    play(chunk)                                              # your audio sink

# 5) metrics of the last call
m = tts.last_metrics
print(f"TTFA {m.ttfa*1000:.0f} ms · {m.audio:.1f}s audio in {m.total:.2f}s · {m.x_realtime:.1f}x real time · {m.device}")

# 6) see exactly what the model reads
print(tts.normalize("كلمني على 01012345678 الساعة 3:30 م"))
```

<details>
<summary><b>List the preset voices</b></summary>

```python
for v in tts.voices():
    print(v["id"], v["name_ar"], v["gender"], v["description_ar"])
```

| Male | | Female | |
|---|---|---|---|
| `mohamed` | محمد | `sara` | سارة |
| `ahmed` | أحمد | `mariam` | مريم |
| `ali` | علي | `nour` | نور |
| `omar` | عمر | `fatma` | فاطمة |
| `youssef` | يوسف | `heba` | هبة |
| `mostafa` | مصطفى | `yasmin` | ياسمين |
| `karim` | كريم | `mona` | منى |
| `hassan` | حسن | `rana` | رنا |
| `tarek` | طارق | `dina` | دينا |
| `yehia` | يحيى | `malak` | ملك |

Each voice has an Arabic and an English description (pitch, style, tone) in `voices/voices.json`. **`tarek` is the default voice** (used when `voice` is not given). The names are made up.
</details>

A complete script is in [`examples/infer.py`](examples/infer.py).

### Command line

```bash
cairo-tts say "ال meeting اتأجل لبكره." -o out.wav --voice ahmed
cairo-tts say "صوتي انا." -o me.wav --voice my_voice.wav --device cpu
echo "النص من ملف" | cairo-tts say - -o out.wav
cairo-tts voices                     # preset voices
cairo-tts normalize "الساعة 3:30 م"   # → الساعة تلاتة ونص العصر
cairo-tts serve --port 8000          # web studio
```

## 🎛️ Web studio

```bash
pip install "cairo-tts[ui]"
cairo-tts serve                       # http://127.0.0.1:8000   (--device cpu|cuda, --host 0.0.0.0)
```

* **Two modes**: **توليد كامل** (generate the whole clip) and **بث مباشر** (live streaming, played while it is generated).
* **Voices**: a searchable dropdown of the 20 presets (descriptions, play/pause previews), or **استنسخ صوت** (clone a voice): upload a file or record from the microphone.
* **Parameters**: temperature, LSD steps, EOS threshold, frames after EOS, noise clamp, chunk size.
* **Metrics** after every run: server TTFA, browser TTFA, total time, audio length, × real time, RTF, chunks / characters, device.
* **Pronunciation toolbar** (فتحة، ضمة، كسرة، سكون، شدة، `ةْ`، `~`), a random Egyptian sentence button, and a preview of the normalized text.
* A minimal Arabic, RTL, responsive layout with dark and light themes (`?theme=light|dark`), a waveform that fills in while streaming, and a generation history.
* Streaming plays through an AudioWorklet at the model's 24 kHz rate with a short pre-buffer, so chunks join without gaps or resampling clicks.
* A JSON API at `/api/docs`: `POST /api/generate`, `POST /api/stream` (NDJSON PCM chunks), `GET /api/voices`, `POST /api/voices/upload`, `POST /api/normalize`.

Use it from Python (e.g. in your own app):

```python
from cairo_tts import CairoTTS
from cairo_tts.server import create_app, serve

serve(CairoTTS.from_pretrained(device="cuda"), host="0.0.0.0", port=8000)
# or: app = create_app(tts)  →  mount / run with any ASGI server
```

## 📓 Colab notebooks

| Notebook | | |
|---|---|---|
| **Quick start**: load, preset voices, cloning, streaming, preprocessing, diacritics, parameters | [`01_quickstart.ipynb`](notebooks/01_quickstart.ipynb) | <a href="https://colab.research.google.com/github/MohammedAly22/Cairo-TTS/blob/main/notebooks/01_quickstart.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open in Colab"></a> |
| **Web UI**: the full studio inside Colab, no local install | [`02_web_ui.ipynb`](notebooks/02_web_ui.ipynb) | <a href="https://colab.research.google.com/github/MohammedAly22/Cairo-TTS/blob/main/notebooks/02_web_ui.ipynb"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open in Colab"></a> |

Both download the public model from Hugging Face; no token is needed.

## 🤗 Hugging Face Space

The [Space](https://huggingface.co/spaces/mohammedaly22/Cairo-TTS) runs the same studio (Docker SDK, port 7860). To deploy your own:

```bash
python scripts/deploy_space.py --space <user>/Cairo-TTS --token-secret   # uploads space/ + the package source
```

`--token-secret` stores your token as the Space's `HF_TOKEN` secret (only needed if you point it at a private model). Optional Space variables: `CAIRO_TTS_MODEL`, `CAIRO_TTS_DEVICE`, `CAIRO_TTS_QUANTIZE=1`.

## ✍️ Pronunciation control

The model adds diacritics on its own. Write them yourself only where it reads a word wrong:

| Write | Meaning | Example |
|---|---|---|
| **`ةْ`** (ta marbuta + sukun) | ة is pronounced **t**, as in the construct state (إضافة); normalized to `تْ` | `عربيةْ ابويا` → *ʿarabiyyit abūya*; plain `عربية` → *ʿarabiyya* |
| **`~`** after a letter | the **Egyptian reading** of that letter, mainly **ق → glottal stop (ء)** | `ق~لبي` → *ʾalbi*, `ق~ولتلك` → *ʾultilak* |
| a vowel (`َ ُ ِ`) | chooses between readings | `نِفسَها` (she wants) vs `نَفسَها` (herself) |
| shadda `ّ` | doubles the consonant | `بَتمَشَّى` |
| sukun `ْ` | no vowel | `اتْحَرَجْت` |
| Latin letters | English words | `ال meeting`, `ال deal` |
| `CEO`, `AI` | upper-case runs are spelled as English letters | → `c e o`, `a i` (write `ceo` to read it as a word) |
| `.`  `,` `،` | sentence end (long pause) / short pause; `?` `!` become `.` | |

> **Order of marks**: letter → `~` → shadda → vowel (e.g. `ق~َ`). The frontend also reorders them for you. `~` is the marker the model learned from [CATT](https://github.com/abjadai/catt)'s Egyptian «؞» sign.

<details>
<summary><b>ملاحظات بالمصري</b></summary>

* اكتب زي ما بتتكلم: «مش عارف»، «عشان كدة»، «إزيك». الفصحى بتشتغل بس العامية أحلى.
* الكلام الإنجليزي اكتبه بالحروف اللاتيني: «ال meeting»، «ال deadline».
* لو كلمة اتقرت غلط شكّلها بس هي: «نِفسَها» بدل «نفسها».
* **ةْ**: التاء المربوطة اللي عليها سكون بتتنطق «ت»: «عربيةْ ابويا» ← عربيِّت أبويا.
* **~**: بعد الحرف معناها انطقه بالمصري، وأشهرها «ق~» تتقري همزة: «ق~لبي» ← ألبي.
* الصوت المرجعي: ٨–١٠ ثواني نضيفة، صوت واحد، من غير مزيكا.
* لو آخر الجملة بيتقطع نزّل «حد النهاية» (`eos_threshold`)، ولو الصوت مش ثابت قلل الحرارة.
</details>

## 🔢 Text preprocessing

`preprocess_egyptian()` runs before the model frontend. You can use it on its own:

```python
from cairo_tts import preprocess_egyptian, prepare_text

preprocess_egyptian("كلمني على 01012345678 او ابعت على ahmed.ali@gmail.com")
# كلمني على زيرو واحد زيرو, واحد اتنين تلاتة اربعة, خمسة ستة سبعة تمانية, او ابعت على احمد دوت علي ات gmail دوت كوم

prepare_text("الاجتماع الساعة 3:30 م و السعر 250 جنيه بخصم 15%")[0]
# الاجتماع الساعة تلاتة ونص العصر و السعر ميتين وخمسين جنية بخصم خمستاشر في المية
```

| Input | Spoken |
|---|---|
| `01012345678`, `+20 10 …` | phone digits grouped 3-4-4: «زيرو واحد زيرو, …», «زائد اتنين زيرو …» |
| `ahmed.ali@gmail.com` | «احمد دوت علي ات gmail دوت كوم» (Arabic names typed in Latin letters are written in Arabic) |
| `www.youtube.com`, `https://…` | «w w w دوت youtube دوت كوم» |
| `3:30 م`, `9:15`, `10:45 pm` | «تلاتة ونص العصر», «تسعة وربع», «حداشر الا ربع بالليل» |
| `15/3/1998` | «خمستاشر مارس سنة الف وتسعمية وتمانية وتسعين» |
| `250 جنيه`, `$99`, `50 EGP`, `1.5 €` | Egyptian amounts with currency names |
| `15%`, `3.5` | «خمستاشر في المية», «تلاتة فاصلة خمسة» |
| `5K`, `2M`, `3Bn` | «خمسة الف», «اتنين مليون», «تلاتة مليار» |
| `120 km/h`, `5 kg`, `64 GB` | units in Arabic |
| `د/ احمد`, `م/`, `ش.`, `ج.م.ع` | «دكتور», «مهندس», «شارع», «جمهورية مصر العربية» |
| `CEO`, `#مصر`, `@user`, `&` | «c e o», «هاشتاج مصر», «user», «و» |

Numbers are verbalized in Egyptian (`1994` → «الف وتسعمية واربعة وتسعين»). Runs of more than 9 digits are read digit by digit.

## ⚙️ Parameters

| Parameter | Default | |
|---|---|---|
| `voice` | default voice | preset id, an audio file (cloning), or an exported `.safetensors` |
| `temperature` | `0.3` | lower is steadier; higher is more expressive and less stable |
| `lsd_steps` | `1` | flow decoding steps per 80 ms frame |
| `eos_threshold` | `-4.0` | end-of-speech threshold: higher stops earlier, lower if the last word gets cut |
| `noise_clamp` | `None` | clamps the sampling noise (e.g. `2.0`) |
| `frames_after_eos` | auto | 80 ms frames kept after the end of speech |
| `max_letters` | `160` | long text is split at `.` then `,` into chunks of this many letters |
| `stop` | `None` | a `threading.Event` that stops streaming early |

## 📊 Speed

Measured with this package after warm-up, on 7–11 s sentences:

| Device | Time to first audio | Throughput |
|---|---|---|
| NVIDIA GTX 1660 Ti (6 GB) | ~80 ms | ~2.2× real time |
| Intel i7-10750H laptop CPU | ~330 ms | ~2.6× real time |

## 🧠 Model

The model is a [Pocket TTS](https://github.com/kyutai-labs/pocket-tts) (Kyutai) architecture: a causal transformer over Mimi latents with a 1-step flow head and a streaming Mimi decoder (24 kHz).

* A 24-layer teacher was fine-tuned on ~747 h of Egyptian podcasts collected with the [EgySpeech](https://github.com/MohammedAly22/EgySpeech) pipeline, then distilled into this 6-layer student.
* Details are on the [model card](https://huggingface.co/mohammedaly22/Cairo-TTS).

## 🗂️ Repository

```
src/cairo_tts/
├── model.py            CairoTTS: loading, devices, voices, generate / stream / save, metrics
├── text/               Egyptian preprocessing + the training frontend (numbers, diacritics, ta marbuta)
├── server/             FastAPI app + static web studio (HTML / CSS / JS)
└── cli.py              cairo-tts say | serve | voices | normalize
notebooks/              Colab quick start + web UI
examples/infer.py       minimal script
space/                  Hugging Face Space (Docker)
scripts/                prepare_voices.py · upload_to_hub.py · deploy_space.py
tests/                  text + inference + API tests (pytest)
```

## ⚖️ Responsible use

Cairo-TTS can imitate voices.

* Do not use it to impersonate anyone without their consent, to deceive, or to commit fraud.
* The preset voices are short clips of podcast speakers and are provided for research and demos only.
* Label synthetic audio when you publish it.

## 📜 License

* The code is [MIT](LICENSE).
* The model weights are fine-tuned from Kyutai's Pocket TTS and follow its terms. See [NOTICE.md](NOTICE.md) and the model card.

<p align="center"><img src="https://raw.githubusercontent.com/MohammedAly22/Cairo-TTS/main/assets/logo.png" width="160" alt=""></p>
