CLI + GUI + REST + MCP text-to-speech for Georgian (🇬🇪 ka, ka-m), Russian (🇷🇺 ru), and English (🇬🇧 en, 🇺🇸 en-US) — built on Microsoft Edge neural voices. Smart chunking, parallel synthesis, streaming playback, ID3 + chapter tagging, SRT/VTT subtitles, document readers (PDF / EPUB / DOCX / HTML / Markdown), and an AI-friendly --live stdin mode + MCP server so an LLM can speak while it generates.
- Three input shapes: a positional string, a file path (auto-detected by extension), or
clipboard/cb/clip/paste. - Three output shapes: an MP3 on disk, immediate playback, or live streaming chunks playing while the rest synthesizes.
- Three integration shapes: standalone CLI, REST server (
tts-ka serve), or MCP server (tts-ka-mcp) for AI agents. - Auto-optimized by default: just give it
--lang(or rely on the config). Chunking and parallelism are picked from text length and machine. No flags needed for the common case.
pip install TTS_ka # core CLI (edge-tts, pydub, rich)
pip install "TTS_ka[readers]" # + PDF / EPUB / DOCX / HTML readers
pip install "TTS_ka[metadata]" # + mutagen for ID3 tags / chapters
pip install "TTS_ka[server]" # + FastAPI / uvicorn (REST server)
pip install "TTS_ka[mcp]" # + MCP SDK (AI-agent integration)
pip install "TTS_ka[soundfile]" # + soundfile merges for non-MP3 output
pip install "TTS_ka[hotkeys]" # + pynput (Windows native hotkeys)
pip install "TTS_ka[gui]" # + Sun Valley theme (light/dark) for tts-ka-gui
pip install "TTS_ka[dev]" # test tools + black / flake8 / mypyffmpeg must be installed and on PATH (used for merging chunked parts). The streaming player (--stream) prefers mpv, then VLC → ffplay → mplayer (override with --player); without any of those, Linux / macOS only play the first chunk via mpg123 / afplay, so install mpv for real streaming.
Verify:
tts-ka --doctor # alias: --check-depsYou should see [OK] rows for edge-tts, pydub, ffmpeg, and (if streaming is wanted) at least one streaming player. Exit code is 1 if a critical piece is missing.
# Direct text
python -m TTS_ka "Hello world" --lang en
# Clipboard (shorthand: cb / clip / paste)
python -m TTS_ka cb --lang ka
# Linux: needs wl-clipboard (Wayland) or xclip/xsel (X11) — `--doctor` reports which.
# File (auto-dispatched by extension)
python -m TTS_ka chapter1.pdf --lang en # needs [readers] extra
python -m TTS_ka notes.md --lang en
python -m TTS_ka document.docx --lang en
# Save to a specific path, refuse to overwrite without --force
python -m TTS_ka "Lecture excerpt" --lang en -o lectures/lec1.mp3Console-script aliases are installed under both spellings — tts-ka and TTS_ka
(likewise tts-ka-gui / TTS_ka-gui, tts-ka-mcp / TTS_ka-mcp). Linux
filesystems are case-sensitive, so pick whichever you prefer and it will resolve:
tts-ka "Hello" -l en
TTS_ka "Hello" -l en-l is --lang; both are accepted everywhere below.
Read stdin incrementally (raw blocks, not lines — a long line is spoken sentence by sentence), accumulate into a sentence buffer, and synthesize each complete sentence as it arrives — no waiting for the whole response. Sentence boundaries are [.!?]+ followed by whitespace, or a \n\n paragraph break, or an idle timeout if the stream pauses. Abbreviations (e.g., Dr., т.е.), initials and 1. list markers don't split; run-ons over 400 characters are cut at a comma. A sentence that fails to synthesize is skipped instead of holding back the rest, and Ctrl+C stops playback immediately.
# Pipe any tool that writes to stdout
claude --print "Explain B-trees in one paragraph" | python -m TTS_ka --live -l en
# Or hand the stream over a Unix pipe
my-llm-cli | tts-ka --live -l en --voice en-US-JennyNeuralIdle flush: if the upstream stalls mid-sentence, the buffer is flushed after --live-idle-ms (default 800). Tighten for snappy local models, loosen for slow networks:
... | tts-ka --live --live-idle-ms 400 -l en # responsive
... | tts-ka --live --live-idle-ms 2000 -l en # patientFenced code is held back until closed. When the LLM emits a ``` fence, the buffer pauses; once the closing fence arrives, the whole block is collapsed by the sanitizer to "omitted fenced code block" instead of letting the voice read symbols. EOF flushes whatever remained.
Install the extra and configure your MCP client to launch tts-ka-mcp:
pip install "TTS_ka[mcp]"Claude Code / Claude Desktop config:
{
"mcpServers": {
"tts-ka": { "command": "tts-ka-mcp" }
}
}Tools exposed:
| Tool | Purpose |
|---|---|
speak(text, lang?, voice?, blocking?, interrupt?) |
One-shot: synthesize and play. blocking=True returns when playback has finished so the agent can sequence speech; interrupt=True stops whatever is playing first. The return string echoes the resolved settings |
stream_open(lang?, voice?) |
Start a streaming session, returns session_id |
stream_append(session_id, text) |
Push text; speaks each complete sentence — including one that ends the appended text — and an unfinished tail after 500 ms of silence |
stream_close(session_id) |
Drain remaining buffer, end the session |
session_status(session_id) |
Inspect progress: total, pending synths, synths_failed + last_error, buffer preview, controllable |
list_sessions() |
All active session IDs |
pause / resume / skip(session_id?) |
Control one session, or everything playing (needs mpv on Linux/macOS) |
stop(session_id?) |
Tear down one session, or abort all playback and sessions |
list_voices(lang?) |
Voice catalog as JSON |
Errors (unknown session_id, invalid lang/voice, synthesis failure, no audio player) come back as MCP tool errors (isError: true), not as "error: …" strings. Sessions idle for 15 minutes are closed when the next session is opened or listed.
Why streaming over single speak calls: the LLM can push tokens as it generates them. Each completed sentence is synthesized immediately, so the user hears audio with sub-second latency from the LLM's first word. session_status reports synths_pending (queue backed up) and synths_failed / last_error (a synthesis failed) so the agent can react.
Suppresses decorative stdout; emits one JSON object per line on stdout (decorations move to stderr):
python -m TTS_ka large.pdf --lang en --json -o out.mp3
{"event": "start", "words": 1284, "lang": "en"}
{"event": "done", "output": "out.mp3", "seconds": 12.317}12 curated Edge neural voices across ka / ru / en (en covers GB, US and AU), listable from the CLI:
python -m TTS_ka --list-voices # all
python -m TTS_ka --list-voices --lang ka # filter to Georgian
python -m TTS_ka --preview-voice en-US-JennyNeural # short sample, then exitsBuilt-in defaults via --lang:
--lang |
Voice | Notes |
|---|---|---|
ka |
ka-GE-EkaNeural |
Georgian, female |
ka-m |
ka-GE-GiorgiNeural |
Georgian, male |
ru |
ru-RU-SvetlanaNeural |
Russian, female |
en |
en-GB-SoniaNeural |
British English, female |
en-US |
en-US-SteffanNeural |
American English, male |
Override per-call with --voice. When --voice is given, --lang is inferred from the voice locale — pass both only if you want the parser to validate they agree (it errors on mismatch):
python -m TTS_ka "Hello" --voice en-US-AriaNeural # lang auto = en
python -m TTS_ka "Привет" --voice ru-RU-DmitryNeural # lang auto = ruPassed through to edge-tts as prosody settings. Values are signed percentages (or Hz for pitch). Both shells need %% literal escapes only inside Windows batch files — in PowerShell / bash, plain % works:
python -m TTS_ka "Slow and low" --lang en --rate=-20% --pitch=-5Hz
python -m TTS_ka "Energetic" --lang en --rate=+30% --volume=+10%Out-of-range values are clamped at parse time (so --rate=+500% becomes the max the engine accepts) rather than failing the call.
With pip install "TTS_ka[readers]", file inputs are dispatched by extension:
| Extension | Reader | Optional dep |
|---|---|---|
.txt, .rst |
plain UTF-8 | — |
.md, .markdown |
strips fences, links, emphasis, headers | — |
.html, .htm |
BeautifulSoup if available, regex fallback | beautifulsoup4 |
.pdf |
text per page, joined; --pages selects pages |
pdftotext (poppler) if installed, else pypdf |
.epub |
each item's text, joined | ebooklib + beautifulsoup4 |
.docx |
paragraph text | python-docx |
Unknown extensions fall back to UTF-8 plain reading. Missing extras raise MissingExtraError with the exact pip install line.
For PDFs, install poppler (sudo apt install poppler-utils, brew install poppler) when you can. Typeset books mark hyphenation points with soft hyphens, and pypdf extracts those as spaces, so different is read aloud as "dif fer ent". pdftotext keeps them as soft hyphens, which TTS_ka removes. --doctor shows which extractor is in use.
python -m TTS_ka book.epub --lang en -o book.mp3 --chapters book-chapters.jsonFor a PDF, --pages reads only part of it: 1-based page numbers, comma-separated, with N- running to the last page and -M starting at the first. Only the selected pages are extracted, so picking a chapter out of a long book is fast. A page past the end, a malformed range, or --pages on anything that isn't a PDF file exits 2.
python -m TTS_ka book.pdf --lang en --pages 20-24 --stream # one chapter
python -m TTS_ka book.pdf --lang en --pages 1-3,7,410- # several ranges, in the order given--stream starts playback while later chunks are still synthesizing. Order is preserved by chunk index even when chunks complete out of order.
# Audio starts within seconds
python -m TTS_ka long_article.txt --lang en --stream
# Headless VLC (no GUI window on Windows)
python -m TTS_ka chapter.epub --lang en --stream --no-gui
# Pick a specific player
python -m TTS_ka text.txt --lang en --stream --player mpvOn Windows with VLC, a single VLC window receives chunks over TCP remote-control as they finish (TTS_KA_VLC_RC=0 disables this and falls back to one VLC process per chunk). On Linux / macOS, headless mpv (MCP, --no-gui) is driven over its JSON IPC socket: it idles between chunks, so a slow chunk never ends playback early, and it can be paused, resumed and skipped. GUI mpv receives chunks over a stdin playlist (--keep-open bridges the gaps); ffplay plays them one by one; VLC and mplayer start once every chunk is ready.
Ctrl+C cancels generation and terminates the active player without waiting for the playback-join timeout.
With headless mpv (Linux / macOS, --stream --no-gui or --live), single keys control playback while it streams:
| Key | Action |
|---|---|
space / p |
Pause / resume |
n |
Skip to the next chunk |
+ / - |
Speed up / slow down by 0.1× (0.5×–2.5×, pitch preserved) |
q |
Quit (same as Ctrl+C) |
Keys are read from the terminal, not stdin, so they work with piped input (… | tts-ka --live). They are off when there is no terminal (hotkeys, GUI), in a background job, in --live when you type the text into the terminal yourself, and with --no-keys.
ID3 tags require pip install "TTS_ka[metadata]" (mutagen). Subtitle export does not need an extra.
python -m TTS_ka chapter.txt --lang en -o ch1.mp3 \
--title "Chapter 1" --author "Jane Doe" --album "My Book" \
--cover cover.jpg \
--chapters chapters.json \
--srt --vttchapters.json shape:
[
{"title": "Intro", "start_ms": 0, "end_ms": 12500},
{"title": "Main idea", "start_ms": 12500, "end_ms": 45000}
]The SRT/VTT writer uses real edge-tts WordBoundary events, so timings line up to spoken-word boundaries (not estimated). Files are written next to the MP3 (ch1.srt, ch1.vtt).
pip install "TTS_ka[server]"
tts-ka serve --host 127.0.0.1 --port 7777 --token "$(openssl rand -hex 32)"
# or set TTS_API_TOKEN in the environmentEndpoints:
GET /voices → JSON catalog (same shape as --list-voices)
POST /synthesize → audio/mpeg stream
body: {"text": "...", "lang": "en", "voice": "..."} # lang: any --lang code
auth: Authorization: Bearer <token>
Concurrency is capped at MAX_PARALLEL_WORKERS (6; see constants.py). Each request is synthesized to a temporary MP3 that is streamed back as audio/mpeg and then deleted.
tts-ka-gui
# or: python -m TTS_ka.guiTkinter window with these tabs (with the [gui] extra it uses the Sun Valley theme and follows the system light/dark setting; TTS_KA_GUI_THEME=light|dark|classic overrides):
- Setup — guided first run: verify dependencies, pick and preview a voice, then speak.
- Speak — paste text or point at a UTF-8 file, choose language and voice, hit Speak with optional Stream.
- Config — edit the JSON config (path, defaults), Save / Reload.
- Hotkeys — edit and start the native global hotkeys.
- Windows shell (Windows only) — install / uninstall the Explorer context menu.
- About — version and project links.
The GUI picks a system font that handles Georgian + Cyrillic (Segoe UI / Sylfaen on Windows, Noto Sans / Noto Sans Georgian on Linux). Symbol-only fonts that lack Mkhedruli are avoided.
Debian/Ubuntu may need Tk: sudo apt install python3-tk.
powershell -ExecutionPolicy Bypass -File .\extras\windows\Install-TTS_ka-Windows.ps1Checks that TTS_ka is runnable and that AutoHotkey v2 is present (printing
winget install AutoHotkey.AutoHotkey if not), then installs the Explorer/Desktop
context menu and the global hotkeys, and prints a short "what you can do now" summary.
Flags: -SkipHotkeys, -SkipContextMenu, -PythonPath, -Uninstall, -WhatIf. The
individual installers below still work if you want finer control.
pip install "TTS_ka[hotkeys]"
tts-ka-hotkeys # or enable on the GUI's "Windows shell" tabDefaults map Ctrl+Alt+1..4 → en / ru / ka / ka-m. Each press spawns python -m TTS_ka clipboard --lang … in a new process. Override in ~/.tts_config.json under the hotkeys key (see extras/tts_config.example.json). JSON null removes a default combo.
powershell -ExecutionPolicy Bypass -File .\extras\autohotkey\Install-TTS_ka-Hotkeys.ps1Copies TTS_ka_hotkeys.ahk into Startup and launches it. Defaults: Alt+E / Alt+R / Alt+X for en / ru / ka. The Menu key or Ctrl+Alt+RightClick pops a small language menu at the cursor for in-app selections (Chrome, Word, etc.) where third-party right-click menu items are blocked. Pass -Uninstall / -NoStart / -WhatIf as needed.
powershell -ExecutionPolicy Bypass -File .\extras\windows\context_menu\Install-TTS_ka-ContextMenu.ps1Adds "Read with TTS_ka" → submenu of languages on empty Explorer space and the Desktop (reads clipboard). Options:
| Flag | Meaning |
|---|---|
-FlatMenu |
One top-level entry per language |
-Languages @('en','ru') |
Subset only |
-IncludeTextFiles |
Also add a "read this file" entry on .txt files |
-Uninstall |
Remove all entries |
On Windows 11, the entries land under Show more options (classic shell).
tts-ka --print-completion bash > /etc/bash_completion.d/tts-ka
tts-ka --print-completion zsh > "${fpath[1]}/_tts-ka"
tts-ka --print-completion fish > ~/.config/fish/completions/tts-ka.fishThe scripts register both tts-ka and TTS_ka, and complete every flag plus the values of --lang, --voice, --player, and file arguments.
tts-ka [TEXT] [OPTIONS] # or: python -m TTS_ka
tts-ka serve [--host HOST] [--port PORT] [--token TOK]
tts-ka-gui
tts-ka-mcp # stdio JSON-RPC for MCP clients
tts-ka-hotkeys # background hotkey listener (Windows, [hotkeys] extra)
| Flag | Description |
|---|---|
-l, --lang {ka,ka-m,ru,en,en-US} |
Voice language |
--voice ID |
Specific voice (overrides default for --lang) |
-o, --output PATH |
Output MP3 path (default data.mp3); refuses overwrite without --force |
--force |
Overwrite an existing output file |
-c, --chunk-seconds N |
Chunk size (0 = auto, 20–60 is the sweet spot) |
-j, --parallel N |
Workers (0 = auto, capped at MAX_PARALLEL_WORKERS = 6) |
-n, --no-play |
Skip automatic playback after generation |
-s, --stream |
Play chunks as they finish |
--no-gui |
With --stream, run VLC headless |
--player NAME |
Preferred streaming player (vlc, mpv, ffplay, mplayer) |
--no-keys |
Don't read playback keys (space, n, +/-, q) from the terminal while streaming |
--no-notify |
Linux: don't show the start notification with the estimated reading time ("🔊 Reading ~7 min · done ≈ 14:32") |
--no-turbo, --legacy |
Disable auto-optimization |
| Flag | Format | Example |
|---|---|---|
--rate |
signed % | --rate=+30%, --rate=-20% |
--pitch |
Hz or signed % | --pitch=+5Hz, --pitch=-10% |
--volume |
signed % | --volume=+10% |
| Flag | ID3 frame |
|---|---|
--title |
TIT2 |
--author |
TPE1 |
--album |
TALB |
--cover PATH |
APIC (JPEG/PNG) |
--chapters PATH |
CHAP + CTOC from a JSON file |
| Flag | Output |
|---|---|
--srt |
<output>.srt next to the MP3 |
--vtt |
<output>.vtt next to the MP3 |
| Flag | Purpose |
|---|---|
--live |
Read stdin incrementally, speak each sentence |
--live-idle-ms N |
Flush a partial sentence after N ms of silence (default 800) |
--json |
One JSON event per line on stdout |
| Flag | Purpose |
|---|---|
-V, --version |
Print version, Python, platform, distribution metadata |
--doctor, --check-deps |
Print ffmpeg + player + Python dep status; exit 1 if critical deps missing |
--list-voices |
Print voice catalog (filterable with --lang) |
--preview-voice ID |
Play a short sample with that voice, then exit |
--help-full, -H |
Comprehensive help screen |
--print-completion {bash,zsh,fish} |
Emit a completion script |
--config PATH |
Use this JSON config (also TTS_KA_CONFIG env var) |
--no-cache |
Synthesize everything fresh this run (don't read or write the chunk cache) |
--clear-cache |
Delete the chunk cache, report the space freed, exit |
A JSON file is loaded from (first hit wins):
--config PATHon the CLITTS_KA_CONFIGenvironment variable~/.tts_config.json
All keys are optional. Real, supported schema:
{
"lang": "en",
"output": "data.mp3",
"chunk_seconds": 0,
"parallel": 0,
"no_play": false,
"stream": false,
"no_turbo": false,
"no_gui": false,
"rate": "+30%",
"pitch": "+0Hz",
"volume": "+0%",
"vlc_rc": true,
"cache": true,
"notify": true,
"cache_max_mb": 500,
"hotkeys": {
"<ctrl>+<alt>+1": "en",
"<ctrl>+<alt>+2": "ru",
"<ctrl>+<alt>+3": "ka",
"<ctrl>+<alt>+4": "ka-m"
}
}rate / pitch / volume are signed strings in the same form --rate / --pitch / --volume accept on the CLI. They become the default for every invocation — CLI, GUI, REST server, and MCP server. Pass them per-call on mcp__tts-ka__speak / stream_open to override for one call; pass --rate etc. on the CLI to override one run.
"vlc_rc": false sets TTS_KA_VLC_RC=0 for the process (unless that variable is already set), so you don't have to export it in every shell. Likewise "cache": false → TTS_KA_CACHE=0, "notify": false → TTS_KA_NOTIFY=0 and "cache_max_mb" → TTS_KA_CACHE_MAX_MB.
hotkeys: pynput combo strings → --lang codes. JSON null removes a default.
| Variable | Effect |
|---|---|
TTS_KA_CONFIG |
Alternate config file path |
TTS_KA_VLC_RC |
0 → disable VLC remote-control mode (one VLC per chunk instead) |
TTS_KA_MPV_IPC |
0 → don't drive headless mpv over JSON IPC (falls back to a stdin playlist; no pause/skip) |
TTS_KA_CACHE |
0 → disable the chunk cache |
TTS_KA_NOTIFY |
0 → no desktop notification (Linux, notify-send) when a read starts |
TTS_KA_CACHE_DIR |
Chunk cache location (default $XDG_CACHE_HOME/tts-ka, ~/Library/Caches/tts-ka, %LOCALAPPDATA%\tts-ka\Cache) |
TTS_KA_CACHE_MAX_MB |
Chunk cache size cap, least-recently-used pruned (default 500, ≈ 23 h of speech) |
TTS_KA_GUI_THEME |
tts-ka-gui theme: light, dark, system (default, needs the [gui] extra) or classic (plain Tk) |
TTS_API_TOKEN |
Bearer token required by tts-ka serve |
Before TTS, the pipeline rewrites noisy input so the voice does not read raw syntax. Implemented in TTS_ka.not_reading.replace_not_readable.
| Kind of input | What the voice says |
|---|---|
```code``` / `inline` |
"omitted fenced code block" / "omitted inline code snippet" |
https://…, www.… |
"omitted hyperlink" |
#!/usr/bin/env python |
"omitted script shebang line" |
<div>…</div> and similar |
"omitted markup tag" |
file.ts, app.py |
"TypeScript", "Python", … (60+ extensions) |
API, HTTPS, JSON, k8s, OAuth, … |
Spelled or expanded (160+ acronyms) |
=>, ≤, ∞, ∀, … |
Spoken words ("implies", "less than or equal to", "infinity", …) |
| 7+ digit runs | "a large number" |
The filter list is composable: import TextProcessingPipeline from not_reading and build your own ordering if you need to skip a filter.
The shape of the call is what matters, not magic flags:
- Short text (under ~200 words, no streaming): one direct edge-tts call. Latency is dominated by the network round-trip.
- Long text: split into ~30-second chunks, synthesized in parallel (
--parallelworkers) into a private temp dir, then joined losslessly by byte-concatenating the MP3 parts (no re-encode). Non-MP3 output falls back tosoundfile→pydub→ffmpeg. A merge that would drop a chunk fails the run instead of writing a shorter file. - Streaming: chunk size drops to
STREAMING_CHUNK_SECONDS = 15so the first chunk lands fast and feeds the player while the rest synthesizes. - Chunk cache: every synthesized chunk is cached by (text, voice, rate/pitch/volume), so re-reading the same text is near-instant and a run that failed part-way resumes: re-run the same command and only the missing chunks are synthesized. Resuming needs the same chunking — keep
--chunk-seconds,-jand--streamunchanged.tts-ka --doctorshows the cache size;--clear-cacheempties it. - Retries: timeouts, dropped connections and 403/408/429/5xx responses are retried with jittered exponential backoff (honouring
Retry-After). The first chunk that still fails stops the run at once rather than spending requests on a result that can't be used.
For honest timings, run python -m TTS_ka your-real-text --lang en and read the printed Completed in X.XXs line. Numbers depend heavily on your network to Edge's TTS endpoint, so machine-published benchmarks are not meaningful.
If you hit 403 or Invalid response status:
pip install -U edge-tts # Microsoft rotates access tokens; 7.2.x is already rejected
# then reduce workers if many chunks still fail:
python -m TTS_ka your-text --lang en --parallel 2No module named 'edge_tts' — pip install -U edge-tts.
FFmpeg not found — install ffmpeg and ensure it is on PATH. Verify with ffmpeg -version. On Windows, download and add the bin\ folder to PATH. On macOS, brew install ffmpeg. On Debian/Ubuntu, sudo apt install ffmpeg.
Empty clipboard — copy text first, then re-run with cb / clipboard. On Linux the reader uses wl-paste (Wayland), then xclip / xsel, before tkinter; elsewhere tkinter first, then PowerShell Get-Clipboard on Windows or pbpaste on macOS. tts-ka --doctor shows which helper was found.
--stream does nothing visible — no player was detected. Install mpv (recommended; Linux: apt install mpv, macOS: brew install mpv) or VLC (Windows: from videolan.org).
MCP client doesn't see the server — confirm tts-ka-mcp is on PATH (it is installed by [mcp] extra). Try running it manually; you should see nothing on stdout and JSON-RPC handshake output only when a client connects.
Hung --live process — the live loop blocks on stdin until EOF. Send Ctrl+D (Unix) / Ctrl+Z + Enter (Windows) to close the input stream, or Ctrl+C to abort.
Ctrl+C left a partial file — chunks are written to a per-run tts-ka-run-* dir in the system temp dir and removed on success, failure, or Ctrl+C; the output file is only replaced once the merge succeeds. Dirs left behind by a crash (or by GUI streaming, whose player reads them lazily) are swept on a later run after 12 hours.
Exit codes — 0 success, 1 synthesis or merge failure, 2 usage error (no text, empty stdin/clipboard, file not found, bad option), 130 Ctrl+C, 143 SIGTERM (kill). Both stop the audio player before exiting. A single word that looks like a path (notes.txt, docs/intro) but doesn't exist is reported as file not found rather than read aloud; pipe it in (echo notes.txt | tts-ka -) to speak it literally.
git clone https://github.com/DavidTbilisi/TTS.git
cd TTS
pip install -e ".[dev]"
pytest # full suite; coverage gate at 70%
pytest tests/test_live_stream.py # one file
pytest -m "not slow" # skip the subprocess-spawning E2E
black src/ tests/
flake8 src/ tests/
mypy src/To release: python scripts/release.py minor bumps the version, commits, tags, and pushes; then publish the GitHub Release for the tag to trigger PyPI upload.
MIT — see LICENSE.
Built on edge-tts (Microsoft Edge voices), pydub + soundfile + ffmpeg (audio merge / encode), rich (terminal progress), mutagen (ID3 tags), mcp (Model Context Protocol SDK), FastAPI + uvicorn (REST server), pynput (Windows hotkeys).
Author: David Chincharashvili — davidchincharashvili@gmail.com — github.com/DavidTbilisi/TTS