faster-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 Jordi Mas i Hernàndez
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,199 @@
1
+ Metadata-Version: 2.4
2
+ Name: faster-tts
3
+ Version: 0.0.1
4
+ Summary: Text-to-speech library on CPU and GPU using CTranslate2
5
+ Author-email: Jordi Mas <jmas@softcatala.org>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jordimas/faster_tts
8
+ Project-URL: Repository, https://github.com/jordimas/faster_tts
9
+ Project-URL: Documentation, https://github.com/jordimas/faster_tts/blob/main/docs/api.md
10
+ Project-URL: Issues, https://github.com/jordimas/faster_tts/issues
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: ctranslate2>=4.8.4
20
+ Requires-Dist: numpy>=1.23
21
+ Requires-Dist: tokenizers>=0.20
22
+ Requires-Dist: huggingface-hub>=0.24
23
+ Provides-Extra: docs
24
+ Requires-Dist: pre-commit>=4; extra == "docs"
25
+ Dynamic: license-file
26
+
27
+ # faster_tts
28
+
29
+ Text-to-speech with **Drama 3 on CPU and GPU**, powered by CTranslate2.
30
+ Provides a Python library API and the `faster-tts` command-line interface (CLI).
31
+ Includes model downloading and caching, sentence splitting, speaker tags, and WAV output.
32
+ Requires CTranslate2 **4.8.4+**.
33
+
34
+ ## Quick start
35
+
36
+ Python API:
37
+
38
+ ```python
39
+ from faster_tts import Model
40
+
41
+ model = Model()
42
+ result = model.synthesize(
43
+ "Hello! Welcome to our application.",
44
+ output="hello.wav", # Save the audio as a WAV file.
45
+ )
46
+ ```
47
+
48
+ `Model()` downloads and caches
49
+ [`jordimas/drama-3-ct2`](https://huggingface.co/jordimas/drama-3-ct2).
50
+ Reuse it across calls.
51
+
52
+ CLI (also available as `python -m faster_tts`):
53
+
54
+ ```bash
55
+ faster-tts --text "Hello!" --output hello.wav
56
+ ```
57
+
58
+ Generate emotion, dialogue, and multilingual audio with the
59
+ [sample scripts](https://github.com/jordimas/faster_tts/blob/dev/samples/README.md), or narrate a UTF-8 article:
60
+
61
+ ```bash
62
+ python samples/article_voice.py article.txt --output article.wav
63
+ ```
64
+
65
+ The article sample selects the device automatically. Use `--device cpu` or
66
+ `--device cuda` to override it. Its default audio budget is 120 seconds
67
+ (`--max-duration-s`); reaching it saves partial narration.
68
+
69
+ ## Models and devices
70
+
71
+ ```python
72
+ model = Model("drama-3") # Default checkpoint
73
+ model = Model("jordimas/drama-3-ct2", revision="main", cache_dir="./models")
74
+ model = Model("/path/to/converted-model")
75
+ model = Model(local_files_only=True) # Cached assets only
76
+
77
+ model = Model(device="cuda", device_index=0, compute_type="float16")
78
+ model = Model(device="cpu", compute_type="int8")
79
+ ```
80
+
81
+ The default `device="auto"` selects GPU when available, otherwise CPU.
82
+ CUDA and ROCm both use `"cuda"` with a matching CT2 build. Compute types depend
83
+ on the build; `"default"` lets CT2 choose. Inspect settings with `model.info()`.
84
+ Set `CT2_DRAMA_GPU_WAVEFORM=0` to decode waveforms on CPU.
85
+
86
+ The CLI accepts the same model and cache options. For local models, use explicit
87
+ paths such as `./model` to avoid interpreting missing directories as Hub IDs.
88
+ Custom checkpoints must match the engine's layout.
89
+
90
+ ## Memory
91
+
92
+ GPU expert caches size automatically. Set a total budget in decimal GB:
93
+
94
+ ```python
95
+ model = Model(
96
+ device="cuda", num_workers=2,
97
+ engine_options={"gpu_expert_cache_gb": 12},
98
+ )
99
+ ```
100
+
101
+ This gives each worker a 6 GB cache. Use `"auto"` or `"uncapped"` instead of a
102
+ number, or CLI `--gpu-expert-cache-gb`. Without an explicit budget,
103
+ `CT2_DRAMA3_GPU_EXPERT_CACHE_GB` sets the per-worker limit (`0` means uncapped).
104
+ Automatic sizing reserves at least 2 GB and 30% of free memory and fails if
105
+ memory cannot be queried or is insufficient. Cache budgets do not cap total VRAM.
106
+
107
+ When `device="auto"` selects CPU, large checkpoints can use expert memory mapping
108
+ on Linux/macOS with `compute_type="default"` or `"float32"`. Override with
109
+ `CT2_DRAMA3_CPU_MMAP=0` or `1`. Explicit `device="cpu"` leaves it off by default.
110
+ Keep the checkpoint unchanged while loaded; mapping does not cap RAM usage.
111
+ Both memory features require a supporting CT2 build.
112
+
113
+ ## Synthesis options and results
114
+
115
+ Pass engine options through `engine_options`. Experimental `directed_spans`
116
+ splits explicit breathing events from speech:
117
+
118
+ ```python
119
+ result = model.synthesize(
120
+ "Set the bag down. [breathing] We can continue now.",
121
+ engine_options={"directed_spans": True, "max_frames": 534},
122
+ )
123
+ ```
124
+
125
+ Speech spans are capped at 256 frames and breathing events at 22, within the
126
+ overall budget. Events use empty history and do not condition later speech.
127
+ Listen to check voice, prosody, and event quality. CLI: `--directed-spans`.
128
+
129
+ [`SynthesisResult`](https://github.com/jordimas/faster_tts/blob/dev/docs/api.md#synthesisresult) includes audio, sample rate, and
130
+ `finish_reason`. A `"length"` result may be truncated; the CLI saves it and exits
131
+ with status 1. `"eos"` does not guarantee complete speech.
132
+ Enable INFO logging for `faster_tts.engines.drama3` for device and segment details.
133
+
134
+ See the [API reference](https://github.com/jordimas/faster_tts/blob/dev/docs/api.md), `python -m faster_tts --help`, and
135
+ [CT2 Drama 3 guide](https://github.com/OpenNMT/CTranslate2/blob/master/docs/guides/drama3.md)
136
+ for options and conversion instructions.
137
+
138
+ ## Benchmark
139
+
140
+ Drama 3 only. See [benchmark instructions](https://github.com/jordimas/faster_tts/blob/dev/benchmark/README.md) to run your own measurements.
141
+
142
+ <!-- current-benchmarks -->
143
+ ### Benchmark results (2026-10-24)
144
+
145
+ #### CPU — AMD EPYC 7B13 (62 GiB RAM)
146
+
147
+ Test text: `Hello! Welcome to our application.`
148
+
149
+ Times in the CPU table are means. RTF is compute time divided by generated audio duration; lower is better, and 1 means real-time synthesis. Loading and warmup are excluded.
150
+
151
+ | Implementation | Precision | Time (s) | Audio duration (s) | RTF | Peak RAM (GiB RSS) | Status |
152
+ | --- | --- | ---: | ---: | ---: | ---: | --- |
153
+ | HF / PyTorch | bfloat16 | 300.9 | 2.8 | 106.2 | 44.5 | success |
154
+ | CT2 | bfloat16 | — | — | — | — | unsupported (no matching precision) |
155
+ | HF / PyTorch | float16 | 351.5 | 3.2 | 109.7 | 44.7 | success |
156
+ | CT2 | float16 | — | — | — | — | unsupported (no matching precision) |
157
+ | HF / PyTorch | int8 | — | — | — | — | unsupported (no matching precision) |
158
+ | CT2 | int8 | 18.4 | 2.4 | 7.6 | 44.7 | success |
159
+ | HF / PyTorch | int8_float32 | — | — | — | — | unsupported (no matching precision) |
160
+ | CT2 | int8_float32 | 17.9 | 2.4 | 7.4 | 44.7 | success |
161
+ | HF / PyTorch | float32 | 155.2 | 3.0 | 52.2 | 51.3 | success |
162
+ | CT2 | float32 | 37.7 | 2.5 | 15.0 | 45.5 | success |
163
+
164
+ <!-- gpu-benchmarks-ci40090377176 -->
165
+ #### GPU — NVIDIA Tesla T4 (15 GiB VRAM)
166
+
167
+ CT2 4.8.4, 8 CPU threads, one warmup and three timed runs per successful configuration.
168
+ Same test text as above. Times are means; peak RAM is process RSS and excludes VRAM.
169
+ Expert cache: auto (10.874 GB), except the successful float16 run (8 GB).
170
+
171
+ | Implementation | Precision | Time (s) | Audio duration (s) | RTF | Peak RAM (GiB RSS) | Status |
172
+ | --- | --- | ---: | ---: | ---: | ---: | --- |
173
+ | CT2 | float16 | — | — | — | — | CUDA OOM during warmup |
174
+ | CT2 | float32 | 34.5 | 2.5 | 13.8 | 34.5 | success |
175
+ | CT2 | int8 | 27.3 | 4.3 | 6.3 | 36.6 | success |
176
+ | CT2 | int8_float16 | 26.9 | 3.0 | 9.0 | 35.9 | success |
177
+ | CT2 | int8_float32 | 27.2 | 4.3 | 6.3 | 36.6 | success |
178
+ | CT2 | float16 | 37.9 | 3.7 | 10.3 | 36.0 | success |
179
+ <!-- /gpu-benchmarks-ci40090377176 -->
180
+
181
+ <!-- gpu-benchmarks-l4-ci40090377176 -->
182
+ #### GPU — NVIDIA L4
183
+
184
+ CT2 4.8.4, 8 CPU threads, a 12 GB expert cache, one warmup and one timed run per configuration.
185
+ Same test text as above. Loading and warmup are excluded; peak RAM is process RSS and excludes VRAM.
186
+ Single runs do not measure timing variability.
187
+
188
+ | Implementation | Precision | Time (s) | Audio duration (s) | RTF | Peak RAM (GiB RSS) | Status |
189
+ | --- | --- | ---: | ---: | ---: | ---: | --- |
190
+ | CT2 | bfloat16 | 26.5 | 2.8 | 9.5 | 35.5 | success |
191
+ | CT2 | float16 | 27.6 | 3.1 | 8.9 | 35.6 | success |
192
+ | CT2 | float32 | 29.4 | 2.5 | 11.7 | 34.6 | success |
193
+ | CT2 | int8 | 22.1 | 4.3 | 5.1 | 36.6 | success |
194
+ | CT2 | int8_bfloat16 | 21.4 | 2.8 | 7.6 | 35.1 | success |
195
+ | CT2 | int8_float16 | 22.8 | 3.0 | 7.7 | 35.9 | success |
196
+ | CT2 | int8_float32 | 22.1 | 4.3 | 5.1 | 36.7 | success |
197
+ <!-- /gpu-benchmarks-l4-ci40090377176 -->
198
+
199
+ Contact: Jordi Mas <jmas@softcatala.org>
@@ -0,0 +1,173 @@
1
+ # faster_tts
2
+
3
+ Text-to-speech with **Drama 3 on CPU and GPU**, powered by CTranslate2.
4
+ Provides a Python library API and the `faster-tts` command-line interface (CLI).
5
+ Includes model downloading and caching, sentence splitting, speaker tags, and WAV output.
6
+ Requires CTranslate2 **4.8.4+**.
7
+
8
+ ## Quick start
9
+
10
+ Python API:
11
+
12
+ ```python
13
+ from faster_tts import Model
14
+
15
+ model = Model()
16
+ result = model.synthesize(
17
+ "Hello! Welcome to our application.",
18
+ output="hello.wav", # Save the audio as a WAV file.
19
+ )
20
+ ```
21
+
22
+ `Model()` downloads and caches
23
+ [`jordimas/drama-3-ct2`](https://huggingface.co/jordimas/drama-3-ct2).
24
+ Reuse it across calls.
25
+
26
+ CLI (also available as `python -m faster_tts`):
27
+
28
+ ```bash
29
+ faster-tts --text "Hello!" --output hello.wav
30
+ ```
31
+
32
+ Generate emotion, dialogue, and multilingual audio with the
33
+ [sample scripts](https://github.com/jordimas/faster_tts/blob/dev/samples/README.md), or narrate a UTF-8 article:
34
+
35
+ ```bash
36
+ python samples/article_voice.py article.txt --output article.wav
37
+ ```
38
+
39
+ The article sample selects the device automatically. Use `--device cpu` or
40
+ `--device cuda` to override it. Its default audio budget is 120 seconds
41
+ (`--max-duration-s`); reaching it saves partial narration.
42
+
43
+ ## Models and devices
44
+
45
+ ```python
46
+ model = Model("drama-3") # Default checkpoint
47
+ model = Model("jordimas/drama-3-ct2", revision="main", cache_dir="./models")
48
+ model = Model("/path/to/converted-model")
49
+ model = Model(local_files_only=True) # Cached assets only
50
+
51
+ model = Model(device="cuda", device_index=0, compute_type="float16")
52
+ model = Model(device="cpu", compute_type="int8")
53
+ ```
54
+
55
+ The default `device="auto"` selects GPU when available, otherwise CPU.
56
+ CUDA and ROCm both use `"cuda"` with a matching CT2 build. Compute types depend
57
+ on the build; `"default"` lets CT2 choose. Inspect settings with `model.info()`.
58
+ Set `CT2_DRAMA_GPU_WAVEFORM=0` to decode waveforms on CPU.
59
+
60
+ The CLI accepts the same model and cache options. For local models, use explicit
61
+ paths such as `./model` to avoid interpreting missing directories as Hub IDs.
62
+ Custom checkpoints must match the engine's layout.
63
+
64
+ ## Memory
65
+
66
+ GPU expert caches size automatically. Set a total budget in decimal GB:
67
+
68
+ ```python
69
+ model = Model(
70
+ device="cuda", num_workers=2,
71
+ engine_options={"gpu_expert_cache_gb": 12},
72
+ )
73
+ ```
74
+
75
+ This gives each worker a 6 GB cache. Use `"auto"` or `"uncapped"` instead of a
76
+ number, or CLI `--gpu-expert-cache-gb`. Without an explicit budget,
77
+ `CT2_DRAMA3_GPU_EXPERT_CACHE_GB` sets the per-worker limit (`0` means uncapped).
78
+ Automatic sizing reserves at least 2 GB and 30% of free memory and fails if
79
+ memory cannot be queried or is insufficient. Cache budgets do not cap total VRAM.
80
+
81
+ When `device="auto"` selects CPU, large checkpoints can use expert memory mapping
82
+ on Linux/macOS with `compute_type="default"` or `"float32"`. Override with
83
+ `CT2_DRAMA3_CPU_MMAP=0` or `1`. Explicit `device="cpu"` leaves it off by default.
84
+ Keep the checkpoint unchanged while loaded; mapping does not cap RAM usage.
85
+ Both memory features require a supporting CT2 build.
86
+
87
+ ## Synthesis options and results
88
+
89
+ Pass engine options through `engine_options`. Experimental `directed_spans`
90
+ splits explicit breathing events from speech:
91
+
92
+ ```python
93
+ result = model.synthesize(
94
+ "Set the bag down. [breathing] We can continue now.",
95
+ engine_options={"directed_spans": True, "max_frames": 534},
96
+ )
97
+ ```
98
+
99
+ Speech spans are capped at 256 frames and breathing events at 22, within the
100
+ overall budget. Events use empty history and do not condition later speech.
101
+ Listen to check voice, prosody, and event quality. CLI: `--directed-spans`.
102
+
103
+ [`SynthesisResult`](https://github.com/jordimas/faster_tts/blob/dev/docs/api.md#synthesisresult) includes audio, sample rate, and
104
+ `finish_reason`. A `"length"` result may be truncated; the CLI saves it and exits
105
+ with status 1. `"eos"` does not guarantee complete speech.
106
+ Enable INFO logging for `faster_tts.engines.drama3` for device and segment details.
107
+
108
+ See the [API reference](https://github.com/jordimas/faster_tts/blob/dev/docs/api.md), `python -m faster_tts --help`, and
109
+ [CT2 Drama 3 guide](https://github.com/OpenNMT/CTranslate2/blob/master/docs/guides/drama3.md)
110
+ for options and conversion instructions.
111
+
112
+ ## Benchmark
113
+
114
+ Drama 3 only. See [benchmark instructions](https://github.com/jordimas/faster_tts/blob/dev/benchmark/README.md) to run your own measurements.
115
+
116
+ <!-- current-benchmarks -->
117
+ ### Benchmark results (2026-10-24)
118
+
119
+ #### CPU — AMD EPYC 7B13 (62 GiB RAM)
120
+
121
+ Test text: `Hello! Welcome to our application.`
122
+
123
+ Times in the CPU table are means. RTF is compute time divided by generated audio duration; lower is better, and 1 means real-time synthesis. Loading and warmup are excluded.
124
+
125
+ | Implementation | Precision | Time (s) | Audio duration (s) | RTF | Peak RAM (GiB RSS) | Status |
126
+ | --- | --- | ---: | ---: | ---: | ---: | --- |
127
+ | HF / PyTorch | bfloat16 | 300.9 | 2.8 | 106.2 | 44.5 | success |
128
+ | CT2 | bfloat16 | — | — | — | — | unsupported (no matching precision) |
129
+ | HF / PyTorch | float16 | 351.5 | 3.2 | 109.7 | 44.7 | success |
130
+ | CT2 | float16 | — | — | — | — | unsupported (no matching precision) |
131
+ | HF / PyTorch | int8 | — | — | — | — | unsupported (no matching precision) |
132
+ | CT2 | int8 | 18.4 | 2.4 | 7.6 | 44.7 | success |
133
+ | HF / PyTorch | int8_float32 | — | — | — | — | unsupported (no matching precision) |
134
+ | CT2 | int8_float32 | 17.9 | 2.4 | 7.4 | 44.7 | success |
135
+ | HF / PyTorch | float32 | 155.2 | 3.0 | 52.2 | 51.3 | success |
136
+ | CT2 | float32 | 37.7 | 2.5 | 15.0 | 45.5 | success |
137
+
138
+ <!-- gpu-benchmarks-ci40090377176 -->
139
+ #### GPU — NVIDIA Tesla T4 (15 GiB VRAM)
140
+
141
+ CT2 4.8.4, 8 CPU threads, one warmup and three timed runs per successful configuration.
142
+ Same test text as above. Times are means; peak RAM is process RSS and excludes VRAM.
143
+ Expert cache: auto (10.874 GB), except the successful float16 run (8 GB).
144
+
145
+ | Implementation | Precision | Time (s) | Audio duration (s) | RTF | Peak RAM (GiB RSS) | Status |
146
+ | --- | --- | ---: | ---: | ---: | ---: | --- |
147
+ | CT2 | float16 | — | — | — | — | CUDA OOM during warmup |
148
+ | CT2 | float32 | 34.5 | 2.5 | 13.8 | 34.5 | success |
149
+ | CT2 | int8 | 27.3 | 4.3 | 6.3 | 36.6 | success |
150
+ | CT2 | int8_float16 | 26.9 | 3.0 | 9.0 | 35.9 | success |
151
+ | CT2 | int8_float32 | 27.2 | 4.3 | 6.3 | 36.6 | success |
152
+ | CT2 | float16 | 37.9 | 3.7 | 10.3 | 36.0 | success |
153
+ <!-- /gpu-benchmarks-ci40090377176 -->
154
+
155
+ <!-- gpu-benchmarks-l4-ci40090377176 -->
156
+ #### GPU — NVIDIA L4
157
+
158
+ CT2 4.8.4, 8 CPU threads, a 12 GB expert cache, one warmup and one timed run per configuration.
159
+ Same test text as above. Loading and warmup are excluded; peak RAM is process RSS and excludes VRAM.
160
+ Single runs do not measure timing variability.
161
+
162
+ | Implementation | Precision | Time (s) | Audio duration (s) | RTF | Peak RAM (GiB RSS) | Status |
163
+ | --- | --- | ---: | ---: | ---: | ---: | --- |
164
+ | CT2 | bfloat16 | 26.5 | 2.8 | 9.5 | 35.5 | success |
165
+ | CT2 | float16 | 27.6 | 3.1 | 8.9 | 35.6 | success |
166
+ | CT2 | float32 | 29.4 | 2.5 | 11.7 | 34.6 | success |
167
+ | CT2 | int8 | 22.1 | 4.3 | 5.1 | 36.6 | success |
168
+ | CT2 | int8_bfloat16 | 21.4 | 2.8 | 7.6 | 35.1 | success |
169
+ | CT2 | int8_float16 | 22.8 | 3.0 | 7.7 | 35.9 | success |
170
+ | CT2 | int8_float32 | 22.1 | 4.3 | 5.1 | 36.7 | success |
171
+ <!-- /gpu-benchmarks-l4-ci40090377176 -->
172
+
173
+ Contact: Jordi Mas <jmas@softcatala.org>
@@ -0,0 +1,9 @@
1
+ """CTranslate2 text-to-speech through the public Model and SynthesisResult API.
2
+
3
+ See README.md for setup and examples. Engine-specific helpers are internal.
4
+ Importing the package does not download checkpoints or initialize a model.
5
+ """
6
+
7
+ from .model import Model, SynthesisResult
8
+
9
+ __all__ = ["Model", "SynthesisResult"]
@@ -0,0 +1,121 @@
1
+ """Speech synthesis: python -m faster_tts [MODEL] --text TEXT --output out.wav.
2
+
3
+ MODEL defaults to jordimas/drama-3-ct2. Accepts drama-3, a converted Hub repository
4
+ ID (optionally prefixed with hf://), or a local converted directory.
5
+ """
6
+
7
+ import argparse
8
+ import time
9
+ from pathlib import Path
10
+
11
+ from .cache import DEFAULT_MODEL
12
+ from .model import Model, _save_wav
13
+
14
+
15
+ def _cache_budget(value):
16
+ if value in ("auto", "uncapped"):
17
+ return value
18
+ try:
19
+ from .memory import validate_cache_budget
20
+
21
+ result = float(value)
22
+ validate_cache_budget(result)
23
+ return result
24
+ except ValueError as exc:
25
+ raise argparse.ArgumentTypeError(
26
+ 'expected "auto", "uncapped", or a finite positive GB budget'
27
+ ) from exc
28
+
29
+
30
+ def main():
31
+ parser = argparse.ArgumentParser(description=__doc__)
32
+ parser.add_argument(
33
+ "model",
34
+ nargs="?",
35
+ default=DEFAULT_MODEL,
36
+ help="model name, Hub repository ID, or local directory (default: %(default)s)",
37
+ )
38
+ parser.add_argument("--revision", help="Hub revision; prefer a pinned commit")
39
+ parser.add_argument(
40
+ "--cache-dir", type=Path, help="Hugging Face snapshot cache directory"
41
+ )
42
+ parser.add_argument(
43
+ "--local-files-only",
44
+ action="store_true",
45
+ help="resolve Hub assets only from the local cache",
46
+ )
47
+ parser.add_argument("--text", required=True)
48
+ parser.add_argument("--output", required=True, type=Path)
49
+ parser.add_argument("--max-duration-s", type=float, default=24.0)
50
+ parser.add_argument("--threads", type=int, default=8)
51
+ parser.add_argument("--device", choices=("cpu", "cuda", "auto"), default="cpu")
52
+ parser.add_argument("--device-index", type=int, default=0)
53
+ parser.add_argument("--compute-type", default="default")
54
+ parser.add_argument(
55
+ "--gpu-expert-cache-gb",
56
+ type=_cache_budget,
57
+ help='GPU expert cache: decimal GB, "auto", or "uncapped"; defaults to environment or auto',
58
+ )
59
+ parser.add_argument("--temperature", type=float, default=0.7)
60
+ parser.add_argument("--top-k", type=int, default=30)
61
+ parser.add_argument("--top-p", type=float, default=0.7)
62
+ parser.add_argument("--seed", type=int, default=0)
63
+ parser.add_argument(
64
+ "--no-split-sentences",
65
+ action="store_true",
66
+ help="diagnostic single-pass generation",
67
+ )
68
+ parser.add_argument(
69
+ "--greedy",
70
+ action="store_true",
71
+ help="diagnostic greedy decoding instead of sampling",
72
+ )
73
+ parser.add_argument(
74
+ "--directed-spans",
75
+ action="store_true",
76
+ help="opt-in Drama 3 cue/turn splitting and bounded breathing events",
77
+ )
78
+ args = parser.parse_args()
79
+ if args.directed_spans and args.no_split_sentences:
80
+ parser.error("--directed-spans requires sentence splitting")
81
+ engine_options = {}
82
+ if args.gpu_expert_cache_gb is not None:
83
+ engine_options["gpu_expert_cache_gb"] = args.gpu_expert_cache_gb
84
+ model = Model(
85
+ args.model,
86
+ intra_threads=args.threads,
87
+ compute_type=args.compute_type,
88
+ device=args.device,
89
+ device_index=args.device_index,
90
+ revision=args.revision,
91
+ cache_dir=args.cache_dir,
92
+ local_files_only=args.local_files_only,
93
+ engine_options=engine_options or None,
94
+ )
95
+ started = time.perf_counter()
96
+ result = model.synthesize(
97
+ args.text,
98
+ max_duration_s=args.max_duration_s,
99
+ temperature=args.temperature,
100
+ top_k=args.top_k,
101
+ top_p=args.top_p,
102
+ seed=args.seed,
103
+ greedy=args.greedy,
104
+ split_sentences=not args.no_split_sentences,
105
+ **({"engine_options": {"directed_spans": True}} if args.directed_spans else {}),
106
+ )
107
+ inference_seconds = time.perf_counter() - started
108
+ _save_wav(result, args.output)
109
+ print(
110
+ f"Saved {result.audio.size / result.sample_rate:.3f}s to {args.output}; "
111
+ f"finish_reason={result.finish_reason}; decoding={'greedy' if args.greedy else 'sampled'}"
112
+ )
113
+ print(f"Inference time (excluding model loading): {inference_seconds:.3f}s")
114
+ if result.finish_reason != "eos":
115
+ raise SystemExit(
116
+ "Incomplete synthesis: frame limit reached; the saved audio may be truncated."
117
+ )
118
+
119
+
120
+ if __name__ == "__main__":
121
+ main()
@@ -0,0 +1,82 @@
1
+ """Engine registration and shared helpers; engine-specific code lives in engines/."""
2
+
3
+ from copy import deepcopy
4
+ from importlib import import_module
5
+
6
+ import numpy as np
7
+
8
+ _ENGINE_REGISTRY: dict[str, type] = {}
9
+
10
+
11
+ def register_engine(model_type):
12
+ """Decorator that registers a :class:`BaseModel` subclass by ``model_type``.
13
+
14
+ ``model_type`` is matched against the ``model_type`` field of the converted
15
+ checkpoint's ``config.json``. Re-registering an existing key replaces the
16
+ previous binding so engines can be reloaded during tests.
17
+ """
18
+ if not isinstance(model_type, str) or not model_type:
19
+ raise ValueError("model_type must be a nonempty string")
20
+
21
+ def decorator(cls):
22
+ _ENGINE_REGISTRY[model_type] = cls
23
+ cls.engine_model_type = model_type
24
+ return cls
25
+
26
+ return decorator
27
+
28
+
29
+ def get_engine_class(model_type):
30
+ """Look up the engine class previously registered for ``model_type``."""
31
+ import_module(".engines.drama3", __package__)
32
+ if not isinstance(model_type, str):
33
+ raise ValueError("Converted checkpoint must specify a string model_type") # noqa: TRY004 - invalid checkpoint data
34
+ try:
35
+ return _ENGINE_REGISTRY[model_type]
36
+ except KeyError:
37
+ raise ValueError(
38
+ f"No CT2 TTS engine registered for model_type={model_type!r}; "
39
+ f"known engines: {sorted(_ENGINE_REGISTRY)}"
40
+ ) from None
41
+
42
+
43
+ def _positive_int(value, name):
44
+ if (
45
+ not isinstance(value, int)
46
+ or isinstance(value, bool)
47
+ or not 1 <= value <= np.iinfo(np.int32).max
48
+ ):
49
+ raise ValueError(f"{name} must be a positive int32 integer")
50
+
51
+
52
+ class BaseModel:
53
+ """Shared surface across CT2 TTS engines.
54
+
55
+ Subclasses set ``self._info`` in their constructor and implement
56
+ ``validate_checkpoint(directory, config)`` and ``synthesize``. Constructors
57
+ receive a resolved directory, checkpoint metadata, and the public loader's
58
+ settings. Engine-specific knobs are declared in ``init_options`` and
59
+ ``synthesize_options`` as frozensets of accepted keys; the dispatcher
60
+ validates ``engine_options`` against these and raises ``ValueError`` on
61
+ unknown keys. Return a SynthesisResult with engine-specific codec
62
+ dimensions. Declare supported features in ``capabilities``. The base class
63
+ adds engine identity and capabilities to an independent copy of ``_info``.
64
+ """
65
+
66
+ engine_model_type: str
67
+ capabilities = ("synthesis",)
68
+ init_options: frozenset = frozenset()
69
+ synthesize_options: frozenset = frozenset()
70
+
71
+ def info(self):
72
+ """Copy engine metadata and add its registered identity and capabilities.
73
+
74
+ Returns:
75
+ dict: Independent deep copy of `_info`, plus `engine` and
76
+ `capabilities`. Public fields are documented in `Model.info`.
77
+ """
78
+ return dict(
79
+ deepcopy(self._info),
80
+ engine=self.engine_model_type,
81
+ capabilities=self.capabilities,
82
+ )