Metadata-Version: 2.4
Name: md2speech
Version: 0.1.3
Summary: Convert Markdown and plain text files to speech audio using Kokoro-82M (free, local TTS)
Project-URL: Homepage, https://github.com/evelasko/md2speech
Project-URL: Repository, https://github.com/evelasko/md2speech
Project-URL: Issues, https://github.com/evelasko/md2speech/issues
Project-URL: Documentation, https://github.com/evelasko/md2speech#readme
Author: Enrique Velasco
License-Expression: MIT
License-File: LICENSE
Keywords: audio,kokoro,markdown,text-to-speech,tts
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Requires-Python: <3.13,>=3.11
Requires-Dist: inflect>=7.0
Requires-Dist: kokoro==0.9.4
Requires-Dist: numpy<2.8,>=2.0
Requires-Dist: pydub>=0.25
Requires-Dist: scipy>=1.14
Requires-Dist: soundfile>=0.12
Requires-Dist: torch>=2.4.1
Requires-Dist: tqdm>=4.66
Provides-Extra: dev
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: ja
Requires-Dist: misaki[ja]; extra == 'ja'
Provides-Extra: zh
Requires-Dist: misaki[zh]; extra == 'zh'
Description-Content-Type: text/markdown

# md2speech

Convert Markdown and plain-text files to speech audio using **Kokoro-82M** — a free, local, offline text-to-speech engine. No API keys, no cloud services.

## Features

- **Free and offline** — Kokoro-82M runs locally via PyTorch; model weights download once from Hugging Face
- **Markdown and plain text** — reads `.md`, `.markdown`, and `.txt` files
- **MP3 by default** — also supports WAV, OGG, and FLAC
- **Smart text normalization** — expands abbreviations, numbers, currency, percentages, and times for natural speech
- **Long-document support** — synthesizes paragraph-by-paragraph with configurable pauses
- **CLI and Python API** — use from the terminal or import as a library
- **Audio post-processing** — peak normalization, silence padding, and resampling to 44.1 kHz

## Requirements

| Requirement | Notes |
|-------------|-------|
| **Python 3.11 or 3.12** | Required for Kokoro/torch compatibility; 3.13+ unsupported |
| **ffmpeg** | Required for MP3, OGG, and FLAC export (not needed for WAV) |
| **espeak-ng** | Phonemization via Kokoro/misaki; required for non-English, recommended for English |
| **~500 MB disk** | Kokoro-82M model weights (downloaded on first synthesis) |
| **Network (first run)** | Model downloads from Hugging Face automatically |

### Before you install

md2speech pulls in roughly **45 Python packages** and needs **~2–4 GB disk** (PyTorch + Kokoro stack). A first install typically takes **5–15 minutes** depending on your network and whether pre-built wheels are available for your platform.

- Use **Python 3.11 or 3.12 only** — Python 3.13+ is not supported
- **Do not install into conda `base`** — NumPy 1.x in base conflicts with md2speech's NumPy 2.x requirement
- After install, run **`md2speech --doctor`** to verify your environment

## Installation

### Ranked install paths

| Path | Audience | Steps |
|------|----------|-------|
| **pipx** | CLI users (recommended) | `pipx install md2speech` |
| **venv + pip** | Library users | `python3.12 -m venv .venv && source .venv/bin/activate && pip install md2speech` |
| **conda** | Data-science users | `conda env create -f environment.yml && conda activate md2speech` |

### With pipx (recommended)

Install the CLI globally and keep it isolated from other Python projects:

```bash
brew install pipx ffmpeg   # macOS; ffmpeg required for MP3
pipx ensurepath            # add ~/.local/bin to PATH (restart terminal once)

pipx install md2speech
md2speech --doctor
md2speech --help
```

Upgrade later:

```bash
pipx upgrade md2speech
```

### From PyPI

```bash
pip install md2speech
md2speech --doctor
```

### With conda

Install system libraries via conda-forge; install md2speech and its Python stack via pip inside the env:

```bash
conda env create -f environment.yml
conda activate md2speech
md2speech --doctor
```

**Important:**
- Do **not** install into conda `base`
- Do **not** `conda install pytorch` in the same env — use pip-installed torch to avoid pip/conda splits
- Do **not** `conda install md2speech` — use pip inside a dedicated env

On Linux, if espeak-ng fails inside conda, see the [Kokoro README](https://github.com/hexgrad/kokoro) for optional `libstdcxx` troubleshooting.

Or use the helper script:

```bash
./scripts/install.sh --conda
```

### From source

```bash
git clone https://github.com/evelasko/md2speech
cd md2speech
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
```

### With uv

```bash
cd md2speech
uv sync
uv pip install -e ".[dev]"
```

### Install helper script

```bash
./scripts/install.sh              # pipx (default)
./scripts/install.sh --venv       # virtualenv in .venv
./scripts/install.sh --conda      # conda env from environment.yml
```

### System dependencies

#### ffmpeg

ffmpeg must be on your `PATH` for compressed audio formats.

| Platform | Command |
|----------|---------|
| **macOS** | `brew install ffmpeg` |
| **Ubuntu/Debian** | `sudo apt install ffmpeg` |
| **Windows** | `winget install ffmpeg` or `choco install ffmpeg` |

#### espeak-ng

espeak-ng is used for phonemization (via Kokoro/misaki). It is **required for non-English** languages and **recommended for English**.

| Platform | Command |
|----------|---------|
| **macOS** | `brew install espeak-ng` |
| **Ubuntu/Debian** | `sudo apt install espeak-ng` |
| **Windows** | Install the [espeak-ng MSI](https://github.com/espeak-ng/espeak-ng/releases), then set `PHONEMIZER_ESPEAK_LIBRARY` to the `espeak-ng.dll` path (see [kokoro#101](https://github.com/hexgrad/kokoro/issues/101)) |

#### Windows build tools

If spaCy/blis wheels are missing for your Python version, install [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) before `pip install md2speech`.

> **First run:** The Kokoro-82M model (~hundreds of MB) downloads automatically from Hugging Face on the first synthesis. Subsequent runs use the cached weights.

## Quick Start

```bash
# Convert article.md → article.mp3 in the same folder
md2speech article.md

# Explicit output path and WAV format (no ffmpeg needed)
md2speech chapter.txt -o ~/Audio/chapter.wav --format wav

# Long document with a specific voice and speed
md2speech book.md -o book.mp3 --voice am_adam --speed 0.95

# British English
md2speech article.md --lang b --voice bf_emma

# Brazilian Portuguese (language inferred from voice if omitted)
md2speech texto.md --voice pm_alex -o texto.mp3

# List supported languages and voices
md2speech --list-languages
md2speech --list-voices
md2speech --list-voices en-gb

# Verbose mode with synthesis progress
md2speech notes.md -V
```

## CLI Reference

| Argument | Flag | Default | Description |
|----------|------|---------|-------------|
| `input` | — | *(required)* | Path to `.md`, `.markdown`, or `.txt` file |
| `--output` | `-o` | Same dir/name as input | Output audio file path |
| `--format` | `-f` | `mp3` | Output format: `mp3`, `wav`, `ogg`, `flac` |
| `--voice` | — | `af_heart` | Kokoro voice ID (see `--list-voices`) |
| `--speed` | — | `1.0` | Speech speed multiplier |
| `--lang` / `--language` | — | inferred from voice | Language code or alias (`a`, `en-gb`, `pt-br`, …) |
| `--list-languages` | — | — | Print supported languages and exit |
| `--list-voices` | `[LANG]` | — | Print available voices, optionally filtered by language |
| `--doctor` | — | — | Check environment dependencies and exit |
| `--no-normalize` | — | off | Skip text normalization |
| `--paragraph-pause` | — | `0.4` | Seconds of silence between paragraphs |
| `--verbose` | `-V` | off | Enable debug logging and progress bar |

**Default output path:** When `--output` is omitted, the output file is written next to the input with the same basename and the chosen format extension (e.g. `article.md` → `article.mp3`).

## Python API

### High-level

```python
from md2speech import synthesize_file

result = synthesize_file(
    "article.md",
    output_path="article.mp3",
    output_format="mp3",
    voice="bf_emma",
    lang="b",                    # or "en-gb"
    speed=1.0,
)
print(result.path)              # Path to written audio
print(result.duration_seconds)  # float
```

### Advanced building blocks

```python
from md2speech import (
    read_document,
    extract_plain_text,
    TTSEngine,
    AudioWriter,
    list_languages,
    list_voices,
    resolve_voice_and_lang,
)
from md2speech.synthesize import synthesize_text, synthesize_long_text
from md2speech.normalize import normalize_text

# Read and extract speakable text
text = read_document("notes.md")
plain = extract_plain_text("# Hello\n\n**World**")

# Normalize for TTS
spoken = normalize_text("Dr. Smith paid $12.50 at 3:45 PM")

# Low-level synthesis
engine = TTSEngine(lang_code="a")
audio = synthesize_long_text(text, voice="af_heart", engine=engine)

# Post-process and write
writer = AudioWriter()
processed = writer.process(audio, orig_sr=24000)
writer.write_audio(processed, "output.mp3", format_name="mp3")
```

Run as a module:

```bash
python -m md2speech article.md -o article.mp3
```

## Languages & Voices

Kokoro supports 9 languages. Use `--lang` / `--language` to set the language and `--voice` to pick a speaker. If `--lang` is omitted, the language is inferred from the voice prefix (e.g. `bf_emma` → British English).

| Code | Language | Default voice | Voices |
|------|----------|---------------|--------|
| `a` | American English | `af_heart` | 20 (`af_*`, `am_*`) |
| `b` | British English | `bf_emma` | 8 (`bf_*`, `bm_*`) |
| `e` | Spanish | `em_alex` | 3 |
| `f` | French | `ff_siwis` | 1 |
| `h` | Hindi | `hf_alpha` | 4 |
| `i` | Italian | `if_sara` | 2 |
| `p` | Brazilian Portuguese | `pm_alex` | 3 |
| `j` | Japanese | `jf_alpha` | 5 |
| `z` | Mandarin Chinese | `zf_xiaoxiao` | 8 |

**Aliases:** `en-us`, `en-gb`, `spanish`, `french`, `hindi`, `italian`, `pt-br`, `japanese`, `chinese`, and more.

```bash
md2speech --list-languages
md2speech --list-voices b
```

If only `--lang` is set (no `--voice`), the default voice for that language is used. Voice and language must match — `md2speech doc.md --lang b --voice af_heart` will error with a list of valid British voices.

> **Note:** Japanese (`j`) and Mandarin Chinese (`z`) require extra packages:
> `pip install "md2speech[ja]"` or `pip install "md2speech[zh]"`.

### Recommended voices

| Voice ID | Gender | Language | Character |
|----------|--------|----------|-----------|
| `af_heart` | Female | American English | Warm, clear — **default** |
| `am_adam` | Male | American English | Deep, steady |
| `af_bella` | Female | American English | Soft |
| `am_michael` | Male | American English | Conversational |
| `bf_emma` | Female | British English | Clear British accent |
| `bm_george` | Male | British English | British male |
| `em_alex` | Male | Spanish | Spanish male |
| `ff_siwis` | Female | French | French female |
| `pm_alex` | Male | Brazilian Portuguese | Portuguese male |
| `jf_alpha` | Female | Japanese | Japanese female |
| `zf_xiaoxiao` | Female | Mandarin Chinese | Mandarin female |

See the [Kokoro documentation](https://github.com/hexgrad/kokoro) for the full voice list.

## Supported Formats

| Format | ffmpeg required | Notes |
|--------|-----------------|-------|
| `mp3` | Yes | **Default** — best for sharing and playback |
| `wav` | No | Uncompressed PCM via soundfile |
| `ogg` | Yes | Vorbis encoding via pydub |
| `flac` | Yes | Lossless compression via pydub |

## How It Works

```
Input file (.md / .txt)
        │
        ▼
  Read & extract plain text
  (strip markup, front matter)
        │
        ▼
  Normalize text
  (numbers, abbreviations, currency)
        │
        ▼
  Split into paragraphs & lines
        │
        ▼
  Synthesize each line (Kokoro-82M)
  + silence between paragraphs
        │
        ▼
  Post-process audio
  (peak normalize → resample 44.1 kHz → pad)
        │
        ▼
  Export (MP3 / WAV / OGG / FLAC)
```

## Markdown Handling

| Element | Behavior |
|---------|----------|
| YAML front matter | Stripped (`---` delimited block at top) |
| Headings (`#`) | Converted to plain text (no "hash hash") |
| Bold / italic | Markup removed, text preserved |
| Links | Link text spoken; URL omitted |
| Images | Alt text spoken if present |
| Fenced code blocks | **Skipped entirely** (not spoken) |
| Inline code | Spoken literally |
| HTML comments | Removed |
| Lists | Marker removed; item text preserved |
| Paragraph breaks | Preserved for natural pacing |

## Troubleshooting

Run **`md2speech --doctor`** after any fix to confirm your environment is healthy.

See also [docs/UPSTREAM_DEPS.md](docs/UPSTREAM_DEPS.md) for dependency ownership and tracked upstream issues.

### `ffmpeg not found`

MP3, OGG, and FLAC export require ffmpeg on your PATH. Install it for your platform (see [System dependencies](#system-dependencies)) or use `--format wav` which does not need ffmpeg. Run `md2speech --doctor` to confirm the fix.

### `espeak not installed on your system`

Kokoro/misaki needs espeak-ng for phonemization. Install for your platform (see [espeak-ng](#espeak-ng)). On Windows, also set `PHONEMIZER_ESPEAK_LIBRARY` to the DLL path. Run `md2speech --doctor` to confirm the fix.

### spaCy / blis / thinc build failure

Common on Python 3.12 when pre-built wheels are unavailable:

```bash
pip install --upgrade pip
pip install md2speech
```

On Linux, if compilation fails:

```bash
BLIS_ARCH=generic pip install md2speech
```

On Windows, install Microsoft C++ Build Tools if wheels are missing. Run `md2speech --doctor` to confirm the fix.

### NumPy 1.x vs 2.x in conda base

Installing into conda `base` often leaves NumPy 1.x, which conflicts with md2speech's NumPy 2.x requirement. Create a fresh env instead:

```bash
python3.12 -m venv .venv && source .venv/bin/activate && pip install md2speech
# or: conda env create -f environment.yml
```

Run `md2speech --doctor` to confirm the fix.

### conda solver conflicts

Do not `conda install md2speech`. Use a dedicated env with system libs from conda-forge and md2speech from pip:

```bash
conda env create -f environment.yml
conda activate md2speech
```

Run `md2speech --doctor` to confirm the fix.

### Model download fails

The first synthesis downloads Kokoro-82M from Hugging Face. Ensure you have internet access and sufficient disk space. If behind a proxy, configure Hugging Face Hub environment variables (`HF_ENDPOINT`, etc.).

### Slow synthesis / memory

Kokoro runs on CPU by default. If you have a CUDA or Apple Silicon GPU, the engine auto-detects and uses `cuda` or `mps` when available. Very long documents synthesize line-by-line with a progress bar in verbose mode (`-V`).

### Torch / MPS / CUDA issues

If GPU inference fails, force CPU by setting `device=None` when creating a `TTSEngine` instance in the Python API. The CLI uses automatic device detection.

### NumPy / PyTorch version mismatch

md2speech requires **PyTorch >= 2.4.1**. Older PyTorch builds are incompatible with current `transformers` releases and with NumPy 2.x, which can surface as:

- `A module that was compiled using NumPy 1.x cannot be run in NumPy 2.x`
- `Disabling PyTorch because PyTorch >= 2.4 is required but found ...`
- `Failed to load Kokoro model` even when Hugging Face is reachable

Reinstall to pull compatible versions:

```bash
pipx reinstall md2speech
# or: pip install --upgrade --force-reinstall md2speech
```

Run `md2speech --doctor` to confirm the fix.

### Empty document error

If the input file is empty or contains only markup/code with no speakable text, md2speech exits with an error.

## Development

```bash
pip install -e ".[dev]"
pytest                  # unit tests (mocked TTS)
pytest -m integration   # optional real synthesis test
```

## Publishing (maintainers)

Releases are automated when a version tag is pushed to `main`.

1. Bump `version` in `pyproject.toml` and `src/md2speech/__init__.py`
2. Commit, push to `main`
3. Create and push a tag:

```bash
git tag v0.1.0
git push origin v0.1.0
```

The [Release workflow](.github/workflows/release.yml) will:

1. Run unit tests on Python 3.12
2. Build the wheel (`.whl`) and source distribution (`.tar.gz`)
3. Publish both to [PyPI](https://pypi.org/project/md2speech/)
4. Create a GitHub Release with the build artifacts attached

**One-time setup (GitHub `pypi` environment):**

1. Create a PyPI API token at [pypi.org/manage/account/token](https://pypi.org/manage/account/token/) scoped to the `md2speech` project.
2. In GitHub: **Settings → Environments → pypi → Environment secrets** → add `PYPI_API_TOKEN` with that token.
3. Ensure the release workflow is on `main` and references the `pypi` environment (see [release.yml](.github/workflows/release.yml)).

**Alternative (no token):** configure [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) for owner `evelasko`, repo `md2speech`, workflow `release.yml`, environment `pypi`. OIDC is already enabled in the workflow (`id-token: write`).

**First release:**

```bash
# bump version in pyproject.toml and src/md2speech/__init__.py, commit, push
git tag v0.1.0
git push origin main
git push origin v0.1.0
```

Watch the **Actions** tab — the Release workflow publishes to PyPI and creates a GitHub Release.

Users install published versions with:

```bash
pipx install md2speech
pipx install md2speech==0.1.0
```

## License

MIT — see [LICENSE](LICENSE).

## Acknowledgments

- [Kokoro-82M](https://github.com/hexgrad/kokoro) by hexgrad — the underlying TTS model
- [Hugging Face](https://huggingface.co/hexgrad/Kokoro-82M) — model hosting and distribution
