Convert Markdown and plain text files to speech audio using Kokoro-82M (free, local TTS)
Project description
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.txtfiles - 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 --doctorto 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:
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:
pipx upgrade md2speech
From PyPI
pip install md2speech
md2speech --doctor
With conda
Install system libraries via conda-forge; install md2speech and its Python stack via pip inside the env:
conda env create -f environment.yml
conda activate md2speech
md2speech --doctor
Important:
- Do not install into conda
base - Do not
conda install pytorchin 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 for optional libstdcxx troubleshooting.
Or use the helper script:
./scripts/install.sh --conda
From source
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
cd md2speech
uv sync
uv pip install -e ".[dev]"
Install helper script
./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, then set PHONEMIZER_ESPEAK_LIBRARY to the espeak-ng.dll path (see kokoro#101) |
Windows build tools
If spaCy/blis wheels are missing for your Python version, install Microsoft C++ 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
# 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
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
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:
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.
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]"orpip 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 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 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) 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). 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:
pip install --upgrade pip
pip install md2speech
On Linux, if compilation fails:
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:
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:
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.xDisabling PyTorch because PyTorch >= 2.4 is required but found ...Failed to load Kokoro modeleven when Hugging Face is reachable
Reinstall to pull compatible versions:
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
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.
- Bump
versioninpyproject.tomlandsrc/md2speech/__init__.py - Commit, push to
main - Create and push a tag:
git tag v0.1.0
git push origin v0.1.0
The Release workflow will:
- Run unit tests on Python 3.12
- Build the wheel (
.whl) and source distribution (.tar.gz) - Publish both to PyPI
- Create a GitHub Release with the build artifacts attached
One-time setup (GitHub pypi environment):
- Create a PyPI API token at pypi.org/manage/account/token scoped to the
md2speechproject. - In GitHub: Settings → Environments → pypi → Environment secrets → add
PYPI_API_TOKENwith that token. - Ensure the release workflow is on
mainand references thepypienvironment (see release.yml).
Alternative (no token): configure PyPI trusted publishing for owner evelasko, repo md2speech, workflow release.yml, environment pypi. OIDC is already enabled in the workflow (id-token: write).
First release:
# 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:
pipx install md2speech
pipx install md2speech==0.1.0
License
MIT — see LICENSE.
Acknowledgments
- Kokoro-82M by hexgrad — the underlying TTS model
- Hugging Face — model hosting and distribution
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file md2speech-0.1.3.tar.gz.
File metadata
- Download URL: md2speech-0.1.3.tar.gz
- Upload date:
- Size: 324.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0885a2fd4611c7dc3cd2fea4ea8748bf316ce04fbcc139be065f10e9e4304471
|
|
| MD5 |
630d0b73b4ae2129de7dbdad97a7f335
|
|
| BLAKE2b-256 |
c8a551e7dba530d30ee23c809b1dab78e140510a17fec19114ca2e4983e115a6
|
Provenance
The following attestation bundles were made for md2speech-0.1.3.tar.gz:
Publisher:
release.yml on evelasko/md2speech
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
md2speech-0.1.3.tar.gz -
Subject digest:
0885a2fd4611c7dc3cd2fea4ea8748bf316ce04fbcc139be065f10e9e4304471 - Sigstore transparency entry: 2138847347
- Sigstore integration time:
-
Permalink:
evelasko/md2speech@8b9dc12381735c3a1d309e79402c9842ac6e4fcd -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/evelasko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8b9dc12381735c3a1d309e79402c9842ac6e4fcd -
Trigger Event:
push
-
Statement type:
File details
Details for the file md2speech-0.1.3-py3-none-any.whl.
File metadata
- Download URL: md2speech-0.1.3-py3-none-any.whl
- Upload date:
- Size: 24.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7958fc55f2aafe39792ae41e2ce68494bcc5a3d18115ce1dce5fd9c2f9a8ebce
|
|
| MD5 |
06f52686520a301cc704474358edb33c
|
|
| BLAKE2b-256 |
caa3d9ddeab4a457c8b69c99fa7d4838bf051292edac964545012c5a1fbba8ee
|
Provenance
The following attestation bundles were made for md2speech-0.1.3-py3-none-any.whl:
Publisher:
release.yml on evelasko/md2speech
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
md2speech-0.1.3-py3-none-any.whl -
Subject digest:
7958fc55f2aafe39792ae41e2ce68494bcc5a3d18115ce1dce5fd9c2f9a8ebce - Sigstore transparency entry: 2138847353
- Sigstore integration time:
-
Permalink:
evelasko/md2speech@8b9dc12381735c3a1d309e79402c9842ac6e4fcd -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/evelasko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8b9dc12381735c3a1d309e79402c9842ac6e4fcd -
Trigger Event:
push
-
Statement type: