md2speech 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- md2speech-0.1.0/.github/workflows/ci.yml +30 -0
- md2speech-0.1.0/.github/workflows/release.yml +57 -0
- md2speech-0.1.0/.gitignore +20 -0
- md2speech-0.1.0/.markdownlint.json +3 -0
- md2speech-0.1.0/.python-version +1 -0
- md2speech-0.1.0/LICENSE +21 -0
- md2speech-0.1.0/MARKDOWN_TTS_PACKAGE_AGENT.md +520 -0
- md2speech-0.1.0/PKG-INFO +396 -0
- md2speech-0.1.0/README.md +362 -0
- md2speech-0.1.0/mac_structure.txt +32753 -0
- md2speech-0.1.0/pyproject.toml +57 -0
- md2speech-0.1.0/src/md2speech/__init__.py +40 -0
- md2speech-0.1.0/src/md2speech/__main__.py +6 -0
- md2speech-0.1.0/src/md2speech/audio.py +147 -0
- md2speech-0.1.0/src/md2speech/cli.py +204 -0
- md2speech-0.1.0/src/md2speech/engine.py +90 -0
- md2speech-0.1.0/src/md2speech/normalize.py +110 -0
- md2speech-0.1.0/src/md2speech/reader.py +73 -0
- md2speech-0.1.0/src/md2speech/synthesize.py +184 -0
- md2speech-0.1.0/src/md2speech/voices.py +240 -0
- md2speech-0.1.0/tests/conftest.py +1 -0
- md2speech-0.1.0/tests/fixtures/sample.md +29 -0
- md2speech-0.1.0/tests/fixtures/sample.txt +3 -0
- md2speech-0.1.0/tests/test_audio.py +66 -0
- md2speech-0.1.0/tests/test_cli.py +44 -0
- md2speech-0.1.0/tests/test_integration.py +23 -0
- md2speech-0.1.0/tests/test_normalize.py +45 -0
- md2speech-0.1.0/tests/test_reader.py +72 -0
- md2speech-0.1.0/tests/test_synthesize.py +69 -0
- md2speech-0.1.0/tests/test_voices.py +107 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
pip install -e ".[dev]"
|
|
28
|
+
|
|
29
|
+
- name: Run unit tests
|
|
30
|
+
run: pytest -m "not integration"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
pip install -e ".[dev]"
|
|
26
|
+
|
|
27
|
+
- name: Run unit tests
|
|
28
|
+
run: pytest -m "not integration"
|
|
29
|
+
|
|
30
|
+
publish:
|
|
31
|
+
needs: test
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
environment:
|
|
34
|
+
name: pypi
|
|
35
|
+
url: https://pypi.org/project/md2speech/
|
|
36
|
+
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
|
|
40
|
+
- uses: actions/setup-python@v5
|
|
41
|
+
with:
|
|
42
|
+
python-version: "3.12"
|
|
43
|
+
|
|
44
|
+
- name: Install build tools
|
|
45
|
+
run: python -m pip install --upgrade pip build
|
|
46
|
+
|
|
47
|
+
- name: Build wheel and sdist
|
|
48
|
+
run: python -m build
|
|
49
|
+
|
|
50
|
+
- name: Publish to PyPI
|
|
51
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
52
|
+
|
|
53
|
+
- name: Create GitHub Release
|
|
54
|
+
uses: softprops/action-gh-release@v2
|
|
55
|
+
with:
|
|
56
|
+
generate_release_notes: true
|
|
57
|
+
files: dist/*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
md2speech-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 md2speech contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# Agent Prompt: Standalone Markdown/Text-to-Speech Python Package
|
|
2
|
+
|
|
3
|
+
> **Generated:** 2026-07-08
|
|
4
|
+
> **Purpose:** Implement a **completely independent** Python package (no DriveSpeak code, imports, or repo coupling) that converts Markdown or plain-text files to speech audio using the same **free, local** TTS stack as DriveSpeak: **Kokoro-82M** via the `kokoro` Python package.
|
|
5
|
+
> **Reference only:** A prior analysis of DriveSpeak's TTS pipeline is summarized in §3 below. Use it as architectural guidance — do **not** copy DriveSpeak source files or depend on `drivespeak_ml`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How to Use This Prompt
|
|
10
|
+
|
|
11
|
+
This is a single deliverable: a new, self-contained Python package with CLI, library API, tests, and user documentation. Execute the phases in order. Verify acceptance criteria after each phase before proceeding.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Phase 1 — Project scaffold & packaging
|
|
15
|
+
Phase 2 — Text ingestion (Markdown + plain text)
|
|
16
|
+
Phase 3 — TTS engine (Kokoro) & long-text synthesis
|
|
17
|
+
Phase 4 — Audio post-processing & format conversion
|
|
18
|
+
Phase 5 — CLI & library public API
|
|
19
|
+
Phase 6 — Tests, docs, and install verification
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Critical rules:**
|
|
23
|
+
|
|
24
|
+
1. The package must be **fully standalone** — installable via `pip install .` or `uv sync` from its own directory, with zero references to DriveSpeak paths, modules, or configs.
|
|
25
|
+
2. **Default engine is Kokoro only** — free, local, offline. Do not add paid API dependencies (ElevenLabs, OpenAI TTS, etc.).
|
|
26
|
+
3. **First-run experience must work** — installation pulls Python dependencies; model weights download automatically on first synthesis (via `huggingface-hub`, as Kokoro does).
|
|
27
|
+
4. **Default output format is MP3** — the package must handle encoding itself (not require the user to run external tools manually).
|
|
28
|
+
5. Write **comprehensive user documentation** in `README.md` (see §8).
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1. Product Requirements
|
|
33
|
+
|
|
34
|
+
### 1.1 CLI Interface
|
|
35
|
+
|
|
36
|
+
Implement a console script (e.g. `md2speech` or `text2speech` — choose a clear, memorable name and use it consistently).
|
|
37
|
+
|
|
38
|
+
**Required positional argument:**
|
|
39
|
+
|
|
40
|
+
| Argument | Description |
|
|
41
|
+
|----------|-------------|
|
|
42
|
+
| `input` | Path to a `.md`, `.markdown`, or `.txt` file |
|
|
43
|
+
|
|
44
|
+
**Optional arguments:**
|
|
45
|
+
|
|
46
|
+
| Flag | Default | Description |
|
|
47
|
+
|------|---------|-------------|
|
|
48
|
+
| `--output` / `-o` | Derived from input filename | Output audio file path |
|
|
49
|
+
| `--format` / `-f` | `mp3` | Output audio format (see §1.3) |
|
|
50
|
+
| `--voice` / `-v` | `af_heart` | Kokoro voice ID (document available voices) |
|
|
51
|
+
| `--speed` | `1.0` | Speech speed multiplier |
|
|
52
|
+
| `--lang` | `a` | Kokoro language code (`a` = American English) |
|
|
53
|
+
| `--no-normalize` | off | Skip text normalization (numbers, abbreviations, etc.) |
|
|
54
|
+
| `--paragraph-pause` | `0.4` | Seconds of silence between paragraphs |
|
|
55
|
+
| `--verbose` / `-v` (or `-V` if `-v` is voice) | off | Enable debug logging |
|
|
56
|
+
|
|
57
|
+
**Default output path logic** (when `--output` is omitted):
|
|
58
|
+
|
|
59
|
+
- Same directory as the input file
|
|
60
|
+
- Basename matches input basename
|
|
61
|
+
- Extension matches `--format` (e.g. `article.md` → `article.mp3`)
|
|
62
|
+
|
|
63
|
+
**Example invocations** (must work after install):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Minimal — converts article.md → article.mp3 in the same folder
|
|
67
|
+
md2speech article.md
|
|
68
|
+
|
|
69
|
+
# Explicit output path and format
|
|
70
|
+
md2speech chapter.txt -o ~/Audio/chapter.wav --format wav
|
|
71
|
+
|
|
72
|
+
# Long Markdown document with a specific voice
|
|
73
|
+
md2speech book.md -o book.mp3 --voice am_adam --speed 0.95
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 1.2 Library API
|
|
77
|
+
|
|
78
|
+
Expose a clean Python API (not only CLI) so the package can be imported:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from <package_name> import synthesize_file
|
|
82
|
+
|
|
83
|
+
result = synthesize_file(
|
|
84
|
+
"article.md",
|
|
85
|
+
output_path="article.mp3", # optional
|
|
86
|
+
output_format="mp3", # optional, default "mp3"
|
|
87
|
+
voice="af_heart",
|
|
88
|
+
speed=1.0,
|
|
89
|
+
)
|
|
90
|
+
# result.path → Path to written audio file
|
|
91
|
+
# result.duration_seconds → float
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Also expose lower-level building blocks (`read_document`, `extract_plain_text`, `TTSEngine`, `AudioWriter`) for advanced users. Document all public symbols.
|
|
95
|
+
|
|
96
|
+
### 1.3 Supported Output Formats
|
|
97
|
+
|
|
98
|
+
| Format | Required | Notes |
|
|
99
|
+
|--------|----------|-------|
|
|
100
|
+
| `mp3` | **Yes (default)** | Primary delivery format |
|
|
101
|
+
| `wav` | Yes | Uncompressed PCM |
|
|
102
|
+
| `ogg` | Nice to have | Vorbis via pydub/ffmpeg |
|
|
103
|
+
| `flac` | Nice to have | Lossless |
|
|
104
|
+
|
|
105
|
+
Implementation: use **`pydub`** (backed by **ffmpeg**) for MP3/OGG/FLAC encoding. Declare `ffmpeg` as a **system prerequisite** in the README and detect it at runtime with a clear error message if missing.
|
|
106
|
+
|
|
107
|
+
For WAV, write directly with `soundfile` (no ffmpeg needed). For MP3 and other compressed formats, pydub/ffmpeg is the standard approach.
|
|
108
|
+
|
|
109
|
+
### 1.4 Input File Handling
|
|
110
|
+
|
|
111
|
+
**Plain text (`.txt`):** Read as UTF-8. Handle BOM if present.
|
|
112
|
+
|
|
113
|
+
**Markdown (`.md`, `.markdown`):**
|
|
114
|
+
|
|
115
|
+
1. Read as UTF-8.
|
|
116
|
+
2. Strip YAML front matter if present (`---` delimited block at top).
|
|
117
|
+
3. Convert Markdown to plain speakable text:
|
|
118
|
+
- Remove or ignore HTML comments.
|
|
119
|
+
- Strip inline/formatting markup (bold, italic, links → link text only, code spans → speak the code text or skip fenced code blocks — **document the choice**; prefer skipping fenced code blocks and speaking inline code literally).
|
|
120
|
+
- Convert headings to plain text (do not say "hash hash heading").
|
|
121
|
+
- Preserve paragraph breaks (`\n\n`) for natural pacing.
|
|
122
|
+
- Preserve list items as speakable lines.
|
|
123
|
+
4. Do **not** require a full Markdown renderer with network access. Use a lightweight approach (`markdown-it-py`, `mistune`, or regex-based stripping). Keep dependencies minimal.
|
|
124
|
+
|
|
125
|
+
Reject unsupported extensions with a clear error.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 2. Technical Architecture
|
|
130
|
+
|
|
131
|
+
### 2.1 Recommended Package Layout
|
|
132
|
+
|
|
133
|
+
Create the package in a **new top-level directory** inside the workspace (sibling to `ml/`, not inside it):
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
<text2speech-package>/
|
|
137
|
+
├── pyproject.toml # Metadata, dependencies, console_scripts entry point
|
|
138
|
+
├── README.md # Comprehensive user documentation (§8)
|
|
139
|
+
├── LICENSE # MIT (or Apache 2.0 to match Kokoro)
|
|
140
|
+
├── src/
|
|
141
|
+
│ └── <package_name>/
|
|
142
|
+
│ ├── __init__.py # Public API exports
|
|
143
|
+
│ ├── cli.py # argparse/typer CLI
|
|
144
|
+
│ ├── reader.py # File reading, Markdown → plain text
|
|
145
|
+
│ ├── normalize.py # Text normalization (numbers, abbreviations)
|
|
146
|
+
│ ├── engine.py # Kokoro TTS wrapper
|
|
147
|
+
│ ├── synthesize.py # Long-text chunking & orchestration
|
|
148
|
+
│ ├── audio.py # Post-processing, resampling, format export
|
|
149
|
+
│ └── __main__.py # python -m <package_name> support
|
|
150
|
+
├── tests/
|
|
151
|
+
│ ├── test_reader.py
|
|
152
|
+
│ ├── test_normalize.py
|
|
153
|
+
│ ├── test_audio.py
|
|
154
|
+
│ └── fixtures/
|
|
155
|
+
│ ├── sample.txt
|
|
156
|
+
│ └── sample.md
|
|
157
|
+
└── .python-version # 3.11 or 3.12
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Use **`hatchling`** or **`uv`** as the build backend (match modern Python packaging). Pin `requires-python = ">=3.11,<3.13"` (Kokoro/torch compatibility).
|
|
161
|
+
|
|
162
|
+
### 2.2 Core Dependencies
|
|
163
|
+
|
|
164
|
+
| Package | Purpose |
|
|
165
|
+
|---------|---------|
|
|
166
|
+
| `kokoro` | TTS engine (wraps Kokoro-82M) |
|
|
167
|
+
| `torch` | Kokoro runtime |
|
|
168
|
+
| `numpy` | Audio arrays |
|
|
169
|
+
| `scipy` | Resampling (polyphase) |
|
|
170
|
+
| `soundfile` | WAV I/O |
|
|
171
|
+
| `pydub` | MP3/OGG/FLAC encoding |
|
|
172
|
+
| `inflect` | Number/word normalization |
|
|
173
|
+
| `tqdm` | Optional progress for long documents |
|
|
174
|
+
|
|
175
|
+
**System dependency:** `ffmpeg` must be on `PATH` for MP3 export. Document install commands for macOS (`brew install ffmpeg`), Ubuntu (`apt install ffmpeg`), and Windows (`winget`/`choco`).
|
|
176
|
+
|
|
177
|
+
Do **not** add DriveSpeak, `drivespeak_ml`, or Firebase as dependencies.
|
|
178
|
+
|
|
179
|
+
### 2.3 Console Script Entry Point
|
|
180
|
+
|
|
181
|
+
In `pyproject.toml`:
|
|
182
|
+
|
|
183
|
+
```toml
|
|
184
|
+
[project.scripts]
|
|
185
|
+
md2speech = "<package_name>.cli:main"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
(Replace `md2speech` and `<package_name>` with your chosen names.)
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 3. Reference Architecture (from DriveSpeak — guidance only)
|
|
193
|
+
|
|
194
|
+
DriveSpeak's free TTS pipeline uses these parameters. Replicate the **behavior**, not the code:
|
|
195
|
+
|
|
196
|
+
### 3.1 TTS Model & Engine
|
|
197
|
+
|
|
198
|
+
| Setting | Value |
|
|
199
|
+
|---------|-------|
|
|
200
|
+
| Model | `hexgrad/Kokoro-82M` on Hugging Face |
|
|
201
|
+
| Python API | `from kokoro import KPipeline` |
|
|
202
|
+
| Language code | `"a"` (American English) |
|
|
203
|
+
| Native sample rate | 24,000 Hz |
|
|
204
|
+
| Inference | Local CPU or GPU via PyTorch |
|
|
205
|
+
|
|
206
|
+
**Engine wrapper pattern:**
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
pipeline = KPipeline(lang_code="a", repo_id="hexgrad/Kokoro-82M", device=None)
|
|
210
|
+
for result in pipeline(text, voice=voice_id, speed=speed):
|
|
211
|
+
# collect result.audio tensors → concatenate → float32 numpy array
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 3.2 Recommended Voices
|
|
215
|
+
|
|
216
|
+
Document these in the README:
|
|
217
|
+
|
|
218
|
+
| Voice ID | Gender | Character |
|
|
219
|
+
|----------|--------|-----------|
|
|
220
|
+
| `af_heart` | Female | Warm, clear — **default** |
|
|
221
|
+
| `am_adam` | Male | Deep, steady |
|
|
222
|
+
| `af_bella` | Female | Soft |
|
|
223
|
+
| `am_michael` | Male | Conversational |
|
|
224
|
+
|
|
225
|
+
Full list: 11 female (`af_*`) and 9 male (`am_*`) — see Kokoro docs.
|
|
226
|
+
|
|
227
|
+
### 3.3 Long-Text Synthesis Strategy
|
|
228
|
+
|
|
229
|
+
For documents with multiple paragraphs, **do not** send the entire file as one Kokoro call. Instead:
|
|
230
|
+
|
|
231
|
+
1. Split text on `\n\n` into paragraphs.
|
|
232
|
+
2. Within each paragraph, split on `\n` into lines.
|
|
233
|
+
3. Synthesize each line separately.
|
|
234
|
+
4. Insert **400 ms silence** between paragraphs (configurable via `--paragraph-pause`).
|
|
235
|
+
5. Concatenate all audio into one output file.
|
|
236
|
+
|
|
237
|
+
Apply text normalization before each synthesis call.
|
|
238
|
+
|
|
239
|
+
### 3.4 Text Normalization
|
|
240
|
+
|
|
241
|
+
Before TTS, expand:
|
|
242
|
+
|
|
243
|
+
- Abbreviations (`Dr.` → `Doctor`, `Mr.` → `Mister`, etc.)
|
|
244
|
+
- Ordinals (`1st` → `first`)
|
|
245
|
+
- Currency (`$12.50` → `twelve dollars and fifty cents`)
|
|
246
|
+
- Percentages (`85%` → `eighty-five percent`)
|
|
247
|
+
- Times (`3:45 PM` → spoken form)
|
|
248
|
+
- Plain numbers → words
|
|
249
|
+
|
|
250
|
+
Use the `inflect` library. This dramatically improves speech quality for non-fiction text.
|
|
251
|
+
|
|
252
|
+
### 3.5 Audio Post-Processing
|
|
253
|
+
|
|
254
|
+
Apply this chain before format export:
|
|
255
|
+
|
|
256
|
+
| Step | Parameter |
|
|
257
|
+
|------|-----------|
|
|
258
|
+
| Peak normalization | **-1.5 dBFS** |
|
|
259
|
+
| Silence padding | **100 ms** before and after the final concatenated audio |
|
|
260
|
+
| Resampling | 24 kHz → **44,100 Hz** (scipy `resample_poly`) |
|
|
261
|
+
| Export | User-selected format |
|
|
262
|
+
|
|
263
|
+
DriveSpeak targets `.caf` (AAC) for iOS; this package targets **MP3 by default** instead.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 4. Phase-by-Phase Implementation
|
|
268
|
+
|
|
269
|
+
### Phase 1 — Project Scaffold & Packaging
|
|
270
|
+
|
|
271
|
+
**Tasks:**
|
|
272
|
+
|
|
273
|
+
1. Create the package directory structure (§2.1).
|
|
274
|
+
2. Write `pyproject.toml` with name, version `0.1.0`, description, authors, license, Python bound, dependencies, and console script.
|
|
275
|
+
3. Add `src/<package_name>/__init__.py` with version string.
|
|
276
|
+
4. Verify `pip install -e .` or `uv sync && uv pip install -e .` succeeds.
|
|
277
|
+
|
|
278
|
+
**Acceptance criteria:**
|
|
279
|
+
|
|
280
|
+
- [ ] `pip install -e .` completes without error
|
|
281
|
+
- [ ] `md2speech --help` prints usage
|
|
282
|
+
- [ ] Package imports: `python -c "import <package_name>"`
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
### Phase 2 — Text Ingestion
|
|
287
|
+
|
|
288
|
+
**Tasks:**
|
|
289
|
+
|
|
290
|
+
1. Implement `read_document(path: Path) -> str` in `reader.py`.
|
|
291
|
+
2. Support `.txt`, `.md`, `.markdown`.
|
|
292
|
+
3. Implement Markdown → plain text extraction (§1.4).
|
|
293
|
+
4. Add unit tests with fixture files (short `.txt` and `.md` with headings, links, code blocks, front matter).
|
|
294
|
+
|
|
295
|
+
**Acceptance criteria:**
|
|
296
|
+
|
|
297
|
+
- [ ] `# Title\n\nHello **world**` → speakable text without markup
|
|
298
|
+
- [ ] Front matter stripped
|
|
299
|
+
- [ ] Fenced code blocks handled consistently (documented behavior)
|
|
300
|
+
- [ ] Tests pass
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
### Phase 3 — TTS Engine & Long-Text Synthesis
|
|
305
|
+
|
|
306
|
+
**Tasks:**
|
|
307
|
+
|
|
308
|
+
1. Implement `engine.py` — thin `TTSEngine` class wrapping `KPipeline`.
|
|
309
|
+
2. Implement `normalize.py` — text normalization (§3.4).
|
|
310
|
+
3. Implement `synthesize.py`:
|
|
311
|
+
- `synthesize_text(text, voice, speed) -> np.ndarray` for short strings
|
|
312
|
+
- `synthesize_long_text(text, voice, speed, paragraph_pause) -> np.ndarray` with chunking (§3.3)
|
|
313
|
+
4. Lazy-load the pipeline (instantiate on first use, not import).
|
|
314
|
+
5. Log model download progress on first run.
|
|
315
|
+
|
|
316
|
+
**Acceptance criteria:**
|
|
317
|
+
|
|
318
|
+
- [ ] Short string synthesizes to 24 kHz float32 numpy array
|
|
319
|
+
- [ ] Multi-paragraph input produces audio with audible pauses between paragraphs
|
|
320
|
+
- [ ] Normalization expands `Dr. Smith paid $5` correctly
|
|
321
|
+
- [ ] Voice and speed parameters work
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
### Phase 4 — Audio Post-Processing & Format Conversion
|
|
326
|
+
|
|
327
|
+
**Tasks:**
|
|
328
|
+
|
|
329
|
+
1. Implement `audio.py`:
|
|
330
|
+
- `peak_normalize(audio, target_dbfs=-1.5)`
|
|
331
|
+
- `add_silence_padding(audio, sample_rate, pad_ms=100)`
|
|
332
|
+
- `resample(audio, orig_sr, target_sr=44100)`
|
|
333
|
+
- `write_audio(audio, path, format, sample_rate=44100)` — dispatches to soundfile (WAV) or pydub (MP3/OGG/FLAC)
|
|
334
|
+
2. Implement ffmpeg presence check with actionable error:
|
|
335
|
+
```
|
|
336
|
+
Error: ffmpeg not found. MP3 export requires ffmpeg.
|
|
337
|
+
Install: brew install ffmpeg (macOS)
|
|
338
|
+
```
|
|
339
|
+
3. Return metadata (duration, output path) from write function.
|
|
340
|
+
|
|
341
|
+
**Acceptance criteria:**
|
|
342
|
+
|
|
343
|
+
- [ ] WAV export works without ffmpeg
|
|
344
|
+
- [ ] MP3 export produces playable file when ffmpeg is installed
|
|
345
|
+
- [ ] Peak level is consistent across outputs
|
|
346
|
+
- [ ] Output sample rate is 44,100 Hz
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
### Phase 5 — CLI & Public API
|
|
351
|
+
|
|
352
|
+
**Tasks:**
|
|
353
|
+
|
|
354
|
+
1. Wire `cli.py` with `argparse` (or `typer` if you prefer).
|
|
355
|
+
2. Implement `synthesize_file()` in `__init__.py` or `synthesize.py`.
|
|
356
|
+
3. Add `python -m <package_name>` entry via `__main__.py`.
|
|
357
|
+
4. Add `--verbose` logging (show paragraph count, synthesis progress, output path, duration).
|
|
358
|
+
5. Handle errors gracefully: missing input file, unsupported format, missing ffmpeg, empty document.
|
|
359
|
+
|
|
360
|
+
**Acceptance criteria:**
|
|
361
|
+
|
|
362
|
+
- [ ] `md2speech sample.md` creates `sample.mp3` next to input
|
|
363
|
+
- [ ] `md2speech sample.txt -o /tmp/out.wav --format wav` works
|
|
364
|
+
- [ ] `--help` documents all options
|
|
365
|
+
- [ ] Exit code 0 on success, non-zero on failure
|
|
366
|
+
- [ ] Library API works from Python REPL
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
### Phase 6 — Tests, Documentation & Install Verification
|
|
371
|
+
|
|
372
|
+
**Tasks:**
|
|
373
|
+
|
|
374
|
+
1. Write tests (§2.1). Mock Kokoro in unit tests to avoid downloading the model in CI; optionally mark one integration test `@pytest.mark.integration` that runs real synthesis.
|
|
375
|
+
2. Write comprehensive `README.md` (§8).
|
|
376
|
+
3. Add a `LICENSE` file.
|
|
377
|
+
4. Run full install on a clean virtualenv and convert a sample file end-to-end.
|
|
378
|
+
|
|
379
|
+
**Acceptance criteria:**
|
|
380
|
+
|
|
381
|
+
- [ ] `pytest` passes (unit tests; integration test optional/skipped in CI)
|
|
382
|
+
- [ ] README covers all sections in §8
|
|
383
|
+
- [ ] Fresh `pip install .` → `md2speech` works on a sample file
|
|
384
|
+
- [ ] No references to DriveSpeak in code or user-facing docs (optional brief credit in README acknowledgments is fine)
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## 5. Error Handling & Edge Cases
|
|
389
|
+
|
|
390
|
+
| Scenario | Behavior |
|
|
391
|
+
|----------|----------|
|
|
392
|
+
| Input file not found | Clear error, exit 1 |
|
|
393
|
+
| Empty file after extraction | Clear error, exit 1 |
|
|
394
|
+
| Unsupported extension | Clear error listing supported types |
|
|
395
|
+
| ffmpeg missing for MP3 | Actionable install instructions |
|
|
396
|
+
| Very long document (100+ paragraphs) | Show tqdm progress, do not load entire audio in duplicate |
|
|
397
|
+
| GPU available | Pass `device="cuda"` or `device="mps"` if torch detects it; default CPU |
|
|
398
|
+
| Model download fails (no network) | Clear error mentioning Hugging Face access |
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## 6. Non-Goals (Do Not Implement)
|
|
403
|
+
|
|
404
|
+
- Real-time / streaming playback
|
|
405
|
+
- SSML or phoneme-level control
|
|
406
|
+
- Voice cloning or custom voice training
|
|
407
|
+
- Paid TTS APIs (ElevenLabs, OpenAI, Google Cloud TTS)
|
|
408
|
+
- Web UI or REST server (CLI + library only)
|
|
409
|
+
- DriveSpeak `.caf` output (iOS-specific; MP3/WAV is sufficient)
|
|
410
|
+
- PDF or DOCX input (text/Markdown only for v0.1)
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## 7. Quality Bar
|
|
415
|
+
|
|
416
|
+
- **Type hints** on all public functions
|
|
417
|
+
- **`logging`** module (not bare `print`) for operational messages; `print` only for CLI output if desired
|
|
418
|
+
- **No over-engineering** — a focused v0.1 beats a framework
|
|
419
|
+
- **Consistent naming** — pick `md2speech` or `text2speech` as the CLI name and stick with it
|
|
420
|
+
- **Pinned or bounded dependencies** in `pyproject.toml` for reproducible installs
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## 8. README Documentation Requirements
|
|
425
|
+
|
|
426
|
+
The `README.md` must include all of the following sections:
|
|
427
|
+
|
|
428
|
+
1. **Overview** — What the tool does in 2–3 sentences
|
|
429
|
+
2. **Features** — Bullet list (free, offline, Markdown support, MP3 default, etc.)
|
|
430
|
+
3. **Requirements** — Python version, ffmpeg (for MP3), disk space for model (~hundreds of MB)
|
|
431
|
+
4. **Installation**
|
|
432
|
+
- `pip install .` from source
|
|
433
|
+
- `uv sync` alternative
|
|
434
|
+
- ffmpeg install per platform
|
|
435
|
+
- Note that first run downloads Kokoro-82M from Hugging Face
|
|
436
|
+
5. **Quick Start** — Copy-paste examples (at least 3)
|
|
437
|
+
6. **CLI Reference** — Table of all arguments
|
|
438
|
+
7. **Python API** — `synthesize_file()` example and advanced imports
|
|
439
|
+
8. **Voice Reference** — Table of recommended Kokoro voice IDs
|
|
440
|
+
9. **Supported Formats** — mp3, wav, etc. and which need ffmpeg
|
|
441
|
+
10. **How It Works** — Brief pipeline diagram or numbered steps (read → normalize → chunk → synthesize → post-process → encode)
|
|
442
|
+
11. **Markdown Handling** — What markup is stripped, how code blocks are treated
|
|
443
|
+
12. **Troubleshooting** — ffmpeg not found, model download, torch/MPS/CUDA, memory
|
|
444
|
+
13. **License** — MIT/Apache
|
|
445
|
+
14. **Acknowledgments** — Kokoro-82M (hexgrad), Hugging Face
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## 9. Suggested Package Names
|
|
450
|
+
|
|
451
|
+
Pick one during implementation (check PyPI availability if publishing):
|
|
452
|
+
|
|
453
|
+
| Name | CLI command |
|
|
454
|
+
|------|-------------|
|
|
455
|
+
| `md2speech` | `md2speech` |
|
|
456
|
+
| `text2speech` | `text2speech` |
|
|
457
|
+
| `kokoro-read` | `kokoro-read` |
|
|
458
|
+
| `speakmd` | `speakmd` |
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## 10. Final Deliverables Checklist
|
|
463
|
+
|
|
464
|
+
Before marking the task complete, confirm:
|
|
465
|
+
|
|
466
|
+
- [ ] Standalone package directory with `pyproject.toml`
|
|
467
|
+
- [ ] `pip install .` installs all Python dependencies
|
|
468
|
+
- [ ] CLI: `md2speech <input.md> [-o path] [--format mp3]`
|
|
469
|
+
- [ ] Default output: same basename as input, `.mp3` extension, same directory
|
|
470
|
+
- [ ] Markdown and plain text supported
|
|
471
|
+
- [ ] Kokoro-82M local TTS (free, no API key)
|
|
472
|
+
- [ ] MP3 export via pydub/ffmpeg; WAV via soundfile
|
|
473
|
+
- [ ] Long-text paragraph chunking with pauses
|
|
474
|
+
- [ ] Text normalization (numbers, abbreviations)
|
|
475
|
+
- [ ] Audio post-processing (normalize, pad, resample to 44.1 kHz)
|
|
476
|
+
- [ ] Comprehensive `README.md`
|
|
477
|
+
- [ ] Unit tests with mocked TTS
|
|
478
|
+
- [ ] No DriveSpeak code dependencies
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## 11. Smoke Test Script
|
|
483
|
+
|
|
484
|
+
After implementation, this sequence must succeed on macOS/Linux:
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
cd <package-directory>
|
|
488
|
+
python -m venv .venv && source .venv/bin/activate
|
|
489
|
+
pip install -e ".[dev]" # or: uv sync
|
|
490
|
+
|
|
491
|
+
# Verify ffmpeg for MP3
|
|
492
|
+
ffmpeg -version
|
|
493
|
+
|
|
494
|
+
# Create a test file
|
|
495
|
+
cat > /tmp/test_doc.md << 'EOF'
|
|
496
|
+
---
|
|
497
|
+
title: Test
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
# Introduction
|
|
501
|
+
|
|
502
|
+
Dr. Smith ordered 3 cups of coffee at 9:15 AM.
|
|
503
|
+
|
|
504
|
+
## Details
|
|
505
|
+
|
|
506
|
+
The total was **$12.50**. Visit [our site](https://example.com) for more.
|
|
507
|
+
EOF
|
|
508
|
+
|
|
509
|
+
# Convert
|
|
510
|
+
md2speech /tmp/test_doc.md -o /tmp/test_doc.mp3 --verbose
|
|
511
|
+
|
|
512
|
+
# Verify output exists and is non-trivial size
|
|
513
|
+
test -f /tmp/test_doc.mp3
|
|
514
|
+
test $(stat -f%z /tmp/test_doc.mp3 2>/dev/null || stat -c%s /tmp/test_doc.mp3) -gt 1000
|
|
515
|
+
echo "Smoke test passed"
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
*End of agent prompt.*
|