syscon-tts 0.0.1__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Syscon International, Inc.
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,267 @@
1
+ Metadata-Version: 2.4
2
+ Name: syscon-tts
3
+ Version: 0.0.1
4
+ Summary: Offline neural text-to-speech for the PlantStar APU
5
+ Author: Keith Hostetler
6
+ Author-email: robh@syscon-intl.com
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/SYSCON-International/syscon_tts
9
+ Project-URL: Source, https://github.com/SYSCON-International/syscon_tts
10
+ Project-URL: Issues, https://github.com/SYSCON-International/syscon_tts/issues
11
+ Keywords: tts,text-to-speech,piper,offline,plantstar,apu
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Manufacturing
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Operating System :: MacOS :: MacOS X
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: piper-tts==1.2.0; sys_platform == "linux" and python_version < "3.12"
26
+ Provides-Extra: piper
27
+ Requires-Dist: piper-tts==1.2.0; extra == "piper"
28
+ Provides-Extra: server
29
+ Requires-Dist: fastapi<1.0,>=0.111; extra == "server"
30
+ Requires-Dist: uvicorn[standard]<1.0,>=0.30; extra == "server"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest<9,>=8.2; extra == "dev"
33
+ Requires-Dist: httpx<1.0,>=0.27; extra == "dev"
34
+ Requires-Dist: fastapi<1.0,>=0.111; extra == "dev"
35
+ Requires-Dist: uvicorn[standard]<1.0,>=0.30; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # Syscon TTS
39
+
40
+ Offline neural text-to-speech for the **PlantStar APU**.
41
+
42
+ Turns alert text into a voice WAV file with no cloud service, no per-request
43
+ licensing, and no internet connection at runtime. It replaces the APU's
44
+ previous VoiceText integration.
45
+
46
+ - **Engine:** [Piper](https://github.com/rhasspy/piper) — fast, CPU-only neural
47
+ TTS. No GPU required.
48
+ - **Interfaces:** a Python library (what the APU uses), a CLI, and an optional
49
+ HTTP server.
50
+ - **Voices:** six voices across four languages out of the box, configured in a
51
+ single JSON manifest.
52
+
53
+ ```bash
54
+ pip install syscon-tts
55
+ syscon-tts download-voices # one-time, needs internet
56
+ syscon-tts doctor # confirm the install
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Platform support
62
+
63
+ Piper ships **Linux-only** binary wheels. Rather than making that a hard
64
+ install failure everywhere else, the dependency is platform-gated, so the same
65
+ `pip install syscon-tts` works on every platform and gives you what that
66
+ platform can actually do:
67
+
68
+ | Platform | Synthesis | Replay cached audio | Library / CLI / manifest |
69
+ |---|:--:|:--:|:--:|
70
+ | **Linux x86-64, Python 3.9–3.11** (APU, customer machines) | yes | yes | yes |
71
+ | **Windows, macOS** (developer machines) | no | yes | yes |
72
+ | Linux, Python 3.12+ | no | yes | yes |
73
+
74
+ Developer machines can therefore install the package, browse voices, run the
75
+ test suite, and exercise the whole alert pipeline against WAVs generated on a
76
+ Linux box — they just can't generate new audio. Attempting to do so raises
77
+ `SynthesisUnavailableError`, which names the platform and explains why.
78
+
79
+ Run `syscon-tts doctor` any time to see which mode you're in.
80
+
81
+ > On Linux with Python 3.12+, Piper is skipped rather than failing the install.
82
+ > That is a deliberate trade — `doctor` reports it as a problem, so make it part
83
+ > of provisioning rather than discovering it at the first alert.
84
+
85
+ ---
86
+
87
+ ## Using it from the APU
88
+
89
+ The only contract the rest of the APU depends on is the side effect: a WAV
90
+ exists at `MEDIA_ROOT/public_alert_sounds/<file_name>.wav`. Everything
91
+ downstream — the websocket push, `/media/` serving, `check_for_file_delete` —
92
+ is agnostic about how the file got there. So the integration is a rewrite of
93
+ `call_tts()` and nothing else.
94
+
95
+ ```python
96
+ from syscon_tts import AlertSynthesizer
97
+
98
+ # Build once and keep it. Each instance caches loaded voice models in memory;
99
+ # constructing one per alert reintroduces a multi-second cold load every time.
100
+ synthesizer = AlertSynthesizer()
101
+
102
+ result = synthesizer.ensure(
103
+ message,
104
+ file_name=file_name, # the APU's get_valid_filename(message)[:50]
105
+ alerts_dir=Path(settings.MEDIA_ROOT, "public_alert_sounds"),
106
+ )
107
+ # result.path -> the WAV on disk
108
+ # result.cached -> True if it already existed (no synthesis happened)
109
+ ```
110
+
111
+ `ensure()` is **cache first**: if the WAV is already there it returns
112
+ immediately without touching Piper. Only a cache miss synthesizes.
113
+
114
+ The package derives file names with the exact algorithm the APU already uses
115
+ (`django.utils.text.get_valid_filename(text)[:50]`), verified byte-for-byte
116
+ including Unicode and the empty-name error cases — so both sides always agree
117
+ on the path. Passing `file_name` explicitly is still recommended, so the APU
118
+ stays the single source of truth.
119
+
120
+ Full integration recipe, including the `IS_LIVE` gate and voice-ID mapping:
121
+ [DEPLOY.md](DEPLOY.md).
122
+
123
+ ---
124
+
125
+ ## Voice profiles (default set)
126
+
127
+ | ID | Language | Voice |
128
+ |---|---|---|
129
+ | `en_us_amy` | en_US | Amy — female (default) |
130
+ | `en_us_ryan` | en_US | Ryan — male |
131
+ | `en_gb_alan` | en_GB | Alan — male |
132
+ | `es_es_davefx` | es_ES | DaveFX — male (Spanish) |
133
+ | `fr_fr_siwis` | fr_FR | Siwis — female (French) |
134
+ | `de_de_thorsten` | de_DE | Thorsten — male (German) |
135
+
136
+ The manifest ships inside the wheel. To use your own catalogue, point
137
+ `SYSCON_TTS_MANIFEST` at a JSON file of the same shape, then run
138
+ `syscon-tts download-voices`. Browse the full ~40-language catalogue at
139
+ <https://huggingface.co/rhasspy/piper-voices>.
140
+
141
+ ---
142
+
143
+ ## CLI
144
+
145
+ ```bash
146
+ syscon-tts doctor # platform, Piper, and model status
147
+ syscon-tts download-voices # fetch all voices (needs internet)
148
+ syscon-tts download-voices en_us_amy # or just one
149
+ syscon-tts list-voices # catalogue + install state
150
+
151
+ syscon-tts speak -v en_us_amy -o hello.wav "Hello from PlantStar"
152
+ syscon-tts speak -v de_de_thorsten -f mp3 -o ansage.mp3 --text-file notice.txt
153
+
154
+ syscon-tts alert "Press 4 cavity pressure exceeded." # cache-first, as the APU does
155
+ syscon-tts serve --port 5002 # needs [server] extra
156
+ ```
157
+
158
+ `alert` exits `2` when the audio is absent *and* this machine cannot
159
+ synthesize — distinguishable from `1` for ordinary errors.
160
+
161
+ ---
162
+
163
+ ## Configuration
164
+
165
+ All optional; every one has a working default.
166
+
167
+ | Variable | Default | Purpose |
168
+ |---|---|---|
169
+ | `SYSCON_TTS_MANIFEST` | bundled `data/voices.json` | Voice catalogue |
170
+ | `SYSCON_TTS_VOICES_DIR` | platform data dir | Where `.onnx` models live |
171
+ | `SYSCON_TTS_ALERTS_DIR` | platform data dir | Where alert WAVs are written |
172
+ | `SYSCON_TTS_DEFAULT_VOICE` | `en_us_amy` | Voice used when none is given |
173
+ | `SYSCON_TTS_HOST` | `127.0.0.1` | HTTP server bind host |
174
+ | `SYSCON_TTS_PORT` | `5002` | HTTP server bind port |
175
+ | `SYSCON_TTS_MAX_CHARS` | `20000` | Max text length per request |
176
+
177
+ The platform data dir is `/var/lib/syscon-tts` on Linux when writable
178
+ (otherwise `~/.local/share/syscon-tts`), `%LOCALAPPDATA%\syscon-tts` on
179
+ Windows, and `~/Library/Application Support/syscon-tts` on macOS.
180
+
181
+ **On the APU**, override the two path variables so audio lands where Django
182
+ already serves it:
183
+
184
+ ```
185
+ SYSCON_TTS_VOICES_DIR=<MEDIA_ROOT>/tts_voices
186
+ SYSCON_TTS_ALERTS_DIR=<MEDIA_ROOT>/public_alert_sounds
187
+ ```
188
+
189
+ `MEDIA_ROOT` is already gitignored and is where every other APU runtime data
190
+ directory lives, so the ~360 MB of voice models stay out of the source tree.
191
+
192
+ ---
193
+
194
+ ## Optional HTTP server
195
+
196
+ ```bash
197
+ pip install 'syscon-tts[server]'
198
+ syscon-tts serve --port 5002
199
+ ```
200
+
201
+ | Method | Path | Description |
202
+ |---|---|---|
203
+ | GET | `/health` | Liveness + whether this host can synthesize |
204
+ | GET | `/voices` | Voice profiles and install state |
205
+ | POST | `/synthesize` | Render text → audio bytes |
206
+
207
+ ```bash
208
+ curl -s -X POST http://localhost:5002/synthesize \
209
+ -H 'Content-Type: application/json' \
210
+ -d '{"text":"Machine 4 cycle complete.","voice":"en_us_ryan"}' \
211
+ -o alert.wav
212
+ ```
213
+
214
+ Status codes: `404` unknown voice · `501` this host can never synthesize
215
+ (no Piper) · `503` voice model not downloaded · `413` text too long ·
216
+ `400` bad input.
217
+
218
+ The APU does **not** need this — it imports the library in-process, which keeps
219
+ FastAPI and uvicorn out of the Django environment entirely.
220
+
221
+ ---
222
+
223
+ ## Development
224
+
225
+ ```bash
226
+ git clone https://github.com/SYSCON-International/syscon_tts.git
227
+ cd syscon_tts
228
+ python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
229
+ pip install -e ".[dev]"
230
+ pytest
231
+ ```
232
+
233
+ The full test suite passes on Windows and macOS without Piper — synthesis is
234
+ mocked throughout, so no voice models are needed. See
235
+ [CONTRIBUTING.md](CONTRIBUTING.md).
236
+
237
+ ---
238
+
239
+ ## Documentation
240
+
241
+ - [DEPLOY.md](DEPLOY.md) — provisioning an APU, the `call_tts()` replacement,
242
+ air-gapped installs, tuning.
243
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — module layout, design
244
+ decisions, operations, troubleshooting.
245
+
246
+ ---
247
+
248
+ ## Performance
249
+
250
+ Piper runs on CPU, so throughput depends on the host. Measure on the target:
251
+
252
+ ```bash
253
+ python scripts/benchmark.py # per-voice cold load, memory, RTF, latency
254
+ ```
255
+
256
+ It reports the **real-time factor** (RTF = synthesis time ÷ audio seconds) for
257
+ short/medium/long messages. Record results with
258
+ [docs/bench-results-template.md](docs/bench-results-template.md) and use them to
259
+ pick quality tiers — switching a voice to its `low`-quality model in the
260
+ manifest is the main lever on a weak CPU.
261
+
262
+ Expect roughly 50–120 MB resident per loaded `medium` voice; models stay in
263
+ memory after first use to avoid reload latency.
264
+
265
+ ## License
266
+
267
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,230 @@
1
+ # Syscon TTS
2
+
3
+ Offline neural text-to-speech for the **PlantStar APU**.
4
+
5
+ Turns alert text into a voice WAV file with no cloud service, no per-request
6
+ licensing, and no internet connection at runtime. It replaces the APU's
7
+ previous VoiceText integration.
8
+
9
+ - **Engine:** [Piper](https://github.com/rhasspy/piper) — fast, CPU-only neural
10
+ TTS. No GPU required.
11
+ - **Interfaces:** a Python library (what the APU uses), a CLI, and an optional
12
+ HTTP server.
13
+ - **Voices:** six voices across four languages out of the box, configured in a
14
+ single JSON manifest.
15
+
16
+ ```bash
17
+ pip install syscon-tts
18
+ syscon-tts download-voices # one-time, needs internet
19
+ syscon-tts doctor # confirm the install
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Platform support
25
+
26
+ Piper ships **Linux-only** binary wheels. Rather than making that a hard
27
+ install failure everywhere else, the dependency is platform-gated, so the same
28
+ `pip install syscon-tts` works on every platform and gives you what that
29
+ platform can actually do:
30
+
31
+ | Platform | Synthesis | Replay cached audio | Library / CLI / manifest |
32
+ |---|:--:|:--:|:--:|
33
+ | **Linux x86-64, Python 3.9–3.11** (APU, customer machines) | yes | yes | yes |
34
+ | **Windows, macOS** (developer machines) | no | yes | yes |
35
+ | Linux, Python 3.12+ | no | yes | yes |
36
+
37
+ Developer machines can therefore install the package, browse voices, run the
38
+ test suite, and exercise the whole alert pipeline against WAVs generated on a
39
+ Linux box — they just can't generate new audio. Attempting to do so raises
40
+ `SynthesisUnavailableError`, which names the platform and explains why.
41
+
42
+ Run `syscon-tts doctor` any time to see which mode you're in.
43
+
44
+ > On Linux with Python 3.12+, Piper is skipped rather than failing the install.
45
+ > That is a deliberate trade — `doctor` reports it as a problem, so make it part
46
+ > of provisioning rather than discovering it at the first alert.
47
+
48
+ ---
49
+
50
+ ## Using it from the APU
51
+
52
+ The only contract the rest of the APU depends on is the side effect: a WAV
53
+ exists at `MEDIA_ROOT/public_alert_sounds/<file_name>.wav`. Everything
54
+ downstream — the websocket push, `/media/` serving, `check_for_file_delete` —
55
+ is agnostic about how the file got there. So the integration is a rewrite of
56
+ `call_tts()` and nothing else.
57
+
58
+ ```python
59
+ from syscon_tts import AlertSynthesizer
60
+
61
+ # Build once and keep it. Each instance caches loaded voice models in memory;
62
+ # constructing one per alert reintroduces a multi-second cold load every time.
63
+ synthesizer = AlertSynthesizer()
64
+
65
+ result = synthesizer.ensure(
66
+ message,
67
+ file_name=file_name, # the APU's get_valid_filename(message)[:50]
68
+ alerts_dir=Path(settings.MEDIA_ROOT, "public_alert_sounds"),
69
+ )
70
+ # result.path -> the WAV on disk
71
+ # result.cached -> True if it already existed (no synthesis happened)
72
+ ```
73
+
74
+ `ensure()` is **cache first**: if the WAV is already there it returns
75
+ immediately without touching Piper. Only a cache miss synthesizes.
76
+
77
+ The package derives file names with the exact algorithm the APU already uses
78
+ (`django.utils.text.get_valid_filename(text)[:50]`), verified byte-for-byte
79
+ including Unicode and the empty-name error cases — so both sides always agree
80
+ on the path. Passing `file_name` explicitly is still recommended, so the APU
81
+ stays the single source of truth.
82
+
83
+ Full integration recipe, including the `IS_LIVE` gate and voice-ID mapping:
84
+ [DEPLOY.md](DEPLOY.md).
85
+
86
+ ---
87
+
88
+ ## Voice profiles (default set)
89
+
90
+ | ID | Language | Voice |
91
+ |---|---|---|
92
+ | `en_us_amy` | en_US | Amy — female (default) |
93
+ | `en_us_ryan` | en_US | Ryan — male |
94
+ | `en_gb_alan` | en_GB | Alan — male |
95
+ | `es_es_davefx` | es_ES | DaveFX — male (Spanish) |
96
+ | `fr_fr_siwis` | fr_FR | Siwis — female (French) |
97
+ | `de_de_thorsten` | de_DE | Thorsten — male (German) |
98
+
99
+ The manifest ships inside the wheel. To use your own catalogue, point
100
+ `SYSCON_TTS_MANIFEST` at a JSON file of the same shape, then run
101
+ `syscon-tts download-voices`. Browse the full ~40-language catalogue at
102
+ <https://huggingface.co/rhasspy/piper-voices>.
103
+
104
+ ---
105
+
106
+ ## CLI
107
+
108
+ ```bash
109
+ syscon-tts doctor # platform, Piper, and model status
110
+ syscon-tts download-voices # fetch all voices (needs internet)
111
+ syscon-tts download-voices en_us_amy # or just one
112
+ syscon-tts list-voices # catalogue + install state
113
+
114
+ syscon-tts speak -v en_us_amy -o hello.wav "Hello from PlantStar"
115
+ syscon-tts speak -v de_de_thorsten -f mp3 -o ansage.mp3 --text-file notice.txt
116
+
117
+ syscon-tts alert "Press 4 cavity pressure exceeded." # cache-first, as the APU does
118
+ syscon-tts serve --port 5002 # needs [server] extra
119
+ ```
120
+
121
+ `alert` exits `2` when the audio is absent *and* this machine cannot
122
+ synthesize — distinguishable from `1` for ordinary errors.
123
+
124
+ ---
125
+
126
+ ## Configuration
127
+
128
+ All optional; every one has a working default.
129
+
130
+ | Variable | Default | Purpose |
131
+ |---|---|---|
132
+ | `SYSCON_TTS_MANIFEST` | bundled `data/voices.json` | Voice catalogue |
133
+ | `SYSCON_TTS_VOICES_DIR` | platform data dir | Where `.onnx` models live |
134
+ | `SYSCON_TTS_ALERTS_DIR` | platform data dir | Where alert WAVs are written |
135
+ | `SYSCON_TTS_DEFAULT_VOICE` | `en_us_amy` | Voice used when none is given |
136
+ | `SYSCON_TTS_HOST` | `127.0.0.1` | HTTP server bind host |
137
+ | `SYSCON_TTS_PORT` | `5002` | HTTP server bind port |
138
+ | `SYSCON_TTS_MAX_CHARS` | `20000` | Max text length per request |
139
+
140
+ The platform data dir is `/var/lib/syscon-tts` on Linux when writable
141
+ (otherwise `~/.local/share/syscon-tts`), `%LOCALAPPDATA%\syscon-tts` on
142
+ Windows, and `~/Library/Application Support/syscon-tts` on macOS.
143
+
144
+ **On the APU**, override the two path variables so audio lands where Django
145
+ already serves it:
146
+
147
+ ```
148
+ SYSCON_TTS_VOICES_DIR=<MEDIA_ROOT>/tts_voices
149
+ SYSCON_TTS_ALERTS_DIR=<MEDIA_ROOT>/public_alert_sounds
150
+ ```
151
+
152
+ `MEDIA_ROOT` is already gitignored and is where every other APU runtime data
153
+ directory lives, so the ~360 MB of voice models stay out of the source tree.
154
+
155
+ ---
156
+
157
+ ## Optional HTTP server
158
+
159
+ ```bash
160
+ pip install 'syscon-tts[server]'
161
+ syscon-tts serve --port 5002
162
+ ```
163
+
164
+ | Method | Path | Description |
165
+ |---|---|---|
166
+ | GET | `/health` | Liveness + whether this host can synthesize |
167
+ | GET | `/voices` | Voice profiles and install state |
168
+ | POST | `/synthesize` | Render text → audio bytes |
169
+
170
+ ```bash
171
+ curl -s -X POST http://localhost:5002/synthesize \
172
+ -H 'Content-Type: application/json' \
173
+ -d '{"text":"Machine 4 cycle complete.","voice":"en_us_ryan"}' \
174
+ -o alert.wav
175
+ ```
176
+
177
+ Status codes: `404` unknown voice · `501` this host can never synthesize
178
+ (no Piper) · `503` voice model not downloaded · `413` text too long ·
179
+ `400` bad input.
180
+
181
+ The APU does **not** need this — it imports the library in-process, which keeps
182
+ FastAPI and uvicorn out of the Django environment entirely.
183
+
184
+ ---
185
+
186
+ ## Development
187
+
188
+ ```bash
189
+ git clone https://github.com/SYSCON-International/syscon_tts.git
190
+ cd syscon_tts
191
+ python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
192
+ pip install -e ".[dev]"
193
+ pytest
194
+ ```
195
+
196
+ The full test suite passes on Windows and macOS without Piper — synthesis is
197
+ mocked throughout, so no voice models are needed. See
198
+ [CONTRIBUTING.md](CONTRIBUTING.md).
199
+
200
+ ---
201
+
202
+ ## Documentation
203
+
204
+ - [DEPLOY.md](DEPLOY.md) — provisioning an APU, the `call_tts()` replacement,
205
+ air-gapped installs, tuning.
206
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — module layout, design
207
+ decisions, operations, troubleshooting.
208
+
209
+ ---
210
+
211
+ ## Performance
212
+
213
+ Piper runs on CPU, so throughput depends on the host. Measure on the target:
214
+
215
+ ```bash
216
+ python scripts/benchmark.py # per-voice cold load, memory, RTF, latency
217
+ ```
218
+
219
+ It reports the **real-time factor** (RTF = synthesis time ÷ audio seconds) for
220
+ short/medium/long messages. Record results with
221
+ [docs/bench-results-template.md](docs/bench-results-template.md) and use them to
222
+ pick quality tiers — switching a voice to its `low`-quality model in the
223
+ manifest is the main lever on a weak CPU.
224
+
225
+ Expect roughly 50–120 MB resident per loaded `medium` voice; models stay in
226
+ memory after first use to avoid reload latency.
227
+
228
+ ## License
229
+
230
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,76 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "syscon-tts"
7
+ version = "0.0.1"
8
+ description = "Offline neural text-to-speech for the PlantStar APU"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ # Split across two entries so the metadata renders as separate `Author` and
14
+ # `Author-email` fields, matching django-search-filter-sort. A single entry
15
+ # with both keys would collapse into `Author-email: Name <addr>` instead.
16
+ authors = [
17
+ { name = "Keith Hostetler" },
18
+ { email = "robh@syscon-intl.com" },
19
+ ]
20
+ keywords = ["tts", "text-to-speech", "piper", "offline", "plantstar", "apu"]
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "Intended Audience :: Manufacturing",
24
+ "Operating System :: POSIX :: Linux",
25
+ "Operating System :: MacOS :: MacOS X",
26
+ "Operating System :: Microsoft :: Windows",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.9",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Topic :: Multimedia :: Sound/Audio :: Speech",
32
+ ]
33
+
34
+ # Piper ships Linux-only binary wheels (piper-phonemize has no Windows or
35
+ # macOS distribution, and none for Python 3.12+). The marker keeps
36
+ # `pip install syscon-tts` working everywhere: Linux hosts get a synthesis
37
+ # engine, other platforms get a package that can still resolve, inspect, and
38
+ # replay previously generated audio. Run `syscon-tts doctor` after installing
39
+ # to confirm which mode you landed in.
40
+ dependencies = [
41
+ "piper-tts==1.2.0; sys_platform == 'linux' and python_version < '3.12'",
42
+ ]
43
+
44
+ [project.optional-dependencies]
45
+ # Force the synthesis engine regardless of the marker above. Fails loudly on a
46
+ # platform Piper cannot support, which is what you want when the install is
47
+ # meant to produce audio.
48
+ piper = ["piper-tts==1.2.0"]
49
+ # The optional HTTP server. Not needed by the APU, which imports the library.
50
+ server = ["fastapi>=0.111,<1.0", "uvicorn[standard]>=0.30,<1.0"]
51
+ dev = [
52
+ "pytest>=8.2,<9",
53
+ "httpx>=0.27,<1.0",
54
+ "fastapi>=0.111,<1.0",
55
+ "uvicorn[standard]>=0.30,<1.0",
56
+ ]
57
+
58
+ [project.urls]
59
+ Homepage = "https://github.com/SYSCON-International/syscon_tts"
60
+ Source = "https://github.com/SYSCON-International/syscon_tts"
61
+ Issues = "https://github.com/SYSCON-International/syscon_tts/issues"
62
+
63
+ [project.scripts]
64
+ syscon-tts = "syscon_tts.cli:main"
65
+
66
+ [tool.setuptools.packages.find]
67
+ where = ["src"]
68
+
69
+ [tool.setuptools.package-data]
70
+ # The voice manifest travels inside the wheel; the models it points at are
71
+ # downloaded at provisioning time (they are far too large to ship).
72
+ syscon_tts = ["data/*.json"]
73
+
74
+ [tool.pytest.ini_options]
75
+ pythonpath = ["src"]
76
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,65 @@
1
+ """Syscon TTS - offline text-to-speech for the PlantStar APU.
2
+
3
+ Wraps the Piper neural TTS engine (CPU-only, fully offline) behind a small
4
+ library, a command-line tool, and an optional HTTP server.
5
+
6
+ Piper only installs on Linux, which is where every APU runs. Importing this
7
+ package is nonetheless safe on Windows and macOS: the Piper import is deferred
8
+ until synthesis is attempted, so developers on those platforms can install the
9
+ package, inspect voices, and replay audio generated elsewhere. Attempting to
10
+ generate new audio there raises
11
+ :class:`~syscon_tts.engine.SynthesisUnavailableError`.
12
+
13
+ Typical APU usage -- resolve alert text to a WAV, synthesizing only on a cache
14
+ miss::
15
+
16
+ from syscon_tts import AlertSynthesizer
17
+
18
+ synth = AlertSynthesizer() # build once, keep it: caches models
19
+ result = synth.ensure("Press 4 cavity pressure exceeded.")
20
+ print(result.path, result.cached)
21
+ """
22
+
23
+ from .alerts import (
24
+ AlertAudio,
25
+ AlertSynthesizer,
26
+ InvalidAlertNameError,
27
+ ensure_alert_wav,
28
+ sanitize_file_name,
29
+ )
30
+ from .config import Settings, load_settings
31
+ from .engine import (
32
+ SynthesisError,
33
+ SynthesisUnavailableError,
34
+ TTSEngine,
35
+ piper_available,
36
+ )
37
+ from .voices import (
38
+ UnknownVoiceError,
39
+ VoiceError,
40
+ VoiceNotInstalledError,
41
+ VoiceProfile,
42
+ VoiceRegistry,
43
+ )
44
+
45
+ __version__ = "0.0.1"
46
+
47
+ __all__ = [
48
+ "AlertAudio",
49
+ "AlertSynthesizer",
50
+ "InvalidAlertNameError",
51
+ "Settings",
52
+ "SynthesisError",
53
+ "SynthesisUnavailableError",
54
+ "TTSEngine",
55
+ "UnknownVoiceError",
56
+ "VoiceError",
57
+ "VoiceNotInstalledError",
58
+ "VoiceProfile",
59
+ "VoiceRegistry",
60
+ "__version__",
61
+ "ensure_alert_wav",
62
+ "load_settings",
63
+ "piper_available",
64
+ "sanitize_file_name",
65
+ ]