ollama-speak 1.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.
@@ -0,0 +1,31 @@
1
+ name: Build, Package & Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: write
11
+
12
+ jobs:
13
+ build-pypi:
14
+ name: Build & Publish to PyPI
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: '3.11'
22
+ - name: Install build tools
23
+ run: pip install --upgrade build twine
24
+ - name: Build wheel and sdist
25
+ run: python -m build
26
+ - name: Publish to PyPI
27
+ if: startsWith(github.ref, 'refs/tags/v')
28
+ env:
29
+ TWINE_USERNAME: __token__
30
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
31
+ run: twine upload --skip-existing dist/*
@@ -0,0 +1,29 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.so
5
+ .Python
6
+ build/
7
+ develop-eggs/
8
+ dist/
9
+ downloads/
10
+ eggs/
11
+ .eggs/
12
+ lib/
13
+ lib64/
14
+ parts/
15
+ sdist/
16
+ var/
17
+ wheels/
18
+ share/python-wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ .env
23
+ .venv
24
+ env/
25
+ venv/
26
+ ENV/
27
+ env.bak/
28
+ venv.bak/
29
+ .git/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cicada & Antigravity Community
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,230 @@
1
+ Metadata-Version: 2.5
2
+ Name: ollama-speak
3
+ Version: 1.1.0
4
+ Summary: 100% Local, Offline Multilingual Neural Text-to-Speech CLI for Ollama (Kokoro-82M & Piper).
5
+ Project-URL: Homepage, https://github.com/AjarnSpencer/ollama-speak
6
+ Project-URL: Repository, https://github.com/AjarnSpencer/ollama-speak
7
+ Project-URL: Issues, https://github.com/AjarnSpencer/ollama-speak/issues
8
+ Author-email: Ajarn Spencer Littlewood <ajarnspencer@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.9
12
+ Requires-Dist: kokoro>=0.8.4
13
+ Requires-Dist: numpy>=1.21.0
14
+ Requires-Dist: soundfile>=0.12.0
15
+ Provides-Extra: all
16
+ Requires-Dist: kokoro>=0.8.4; extra == 'all'
17
+ Requires-Dist: piper-tts>=1.2.0; extra == 'all'
18
+ Requires-Dist: soundfile>=0.12.0; extra == 'all'
19
+ Provides-Extra: piper
20
+ Requires-Dist: piper-tts>=1.2.0; extra == 'piper'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # 🗣️ ollama-speak (`ospeak`)
24
+
25
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
26
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/)
27
+ [![Ollama](https://img.shields.io/badge/Ollama-Local%20AI-orange.svg)](https://ollama.ai/)
28
+ [![100% Local AI](https://img.shields.io/badge/Privacy-100%25%20Offline%20Local-success.svg)](#100-offline-and-sovereign-guarantee)
29
+
30
+ > **100% Local, Offline Multilingual Neural Text-to-Speech CLI for Ollama (Kokoro-82M and Piper ONNX).**
31
+
32
+ `ollama-speak` (`ospeak`) is a standalone, sovereign voice engine built specifically for the local AI and Ollama community. It runs **100% locally on your own machine** using open-weights models:
33
+
34
+ - **Kokoro-82M (Default):** Apache 2.0 open-weight 82M-parameter neural model providing studio-grade broadcast speech.
35
+ - **Piper TTS (Ultra-lightweight):** Fast ONNX neural voice engine designed to run with sub-100ms latency even on low-spec hardware or a Raspberry Pi.
36
+
37
+ Zero cloud API calls, zero telemetry, zero corporate tracking, and zero accounts. Once local weights are downloaded, `ollama-speak` works completely with your internet disconnected.
38
+
39
+ ---
40
+
41
+ ## 💻 Minimum System and Hardware Requirements
42
+
43
+ `ollama-speak` is designed to run smoothly across consumer laptops, older desktop PCs, MacBooks, and high-performance workstations.
44
+
45
+ ### Hardware Requirements Table
46
+
47
+ | Component | Piper ONNX (Ultra-Lightweight) | Kokoro-82M (Studio Quality Default) |
48
+ | :--- | :--- | :--- |
49
+ | **CPU** | Any 64-bit CPU (Intel, AMD, ARM64 / Apple Silicon, Raspberry Pi 4/5) | Modern multi-core CPU (AVX2 recommended) or Apple Silicon M1–M4 |
50
+ | **GPU (Optional)** | Not needed (pure CPU ONNX) | Accelerated on CUDA / ROCm / Metal, runs easily on CPU |
51
+ | **System RAM** | **1 GB minimum** (uses ~80 MB during inference) | **2 GB to 4 GB RAM** (uses ~350 MB during inference) |
52
+ | **Disk Space** | **~35 MB** for voice model | **~350 MB** for open-weight model checkpoint |
53
+ | **Audio System** | Standard OS sound (ALSA, PulseAudio, PipeWire, CoreAudio, WASAPI) | Standard OS sound |
54
+ | **Internet** | Only required once for initial model download | Only required once for initial model download |
55
+
56
+ ### Supported Operating Systems
57
+
58
+ - **Linux:** Ubuntu 20.04+, Debian 11+, Fedora 38+, Arch Linux, DGX OS (x86_64 and ARM64).
59
+ - **macOS:** macOS 11 (Big Sur) and newer (Intel and Apple Silicon M1/M2/M3/M4). Uses native `afplay`.
60
+ - **Windows:** Windows 10 & 11 (64-bit). Uses native PowerShell / System.Media audio backend.
61
+
62
+ ---
63
+
64
+ ## ✨ Features
65
+
66
+ - **100% Sovereign and Offline:** No queries leave your computer. Never sends your text to Microsoft, Google, Bing, or any remote server.
67
+ - **Dual Local Engine Architecture:**
68
+ - **Kokoro-82M:** Deeply expressive, human-like voice with natural breath pacing and emotional cadence.
69
+ - **Piper ONNX:** Blazing fast sub-100ms latency for real-time voice assistants.
70
+ - **Self-Contained Model Downloader:** Run `ollama-speak --setup` to pre-download open weights once, or let it fetch them on first run. Once cached, it runs offline indefinitely.
71
+ - **Zero-Swallow Pipe Echo:** When piping text from any command (`ollama run llama3 "..." | ollama-speak`), it mirrors text directly to your terminal so your transcript remains visible and copy-pasteable.
72
+ - **Zero-Flag Pairing with `ollama-web`:** Simply run `ollama-web "your question"`. The text streams live to your terminal while local voice speaks the answer automatically.
73
+ - **Native Cross-Platform Playback:** Automatically utilizes local audio players (`pw-play`, `paplay`, `mpv`, `ffplay`, `afplay` on macOS, PowerShell on Windows).
74
+
75
+ ---
76
+
77
+ ## 🚀 Installation
78
+
79
+ ### Option 1 (Recommended) — One-Line Turnkey Installer
80
+ Works automatically on fresh Linux, macOS, and WSL systems without requiring manual virtual environments or encountering PEP 668 system python locks.
81
+
82
+ **Linux and macOS:**
83
+ ```bash
84
+ curl -fsSL https://raw.githubusercontent.com/AjarnSpencer/ollama-speak/main/install.sh | bash
85
+ ```
86
+
87
+ **Windows (PowerShell):**
88
+ ```powershell
89
+ irm https://raw.githubusercontent.com/AjarnSpencer/ollama-speak/main/install.ps1 | iex
90
+ ```
91
+
92
+ What the turnkey installer does:
93
+ - Sets up an isolated virtual environment in `~/.local/share/ollama-speak/venv` (zero system pollution).
94
+ - Symlinks `ollama-speak` and `ospeak` launchers into `~/.local/bin/`.
95
+ - Configures user `PATH` if not already set.
96
+ - Automatically pre-downloads local model weights so it works offline immediately.
97
+
98
+ ---
99
+
100
+ ### Option 2 — Install from GitHub with pipx or pip
101
+ If you prefer managing Python CLI tools via `pipx` (recommended by Python Packaging Authority for standalone tools) or an active virtual environment:
102
+
103
+ **Using pipx (Recommended for Python users):**
104
+ ```bash
105
+ pipx install git+https://github.com/AjarnSpencer/ollama-speak.git
106
+ ```
107
+
108
+ **Using pip in a virtual environment:**
109
+ ```bash
110
+ pip install git+https://github.com/AjarnSpencer/ollama-speak.git
111
+ ```
112
+
113
+ ---
114
+
115
+ ### Option 3 — Clone and Install Locally
116
+ ```bash
117
+ git clone https://github.com/AjarnSpencer/ollama-speak.git
118
+ cd ollama-speak
119
+ ./install.sh
120
+ ```
121
+
122
+ ---
123
+
124
+ ### Pre-Download Local Models for 100% Offline Use
125
+ ```bash
126
+ ollama-speak --setup
127
+ ```
128
+ This downloads and caches the Kokoro-82M and Piper open weights into your local user directory. After this step, you can disconnect your computer from the internet completely.
129
+
130
+ ---
131
+
132
+ ### 📦 Publishing to PyPI (For Maintainers)
133
+ To unlock global installation via `pip install ollama-speak` worldwide:
134
+ 1. Build distributions: `python3 -m build` (generates wheels and tarballs in `dist/`).
135
+ 2. Upload to PyPI: `twine upload dist/*` with your PyPI token.
136
+ Once published, users can install directly via `pip install --upgrade ollama-speak`.
137
+
138
+ ---
139
+
140
+ ## 📖 Command Line Reference and Flags
141
+
142
+ `ollama-speak` can be invoked as either `ollama-speak` or `ospeak`.
143
+
144
+ | Flag | Short | Default | Description |
145
+ | :--- | :--- | :--- | :--- |
146
+ | `text` | | STDIN | Text string to speak aloud (reads from STDIN pipe if omitted) |
147
+ | `--engine ENGINE` | `-e` | `kokoro` | Local TTS engine: `kokoro` (studio quality) or `piper` (ultra-fast ONNX) |
148
+ | `--kokoro` | | | Shortcut for `--engine kokoro` |
149
+ | `--piper` | | | Shortcut for `--engine piper` |
150
+ | `--voice VOICE` | `-v` | `bm_george` | Select specific local voice (e.g. `bm_george`, `af_heart`, `bf_alice`) |
151
+ | `--output FILE` | `-o` | `None` | Save spoken audio to a local WAV/MP3 file instead of playing |
152
+ | `--setup` | `--download-models` | | Pre-download all local weights for 100% offline use |
153
+ | `--list-voices` | | | List all available local open-weight voices and aliases |
154
+ | `-h`, `--help` | | | Show full help manual and flags |
155
+
156
+ ---
157
+
158
+ ## 🎙️ Supported Local Open-Weight Voices
159
+
160
+ Run `ollama-speak --list-voices` to see all local voices:
161
+
162
+ | Voice Name | Language | Gender / Style | Engine |
163
+ | :--- | :--- | :--- | :--- |
164
+ | **`bm_george` (Default)** | British English | Male (Warm Baritone) | Kokoro-82M |
165
+ | **`bm_lewis`** | British English | Male | Kokoro-82M |
166
+ | **`bf_alice`** | British English | Female | Kokoro-82M |
167
+ | **`af_heart`** | American English | Female (Warm) | Kokoro-82M |
168
+ | **`am_adam`** | American English | Male | Kokoro-82M |
169
+ | **`jf_tebukuro`** | Japanese | Female | Kokoro-82M |
170
+ | **`zf_xiaobei`** | Mandarin Chinese | Female | Kokoro-82M |
171
+ | **`ef_dora`** | Spanish | Female | Kokoro-82M |
172
+ | **`ff_siwis`** | French | Female | Kokoro-82M |
173
+ | **`if_sara`** | Italian | Female | Kokoro-82M |
174
+ | **`pf_dora`** | Portuguese | Female | Kokoro-82M |
175
+ | **`hf_alpha`** | Hindi | Female | Kokoro-82M |
176
+ | **`piper`** | British English | Clear / Fast ONNX | Piper |
177
+
178
+ ---
179
+
180
+ ## 💡 Practical Tutorials and Workflows
181
+
182
+ ### Tutorial 1 — Direct Local Text Narration
183
+ Speak any text locally using your default Kokoro voice:
184
+ ```bash
185
+ ollama-speak "All local systems are operational and running completely offline."
186
+ ```
187
+
188
+ ### Tutorial 2 — Zero-Swallow Piped Input from Local Ollama
189
+ Pipe output from any local Ollama model directly into `ollama-speak`. The text prints on screen in real-time, and local voice speaks the answer:
190
+ ```bash
191
+ ollama run llama3 "Explain quantum computing in two sentences" | ollama-speak
192
+ ```
193
+
194
+ ### Tutorial 3 — Fast Piper ONNX for Low-Spec PCs
195
+ If you are running on a Raspberry Pi or low-spec laptop, pass `--piper` for ultra-fast, lightweight inference:
196
+ ```bash
197
+ ollama run llama3 "Give me one piece of life advice" | ollama-speak --piper
198
+ ```
199
+
200
+ ### Tutorial 4 — Changing Local Voices
201
+ Choose between different genders, accents, and languages:
202
+ ```bash
203
+ # American Female (Kokoro-82M):
204
+ ollama-speak -v af_heart "Welcome to the future of sovereign, local artificial intelligence."
205
+
206
+ # British Female (Kokoro-82M):
207
+ ollama-speak -v bf_alice "It is a pleasure to assist you today."
208
+ ```
209
+
210
+ ### Tutorial 5 — Exporting Audio to File
211
+ Save the synthesized speech directly to a standard audio file:
212
+ ```bash
213
+ ollama-speak -o ~/Music/summary.wav "Your daily briefing has been saved locally."
214
+ ```
215
+
216
+ ---
217
+
218
+ ## 🔒 100% Offline and Sovereign Guarantee
219
+
220
+ - **No Remote Servers:** Text is synthesized entirely inside your computer's RAM using open-weights models.
221
+ - **No Cloud Fallbacks:** It will never silently phone home to Microsoft, Bing, Google, or any cloud API.
222
+ - **No API Keys or Subscriptions:** You own your hardware, and you own your voice assistant.
223
+
224
+ ---
225
+
226
+ ## 📄 License
227
+
228
+ This project is licensed under the [MIT License](LICENSE).
229
+ Kokoro-82M model weights are released under the [Apache 2.0 License](https://huggingface.co/hexgrad/Kokoro-82M).
230
+ Piper TTS is released under the [MIT License](https://github.com/rhasspy/piper).
@@ -0,0 +1,208 @@
1
+ # 🗣️ ollama-speak (`ospeak`)
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/)
5
+ [![Ollama](https://img.shields.io/badge/Ollama-Local%20AI-orange.svg)](https://ollama.ai/)
6
+ [![100% Local AI](https://img.shields.io/badge/Privacy-100%25%20Offline%20Local-success.svg)](#100-offline-and-sovereign-guarantee)
7
+
8
+ > **100% Local, Offline Multilingual Neural Text-to-Speech CLI for Ollama (Kokoro-82M and Piper ONNX).**
9
+
10
+ `ollama-speak` (`ospeak`) is a standalone, sovereign voice engine built specifically for the local AI and Ollama community. It runs **100% locally on your own machine** using open-weights models:
11
+
12
+ - **Kokoro-82M (Default):** Apache 2.0 open-weight 82M-parameter neural model providing studio-grade broadcast speech.
13
+ - **Piper TTS (Ultra-lightweight):** Fast ONNX neural voice engine designed to run with sub-100ms latency even on low-spec hardware or a Raspberry Pi.
14
+
15
+ Zero cloud API calls, zero telemetry, zero corporate tracking, and zero accounts. Once local weights are downloaded, `ollama-speak` works completely with your internet disconnected.
16
+
17
+ ---
18
+
19
+ ## 💻 Minimum System and Hardware Requirements
20
+
21
+ `ollama-speak` is designed to run smoothly across consumer laptops, older desktop PCs, MacBooks, and high-performance workstations.
22
+
23
+ ### Hardware Requirements Table
24
+
25
+ | Component | Piper ONNX (Ultra-Lightweight) | Kokoro-82M (Studio Quality Default) |
26
+ | :--- | :--- | :--- |
27
+ | **CPU** | Any 64-bit CPU (Intel, AMD, ARM64 / Apple Silicon, Raspberry Pi 4/5) | Modern multi-core CPU (AVX2 recommended) or Apple Silicon M1–M4 |
28
+ | **GPU (Optional)** | Not needed (pure CPU ONNX) | Accelerated on CUDA / ROCm / Metal, runs easily on CPU |
29
+ | **System RAM** | **1 GB minimum** (uses ~80 MB during inference) | **2 GB to 4 GB RAM** (uses ~350 MB during inference) |
30
+ | **Disk Space** | **~35 MB** for voice model | **~350 MB** for open-weight model checkpoint |
31
+ | **Audio System** | Standard OS sound (ALSA, PulseAudio, PipeWire, CoreAudio, WASAPI) | Standard OS sound |
32
+ | **Internet** | Only required once for initial model download | Only required once for initial model download |
33
+
34
+ ### Supported Operating Systems
35
+
36
+ - **Linux:** Ubuntu 20.04+, Debian 11+, Fedora 38+, Arch Linux, DGX OS (x86_64 and ARM64).
37
+ - **macOS:** macOS 11 (Big Sur) and newer (Intel and Apple Silicon M1/M2/M3/M4). Uses native `afplay`.
38
+ - **Windows:** Windows 10 & 11 (64-bit). Uses native PowerShell / System.Media audio backend.
39
+
40
+ ---
41
+
42
+ ## ✨ Features
43
+
44
+ - **100% Sovereign and Offline:** No queries leave your computer. Never sends your text to Microsoft, Google, Bing, or any remote server.
45
+ - **Dual Local Engine Architecture:**
46
+ - **Kokoro-82M:** Deeply expressive, human-like voice with natural breath pacing and emotional cadence.
47
+ - **Piper ONNX:** Blazing fast sub-100ms latency for real-time voice assistants.
48
+ - **Self-Contained Model Downloader:** Run `ollama-speak --setup` to pre-download open weights once, or let it fetch them on first run. Once cached, it runs offline indefinitely.
49
+ - **Zero-Swallow Pipe Echo:** When piping text from any command (`ollama run llama3 "..." | ollama-speak`), it mirrors text directly to your terminal so your transcript remains visible and copy-pasteable.
50
+ - **Zero-Flag Pairing with `ollama-web`:** Simply run `ollama-web "your question"`. The text streams live to your terminal while local voice speaks the answer automatically.
51
+ - **Native Cross-Platform Playback:** Automatically utilizes local audio players (`pw-play`, `paplay`, `mpv`, `ffplay`, `afplay` on macOS, PowerShell on Windows).
52
+
53
+ ---
54
+
55
+ ## 🚀 Installation
56
+
57
+ ### Option 1 (Recommended) — One-Line Turnkey Installer
58
+ Works automatically on fresh Linux, macOS, and WSL systems without requiring manual virtual environments or encountering PEP 668 system python locks.
59
+
60
+ **Linux and macOS:**
61
+ ```bash
62
+ curl -fsSL https://raw.githubusercontent.com/AjarnSpencer/ollama-speak/main/install.sh | bash
63
+ ```
64
+
65
+ **Windows (PowerShell):**
66
+ ```powershell
67
+ irm https://raw.githubusercontent.com/AjarnSpencer/ollama-speak/main/install.ps1 | iex
68
+ ```
69
+
70
+ What the turnkey installer does:
71
+ - Sets up an isolated virtual environment in `~/.local/share/ollama-speak/venv` (zero system pollution).
72
+ - Symlinks `ollama-speak` and `ospeak` launchers into `~/.local/bin/`.
73
+ - Configures user `PATH` if not already set.
74
+ - Automatically pre-downloads local model weights so it works offline immediately.
75
+
76
+ ---
77
+
78
+ ### Option 2 — Install from GitHub with pipx or pip
79
+ If you prefer managing Python CLI tools via `pipx` (recommended by Python Packaging Authority for standalone tools) or an active virtual environment:
80
+
81
+ **Using pipx (Recommended for Python users):**
82
+ ```bash
83
+ pipx install git+https://github.com/AjarnSpencer/ollama-speak.git
84
+ ```
85
+
86
+ **Using pip in a virtual environment:**
87
+ ```bash
88
+ pip install git+https://github.com/AjarnSpencer/ollama-speak.git
89
+ ```
90
+
91
+ ---
92
+
93
+ ### Option 3 — Clone and Install Locally
94
+ ```bash
95
+ git clone https://github.com/AjarnSpencer/ollama-speak.git
96
+ cd ollama-speak
97
+ ./install.sh
98
+ ```
99
+
100
+ ---
101
+
102
+ ### Pre-Download Local Models for 100% Offline Use
103
+ ```bash
104
+ ollama-speak --setup
105
+ ```
106
+ This downloads and caches the Kokoro-82M and Piper open weights into your local user directory. After this step, you can disconnect your computer from the internet completely.
107
+
108
+ ---
109
+
110
+ ### 📦 Publishing to PyPI (For Maintainers)
111
+ To unlock global installation via `pip install ollama-speak` worldwide:
112
+ 1. Build distributions: `python3 -m build` (generates wheels and tarballs in `dist/`).
113
+ 2. Upload to PyPI: `twine upload dist/*` with your PyPI token.
114
+ Once published, users can install directly via `pip install --upgrade ollama-speak`.
115
+
116
+ ---
117
+
118
+ ## 📖 Command Line Reference and Flags
119
+
120
+ `ollama-speak` can be invoked as either `ollama-speak` or `ospeak`.
121
+
122
+ | Flag | Short | Default | Description |
123
+ | :--- | :--- | :--- | :--- |
124
+ | `text` | | STDIN | Text string to speak aloud (reads from STDIN pipe if omitted) |
125
+ | `--engine ENGINE` | `-e` | `kokoro` | Local TTS engine: `kokoro` (studio quality) or `piper` (ultra-fast ONNX) |
126
+ | `--kokoro` | | | Shortcut for `--engine kokoro` |
127
+ | `--piper` | | | Shortcut for `--engine piper` |
128
+ | `--voice VOICE` | `-v` | `bm_george` | Select specific local voice (e.g. `bm_george`, `af_heart`, `bf_alice`) |
129
+ | `--output FILE` | `-o` | `None` | Save spoken audio to a local WAV/MP3 file instead of playing |
130
+ | `--setup` | `--download-models` | | Pre-download all local weights for 100% offline use |
131
+ | `--list-voices` | | | List all available local open-weight voices and aliases |
132
+ | `-h`, `--help` | | | Show full help manual and flags |
133
+
134
+ ---
135
+
136
+ ## 🎙️ Supported Local Open-Weight Voices
137
+
138
+ Run `ollama-speak --list-voices` to see all local voices:
139
+
140
+ | Voice Name | Language | Gender / Style | Engine |
141
+ | :--- | :--- | :--- | :--- |
142
+ | **`bm_george` (Default)** | British English | Male (Warm Baritone) | Kokoro-82M |
143
+ | **`bm_lewis`** | British English | Male | Kokoro-82M |
144
+ | **`bf_alice`** | British English | Female | Kokoro-82M |
145
+ | **`af_heart`** | American English | Female (Warm) | Kokoro-82M |
146
+ | **`am_adam`** | American English | Male | Kokoro-82M |
147
+ | **`jf_tebukuro`** | Japanese | Female | Kokoro-82M |
148
+ | **`zf_xiaobei`** | Mandarin Chinese | Female | Kokoro-82M |
149
+ | **`ef_dora`** | Spanish | Female | Kokoro-82M |
150
+ | **`ff_siwis`** | French | Female | Kokoro-82M |
151
+ | **`if_sara`** | Italian | Female | Kokoro-82M |
152
+ | **`pf_dora`** | Portuguese | Female | Kokoro-82M |
153
+ | **`hf_alpha`** | Hindi | Female | Kokoro-82M |
154
+ | **`piper`** | British English | Clear / Fast ONNX | Piper |
155
+
156
+ ---
157
+
158
+ ## 💡 Practical Tutorials and Workflows
159
+
160
+ ### Tutorial 1 — Direct Local Text Narration
161
+ Speak any text locally using your default Kokoro voice:
162
+ ```bash
163
+ ollama-speak "All local systems are operational and running completely offline."
164
+ ```
165
+
166
+ ### Tutorial 2 — Zero-Swallow Piped Input from Local Ollama
167
+ Pipe output from any local Ollama model directly into `ollama-speak`. The text prints on screen in real-time, and local voice speaks the answer:
168
+ ```bash
169
+ ollama run llama3 "Explain quantum computing in two sentences" | ollama-speak
170
+ ```
171
+
172
+ ### Tutorial 3 — Fast Piper ONNX for Low-Spec PCs
173
+ If you are running on a Raspberry Pi or low-spec laptop, pass `--piper` for ultra-fast, lightweight inference:
174
+ ```bash
175
+ ollama run llama3 "Give me one piece of life advice" | ollama-speak --piper
176
+ ```
177
+
178
+ ### Tutorial 4 — Changing Local Voices
179
+ Choose between different genders, accents, and languages:
180
+ ```bash
181
+ # American Female (Kokoro-82M):
182
+ ollama-speak -v af_heart "Welcome to the future of sovereign, local artificial intelligence."
183
+
184
+ # British Female (Kokoro-82M):
185
+ ollama-speak -v bf_alice "It is a pleasure to assist you today."
186
+ ```
187
+
188
+ ### Tutorial 5 — Exporting Audio to File
189
+ Save the synthesized speech directly to a standard audio file:
190
+ ```bash
191
+ ollama-speak -o ~/Music/summary.wav "Your daily briefing has been saved locally."
192
+ ```
193
+
194
+ ---
195
+
196
+ ## 🔒 100% Offline and Sovereign Guarantee
197
+
198
+ - **No Remote Servers:** Text is synthesized entirely inside your computer's RAM using open-weights models.
199
+ - **No Cloud Fallbacks:** It will never silently phone home to Microsoft, Bing, Google, or any cloud API.
200
+ - **No API Keys or Subscriptions:** You own your hardware, and you own your voice assistant.
201
+
202
+ ---
203
+
204
+ ## 📄 License
205
+
206
+ This project is licensed under the [MIT License](LICENSE).
207
+ Kokoro-82M model weights are released under the [Apache 2.0 License](https://huggingface.co/hexgrad/Kokoro-82M).
208
+ Piper TTS is released under the [MIT License](https://github.com/rhasspy/piper).
@@ -0,0 +1,65 @@
1
+ # ollama-speak turnkey isolated installer for Windows PowerShell
2
+ # 100% Local Neural Voice CLI (Kokoro-82M and Piper ONNX)
3
+ $ErrorActionPreference = "Stop"
4
+
5
+ Write-Host "==================================================" -ForegroundColor Cyan
6
+ Write-Host " 🗣️ Installing ollama-speak for Windows (Isolated)" -ForegroundColor Cyan
7
+ Write-Host "==================================================" -ForegroundColor Cyan
8
+
9
+ # 1. Detect Python
10
+ $pythonCmd = Get-Command python -ErrorAction SilentlyContinue
11
+ if (-not $pythonCmd) {
12
+ $pythonCmd = Get-Command py -ErrorAction SilentlyContinue
13
+ }
14
+
15
+ if (-not $pythonCmd) {
16
+ Write-Host "[!] Error: Python 3.9+ was not found on your system PATH." -ForegroundColor Red
17
+ Write-Host "Please install Python from https://www.python.org/ (ensure 'Add Python to PATH' is checked)." -ForegroundColor Yellow
18
+ exit 1
19
+ }
20
+
21
+ $InstallDir = Join-Path $env:LOCALAPPDATA "ollama-speak"
22
+ $VenvDir = Join-Path $InstallDir "venv"
23
+ $VenvPython = Join-Path $VenvDir "Scripts\python.exe"
24
+ $VenvPip = Join-Path $VenvDir "Scripts\pip.exe"
25
+ $VenvScripts = Join-Path $VenvDir "Scripts"
26
+
27
+ # 2. Create isolated venv
28
+ Write-Host "→ Creating isolated virtual environment in $VenvDir..." -ForegroundColor Green
29
+ if (-not (Test-Path $VenvDir)) {
30
+ & $pythonCmd.Source -m venv $VenvDir
31
+ }
32
+
33
+ # 3. Upgrade pip and install package
34
+ Write-Host "→ Installing dependencies in isolated environment..." -ForegroundColor Green
35
+ & $VenvPip install --quiet --upgrade pip wheel setuptools
36
+
37
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
38
+ if (Test-Path (Join-Path $ScriptDir "pyproject.toml")) {
39
+ Write-Host "→ Installing from local directory ($ScriptDir)..." -ForegroundColor Green
40
+ & $VenvPip install --upgrade $ScriptDir
41
+ } else {
42
+ Write-Host "→ Installing from GitHub..." -ForegroundColor Green
43
+ & $VenvPip install --upgrade "git+https://github.com/AjarnSpencer/ollama-speak.git"
44
+ }
45
+
46
+ # 4. Add Scripts to User PATH
47
+ $UserPath = [Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::User)
48
+ if ($UserPath -notlike "*$VenvScripts*") {
49
+ Write-Host "→ Adding $VenvScripts to User PATH..." -ForegroundColor Green
50
+ [Environment]::SetEnvironmentVariable("Path", "$UserPath;$VenvScripts", [EnvironmentVariableTarget]::User)
51
+ $env:Path = "$env:Path;$VenvScripts"
52
+ }
53
+
54
+ # 5. Pre-download open weights
55
+ $OllamaSpeakExe = Join-Path $VenvScripts "ollama-speak.exe"
56
+ if (Test-Path $OllamaSpeakExe) {
57
+ Write-Host "→ Pre-downloading open weights for 100% offline use..." -ForegroundColor Green
58
+ & $OllamaSpeakExe --setup
59
+ }
60
+
61
+ Write-Host "==================================================" -ForegroundColor Cyan
62
+ Write-Host " ✓ Installation complete!" -ForegroundColor Green
63
+ Write-Host " Run: 'ollama-speak ""Hello from local voice""'" -ForegroundColor White
64
+ Write-Host " Or shortcut: 'ospeak ""Fast voice""'" -ForegroundColor White
65
+ Write-Host "==================================================" -ForegroundColor Cyan
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env bash
2
+ # ollama-speak turnkey isolated installer for Linux and macOS
3
+ # 100% Local Neural Voice CLI (Kokoro-82M and Piper ONNX)
4
+ set -e
5
+
6
+ APP_NAME="ollama-speak"
7
+ INSTALL_DIR="${HOME}/.local/share/${APP_NAME}"
8
+ BIN_DIR="${HOME}/.local/bin"
9
+ REPO_URL="https://github.com/AjarnSpencer/ollama-speak.git"
10
+
11
+ echo "=================================================="
12
+ echo " 🗣️ Installing ${APP_NAME} (Local Neural Voice CLI)"
13
+ echo "=================================================="
14
+
15
+ # 1. Detect Python 3.9+
16
+ if command -v python3 >/dev/null 2>&1; then
17
+ PY_SYSTEM="python3"
18
+ elif command -v python >/dev/null 2>&1; then
19
+ PY_SYSTEM="python"
20
+ else
21
+ echo "❌ Error: Python 3.9+ is required but not installed." >&2
22
+ echo " Ubuntu/Debian: sudo apt update && sudo apt install -y python3 python3-venv python3-pip" >&2
23
+ echo " Fedora/RHEL: sudo dnf install -y python3" >&2
24
+ echo " macOS: brew install python" >&2
25
+ exit 1
26
+ fi
27
+
28
+ # Verify Python version >= 3.9
29
+ PY_VER=$($PY_SYSTEM -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
30
+ PY_MAJOR=$($PY_SYSTEM -c "import sys; print(sys.version_info.major)")
31
+ PY_MINOR=$($PY_SYSTEM -c "import sys; print(sys.version_info.minor)")
32
+ if [ "$PY_MAJOR" -lt 3 ] || ([ "$PY_MAJOR" -eq 3 ] && [ "$PY_MINOR" -lt 9 ]); then
33
+ echo "❌ Error: Python 3.9+ required. Found Python ${PY_VER}." >&2
34
+ exit 1
35
+ fi
36
+ echo "✓ Detected Python ${PY_VER} (${PY_SYSTEM})"
37
+
38
+ # 2. Check for python3-venv support
39
+ if ! $PY_SYSTEM -m venv --help >/dev/null 2>&1; then
40
+ echo "❌ Error: The 'venv' module is missing from your Python installation." >&2
41
+ echo " On Ubuntu/Debian, run: sudo apt install -y python3-venv" >&2
42
+ exit 1
43
+ fi
44
+
45
+ # 3. Create dedicated isolated virtual environment (bypasses PEP 668 cleanly)
46
+ echo "→ Setting up isolated virtual environment in ${INSTALL_DIR}/venv..."
47
+ mkdir -p "${INSTALL_DIR}"
48
+ $PY_SYSTEM -m venv "${INSTALL_DIR}/venv"
49
+ VENV_PY="${INSTALL_DIR}/venv/bin/python"
50
+ VENV_PIP="${INSTALL_DIR}/venv/bin/pip"
51
+
52
+ # Upgrade pip and wheel in isolated venv
53
+ "${VENV_PIP}" install --quiet --upgrade pip wheel setuptools
54
+
55
+ # 4. Install the package
56
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
57
+ if [ -f "${SCRIPT_DIR}/pyproject.toml" ]; then
58
+ echo "→ Installing from local source (${SCRIPT_DIR})..."
59
+ "${VENV_PIP}" install --upgrade "${SCRIPT_DIR}"
60
+ else
61
+ echo "→ Installing latest release from GitHub (${REPO_URL})..."
62
+ "${VENV_PIP}" install --upgrade "git+${REPO_URL}"
63
+ fi
64
+
65
+ # 5. Create symlinks in ~/.local/bin
66
+ mkdir -p "${BIN_DIR}"
67
+ ln -sf "${INSTALL_DIR}/venv/bin/ollama-speak" "${BIN_DIR}/ollama-speak"
68
+ ln -sf "${INSTALL_DIR}/venv/bin/ospeak" "${BIN_DIR}/ospeak"
69
+ echo "✓ Created launcher symlinks in ${BIN_DIR}"
70
+
71
+ # 6. Ensure ~/.local/bin is on PATH
72
+ PATH_CONFIGURED=false
73
+ case ":$PATH:" in
74
+ *":${BIN_DIR}:"*) PATH_CONFIGURED=true ;;
75
+ esac
76
+
77
+ if [ "$PATH_CONFIGURED" = false ]; then
78
+ echo "→ Adding ${BIN_DIR} to your shell profile..."
79
+ SHELL_NAME=$(basename "${SHELL:-bash}")
80
+ RC_FILE=""
81
+ if [ "$SHELL_NAME" = "zsh" ]; then
82
+ RC_FILE="${HOME}/.zshrc"
83
+ elif [ "$SHELL_NAME" = "bash" ]; then
84
+ if [ -f "${HOME}/.bashrc" ]; then
85
+ RC_FILE="${HOME}/.bashrc"
86
+ elif [ -f "${HOME}/.bash_profile" ]; then
87
+ RC_FILE="${HOME}/.bash_profile"
88
+ fi
89
+ fi
90
+ if [ -n "$RC_FILE" ] && [ -w "$RC_FILE" ]; then
91
+ if ! grep -q ".local/bin" "$RC_FILE"; then
92
+ echo '' >> "$RC_FILE"
93
+ echo '# Added by ollama-speak installer' >> "$RC_FILE"
94
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$RC_FILE"
95
+ echo "✓ Added PATH export to ${RC_FILE}"
96
+ fi
97
+ fi
98
+ fi
99
+
100
+ # 7. Pre-download open weights for 100% offline use
101
+ echo "→ Pre-downloading open weights for 100% offline use..."
102
+ "${BIN_DIR}/ollama-speak" --setup || true
103
+
104
+ echo "=================================================="
105
+ echo " 🎉 Installation Complete!"
106
+ echo "=================================================="
107
+ echo " You can now run:"
108
+ echo " ollama-speak \"Hello from local voice\""
109
+ echo " ospeak \"Fast shortcut\""
110
+ echo " Or pipe directly from Ollama:"
111
+ echo " ollama run llama3 \"Tell me a one-liner\" | ollama-speak"
112
+ if [ "$PATH_CONFIGURED" = false ]; then
113
+ echo ""
114
+ echo " ⚠️ Note: If 'ollama-speak' is not recognized immediately in your current terminal,"
115
+ echo " run: export PATH=\"\$HOME/.local/bin:\$PATH\""
116
+ echo " or restart your terminal."
117
+ fi
118
+ echo "=================================================="
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ollama-speak"
7
+ version = "1.1.0"
8
+ description = "100% Local, Offline Multilingual Neural Text-to-Speech CLI for Ollama (Kokoro-82M & Piper)."
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Ajarn Spencer Littlewood", email = "ajarnspencer@gmail.com" }
12
+ ]
13
+ license = { text = "MIT" }
14
+ requires-python = ">=3.9"
15
+ dependencies = [
16
+ "kokoro>=0.8.4",
17
+ "soundfile>=0.12.0",
18
+ "numpy>=1.21.0",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ piper = [
23
+ "piper-tts>=1.2.0",
24
+ ]
25
+ all = [
26
+ "kokoro>=0.8.4",
27
+ "piper-tts>=1.2.0",
28
+ "soundfile>=0.12.0",
29
+ ]
30
+
31
+ [project.scripts]
32
+ ollama-speak = "ollama_speak.cli:main"
33
+ ospeak = "ollama_speak.cli:main"
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/AjarnSpencer/ollama-speak"
37
+ Repository = "https://github.com/AjarnSpencer/ollama-speak"
38
+ Issues = "https://github.com/AjarnSpencer/ollama-speak/issues"
39
+
40
+ [tool.hatch.build.targets.wheel]
41
+ packages = ["src/ollama_speak"]
@@ -0,0 +1,4 @@
1
+ kokoro>=0.8.4
2
+ soundfile>=0.12.0
3
+ numpy>=1.21.0
4
+ piper-tts>=1.2.0
@@ -0,0 +1,5 @@
1
+ """
2
+ ollama-speak — Universal multilingual neural text-to-speech CLI and pipe companion for Ollama models.
3
+ """
4
+
5
+ __version__ = "1.0.0"
@@ -0,0 +1,4 @@
1
+ from ollama_speak.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,100 @@
1
+ """
2
+ Command-line interface for ollama-speak.
3
+ 100% Local, Offline Multilingual Neural Text-to-Speech CLI for Ollama (Kokoro-82M & Piper).
4
+ Accepts text from arguments or STDIN pipes (e.g. `ollama-web "query" | ollama-speak`).
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import argparse
10
+ from .voice import (
11
+ synthesize_and_speak,
12
+ download_all_models,
13
+ KOKORO_VOICES,
14
+ DEFAULT_KOKORO_VOICE
15
+ )
16
+
17
+
18
+ def print_voices():
19
+ print("==================================================================")
20
+ print(" 🗣️ ollama-speak 100% Local Neural Voices (Kokoro-82M & Piper)")
21
+ print("==================================================================")
22
+ print(f"\nDefault Local Voice: {DEFAULT_KOKORO_VOICE} (Warm British Baritone, Kokoro-82M)")
23
+ print("\nSupported Local Open-Weight Voices:")
24
+ print("-" * 66)
25
+ print(f" {'ALIAS / NAME':<18} {'LANGUAGE':<18} {'GENDER / STYLE'}")
26
+ print("-" * 66)
27
+ print(f" {'bm_george (default)':<18} {'British English':<18} {'Male (Warm Baritone)'}")
28
+ print(f" {'bm_lewis':<18} {'British English':<18} {'Male'}")
29
+ print(f" {'bf_alice':<18} {'British English':<18} {'Female'}")
30
+ print(f" {'af_heart':<18} {'American English':<18} {'Female (Warm)'}")
31
+ print(f" {'am_adam':<18} {'American English':<18} {'Male'}")
32
+ print(f" {'jf_tebukuro':<18} {'Japanese':<18} {'Female'}")
33
+ print(f" {'zf_xiaobei':<18} {'Mandarin Chinese':<18} {'Female'}")
34
+ print(f" {'ef_dora':<18} {'Spanish':<18} {'Female'}")
35
+ print(f" {'ff_siwis':<18} {'French':<18} {'Female'}")
36
+ print(f" {'if_sara':<18} {'Italian':<18} {'Female'}")
37
+ print(f" {'pf_dora':<18} {'Portuguese':<18} {'Female'}")
38
+ print(f" {'hf_alpha':<18} {'Hindi':<18} {'Female'}")
39
+ print(f" {'piper (engine)':<18} {'British / English':<18} {'Ultra-lightweight ONNX'}")
40
+ print("\nAll models run 100% locally and offline on your CPU or GPU.")
41
+
42
+
43
+ def main():
44
+ parser = argparse.ArgumentParser(
45
+ prog="ollama-speak",
46
+ description="100%% Local, Offline Multilingual Neural Text-to-Speech CLI for Ollama (Kokoro-82M and Piper)."
47
+ )
48
+ parser.add_argument("text", nargs="*", help="Text to speak (reads from STDIN if omitted)")
49
+ parser.add_argument("-v", "--voice", default=None, help="Local voice name (e.g. bm_george, af_heart, bf_alice)")
50
+ parser.add_argument("-e", "--engine", choices=["kokoro", "piper"], default="kokoro", help="TTS engine: kokoro (default, studio quality) or piper (ultra-fast ONNX)")
51
+ parser.add_argument("--piper", action="store_true", help="Shortcut for --engine piper (ultra-lightweight local ONNX)")
52
+ parser.add_argument("--kokoro", action="store_true", help="Shortcut for --engine kokoro (studio quality open-weights)")
53
+ parser.add_argument("-o", "--output", default=None, help="Save audio to specified WAV/MP3 file instead of playing")
54
+ parser.add_argument("--setup", "--download-models", action="store_true", help="Pre-download local model weights for 100%% offline use")
55
+ parser.add_argument("--list-voices", action="store_true", help="List available local open-weight voices")
56
+
57
+ args = parser.parse_args()
58
+
59
+ if args.setup:
60
+ download_all_models()
61
+ return
62
+
63
+ if args.list_voices:
64
+ print_voices()
65
+ return
66
+
67
+ engine = "piper" if args.piper else ("kokoro" if args.kokoro else args.engine)
68
+
69
+ # 1. Check positional arguments
70
+ if args.text:
71
+ content = " ".join(args.text).strip()
72
+ # 2. Check STDIN pipe (e.g. ollama-web ... | ollama-speak)
73
+ elif not sys.stdin.isatty():
74
+ content = sys.stdin.read().strip()
75
+ # Always echo piped text to stdout so the human sees, reads, and can copy-paste it!
76
+ if content:
77
+ print(content, flush=True)
78
+ else:
79
+ parser.print_help()
80
+ sys.exit(0)
81
+
82
+ if not content:
83
+ sys.exit(0)
84
+
85
+ output_path = os.path.expanduser(args.output) if args.output else None
86
+
87
+ # Synthesize and speak using 100% local model
88
+ success = synthesize_and_speak(
89
+ text=content,
90
+ voice=args.voice,
91
+ engine=engine,
92
+ output_file=output_path
93
+ )
94
+
95
+ if output_path and success:
96
+ print(f"✓ Saved audio to: {output_path}")
97
+
98
+
99
+ if __name__ == "__main__":
100
+ main()
@@ -0,0 +1,371 @@
1
+ """
2
+ Core local voice engine for ollama-speak.
3
+ 100% Local, Offline, Private Neural Text-to-Speech using Kokoro-82M and Piper ONNX.
4
+ Zero cloud APIs, zero big-tech telemetry, zero external data leaks.
5
+ """
6
+
7
+ import os
8
+ import re
9
+ import sys
10
+ import time
11
+ import socket
12
+ import shutil
13
+ import urllib.request
14
+ import tempfile
15
+ import subprocess
16
+ from pathlib import Path
17
+
18
+ # Common Kokoro-82M open-weight voices
19
+ KOKORO_VOICES = {
20
+ # British English (b)
21
+ "en-gb": ("bm_george", "b"),
22
+ "british": ("bm_george", "b"),
23
+ "bm_george": ("bm_george", "b"),
24
+ "bm_lewis": ("bm_lewis", "b"),
25
+ "bm_daniel": ("bm_daniel", "b"),
26
+ "bm_fable": ("bm_fable", "b"),
27
+ "bf_alice": ("bf_alice", "b"),
28
+ "bf_isabella": ("bf_isabella", "b"),
29
+ "bf_emma": ("bf_emma", "b"),
30
+ "bf_lily": ("bf_lily", "b"),
31
+
32
+ # American English (a)
33
+ "en-us": ("af_heart", "a"),
34
+ "american": ("af_heart", "a"),
35
+ "af_heart": ("af_heart", "a"),
36
+ "af_bella": ("af_bella", "a"),
37
+ "af_sarah": ("af_sarah", "a"),
38
+ "af_nicole": ("af_nicole", "a"),
39
+ "af_sky": ("af_sky", "a"),
40
+ "am_adam": ("am_adam", "a"),
41
+ "am_michael": ("am_michael", "a"),
42
+ "am_echo": ("am_echo", "a"),
43
+ "am_eric": ("am_eric", "a"),
44
+ "am_onyx": ("am_onyx", "a"),
45
+
46
+ # Japanese (j)
47
+ "ja": ("jf_tebukuro", "j"),
48
+ "japanese": ("jf_tebukuro", "j"),
49
+ "jf_tebukuro": ("jf_tebukuro", "j"),
50
+ "jm_kumo": ("jm_kumo", "j"),
51
+
52
+ # Mandarin Chinese (z)
53
+ "zh": ("zf_xiaobei", "z"),
54
+ "chinese": ("zf_xiaobei", "z"),
55
+ "zf_xiaobei": ("zf_xiaobei", "z"),
56
+ "zm_yunjian": ("zm_yunjian", "z"),
57
+
58
+ # Spanish (e)
59
+ "es": ("ef_dora", "e"),
60
+ "spanish": ("ef_dora", "e"),
61
+ "ef_dora": ("ef_dora", "e"),
62
+ "em_alex": ("em_alex", "e"),
63
+
64
+ # French (f)
65
+ "fr": ("ff_siwis", "f"),
66
+ "french": ("ff_siwis", "f"),
67
+ "ff_siwis": ("ff_siwis", "f"),
68
+
69
+ # Italian (i)
70
+ "it": ("if_sara", "i"),
71
+ "italian": ("if_sara", "i"),
72
+ "if_sara": ("if_sara", "i"),
73
+ "im_nicola": ("im_nicola", "i"),
74
+
75
+ # Portuguese (p)
76
+ "pt": ("pf_dora", "p"),
77
+ "portuguese": ("pf_dora", "p"),
78
+ "pf_dora": ("pf_dora", "p"),
79
+ "pm_alex": ("pm_alex", "p"),
80
+
81
+ # Hindi (h)
82
+ "hi": ("hf_alpha", "h"),
83
+ "hindi": ("hf_alpha", "h"),
84
+ "hf_alpha": ("hf_alpha", "h"),
85
+ "hm_psi": ("hm_psi", "h"),
86
+ }
87
+
88
+ DEFAULT_KOKORO_VOICE = "bm_george"
89
+ DEFAULT_KOKORO_LANG = "b"
90
+
91
+
92
+ def get_models_dir() -> Path:
93
+ """Returns local model cache directory (~/.local/share/ollama-speak/models)."""
94
+ base = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share"))
95
+ d = base / "ollama-speak" / "models"
96
+ d.mkdir(parents=True, exist_ok=True)
97
+ return d
98
+
99
+
100
+ def find_piper_model() -> tuple[str, str]:
101
+ """Finds or returns expected path for local Piper ONNX model and config."""
102
+ # 1. Custom env var
103
+ custom = os.environ.get("PIPER_MODEL")
104
+ if custom and os.path.exists(custom):
105
+ json_path = custom + ".json" if not custom.endswith(".json") else custom
106
+ onnx_path = custom.replace(".json", "")
107
+ return onnx_path, json_path
108
+
109
+ # 2. DGX / Host workspace path
110
+ workspace_piper = Path.home() / "ai-workspace" / "models" / "tts" / "piper" / "en_GB-alan-medium.onnx"
111
+ if workspace_piper.exists():
112
+ return str(workspace_piper), str(workspace_piper) + ".json"
113
+
114
+ # 3. Standard user data dir
115
+ user_piper = get_models_dir() / "piper" / "en_GB-alan-medium.onnx"
116
+ return str(user_piper), str(user_piper) + ".json"
117
+
118
+
119
+ def play_audio_file(file_path: str) -> bool:
120
+ """Plays an audio file (.wav or .mp3) natively across Linux, macOS, and Windows."""
121
+ if not os.path.exists(file_path) or os.path.getsize(file_path) < 100:
122
+ return False
123
+
124
+ # macOS: native afplay
125
+ if sys.platform == "darwin":
126
+ if shutil.which("afplay"):
127
+ res = subprocess.run(["afplay", file_path], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
128
+ return res.returncode == 0
129
+
130
+ # Windows: native PowerShell
131
+ if sys.platform == "win32":
132
+ abs_p = os.path.abspath(file_path).replace("\\", "\\\\")
133
+ ps_cmd = (
134
+ f"$player = New-Object System.Media.SoundPlayer('{abs_p}'); "
135
+ f"if ($player) {{ $player.PlaySync() }}"
136
+ )
137
+ res = subprocess.run(["powershell", "-NoProfile", "-Command", ps_cmd], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
138
+ return res.returncode == 0
139
+
140
+ # Linux: check audio players
141
+ for cmd in ["pw-play", "paplay", "mpv", "ffplay", "aplay"]:
142
+ if shutil.which(cmd):
143
+ if cmd == "mpv":
144
+ res = subprocess.run(["mpv", "--no-video", "--really-quiet", file_path], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
145
+ elif cmd == "ffplay":
146
+ res = subprocess.run(["ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", file_path], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
147
+ elif cmd in ("pw-play", "paplay"):
148
+ res = subprocess.run([cmd, file_path], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
149
+ elif cmd == "aplay" and file_path.endswith(".wav"):
150
+ res = subprocess.run(["aplay", "-q", file_path], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
151
+ else:
152
+ continue
153
+
154
+ if res.returncode == 0:
155
+ return True
156
+
157
+ return False
158
+
159
+
160
+ def try_local_daemon(text: str, engine: str = "kokoro") -> bool:
161
+ """
162
+ Checks if a local sovereign voice daemon is already active on localhost.
163
+ Provides sub-50ms warm inference without reloading weights into memory.
164
+ """
165
+ uid = os.getuid() if hasattr(os, "getuid") else 1000
166
+ socket_path = f"/run/user/{uid}/voice_dictate.sock"
167
+ if not os.path.exists(socket_path):
168
+ return False
169
+
170
+ cmd = f"SPEAK_KOKORO:{text}\n" if engine == "kokoro" else f"SPEAK:{text}\n"
171
+ try:
172
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
173
+ s.settimeout(10.0)
174
+ s.connect(socket_path)
175
+ s.sendall(cmd.encode("utf-8"))
176
+ resp = s.recv(1024).decode("utf-8", errors="replace")
177
+ s.close()
178
+ return "OK:" in resp
179
+ except Exception:
180
+ return False
181
+
182
+
183
+ def synthesize_with_kokoro(
184
+ text: str,
185
+ voice_name: str = None,
186
+ speed: float = 1.0,
187
+ output_path: str = None
188
+ ) -> bool:
189
+ """
190
+ Synthesizes speech using local Kokoro-82M open-weight model.
191
+ Runs 100% locally on CPU or GPU with zero cloud network calls.
192
+ """
193
+ try:
194
+ import numpy as np
195
+ import soundfile as sf
196
+ from kokoro import KPipeline
197
+ except ImportError as e:
198
+ print(f"[!] Kokoro dependencies not found ({e}). Run: pip install kokoro soundfile", file=sys.stderr)
199
+ return False
200
+
201
+ # Resolve voice and language code
202
+ selected_voice = DEFAULT_KOKORO_VOICE
203
+ selected_lang = DEFAULT_KOKORO_LANG
204
+
205
+ if voice_name:
206
+ v_lower = voice_name.lower().strip()
207
+ if v_lower in KOKORO_VOICES:
208
+ selected_voice, selected_lang = KOKORO_VOICES[v_lower]
209
+ else:
210
+ selected_voice = voice_name
211
+ # Auto-detect lang from prefix
212
+ prefix = voice_name[0].lower()
213
+ if prefix in ("b", "a", "j", "z", "e", "f", "i", "p", "h"):
214
+ selected_lang = prefix
215
+
216
+ # Load local Kokoro pipeline
217
+ try:
218
+ pipeline = KPipeline(lang_code=selected_lang, repo_id="hexgrad/Kokoro-82M")
219
+ generator = pipeline(text, voice=selected_voice, speed=speed)
220
+ chunks = [a for _, _, a in generator]
221
+ if not chunks:
222
+ return False
223
+
224
+ audio = np.concatenate(chunks)
225
+ save_file = output_path
226
+ is_temp = False
227
+ if not save_file:
228
+ is_temp = True
229
+ tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
230
+ save_file = tmp.name
231
+ tmp.close()
232
+
233
+ sf.write(save_file, audio, 24000)
234
+
235
+ if is_temp:
236
+ success = play_audio_file(save_file)
237
+ try:
238
+ os.remove(save_file)
239
+ except OSError:
240
+ pass
241
+ return success
242
+ else:
243
+ return os.path.exists(save_file) and os.path.getsize(save_file) > 100
244
+ except Exception as e:
245
+ print(f"[!] Kokoro synthesis error: {e}", file=sys.stderr)
246
+ return False
247
+
248
+
249
+ def synthesize_with_piper(
250
+ text: str,
251
+ output_path: str = None
252
+ ) -> bool:
253
+ """
254
+ Synthesizes speech using local Piper ONNX model.
255
+ Ultra-lightweight local engine (~35MB model) for low-spec PCs.
256
+ """
257
+ onnx_path, json_path = find_piper_model()
258
+ if not os.path.exists(onnx_path):
259
+ print(f"[!] Piper ONNX model not found at: {onnx_path}", file=sys.stderr)
260
+ print(" Download it with: ollama-speak --setup", file=sys.stderr)
261
+ return False
262
+
263
+ save_file = output_path
264
+ is_temp = False
265
+ if not save_file:
266
+ is_temp = True
267
+ tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
268
+ save_file = tmp.name
269
+ tmp.close()
270
+
271
+ try:
272
+ # Try python piper module
273
+ import wave
274
+ from piper.voice import PiperVoice
275
+ voice = PiperVoice.load(onnx_path, config_path=json_path if os.path.exists(json_path) else None)
276
+ with wave.open(save_file, "wb") as wf:
277
+ voice.synthesize_wav(text, wf)
278
+ except Exception:
279
+ # Fall back to piper CLI if available
280
+ piper_bin = shutil.which("piper")
281
+ if piper_bin:
282
+ res = subprocess.run([piper_bin, "-m", onnx_path, "-f", save_file], input=text.encode("utf-8"), check=False)
283
+ if res.returncode != 0:
284
+ return False
285
+ else:
286
+ return False
287
+
288
+ if is_temp:
289
+ success = play_audio_file(save_file)
290
+ try:
291
+ os.remove(save_file)
292
+ except OSError:
293
+ pass
294
+ return success
295
+ else:
296
+ return os.path.exists(save_file) and os.path.getsize(save_file) > 100
297
+
298
+
299
+ def download_all_models() -> None:
300
+ """
301
+ Pre-downloads Kokoro-82M and Piper open-weight models for 100% offline use.
302
+ """
303
+ print("==================================================================")
304
+ print(" 📥 ollama-speak Local Model Downloader (100% Offline Setup)")
305
+ print("==================================================================")
306
+
307
+ # 1. Kokoro-82M
308
+ print("\n1. Verifying / Downloading Kokoro-82M open-weight model (~350 MB)...")
309
+ try:
310
+ from kokoro import KPipeline
311
+ p = KPipeline(lang_code="b", repo_id="hexgrad/Kokoro-82M")
312
+ # Synthesize a 1-word smoke test to cache default voices
313
+ for v in ["bm_george", "af_heart"]:
314
+ gen = p("Ready", voice=v)
315
+ _ = [a for _, _, a in gen]
316
+ print("✓ Kokoro-82M weights verified and cached locally.")
317
+ except Exception as e:
318
+ print(f"[!] Kokoro download error: {e}")
319
+
320
+ # 2. Piper ONNX
321
+ print("\n2. Verifying / Downloading Piper British ONNX model (~35 MB)...")
322
+ onnx_path, json_path = find_piper_model()
323
+ if os.path.exists(onnx_path):
324
+ print(f"✓ Piper ONNX already exists at: {onnx_path}")
325
+ else:
326
+ try:
327
+ target_dir = Path(onnx_path).parent
328
+ target_dir.mkdir(parents=True, exist_ok=True)
329
+ url_base = "https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0/en/en_GB/alan/medium"
330
+ print(f"Downloading {onnx_path}...")
331
+ urllib.request.urlretrieve(f"{url_base}/en_GB-alan-medium.onnx", onnx_path)
332
+ urllib.request.urlretrieve(f"{url_base}/en_GB-alan-medium.onnx.json", json_path)
333
+ print("✓ Piper ONNX model downloaded successfully.")
334
+ except Exception as e:
335
+ print(f"[!] Piper download warning: {e}")
336
+
337
+ print("\n==================================================================")
338
+ print(" All local models are ready. ollama-speak is 100% offline and sovereign!")
339
+ print("==================================================================")
340
+
341
+
342
+ def synthesize_and_speak(
343
+ text: str,
344
+ voice: str = None,
345
+ engine: str = "kokoro",
346
+ output_file: str = None
347
+ ) -> bool:
348
+ """
349
+ Main dispatch function for ollama-speak.
350
+ Synthesizes and speaks text using 100% LOCAL models.
351
+ """
352
+ clean = re.sub(r"https?://\S+", "", text)
353
+ clean = re.sub(r"[`*_#>\[\]()~|]+", " ", clean)
354
+ clean = re.sub(r"\s+", " ", clean).strip()
355
+ if not clean:
356
+ return False
357
+
358
+ # Detect unsupported scripts for local models
359
+ if re.search(r"[\u0e00-\u0e7f]", clean):
360
+ print("[ollama-speak] Note: Thai script detected. Local Kokoro/Piper currently support English, Japanese, Chinese, Spanish, French, Italian, Portuguese, and Hindi.", file=sys.stderr)
361
+
362
+ # 1. Fast-path: Check for already warm local voice daemon on host
363
+ if not output_file and try_local_daemon(clean, engine=engine):
364
+ return True
365
+
366
+ # 2. Piper Engine
367
+ if engine.lower() in ("piper", "fast"):
368
+ return synthesize_with_piper(clean, output_path=output_file)
369
+
370
+ # 3. Kokoro Engine (Default Local SOTA)
371
+ return synthesize_with_kokoro(clean, voice_name=voice, output_path=output_file)
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Diagnostic & Verification Suite for ollama-speak.
4
+ Runs automated smoke tests to verify local Kokoro-82M, Piper ONNX, and OS audio playback.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import shutil
10
+ import tempfile
11
+
12
+ # Auto re-exec into isolated venv if run from bare system python without dependencies
13
+ venv_dir = os.path.expanduser("~/.local/share/ollama-speak/venv")
14
+ venv_py = os.path.join(venv_dir, "bin", "python")
15
+ if os.path.exists(venv_py) and sys.prefix != venv_dir:
16
+ try:
17
+ import kokoro
18
+ except ImportError:
19
+ os.execv(venv_py, [venv_py] + sys.argv)
20
+
21
+ def test_audio_backends():
22
+ print("[1/3] Checking OS audio playback engines...", end=" ", flush=True)
23
+ found = []
24
+ for cmd in ["pw-play", "paplay", "mpv", "ffplay", "aplay", "afplay"]:
25
+ if shutil.which(cmd):
26
+ found.append(cmd)
27
+ if sys.platform == "win32":
28
+ found.append("powershell (System.Media)")
29
+
30
+ if found:
31
+ print(f"✓ OK (Found: {', '.join(found)})")
32
+ return True
33
+ else:
34
+ print("❌ Warning: No audio player found. Install mpv or ffmpeg.")
35
+ return False
36
+
37
+ def test_kokoro_local():
38
+ print("[2/3] Checking local Kokoro-82M open-weight model...", end=" ", flush=True)
39
+ try:
40
+ from kokoro import KPipeline
41
+ import soundfile as sf
42
+ import numpy as np
43
+
44
+ p = KPipeline(lang_code="b", repo_id="hexgrad/Kokoro-82M")
45
+ gen = p("Diagnostic test.", voice="bm_george", speed=1.0)
46
+ chunks = [a for _, _, a in gen]
47
+ if chunks:
48
+ audio = np.concatenate(chunks)
49
+ tmp = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
50
+ sf.write(tmp.name, audio, 24000)
51
+ sz = os.path.getsize(tmp.name)
52
+ os.remove(tmp.name)
53
+ print(f"✓ OK (Synthesized {len(audio)} samples, {sz} bytes)")
54
+ return True
55
+ else:
56
+ print("❌ Failed (Empty audio generated)")
57
+ return False
58
+ except Exception as e:
59
+ print(f"❌ Error: {e}")
60
+ return False
61
+
62
+ def test_piper_local():
63
+ print("[3/3] Checking local Piper ONNX model...", end=" ", flush=True)
64
+ from ollama_speak.voice import find_piper_model
65
+ onnx_path, json_path = find_piper_model()
66
+ if os.path.exists(onnx_path):
67
+ print(f"✓ OK (Found: {onnx_path})")
68
+ return True
69
+ else:
70
+ print(f"⚠️ Notice: Piper ONNX not cached yet at {onnx_path}. Run 'ollama-speak --setup' to pre-download.")
71
+ return True
72
+
73
+ def main():
74
+ print("==================================================")
75
+ print(" 🛠️ ollama-speak System Diagnostic & Smoke Test")
76
+ print("==================================================")
77
+ t1 = test_audio_backends()
78
+ t2 = test_kokoro_local()
79
+ t3 = test_piper_local()
80
+ print("==================================================")
81
+ if t1 and t2 and t3:
82
+ print("🎉 100% Local voice engine verified and operational!")
83
+ sys.exit(0)
84
+ else:
85
+ print("❌ Diagnostic failures detected. Check logs above.")
86
+ sys.exit(1)
87
+
88
+ if __name__ == "__main__":
89
+ main()