Shusek00 commited on
Commit
c37ff1f
·
verified ·
1 Parent(s): 4e3d23a

Publish Kokoro PL/DE model catalog v1.0.0

Browse files
.gitattributes CHANGED
@@ -1,35 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
  *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
1
  *.bin filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
2
  *.onnx filter=lfs diff=lfs merge=lfs -text
3
+ *.wav filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .DS_Store
2
+ :memory:.ses
3
+ __pycache__/
4
+ *.pyc
5
+ node_modules/
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ pipeline_tag: text-to-speech
4
+ library_name: onnxruntime
5
+ language:
6
+ - pl
7
+ - de
8
+ base_model: hexgrad/Kokoro-82M
9
+ tags:
10
+ - kokoro
11
+ - onnx
12
+ - on-device
13
+ - kotlin-multiplatform
14
+ - android
15
+ - jvm
16
+ - web
17
+ ---
18
+
19
+ # Kokoro PL/DE for ONNX Runtime and Kotlin Multiplatform
20
+
21
+ Native Polish and German Kokoro-82M language packs prepared for on-device inference with ONNX
22
+ Runtime. The repository is organized for a client that first chooses a language and then a voice.
23
+ Each language can be downloaded independently; users do not need to fetch both packs.
24
+
25
+ ## Available packs
26
+
27
+ | Language | Model | Voice | Model size | Phonemizer | Voice |
28
+ |---|---|---|---:|---:|---:|
29
+ | Polish (`pl-PL`) | `kokoro-pl-fp32` | Mateusz (male) | 310.5 MiB | 6.8 MiB | 510 KiB |
30
+ | German (`de-DE`) | `kokoro-de-fp32` | Anna (female) | 310.5 MiB | 6.8 MiB | 510 KiB |
31
+
32
+ The complete downloadable pack for one language is about 318 MiB plus tiny JSON metadata. Models
33
+ are FP32 to preserve quality; no quantized variant is included in version 1.0.0.
34
+
35
+ ## Client flow
36
+
37
+ 1. Fetch [`catalog.json`](catalog.json) from a pinned Hub revision such as `v1.0.0`.
38
+ 2. Show `languages`, then show the selected language's `voices`.
39
+ 3. Download only that language's `model.artifact`, `phonemizer.artifact`, the selected voice
40
+ `artifact`, and the shared 2.3 KiB tokenizer config.
41
+ 4. Verify `sizeBytes` and `sha256` before activating a download.
42
+ 5. Resolve artifact paths relative to the catalog URL, so a tagged catalog automatically keeps all
43
+ files on the same immutable release.
44
+
45
+ The JSON format is versioned and described by
46
+ [`schema/kokoro-catalog.schema.json`](schema/kokoro-catalog.schema.json). New voices can be added to
47
+ one language without changing the application API.
48
+
49
+ ## Inference contract
50
+
51
+ The ONNX graph takes:
52
+
53
+ - `input_ids`: `int64[1, sequence_length]`, with token `0` added at both ends;
54
+ - `style`: `float32[1, 256]`;
55
+ - `speed`: `float32[1]`.
56
+
57
+ It returns a mono `float32` waveform at 24 kHz and an `int64` duration for every input token.
58
+ Batch size is fixed to one; sequence length is dynamic up to 510 phoneme Unicode code points plus
59
+ the two boundary tokens.
60
+
61
+ Voice files are raw little-endian `float32[510, 256]` matrices. Select style row
62
+ `phonemeCodePointCount - 1`. Count Unicode code points, not Kotlin/JVM UTF-16 code units; combining
63
+ marks such as the nasalization mark in `ɔ̃` count separately.
64
+
65
+ ## Phonemization
66
+
67
+ The included 6.8 MiB `.bin` files are neural Protophone data for
68
+ [`Phonemis`](https://github.com/IgorSwat/Phonemis). They produce Kokoro-compatible IPA for Polish
69
+ and German without shipping eSpeak or GPL components. Phonemis itself is a small C++20 library and
70
+ is not embedded in these model-data files.
71
+
72
+ Android/JVM consumers can expose Phonemis through a shared native bridge. A Web client needs a
73
+ WebAssembly build of that bridge; this repository provides the same language data but does not ship
74
+ a JavaScript/Wasm wrapper.
75
+
76
+ ## Validation
77
+
78
+ Both graphs pass ONNX validation, ONNX Runtime 1.29 CPU inference, and real
79
+ `onnxruntime-web 1.29.0` WebAssembly inference. Export validation compares each graph with its
80
+ PyTorch checkpoint. Token durations match exactly; waveform correlation is `0.9948` for Polish and
81
+ `0.9970` for German. Kokoro intentionally injects a small random excitation noise, so waveforms are
82
+ not expected to be bit-identical across runs.
83
+
84
+ Four natural-language runtime cases plus explicit 1-, 128-, and 510-phoneme boundary cases for
85
+ both languages are recorded in
86
+ [`validation/package-validation.json`](validation/package-validation.json). Generated previews are
87
+ available as [`samples/pl-mateusz.wav`](samples/pl-mateusz.wav) and
88
+ [`samples/de-anna.wav`](samples/de-anna.wav).
89
+
90
+ To repeat the local checks after rebuilding the graphs:
91
+
92
+ ```bash
93
+ python scripts/build_catalog.py
94
+ python scripts/validate_package.py
95
+ npm install
96
+ npm run validate:web
97
+ ```
98
+
99
+ Maintainers can publish the fully validated folder after authenticating with the Hugging Face CLI:
100
+
101
+ ```bash
102
+ python scripts/publish_hf.py <namespace>/kokoro-kmp-models
103
+ ```
104
+
105
+ The publisher excludes local dependencies and refuses to overwrite an existing immutable release
106
+ tag with a different commit.
107
+
108
+ ## Sources and reproducibility
109
+
110
+ All source repositories, immutable revisions, and source checksums are pinned in
111
+ [`SOURCES.lock.json`](SOURCES.lock.json). `scripts/export_onnx.py` exports a self-contained opset 17
112
+ graph and rejects outputs that fail ONNX validation or diverge materially from PyTorch.
113
+
114
+ ## Limitations
115
+
116
+ - Version 1.0.0 contains one native voice per language.
117
+ - Polish and German use separate fine-tuned model weights; a voice file cannot turn one language
118
+ model into the other.
119
+ - Text normalization and pronunciation still need application-level tests for names,
120
+ abbreviations, mixed-language text, and unusual numbers.
121
+ - The graphs are intentionally unquantized and prioritize quality over download size.
122
+
123
+ ## License
124
+
125
+ Kokoro, the fine-tuned checkpoints, the converted ONNX graphs, speaker embeddings, and this
126
+ packaging remain under **Apache License 2.0**. The repository license has not been changed.
127
+
128
+ Phonemis assets remain under their upstream **MIT License**. See
129
+ [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) for exact provenance and license boundaries.
SOURCES.lock.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "kokoroBase": {
3
+ "repository": "hexgrad/Kokoro-82M",
4
+ "revision": "f3ff3571791e39611d31c381e3a41a3af07b4987",
5
+ "files": {
6
+ "config.json": "5abb01e2403b072bf03d04fde160443e209d7a0dad49a423be15196b9b43c17f"
7
+ }
8
+ },
9
+ "languageModels": {
10
+ "repository": "software-mansion/react-native-executorch-kokoro",
11
+ "revision": "3744b57964eab7df6e8c48f0b84badb29e14df07",
12
+ "files": {
13
+ "finetunes/kokoro_polish_converted.pth": "e3202dc4d1f6e65dddff8a8e8d2e091ce9a3a66cf989c6c581a81b2f1969af49",
14
+ "finetunes/kokoro_german_converted.pth": "b8b2ab322963e7662c6036035c76c34a6a5582f814917407c94d632a1c930f71",
15
+ "voices/pm_mateusz.bin": "dc8f2919ede945e6962310b5204b912e64689d5698c571822bf75ad704870cb4",
16
+ "voices/df_anna.bin": "d583ccff3cdca2f7fae535cb998ac07e9fcb90f09737b9a41fa2734ec44a8f0b"
17
+ }
18
+ },
19
+ "phonemis": {
20
+ "repository": "IgorSwat/Phonemis",
21
+ "revision": "71eb1ce33bd586d38cbac037843b8539d7829c3b",
22
+ "files": {
23
+ "data/pl/phonemizer_pl.bin": "ec85f4dc2c4ac7a72ff88b98b0664a4ed887bd15c0d5add2eb1d6a6ee05b73f2",
24
+ "data/de/phonemizer_de.bin": "4888dc7e54dc66098551555096562063364091fc246da0d057b629587bedaa0b"
25
+ }
26
+ },
27
+ "exporter": {
28
+ "repository": "SuvioMedia/kokoro",
29
+ "revision": "8ee8ec3179c38990dd0564fb703c4deb68be58e9"
30
+ }
31
+ }
THIRD_PARTY_NOTICES.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Third-party notices
2
+
3
+ This repository keeps the Kokoro licensing intact. Packaging and conversion do not replace or
4
+ relicense upstream work.
5
+
6
+ ## Kokoro and the Polish/German fine-tunes
7
+
8
+ - Base model and architecture: [`hexgrad/Kokoro-82M`](https://huggingface.co/hexgrad/Kokoro-82M),
9
+ revision `f3ff3571791e39611d31c381e3a41a3af07b4987`.
10
+ - Polish and German converted checkpoints and speaker embeddings:
11
+ [`software-mansion/react-native-executorch-kokoro`](https://huggingface.co/software-mansion/react-native-executorch-kokoro),
12
+ revision `3744b57964eab7df6e8c48f0b84badb29e14df07`.
13
+ - ONNX exporter: [`SuvioMedia/kokoro`](https://github.com/SuvioMedia/kokoro), revision
14
+ `8ee8ec3179c38990dd0564fb703c4deb68be58e9`.
15
+
16
+ These materials and the resulting ONNX model files are distributed under Apache License 2.0.
17
+ The license text is available in [`LICENSE`](LICENSE) and [`third-party/kokoro/LICENSE`](third-party/kokoro/LICENSE).
18
+
19
+ ## Phonemis
20
+
21
+ The files below come from [`IgorSwat/Phonemis`](https://github.com/IgorSwat/Phonemis), revision
22
+ `71eb1ce33bd586d38cbac037843b8539d7829c3b`:
23
+
24
+ - `phonemizers/pl/phonemizer_pl.bin`
25
+ - `phonemizers/de/phonemizer_de.bin`
26
+
27
+ Phonemis is distributed under the MIT License. Its unchanged license text is available in
28
+ [`third-party/phonemis/LICENSE`](third-party/phonemis/LICENSE). The MIT exception applies to
29
+ the Phonemis files; it does not alter the Apache-2.0 license of Kokoro.
30
+
31
+ Exact source file hashes are recorded in [`SOURCES.lock.json`](SOURCES.lock.json).
catalog.json ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "schema/kokoro-catalog.schema.json",
3
+ "schemaVersion": 1,
4
+ "catalogVersion": "1.0.0",
5
+ "distribution": {
6
+ "provider": "huggingface",
7
+ "artifactResolution": "relative-to-catalog",
8
+ "recommendedRevision": "v1.0.0"
9
+ },
10
+ "license": "Apache-2.0",
11
+ "modelFamily": "kokoro-82m",
12
+ "audio": {
13
+ "sampleRateHz": 24000,
14
+ "channels": 1,
15
+ "sampleType": "float32"
16
+ },
17
+ "runtime": {
18
+ "engine": "onnxruntime",
19
+ "opset": 17,
20
+ "batchSize": 1,
21
+ "maxPhonemeCodePoints": 510,
22
+ "durationToSamplesFactor": 600,
23
+ "inputs": [
24
+ {
25
+ "name": "input_ids",
26
+ "type": "int64",
27
+ "shape": [
28
+ 1,
29
+ "sequence_length"
30
+ ]
31
+ },
32
+ {
33
+ "name": "style",
34
+ "type": "float32",
35
+ "shape": [
36
+ 1,
37
+ 256
38
+ ]
39
+ },
40
+ {
41
+ "name": "speed",
42
+ "type": "float32",
43
+ "shape": [
44
+ 1
45
+ ]
46
+ }
47
+ ],
48
+ "outputs": [
49
+ {
50
+ "name": "waveform",
51
+ "type": "float32",
52
+ "shape": [
53
+ "num_samples"
54
+ ]
55
+ },
56
+ {
57
+ "name": "duration",
58
+ "type": "int64",
59
+ "shape": [
60
+ "sequence_length"
61
+ ]
62
+ }
63
+ ],
64
+ "tokenizer": {
65
+ "bosTokenId": 0,
66
+ "eosTokenId": 0,
67
+ "vocabularyField": "vocab",
68
+ "artifact": {
69
+ "path": "runtime/config.json",
70
+ "mediaType": "application/json",
71
+ "sizeBytes": 2351,
72
+ "sha256": "5abb01e2403b072bf03d04fde160443e209d7a0dad49a423be15196b9b43c17f"
73
+ }
74
+ },
75
+ "voiceStyle": {
76
+ "format": "raw-float32-le",
77
+ "shape": [
78
+ 510,
79
+ 256
80
+ ],
81
+ "rowSelection": "phoneme_code_point_count_minus_one"
82
+ }
83
+ },
84
+ "languages": [
85
+ {
86
+ "id": "pl",
87
+ "locale": "pl-PL",
88
+ "displayName": "Polish",
89
+ "nativeName": "polski",
90
+ "model": {
91
+ "id": "kokoro-pl-fp32",
92
+ "format": "onnx",
93
+ "precision": "fp32",
94
+ "artifact": {
95
+ "path": "models/pl/kokoro-pl-fp32.onnx",
96
+ "mediaType": "application/onnx",
97
+ "sizeBytes": 325563781,
98
+ "sha256": "66637ea517e8ea4fea6c473c18a6f3b4fe982a8e7eb3879bb5f8250c7dc5679a"
99
+ },
100
+ "validation": {
101
+ "path": "models/pl/kokoro-pl-fp32.validation.json",
102
+ "mediaType": "application/json",
103
+ "sizeBytes": 1007,
104
+ "sha256": "63768a6dde6c8dabecec1ad673b3819c8d707f8e3cf565dda40950ec9c5c4476"
105
+ }
106
+ },
107
+ "phonemizer": {
108
+ "engine": "phonemis-protophone",
109
+ "language": "pl",
110
+ "license": "MIT",
111
+ "sourceRevision": "71eb1ce33bd586d38cbac037843b8539d7829c3b",
112
+ "artifact": {
113
+ "path": "phonemizers/pl/phonemizer_pl.bin",
114
+ "mediaType": "application/octet-stream",
115
+ "sizeBytes": 7094120,
116
+ "sha256": "ec85f4dc2c4ac7a72ff88b98b0664a4ed887bd15c0d5add2eb1d6a6ee05b73f2"
117
+ }
118
+ },
119
+ "voices": [
120
+ {
121
+ "id": "mateusz",
122
+ "displayName": "Mateusz",
123
+ "gender": "male",
124
+ "artifact": {
125
+ "path": "voices/pl/pm_mateusz.bin",
126
+ "mediaType": "application/octet-stream",
127
+ "sizeBytes": 522240,
128
+ "sha256": "dc8f2919ede945e6962310b5204b912e64689d5698c571822bf75ad704870cb4"
129
+ }
130
+ }
131
+ ],
132
+ "previewAudioPath": "samples/pl-mateusz.wav"
133
+ },
134
+ {
135
+ "id": "de",
136
+ "locale": "de-DE",
137
+ "displayName": "German",
138
+ "nativeName": "Deutsch",
139
+ "model": {
140
+ "id": "kokoro-de-fp32",
141
+ "format": "onnx",
142
+ "precision": "fp32",
143
+ "artifact": {
144
+ "path": "models/de/kokoro-de-fp32.onnx",
145
+ "mediaType": "application/onnx",
146
+ "sizeBytes": 325563781,
147
+ "sha256": "ecea710e0e4481d5930fc3486c5ed1c3dfd2dc752c2beb676135343d0236b1f3"
148
+ },
149
+ "validation": {
150
+ "path": "models/de/kokoro-de-fp32.validation.json",
151
+ "mediaType": "application/json",
152
+ "sizeBytes": 1142,
153
+ "sha256": "b9c7ff15e5be4824ff9e608d26ec21026d9e33ee3d4b430ad8ffecaa7af9e2d8"
154
+ }
155
+ },
156
+ "phonemizer": {
157
+ "engine": "phonemis-protophone",
158
+ "language": "de",
159
+ "license": "MIT",
160
+ "sourceRevision": "71eb1ce33bd586d38cbac037843b8539d7829c3b",
161
+ "artifact": {
162
+ "path": "phonemizers/de/phonemizer_de.bin",
163
+ "mediaType": "application/octet-stream",
164
+ "sizeBytes": 7094120,
165
+ "sha256": "4888dc7e54dc66098551555096562063364091fc246da0d057b629587bedaa0b"
166
+ }
167
+ },
168
+ "voices": [
169
+ {
170
+ "id": "anna",
171
+ "displayName": "Anna",
172
+ "gender": "female",
173
+ "artifact": {
174
+ "path": "voices/de/df_anna.bin",
175
+ "mediaType": "application/octet-stream",
176
+ "sizeBytes": 522240,
177
+ "sha256": "d583ccff3cdca2f7fae535cb998ac07e9fcb90f09737b9a41fa2734ec44a8f0b"
178
+ }
179
+ }
180
+ ],
181
+ "previewAudioPath": "samples/de-anna.wav"
182
+ }
183
+ ]
184
+ }
catalog.template.json ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "schema/kokoro-catalog.schema.json",
3
+ "schemaVersion": 1,
4
+ "catalogVersion": "1.0.0",
5
+ "distribution": {
6
+ "provider": "huggingface",
7
+ "artifactResolution": "relative-to-catalog",
8
+ "recommendedRevision": "v1.0.0"
9
+ },
10
+ "license": "Apache-2.0",
11
+ "modelFamily": "kokoro-82m",
12
+ "audio": {
13
+ "sampleRateHz": 24000,
14
+ "channels": 1,
15
+ "sampleType": "float32"
16
+ },
17
+ "runtime": {
18
+ "engine": "onnxruntime",
19
+ "opset": 17,
20
+ "batchSize": 1,
21
+ "maxPhonemeCodePoints": 510,
22
+ "durationToSamplesFactor": 600,
23
+ "inputs": [
24
+ {
25
+ "name": "input_ids",
26
+ "type": "int64",
27
+ "shape": [1, "sequence_length"]
28
+ },
29
+ {
30
+ "name": "style",
31
+ "type": "float32",
32
+ "shape": [1, 256]
33
+ },
34
+ {
35
+ "name": "speed",
36
+ "type": "float32",
37
+ "shape": [1]
38
+ }
39
+ ],
40
+ "outputs": [
41
+ {
42
+ "name": "waveform",
43
+ "type": "float32",
44
+ "shape": ["num_samples"]
45
+ },
46
+ {
47
+ "name": "duration",
48
+ "type": "int64",
49
+ "shape": ["sequence_length"]
50
+ }
51
+ ],
52
+ "tokenizer": {
53
+ "bosTokenId": 0,
54
+ "eosTokenId": 0,
55
+ "vocabularyField": "vocab",
56
+ "artifact": {
57
+ "$artifact": {
58
+ "path": "runtime/config.json",
59
+ "mediaType": "application/json"
60
+ }
61
+ }
62
+ },
63
+ "voiceStyle": {
64
+ "format": "raw-float32-le",
65
+ "shape": [510, 256],
66
+ "rowSelection": "phoneme_code_point_count_minus_one"
67
+ }
68
+ },
69
+ "languages": [
70
+ {
71
+ "id": "pl",
72
+ "locale": "pl-PL",
73
+ "displayName": "Polish",
74
+ "nativeName": "polski",
75
+ "model": {
76
+ "id": "kokoro-pl-fp32",
77
+ "format": "onnx",
78
+ "precision": "fp32",
79
+ "artifact": {
80
+ "$artifact": {
81
+ "path": "models/pl/kokoro-pl-fp32.onnx",
82
+ "mediaType": "application/onnx"
83
+ }
84
+ },
85
+ "validation": {
86
+ "$artifact": {
87
+ "path": "models/pl/kokoro-pl-fp32.validation.json",
88
+ "mediaType": "application/json"
89
+ }
90
+ }
91
+ },
92
+ "phonemizer": {
93
+ "engine": "phonemis-protophone",
94
+ "language": "pl",
95
+ "license": "MIT",
96
+ "sourceRevision": "71eb1ce33bd586d38cbac037843b8539d7829c3b",
97
+ "artifact": {
98
+ "$artifact": {
99
+ "path": "phonemizers/pl/phonemizer_pl.bin",
100
+ "mediaType": "application/octet-stream"
101
+ }
102
+ }
103
+ },
104
+ "voices": [
105
+ {
106
+ "id": "mateusz",
107
+ "displayName": "Mateusz",
108
+ "gender": "male",
109
+ "artifact": {
110
+ "$artifact": {
111
+ "path": "voices/pl/pm_mateusz.bin",
112
+ "mediaType": "application/octet-stream"
113
+ }
114
+ }
115
+ }
116
+ ],
117
+ "previewAudioPath": "samples/pl-mateusz.wav"
118
+ },
119
+ {
120
+ "id": "de",
121
+ "locale": "de-DE",
122
+ "displayName": "German",
123
+ "nativeName": "Deutsch",
124
+ "model": {
125
+ "id": "kokoro-de-fp32",
126
+ "format": "onnx",
127
+ "precision": "fp32",
128
+ "artifact": {
129
+ "$artifact": {
130
+ "path": "models/de/kokoro-de-fp32.onnx",
131
+ "mediaType": "application/onnx"
132
+ }
133
+ },
134
+ "validation": {
135
+ "$artifact": {
136
+ "path": "models/de/kokoro-de-fp32.validation.json",
137
+ "mediaType": "application/json"
138
+ }
139
+ }
140
+ },
141
+ "phonemizer": {
142
+ "engine": "phonemis-protophone",
143
+ "language": "de",
144
+ "license": "MIT",
145
+ "sourceRevision": "71eb1ce33bd586d38cbac037843b8539d7829c3b",
146
+ "artifact": {
147
+ "$artifact": {
148
+ "path": "phonemizers/de/phonemizer_de.bin",
149
+ "mediaType": "application/octet-stream"
150
+ }
151
+ }
152
+ },
153
+ "voices": [
154
+ {
155
+ "id": "anna",
156
+ "displayName": "Anna",
157
+ "gender": "female",
158
+ "artifact": {
159
+ "$artifact": {
160
+ "path": "voices/de/df_anna.bin",
161
+ "mediaType": "application/octet-stream"
162
+ }
163
+ }
164
+ }
165
+ ],
166
+ "previewAudioPath": "samples/de-anna.wav"
167
+ }
168
+ ]
169
+ }
models/de/kokoro-de-fp32.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ecea710e0e4481d5930fc3486c5ed1c3dfd2dc752c2beb676135343d0236b1f3
3
+ size 325563781
models/de/kokoro-de-fp32.validation.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "language": "de-DE",
3
+ "model": "kokoro-de-fp32.onnx",
4
+ "modelBytes": 325563781,
5
+ "modelSha256": "ecea710e0e4481d5930fc3486c5ed1c3dfd2dc752c2beb676135343d0236b1f3",
6
+ "sourceCheckpoint": "kokoro_german_converted.pth",
7
+ "sourceCheckpointSha256": "b8b2ab322963e7662c6036035c76c34a6a5582f814917407c94d632a1c930f71",
8
+ "sourceRevision": "3744b57964eab7df6e8c48f0b84badb29e14df07",
9
+ "baseRevision": "f3ff3571791e39611d31c381e3a41a3af07b4987",
10
+ "opset": 17,
11
+ "sampleRateHz": 24000,
12
+ "voice": "df_anna.bin",
13
+ "voiceSha256": "d583ccff3cdca2f7fae535cb998ac07e9fcb90f09737b9a41fa2734ec44a8f0b",
14
+ "referencePhonemes": "f\u02c8al\u0283\u0259s \u02c8y\u02d0b\u0259n f\u0254n ks\u02c8y\u02d0lo\u02d0f\u02cc\u0254nmu\u02d0z\u02cci\u02d0k kv\u02c8\u025blt j\u02c8e\u02d0d\u0259n \u0261\u027e\u02c8\u00f8\u02d0s\u0259r\u0259n \u02a6v\u02c8\u025b\u027ek.",
15
+ "referenceInputTokens": 75,
16
+ "referenceOutputSamples": 90600,
17
+ "maxAbsoluteError": 0.06611382961273193,
18
+ "meanAbsoluteError": 0.0024777022190392017,
19
+ "waveformCorrelation": 0.996961705255154,
20
+ "durationExactMatch": true,
21
+ "onnxRuntimeVersion": "1.29.0",
22
+ "torchVersion": "2.6.0"
23
+ }
models/pl/kokoro-pl-fp32.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:66637ea517e8ea4fea6c473c18a6f3b4fe982a8e7eb3879bb5f8250c7dc5679a
3
+ size 325563781
models/pl/kokoro-pl-fp32.validation.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "language": "pl-PL",
3
+ "model": "kokoro-pl-fp32.onnx",
4
+ "modelBytes": 325563781,
5
+ "modelSha256": "66637ea517e8ea4fea6c473c18a6f3b4fe982a8e7eb3879bb5f8250c7dc5679a",
6
+ "sourceCheckpoint": "kokoro_polish_converted.pth",
7
+ "sourceCheckpointSha256": "e3202dc4d1f6e65dddff8a8e8d2e091ce9a3a66cf989c6c581a81b2f1969af49",
8
+ "sourceRevision": "3744b57964eab7df6e8c48f0b84badb29e14df07",
9
+ "baseRevision": "f3ff3571791e39611d31c381e3a41a3af07b4987",
10
+ "opset": 17,
11
+ "sampleRateHz": 24000,
12
+ "voice": "pm_mateusz.bin",
13
+ "voiceSha256": "dc8f2919ede945e6962310b5204b912e64689d5698c571822bf75ad704870cb4",
14
+ "referencePhonemes": "z\u02c8a\u0292uwd\u0292 \u0261\u02c8\u025b\u0272\u0255l\u0254\u0303 j\u02c8a\u0292\u0272.",
15
+ "referenceInputTokens": 26,
16
+ "referenceOutputSamples": 49800,
17
+ "maxAbsoluteError": 0.07184462249279022,
18
+ "meanAbsoluteError": 0.0013195093488320708,
19
+ "waveformCorrelation": 0.9947539311111714,
20
+ "durationExactMatch": true,
21
+ "onnxRuntimeVersion": "1.29.0",
22
+ "torchVersion": "2.6.0"
23
+ }
package-lock.json ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "kokoro-kmp-model-validation",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "kokoro-kmp-model-validation",
9
+ "version": "1.0.0",
10
+ "devDependencies": {
11
+ "onnxruntime-web": "1.29.0"
12
+ }
13
+ },
14
+ "node_modules/@protobufjs/aspromise": {
15
+ "version": "1.1.2",
16
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
17
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
18
+ "dev": true,
19
+ "license": "BSD-3-Clause"
20
+ },
21
+ "node_modules/@protobufjs/base64": {
22
+ "version": "1.1.2",
23
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
24
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
25
+ "dev": true,
26
+ "license": "BSD-3-Clause"
27
+ },
28
+ "node_modules/@protobufjs/codegen": {
29
+ "version": "2.0.5",
30
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
31
+ "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
32
+ "dev": true,
33
+ "license": "BSD-3-Clause"
34
+ },
35
+ "node_modules/@protobufjs/eventemitter": {
36
+ "version": "1.1.1",
37
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
38
+ "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
39
+ "dev": true,
40
+ "license": "BSD-3-Clause"
41
+ },
42
+ "node_modules/@protobufjs/fetch": {
43
+ "version": "1.1.1",
44
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
45
+ "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
46
+ "dev": true,
47
+ "license": "BSD-3-Clause",
48
+ "dependencies": {
49
+ "@protobufjs/aspromise": "^1.1.1"
50
+ }
51
+ },
52
+ "node_modules/@protobufjs/float": {
53
+ "version": "1.0.2",
54
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
55
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
56
+ "dev": true,
57
+ "license": "BSD-3-Clause"
58
+ },
59
+ "node_modules/@protobufjs/path": {
60
+ "version": "1.1.2",
61
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
62
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
63
+ "dev": true,
64
+ "license": "BSD-3-Clause"
65
+ },
66
+ "node_modules/@protobufjs/pool": {
67
+ "version": "1.1.0",
68
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
69
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
70
+ "dev": true,
71
+ "license": "BSD-3-Clause"
72
+ },
73
+ "node_modules/@protobufjs/utf8": {
74
+ "version": "1.1.2",
75
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz",
76
+ "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==",
77
+ "dev": true,
78
+ "license": "BSD-3-Clause"
79
+ },
80
+ "node_modules/@types/node": {
81
+ "version": "26.4.1",
82
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz",
83
+ "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==",
84
+ "dev": true,
85
+ "license": "MIT",
86
+ "dependencies": {
87
+ "undici-types": "~8.3.0"
88
+ }
89
+ },
90
+ "node_modules/flatbuffers": {
91
+ "version": "25.9.23",
92
+ "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz",
93
+ "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==",
94
+ "dev": true,
95
+ "license": "Apache-2.0"
96
+ },
97
+ "node_modules/guid-typescript": {
98
+ "version": "1.0.9",
99
+ "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
100
+ "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
101
+ "dev": true,
102
+ "license": "ISC"
103
+ },
104
+ "node_modules/long": {
105
+ "version": "5.3.2",
106
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
107
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
108
+ "dev": true,
109
+ "license": "Apache-2.0"
110
+ },
111
+ "node_modules/onnxruntime-common": {
112
+ "version": "1.29.0",
113
+ "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.29.0.tgz",
114
+ "integrity": "sha512-/F63/e2VJoaVXGGNu6S5QH7jivBThGO95OzAVXXQ8hTta/b1QxI8udHa6cI3+3mAb5WWIIaMMwfZw01oivjJ1g==",
115
+ "dev": true,
116
+ "license": "MIT"
117
+ },
118
+ "node_modules/onnxruntime-web": {
119
+ "version": "1.29.0",
120
+ "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.29.0.tgz",
121
+ "integrity": "sha512-LuQlpX6MFLJZu756erwUeb1mNfoJGbs1kzDwJGNlf5RvfYMdqhcY3vNpDPK40CUV2HoWTkIj+uS0o36GFHjeYw==",
122
+ "dev": true,
123
+ "license": "MIT",
124
+ "dependencies": {
125
+ "flatbuffers": "^25.1.24",
126
+ "guid-typescript": "^1.0.9",
127
+ "long": "^5.2.3",
128
+ "onnxruntime-common": "1.29.0",
129
+ "platform": "^1.3.6",
130
+ "protobufjs": "^7.2.4"
131
+ }
132
+ },
133
+ "node_modules/platform": {
134
+ "version": "1.3.6",
135
+ "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
136
+ "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
137
+ "dev": true,
138
+ "license": "MIT"
139
+ },
140
+ "node_modules/protobufjs": {
141
+ "version": "7.6.6",
142
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz",
143
+ "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==",
144
+ "dev": true,
145
+ "hasInstallScript": true,
146
+ "license": "BSD-3-Clause",
147
+ "dependencies": {
148
+ "@protobufjs/aspromise": "^1.1.2",
149
+ "@protobufjs/base64": "^1.1.2",
150
+ "@protobufjs/codegen": "^2.0.5",
151
+ "@protobufjs/eventemitter": "^1.1.1",
152
+ "@protobufjs/fetch": "^1.1.1",
153
+ "@protobufjs/float": "^1.0.2",
154
+ "@protobufjs/path": "^1.1.2",
155
+ "@protobufjs/pool": "^1.1.0",
156
+ "@protobufjs/utf8": "^1.1.1",
157
+ "@types/node": ">=13.7.0",
158
+ "long": "^5.3.2"
159
+ },
160
+ "engines": {
161
+ "node": ">=12.0.0"
162
+ }
163
+ },
164
+ "node_modules/undici-types": {
165
+ "version": "8.3.0",
166
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
167
+ "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
168
+ "dev": true,
169
+ "license": "MIT"
170
+ }
171
+ }
172
+ }
package.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "kokoro-kmp-model-validation",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "validate:web": "node scripts/validate_web.cjs pl && node scripts/validate_web.cjs de",
7
+ "validate:web:pl": "node scripts/validate_web.cjs pl",
8
+ "validate:web:de": "node scripts/validate_web.cjs de"
9
+ },
10
+ "devDependencies": {
11
+ "onnxruntime-web": "1.29.0"
12
+ }
13
+ }
phonemizers/de/phonemizer_de.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4888dc7e54dc66098551555096562063364091fc246da0d057b629587bedaa0b
3
+ size 7094120
phonemizers/pl/phonemizer_pl.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec85f4dc2c4ac7a72ff88b98b0664a4ed887bd15c0d5add2eb1d6a6ee05b73f2
3
+ size 7094120
requirements-export.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ numpy==2.5.2
2
+ huggingface-hub==0.36.2
3
+ jsonschema==4.26.0
4
+ onnx==1.19.0
5
+ onnxruntime==1.29.0
6
+ soundfile==0.13.1
7
+ torch==2.6.0
runtime/config.json ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "istftnet": {
3
+ "upsample_kernel_sizes": [20, 12],
4
+ "upsample_rates": [10, 6],
5
+ "gen_istft_hop_size": 5,
6
+ "gen_istft_n_fft": 20,
7
+ "resblock_dilation_sizes": [
8
+ [1, 3, 5],
9
+ [1, 3, 5],
10
+ [1, 3, 5]
11
+ ],
12
+ "resblock_kernel_sizes": [3, 7, 11],
13
+ "upsample_initial_channel": 512
14
+ },
15
+ "dim_in": 64,
16
+ "dropout": 0.2,
17
+ "hidden_dim": 512,
18
+ "max_conv_dim": 512,
19
+ "max_dur": 50,
20
+ "multispeaker": true,
21
+ "n_layer": 3,
22
+ "n_mels": 80,
23
+ "n_token": 178,
24
+ "style_dim": 128,
25
+ "text_encoder_kernel_size": 5,
26
+ "plbert": {
27
+ "hidden_size": 768,
28
+ "num_attention_heads": 12,
29
+ "intermediate_size": 2048,
30
+ "max_position_embeddings": 512,
31
+ "num_hidden_layers": 12,
32
+ "dropout": 0.1
33
+ },
34
+ "vocab": {
35
+ ";": 1,
36
+ ":": 2,
37
+ ",": 3,
38
+ ".": 4,
39
+ "!": 5,
40
+ "?": 6,
41
+ "—": 9,
42
+ "…": 10,
43
+ "\"": 11,
44
+ "(": 12,
45
+ ")": 13,
46
+ "“": 14,
47
+ "”": 15,
48
+ " ": 16,
49
+ "\u0303": 17,
50
+ "ʣ": 18,
51
+ "ʥ": 19,
52
+ "ʦ": 20,
53
+ "ʨ": 21,
54
+ "ᵝ": 22,
55
+ "\uAB67": 23,
56
+ "A": 24,
57
+ "I": 25,
58
+ "O": 31,
59
+ "Q": 33,
60
+ "S": 35,
61
+ "T": 36,
62
+ "W": 39,
63
+ "Y": 41,
64
+ "ᵊ": 42,
65
+ "a": 43,
66
+ "b": 44,
67
+ "c": 45,
68
+ "d": 46,
69
+ "e": 47,
70
+ "f": 48,
71
+ "h": 50,
72
+ "i": 51,
73
+ "j": 52,
74
+ "k": 53,
75
+ "l": 54,
76
+ "m": 55,
77
+ "n": 56,
78
+ "o": 57,
79
+ "p": 58,
80
+ "q": 59,
81
+ "r": 60,
82
+ "s": 61,
83
+ "t": 62,
84
+ "u": 63,
85
+ "v": 64,
86
+ "w": 65,
87
+ "x": 66,
88
+ "y": 67,
89
+ "z": 68,
90
+ "ɑ": 69,
91
+ "ɐ": 70,
92
+ "ɒ": 71,
93
+ "æ": 72,
94
+ "β": 75,
95
+ "ɔ": 76,
96
+ "ɕ": 77,
97
+ "ç": 78,
98
+ "ɖ": 80,
99
+ "ð": 81,
100
+ "ʤ": 82,
101
+ "ə": 83,
102
+ "ɚ": 85,
103
+ "ɛ": 86,
104
+ "ɜ": 87,
105
+ "ɟ": 90,
106
+ "ɡ": 92,
107
+ "ɥ": 99,
108
+ "ɨ": 101,
109
+ "ɪ": 102,
110
+ "ʝ": 103,
111
+ "ɯ": 110,
112
+ "ɰ": 111,
113
+ "ŋ": 112,
114
+ "ɳ": 113,
115
+ "ɲ": 114,
116
+ "ɴ": 115,
117
+ "ø": 116,
118
+ "ɸ": 118,
119
+ "θ": 119,
120
+ "œ": 120,
121
+ "ɹ": 123,
122
+ "ɾ": 125,
123
+ "ɻ": 126,
124
+ "ʁ": 128,
125
+ "ɽ": 129,
126
+ "ʂ": 130,
127
+ "ʃ": 131,
128
+ "ʈ": 132,
129
+ "ʧ": 133,
130
+ "ʊ": 135,
131
+ "ʋ": 136,
132
+ "ʌ": 138,
133
+ "ɣ": 139,
134
+ "ɤ": 140,
135
+ "χ": 142,
136
+ "ʎ": 143,
137
+ "ʒ": 147,
138
+ "ʔ": 148,
139
+ "ˈ": 156,
140
+ "ˌ": 157,
141
+ "ː": 158,
142
+ "ʰ": 162,
143
+ "ʲ": 164,
144
+ "↓": 169,
145
+ "→": 171,
146
+ "↗": 172,
147
+ "↘": 173,
148
+ "ᵻ": 177
149
+ }
150
+ }
samples/de-anna.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ab75032aeb5be87b1844fb7008c58c0bda40ca52f907ed356466e11abb4adcab
3
+ size 180044
samples/pl-mateusz.wav ADDED
Binary file (98.4 kB). View file
 
schema/kokoro-catalog.schema.json ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "Kokoro KMP model catalog",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": [
7
+ "schemaVersion",
8
+ "catalogVersion",
9
+ "distribution",
10
+ "license",
11
+ "modelFamily",
12
+ "audio",
13
+ "runtime",
14
+ "languages"
15
+ ],
16
+ "properties": {
17
+ "$schema": { "type": "string" },
18
+ "schemaVersion": { "const": 1 },
19
+ "catalogVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" },
20
+ "distribution": {
21
+ "type": "object",
22
+ "additionalProperties": false,
23
+ "required": ["provider", "artifactResolution", "recommendedRevision"],
24
+ "properties": {
25
+ "provider": { "const": "huggingface" },
26
+ "artifactResolution": { "const": "relative-to-catalog" },
27
+ "recommendedRevision": { "type": "string", "minLength": 1 }
28
+ }
29
+ },
30
+ "license": { "const": "Apache-2.0" },
31
+ "modelFamily": { "const": "kokoro-82m" },
32
+ "audio": {
33
+ "type": "object",
34
+ "additionalProperties": false,
35
+ "required": ["sampleRateHz", "channels", "sampleType"],
36
+ "properties": {
37
+ "sampleRateHz": { "const": 24000 },
38
+ "channels": { "const": 1 },
39
+ "sampleType": { "const": "float32" }
40
+ }
41
+ },
42
+ "runtime": {
43
+ "type": "object",
44
+ "additionalProperties": true,
45
+ "required": [
46
+ "engine",
47
+ "opset",
48
+ "batchSize",
49
+ "maxPhonemeCodePoints",
50
+ "inputs",
51
+ "outputs",
52
+ "tokenizer",
53
+ "voiceStyle"
54
+ ],
55
+ "properties": {
56
+ "engine": { "const": "onnxruntime" },
57
+ "opset": { "type": "integer", "minimum": 1 },
58
+ "batchSize": { "const": 1 },
59
+ "maxPhonemeCodePoints": { "type": "integer", "minimum": 1 },
60
+ "tokenizer": {
61
+ "type": "object",
62
+ "required": ["bosTokenId", "eosTokenId", "vocabularyField", "artifact"],
63
+ "properties": {
64
+ "artifact": { "$ref": "#/$defs/artifact" }
65
+ }
66
+ },
67
+ "voiceStyle": {
68
+ "type": "object",
69
+ "required": ["format", "shape", "rowSelection"]
70
+ }
71
+ }
72
+ },
73
+ "languages": {
74
+ "type": "array",
75
+ "minItems": 1,
76
+ "items": { "$ref": "#/$defs/language" }
77
+ }
78
+ },
79
+ "$defs": {
80
+ "artifact": {
81
+ "type": "object",
82
+ "additionalProperties": false,
83
+ "required": ["path", "mediaType", "sizeBytes", "sha256"],
84
+ "properties": {
85
+ "path": {
86
+ "type": "string",
87
+ "minLength": 1,
88
+ "not": { "pattern": "(^/|(^|/)\\.\\.(/|$))" }
89
+ },
90
+ "mediaType": { "type": "string", "minLength": 1 },
91
+ "sizeBytes": { "type": "integer", "minimum": 0 },
92
+ "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
93
+ }
94
+ },
95
+ "voice": {
96
+ "type": "object",
97
+ "additionalProperties": false,
98
+ "required": ["id", "displayName", "gender", "artifact"],
99
+ "properties": {
100
+ "id": { "type": "string", "pattern": "^[a-z0-9_-]+$" },
101
+ "displayName": { "type": "string", "minLength": 1 },
102
+ "gender": { "enum": ["female", "male", "neutral", "unknown"] },
103
+ "artifact": { "$ref": "#/$defs/artifact" }
104
+ }
105
+ },
106
+ "language": {
107
+ "type": "object",
108
+ "additionalProperties": false,
109
+ "required": [
110
+ "id",
111
+ "locale",
112
+ "displayName",
113
+ "nativeName",
114
+ "model",
115
+ "phonemizer",
116
+ "voices",
117
+ "previewAudioPath"
118
+ ],
119
+ "properties": {
120
+ "id": { "type": "string", "pattern": "^[a-z]{2,3}$" },
121
+ "locale": { "type": "string", "minLength": 2 },
122
+ "displayName": { "type": "string", "minLength": 1 },
123
+ "nativeName": { "type": "string", "minLength": 1 },
124
+ "model": {
125
+ "type": "object",
126
+ "additionalProperties": false,
127
+ "required": ["id", "format", "precision", "artifact", "validation"],
128
+ "properties": {
129
+ "id": { "type": "string", "minLength": 1 },
130
+ "format": { "const": "onnx" },
131
+ "precision": { "enum": ["fp32", "fp16", "int8"] },
132
+ "artifact": { "$ref": "#/$defs/artifact" },
133
+ "validation": { "$ref": "#/$defs/artifact" }
134
+ }
135
+ },
136
+ "phonemizer": {
137
+ "type": "object",
138
+ "additionalProperties": false,
139
+ "required": ["engine", "language", "license", "sourceRevision", "artifact"],
140
+ "properties": {
141
+ "engine": { "const": "phonemis-protophone" },
142
+ "language": { "type": "string", "minLength": 2 },
143
+ "license": { "const": "MIT" },
144
+ "sourceRevision": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
145
+ "artifact": { "$ref": "#/$defs/artifact" }
146
+ }
147
+ },
148
+ "voices": {
149
+ "type": "array",
150
+ "minItems": 1,
151
+ "items": { "$ref": "#/$defs/voice" }
152
+ },
153
+ "previewAudioPath": { "type": "string", "minLength": 1 }
154
+ }
155
+ }
156
+ }
157
+ }
scripts/build_catalog.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Hydrate catalog.template.json with immutable artifact sizes and checksums."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+
12
+ ROOT = Path(__file__).resolve().parents[1]
13
+
14
+
15
+ def sha256(path: Path) -> str:
16
+ digest = hashlib.sha256()
17
+ with path.open("rb") as source:
18
+ for block in iter(lambda: source.read(1024 * 1024), b""):
19
+ digest.update(block)
20
+ return digest.hexdigest()
21
+
22
+
23
+ def hydrate(value: Any) -> Any:
24
+ if isinstance(value, list):
25
+ return [hydrate(item) for item in value]
26
+ if not isinstance(value, dict):
27
+ return value
28
+ if "$artifact" in value:
29
+ if len(value) != 1 or not isinstance(value["$artifact"], dict):
30
+ raise ValueError("$artifact must be the only key and contain an object")
31
+ artifact = dict(value["$artifact"])
32
+ relative_path = Path(artifact["path"])
33
+ if relative_path.is_absolute() or ".." in relative_path.parts:
34
+ raise ValueError(f"Artifact path must stay inside the repository: {relative_path}")
35
+ path = ROOT / relative_path
36
+ if not path.is_file():
37
+ raise FileNotFoundError(path)
38
+ artifact["sizeBytes"] = path.stat().st_size
39
+ artifact["sha256"] = sha256(path)
40
+ return artifact
41
+ return {key: hydrate(item) for key, item in value.items()}
42
+
43
+
44
+ template = json.loads((ROOT / "catalog.template.json").read_text(encoding="utf-8"))
45
+ catalog = hydrate(template)
46
+ (ROOT / "catalog.json").write_text(
47
+ json.dumps(catalog, ensure_ascii=False, indent=2) + "\n",
48
+ encoding="utf-8",
49
+ )
50
+ print(ROOT / "catalog.json")
scripts/export_onnx.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Export a Kokoro checkpoint to a self-contained ONNX model and verify it."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import json
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+ import onnx
13
+ import onnxruntime as ort
14
+ import torch
15
+
16
+ from kokoro import KModel
17
+ from kokoro.model import KModelForONNX
18
+
19
+
20
+ def sha256(path: Path) -> str:
21
+ digest = hashlib.sha256()
22
+ with path.open("rb") as source:
23
+ for block in iter(lambda: source.read(1024 * 1024), b""):
24
+ digest.update(block)
25
+ return digest.hexdigest()
26
+
27
+
28
+ def make_reference_inputs(
29
+ model: KModel,
30
+ voice: Path,
31
+ phonemes: str,
32
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
33
+ missing = sorted({phoneme for phoneme in phonemes if model.vocab.get(phoneme) is None})
34
+ if missing:
35
+ raise RuntimeError(f"Reference phonemes missing from the Kokoro vocabulary: {missing}")
36
+ if not 1 <= len(phonemes) <= 510:
37
+ raise RuntimeError(f"Reference phoneme length must be in 1..510, got {len(phonemes)}")
38
+
39
+ voice_pack = np.fromfile(voice, dtype=np.float32)
40
+ if voice_pack.size != 510 * 256:
41
+ raise RuntimeError(
42
+ f"Expected a raw 510x256 float32 voice pack, got {voice_pack.size} values"
43
+ )
44
+ voice_pack = voice_pack.reshape(510, 256)
45
+
46
+ token_ids = [int(model.vocab[phoneme]) for phoneme in phonemes]
47
+ input_ids = torch.tensor([[0, *token_ids, 0]], dtype=torch.long)
48
+ style = torch.from_numpy(voice_pack[len(phonemes) - 1]).unsqueeze(0)
49
+ speed = torch.tensor([1.0], dtype=torch.float32)
50
+ return input_ids, style, speed
51
+
52
+
53
+ def add_metadata(
54
+ output: Path,
55
+ *,
56
+ language: str,
57
+ checkpoint: Path,
58
+ source_revision: str,
59
+ base_revision: str,
60
+ ) -> None:
61
+ model = onnx.load(output)
62
+ onnx.helper.set_model_props(
63
+ model,
64
+ {
65
+ "architecture": "Kokoro-82M",
66
+ "language": language,
67
+ "license": "Apache-2.0",
68
+ "sample_rate_hz": "24000",
69
+ "source_repository": "software-mansion/react-native-executorch-kokoro",
70
+ "source_revision": source_revision,
71
+ "source_checkpoint": checkpoint.name,
72
+ "source_checkpoint_sha256": sha256(checkpoint),
73
+ "base_repository": "hexgrad/Kokoro-82M",
74
+ "base_revision": base_revision,
75
+ },
76
+ )
77
+ onnx.save_model(model, output)
78
+
79
+
80
+ def export(args: argparse.Namespace) -> None:
81
+ checkpoint = args.checkpoint.resolve()
82
+ config = args.config.resolve()
83
+ voice = args.voice.resolve()
84
+ output = args.output.resolve()
85
+ output.parent.mkdir(parents=True, exist_ok=True)
86
+
87
+ torch.manual_seed(0)
88
+ model = KModel(
89
+ repo_id="hexgrad/Kokoro-82M",
90
+ config=str(config),
91
+ model=str(checkpoint),
92
+ disable_complex=True,
93
+ ).cpu().eval()
94
+ export_model = KModelForONNX(model).cpu().eval()
95
+ input_ids, style, speed = make_reference_inputs(
96
+ model,
97
+ voice,
98
+ args.reference_phonemes,
99
+ )
100
+
101
+ torch.onnx.export(
102
+ export_model,
103
+ args=(input_ids, style, speed),
104
+ f=str(output),
105
+ input_names=["input_ids", "style", "speed"],
106
+ output_names=["waveform", "duration"],
107
+ opset_version=17,
108
+ dynamic_axes={
109
+ "input_ids": {1: "sequence_length"},
110
+ "waveform": {0: "num_samples"},
111
+ "duration": {0: "sequence_length"},
112
+ },
113
+ do_constant_folding=True,
114
+ dynamo=False,
115
+ )
116
+
117
+ add_metadata(
118
+ output,
119
+ language=args.language,
120
+ checkpoint=checkpoint,
121
+ source_revision=args.source_revision,
122
+ base_revision=args.base_revision,
123
+ )
124
+ onnx.checker.check_model(str(output), full_check=True)
125
+
126
+ options = ort.SessionOptions()
127
+ options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
128
+ session = ort.InferenceSession(str(output), sess_options=options, providers=["CPUExecutionProvider"])
129
+
130
+ # Kokoro deliberately injects a small amount of random excitation noise. As a
131
+ # result, PyTorch and ONNX waveforms should be highly correlated, but are not
132
+ # expected to be bit-identical or pass a point-wise allclose assertion.
133
+ torch.manual_seed(0)
134
+ with torch.inference_mode():
135
+ torch_waveform, torch_duration = export_model(input_ids, style, speed)
136
+ ort_waveform, ort_duration = session.run(
137
+ None,
138
+ {
139
+ "input_ids": input_ids.numpy(),
140
+ "style": style.numpy(),
141
+ "speed": speed.numpy(),
142
+ },
143
+ )
144
+
145
+ expected_waveform = torch_waveform.detach().cpu().numpy()
146
+ expected_duration = torch_duration.detach().cpu().numpy()
147
+ np.testing.assert_array_equal(ort_duration, expected_duration)
148
+ if not np.isfinite(ort_waveform).all():
149
+ raise RuntimeError("ONNX Runtime produced non-finite waveform samples")
150
+
151
+ delta = np.abs(ort_waveform - expected_waveform)
152
+ correlation = float(np.corrcoef(ort_waveform, expected_waveform)[0, 1])
153
+ mean_absolute_error = float(delta.mean())
154
+ if correlation < 0.99 or mean_absolute_error > 0.005:
155
+ raise RuntimeError(
156
+ "ONNX/PyTorch validation failed: "
157
+ f"correlation={correlation:.6f}, meanAbsoluteError={mean_absolute_error:.6f}"
158
+ )
159
+ validation = {
160
+ "language": args.language,
161
+ "model": output.name,
162
+ "modelBytes": output.stat().st_size,
163
+ "modelSha256": sha256(output),
164
+ "sourceCheckpoint": checkpoint.name,
165
+ "sourceCheckpointSha256": sha256(checkpoint),
166
+ "sourceRevision": args.source_revision,
167
+ "baseRevision": args.base_revision,
168
+ "opset": 17,
169
+ "sampleRateHz": 24000,
170
+ "voice": voice.name,
171
+ "voiceSha256": sha256(voice),
172
+ "referencePhonemes": args.reference_phonemes,
173
+ "referenceInputTokens": int(input_ids.shape[1]),
174
+ "referenceOutputSamples": int(ort_waveform.size),
175
+ "maxAbsoluteError": float(delta.max(initial=0.0)),
176
+ "meanAbsoluteError": mean_absolute_error,
177
+ "waveformCorrelation": correlation,
178
+ "durationExactMatch": True,
179
+ "onnxRuntimeVersion": ort.__version__,
180
+ "torchVersion": torch.__version__,
181
+ }
182
+ validation_path = output.with_suffix(".validation.json")
183
+ validation_path.write_text(json.dumps(validation, indent=2) + "\n", encoding="utf-8")
184
+ print(json.dumps(validation, indent=2))
185
+
186
+
187
+ def parse_args() -> argparse.Namespace:
188
+ parser = argparse.ArgumentParser()
189
+ parser.add_argument("--checkpoint", type=Path, required=True)
190
+ parser.add_argument("--config", type=Path, required=True)
191
+ parser.add_argument("--voice", type=Path, required=True)
192
+ parser.add_argument("--output", type=Path, required=True)
193
+ parser.add_argument("--language", required=True)
194
+ parser.add_argument("--reference-phonemes", required=True)
195
+ parser.add_argument("--source-revision", required=True)
196
+ parser.add_argument("--base-revision", required=True)
197
+ return parser.parse_args()
198
+
199
+
200
+ if __name__ == "__main__":
201
+ export(parse_args())
scripts/publish_hf.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Validate and publish this folder as a public Hugging Face model repository."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ from huggingface_hub import HfApi
12
+
13
+
14
+ ROOT = Path(__file__).resolve().parents[1]
15
+ EXPECTED_REPOSITORY_NAME = "kokoro-kmp-models"
16
+
17
+
18
+ def run_validation(skip_web: bool) -> None:
19
+ subprocess.run([sys.executable, ROOT / "scripts/build_catalog.py"], check=True, cwd=ROOT)
20
+ subprocess.run([sys.executable, ROOT / "scripts/validate_package.py"], check=True, cwd=ROOT)
21
+ if not skip_web:
22
+ subprocess.run(["npm", "run", "validate:web"], check=True, cwd=ROOT)
23
+
24
+
25
+ def parse_args() -> argparse.Namespace:
26
+ parser = argparse.ArgumentParser()
27
+ parser.add_argument("repo_id", help="Hugging Face repo, e.g. username/kokoro-kmp-models")
28
+ parser.add_argument("--tag", default="v1.0.0")
29
+ parser.add_argument("--skip-web-validation", action="store_true")
30
+ return parser.parse_args()
31
+
32
+
33
+ def main() -> None:
34
+ args = parse_args()
35
+ parts = args.repo_id.split("/")
36
+ if len(parts) != 2 or parts[1] != EXPECTED_REPOSITORY_NAME:
37
+ raise SystemExit(
38
+ f"repo_id must be <namespace>/{EXPECTED_REPOSITORY_NAME}; got {args.repo_id!r}"
39
+ )
40
+
41
+ run_validation(args.skip_web_validation)
42
+
43
+ api = HfApi()
44
+ identity = api.whoami()
45
+ writable_namespaces = {identity["name"]}
46
+ writable_namespaces.update(
47
+ org["name"]
48
+ for org in identity.get("orgs", [])
49
+ if org.get("roleInOrg") in {"admin", "write"}
50
+ )
51
+ if parts[0] not in writable_namespaces:
52
+ raise SystemExit(f"The authenticated account cannot publish to namespace {parts[0]!r}")
53
+
54
+ api.create_repo(args.repo_id, repo_type="model", private=False, exist_ok=True)
55
+ commit = api.upload_folder(
56
+ repo_id=args.repo_id,
57
+ repo_type="model",
58
+ folder_path=ROOT,
59
+ commit_message=f"Publish Kokoro PL/DE model catalog {args.tag}",
60
+ ignore_patterns=[
61
+ ".git/**",
62
+ "node_modules/**",
63
+ "**/__pycache__/**",
64
+ "**/*.pyc",
65
+ ":memory:.ses",
66
+ ],
67
+ )
68
+
69
+ tags = {tag.ref: tag.target_commit for tag in api.list_repo_refs(args.repo_id).tags}
70
+ tag_ref = f"refs/tags/{args.tag}"
71
+ existing_target = tags.get(tag_ref)
72
+ if existing_target and existing_target != commit.oid:
73
+ raise SystemExit(
74
+ f"Refusing to move immutable tag {args.tag}: it already points to {existing_target}"
75
+ )
76
+ if not existing_target:
77
+ api.create_tag(
78
+ args.repo_id,
79
+ repo_type="model",
80
+ tag=args.tag,
81
+ revision=commit.oid,
82
+ tag_message=f"Kokoro PL/DE catalog {args.tag}",
83
+ )
84
+ print(f"https://huggingface.co/{args.repo_id}/tree/{args.tag}")
85
+
86
+
87
+ if __name__ == "__main__":
88
+ main()
scripts/validate_package.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Validate every catalog artifact and exercise all ONNX language packs."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Any, Iterator
10
+
11
+ import numpy as np
12
+ import onnxruntime as ort
13
+ import soundfile as sf
14
+ from jsonschema import Draft202012Validator
15
+
16
+
17
+ ROOT = Path(__file__).resolve().parents[1]
18
+
19
+
20
+ def sha256(path: Path) -> str:
21
+ digest = hashlib.sha256()
22
+ with path.open("rb") as source:
23
+ for block in iter(lambda: source.read(1024 * 1024), b""):
24
+ digest.update(block)
25
+ return digest.hexdigest()
26
+
27
+
28
+ def artifacts(value: Any) -> Iterator[dict[str, Any]]:
29
+ if isinstance(value, list):
30
+ for item in value:
31
+ yield from artifacts(item)
32
+ elif isinstance(value, dict):
33
+ if {"path", "sizeBytes", "sha256"} <= value.keys():
34
+ yield value
35
+ for item in value.values():
36
+ yield from artifacts(item)
37
+
38
+
39
+ catalog = json.loads((ROOT / "catalog.json").read_text(encoding="utf-8"))
40
+ schema = json.loads(
41
+ (ROOT / "schema/kokoro-catalog.schema.json").read_text(encoding="utf-8")
42
+ )
43
+ Draft202012Validator.check_schema(schema)
44
+ Draft202012Validator(schema).validate(catalog)
45
+ for artifact in artifacts(catalog):
46
+ path = ROOT / artifact["path"]
47
+ if not path.is_file():
48
+ raise FileNotFoundError(path)
49
+ if path.stat().st_size != artifact["sizeBytes"]:
50
+ raise RuntimeError(f"Size mismatch: {path}")
51
+ if sha256(path) != artifact["sha256"]:
52
+ raise RuntimeError(f"SHA-256 mismatch: {path}")
53
+
54
+ tokenizer = catalog["runtime"]["tokenizer"]
55
+ config_artifact = tokenizer["artifact"]
56
+ config = json.loads((ROOT / config_artifact["path"]).read_text(encoding="utf-8"))
57
+ vocab = config[tokenizer["vocabularyField"]]
58
+ languages = {language["id"]: language for language in catalog["languages"]}
59
+ cases = json.loads((ROOT / "validation/cases.json").read_text(encoding="utf-8"))
60
+ sessions: dict[str, ort.InferenceSession] = {}
61
+ results = []
62
+
63
+ for case in cases:
64
+ language = languages[case["language"]]
65
+ voices = {voice["id"]: voice for voice in language["voices"]}
66
+ voice = voices[case["voice"]]
67
+ phonemes = case["phonemes"]
68
+ missing = sorted({phoneme for phoneme in phonemes if phoneme not in vocab})
69
+ if missing:
70
+ raise RuntimeError(f"{case['id']}: phonemes missing from vocabulary: {missing}")
71
+ if not 1 <= len(phonemes) <= catalog["runtime"]["maxPhonemeCodePoints"]:
72
+ raise RuntimeError(f"{case['id']}: invalid phoneme length {len(phonemes)}")
73
+
74
+ token_ids = [vocab[phoneme] for phoneme in phonemes]
75
+ input_ids = np.asarray([[0, *token_ids, 0]], dtype=np.int64)
76
+ voice_pack = np.fromfile(ROOT / voice["artifact"]["path"], dtype="<f4").reshape(510, 256)
77
+ style = voice_pack[len(phonemes) - 1].reshape(1, 256)
78
+ speed = np.asarray([1.0], dtype=np.float32)
79
+
80
+ model_path = language["model"]["artifact"]["path"]
81
+ if model_path not in sessions:
82
+ sessions[model_path] = ort.InferenceSession(
83
+ str(ROOT / model_path),
84
+ providers=["CPUExecutionProvider"],
85
+ )
86
+ waveform, duration = sessions[model_path].run(
87
+ None,
88
+ {"input_ids": input_ids, "style": style, "speed": speed},
89
+ )
90
+ if not np.isfinite(waveform).all():
91
+ raise RuntimeError(f"{case['id']}: non-finite waveform")
92
+ if duration.shape != (input_ids.shape[1],):
93
+ raise RuntimeError(f"{case['id']}: unexpected duration shape {duration.shape}")
94
+ expected_samples = int(duration.sum()) * catalog["runtime"]["durationToSamplesFactor"]
95
+ if waveform.shape != (expected_samples,):
96
+ raise RuntimeError(
97
+ f"{case['id']}: expected {expected_samples} samples, got {waveform.shape}"
98
+ )
99
+
100
+ if not case["id"].endswith("-dynamic"):
101
+ preview_path = ROOT / language["previewAudioPath"]
102
+ preview_path.parent.mkdir(parents=True, exist_ok=True)
103
+ sf.write(preview_path, waveform, catalog["audio"]["sampleRateHz"], subtype="PCM_16")
104
+
105
+ results.append(
106
+ {
107
+ "id": case["id"],
108
+ "phonemeCodePoints": len(phonemes),
109
+ "inputTokens": int(input_ids.shape[1]),
110
+ "outputSamples": int(waveform.size),
111
+ "durationSeconds": waveform.size / catalog["audio"]["sampleRateHz"],
112
+ "peakAbsoluteAmplitude": float(np.abs(waveform).max(initial=0.0)),
113
+ }
114
+ )
115
+
116
+ boundary_results = []
117
+ for language in catalog["languages"]:
118
+ voice = language["voices"][0]
119
+ voice_pack = np.fromfile(ROOT / voice["artifact"]["path"], dtype="<f4").reshape(510, 256)
120
+ model_path = language["model"]["artifact"]["path"]
121
+ session = sessions[model_path]
122
+ for phoneme_count in (1, 128, catalog["runtime"]["maxPhonemeCodePoints"]):
123
+ token_ids = [vocab["a"]] * phoneme_count
124
+ input_ids = np.asarray([[0, *token_ids, 0]], dtype=np.int64)
125
+ waveform, duration = session.run(
126
+ None,
127
+ {
128
+ "input_ids": input_ids,
129
+ "style": voice_pack[phoneme_count - 1].reshape(1, 256),
130
+ "speed": np.asarray([1.0], dtype=np.float32),
131
+ },
132
+ )
133
+ expected_samples = int(duration.sum()) * catalog["runtime"]["durationToSamplesFactor"]
134
+ if waveform.shape != (expected_samples,) or not np.isfinite(waveform).all():
135
+ raise RuntimeError(
136
+ f"{language['id']}: failed boundary case with {phoneme_count} phonemes"
137
+ )
138
+ boundary_results.append(
139
+ {
140
+ "language": language["id"],
141
+ "phonemeCodePoints": phoneme_count,
142
+ "inputTokens": int(input_ids.shape[1]),
143
+ "outputSamples": int(waveform.size),
144
+ }
145
+ )
146
+
147
+ report = {
148
+ "catalogVersion": catalog["catalogVersion"],
149
+ "catalogSchemaValidated": True,
150
+ "onnxRuntimeVersion": ort.__version__,
151
+ "artifactsVerified": sum(1 for _ in artifacts(catalog)),
152
+ "cases": results,
153
+ "boundaryCases": boundary_results,
154
+ }
155
+ (ROOT / "validation/package-validation.json").write_text(
156
+ json.dumps(report, ensure_ascii=False, indent=2) + "\n",
157
+ encoding="utf-8",
158
+ )
159
+ print(json.dumps(report, ensure_ascii=False, indent=2))
scripts/validate_web.cjs ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const ort = require('onnxruntime-web');
6
+
7
+ const root = path.resolve(__dirname, '..');
8
+ const languageId = process.argv[2];
9
+ if (!languageId) throw new Error('usage: node scripts/validate_web.cjs <pl|de>');
10
+
11
+ ort.env.wasm.numThreads = 1;
12
+ ort.env.wasm.proxy = false;
13
+
14
+ async function main() {
15
+ const catalog = JSON.parse(fs.readFileSync(path.join(root, 'catalog.json'), 'utf8'));
16
+ const cases = JSON.parse(fs.readFileSync(path.join(root, 'validation/cases.json'), 'utf8'));
17
+ const config = JSON.parse(fs.readFileSync(path.join(root, 'runtime/config.json'), 'utf8'));
18
+ const language = catalog.languages.find((item) => item.id === languageId);
19
+ const testCase = cases.find(
20
+ (item) => item.language === languageId && item.id.endsWith('-dynamic'),
21
+ );
22
+ if (!language || !testCase) throw new Error(`unknown language: ${languageId}`);
23
+ const voice = language.voices.find((item) => item.id === testCase.voice);
24
+
25
+ const phonemes = Array.from(testCase.phonemes);
26
+ const ids = [
27
+ 0,
28
+ ...phonemes.map((phoneme) => {
29
+ const id = config.vocab[phoneme];
30
+ if (id === undefined) throw new Error(`missing phoneme: ${phoneme}`);
31
+ return id;
32
+ }),
33
+ 0,
34
+ ];
35
+
36
+ const voiceBytes = fs.readFileSync(path.join(root, voice.artifact.path));
37
+ function styleAt(row) {
38
+ const styleOffset = row * 256 * 4;
39
+ const style = new Float32Array(256);
40
+ for (let index = 0; index < style.length; index += 1) {
41
+ style[index] = voiceBytes.readFloatLE(styleOffset + index * 4);
42
+ }
43
+ return style;
44
+ }
45
+
46
+ const modelBytes = fs.readFileSync(path.join(root, language.model.artifact.path));
47
+ const session = await ort.InferenceSession.create(modelBytes, {
48
+ executionProviders: ['wasm'],
49
+ graphOptimizationLevel: 'all',
50
+ });
51
+
52
+ async function validate(caseId, caseIds, style) {
53
+ const output = await session.run({
54
+ input_ids: new ort.Tensor(
55
+ 'int64',
56
+ BigInt64Array.from(caseIds.map(BigInt)),
57
+ [1, caseIds.length],
58
+ ),
59
+ style: new ort.Tensor('float32', style, [1, 256]),
60
+ speed: new ort.Tensor('float32', Float32Array.of(1), [1]),
61
+ });
62
+ const waveform = output.waveform.data;
63
+ const durationSum = Array.from(output.duration.data).reduce(
64
+ (sum, item) => sum + Number(item),
65
+ 0,
66
+ );
67
+ const expectedSamples = durationSum * catalog.runtime.durationToSamplesFactor;
68
+ if (waveform.length !== expectedSamples) {
69
+ throw new Error(`waveform/duration mismatch: ${waveform.length} vs ${expectedSamples}`);
70
+ }
71
+ let peak = 0;
72
+ for (const sample of waveform) {
73
+ if (!Number.isFinite(sample)) throw new Error('non-finite waveform');
74
+ peak = Math.max(peak, Math.abs(sample));
75
+ }
76
+ return {
77
+ id: caseId,
78
+ inputTokens: caseIds.length,
79
+ outputSamples: waveform.length,
80
+ peakAbsoluteAmplitude: peak,
81
+ };
82
+ }
83
+
84
+ const maxPhonemes = catalog.runtime.maxPhonemeCodePoints;
85
+ const maxIds = [0, ...Array(maxPhonemes).fill(config.vocab.a), 0];
86
+ const result = {
87
+ language: languageId,
88
+ onnxRuntimeWebVersion: JSON.parse(
89
+ fs.readFileSync(path.join(root, 'node_modules/onnxruntime-web/package.json'), 'utf8'),
90
+ ).version,
91
+ executionProvider: 'wasm',
92
+ cases: [
93
+ await validate(testCase.id, ids, styleAt(phonemes.length - 1)),
94
+ await validate('max-phoneme-boundary', maxIds, styleAt(maxPhonemes - 1)),
95
+ ],
96
+ };
97
+ fs.writeFileSync(
98
+ path.join(root, 'validation', `web-${languageId}.json`),
99
+ `${JSON.stringify(result, null, 2)}\n`,
100
+ );
101
+ console.log(JSON.stringify(result));
102
+ }
103
+
104
+ main().catch((error) => {
105
+ console.error(error);
106
+ process.exitCode = 1;
107
+ });
third-party/kokoro/LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
third-party/phonemis/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 IgorSwat
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.
validation/cases.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "pl-mateusz",
4
+ "language": "pl",
5
+ "voice": "mateusz",
6
+ "text": "Zażółć gęślą jaźń.",
7
+ "phonemes": "zˈaʒuwtɕ ɡˈɛɲɕlɔ̃ jˈaʒɲ."
8
+ },
9
+ {
10
+ "id": "pl-dynamic",
11
+ "language": "pl",
12
+ "voice": "mateusz",
13
+ "text": "W Szczebrzeszynie chrząszcz brzmi w trzcinie.",
14
+ "phonemes": "f ʃʧˌɛbʒɛʃˈɨɲʲɛ xʃˈɔ̃ʃʧ bʒmˈi f tʃtɕˈiɲʲɛ."
15
+ },
16
+ {
17
+ "id": "de-anna",
18
+ "language": "de",
19
+ "voice": "anna",
20
+ "text": "Falsches Üben von Xylophonmusik quält jeden größeren Zwerg.",
21
+ "phonemes": "fˈalʃəs ˈyːbən fˈɔn ksˈyːloːfˌɔnmuːzˌiːk kvˈɛlt jˈeːdən ɡɾˈøːsərən ʦvˈɛɾk."
22
+ },
23
+ {
24
+ "id": "de-dynamic",
25
+ "language": "de",
26
+ "voice": "anna",
27
+ "text": "Franz jagt im komplett verwahrlosten Taxi quer durch Bayern.",
28
+ "phonemes": "frˈanʦ jˈɑːkt ˈɪm kɔmplˈɛt fɛɾvˈɑːɾlɔstən tˈaksiː kvˈeːɾ dˈʊɐç bˈIɜn."
29
+ }
30
+ ]
validation/package-validation.json ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "catalogVersion": "1.0.0",
3
+ "catalogSchemaValidated": true,
4
+ "onnxRuntimeVersion": "1.29.0",
5
+ "artifactsVerified": 9,
6
+ "cases": [
7
+ {
8
+ "id": "pl-mateusz",
9
+ "phonemeCodePoints": 24,
10
+ "inputTokens": 26,
11
+ "outputSamples": 49200,
12
+ "durationSeconds": 2.05,
13
+ "peakAbsoluteAmplitude": 0.24918872117996216
14
+ },
15
+ {
16
+ "id": "pl-dynamic",
17
+ "phonemeCodePoints": 42,
18
+ "inputTokens": 44,
19
+ "outputSamples": 60000,
20
+ "durationSeconds": 2.5,
21
+ "peakAbsoluteAmplitude": 0.25136497616767883
22
+ },
23
+ {
24
+ "id": "de-anna",
25
+ "phonemeCodePoints": 74,
26
+ "inputTokens": 76,
27
+ "outputSamples": 90000,
28
+ "durationSeconds": 3.75,
29
+ "peakAbsoluteAmplitude": 0.35915330052375793
30
+ },
31
+ {
32
+ "id": "de-dynamic",
33
+ "phonemeCodePoints": 69,
34
+ "inputTokens": 71,
35
+ "outputSamples": 84000,
36
+ "durationSeconds": 3.5,
37
+ "peakAbsoluteAmplitude": 0.3485631048679352
38
+ }
39
+ ],
40
+ "boundaryCases": [
41
+ {
42
+ "language": "pl",
43
+ "phonemeCodePoints": 1,
44
+ "inputTokens": 3,
45
+ "outputSamples": 13800
46
+ },
47
+ {
48
+ "language": "pl",
49
+ "phonemeCodePoints": 128,
50
+ "inputTokens": 130,
51
+ "outputSamples": 177600
52
+ },
53
+ {
54
+ "language": "pl",
55
+ "phonemeCodePoints": 510,
56
+ "inputTokens": 512,
57
+ "outputSamples": 616800
58
+ },
59
+ {
60
+ "language": "de",
61
+ "phonemeCodePoints": 1,
62
+ "inputTokens": 3,
63
+ "outputSamples": 16200
64
+ },
65
+ {
66
+ "language": "de",
67
+ "phonemeCodePoints": 128,
68
+ "inputTokens": 130,
69
+ "outputSamples": 79800
70
+ },
71
+ {
72
+ "language": "de",
73
+ "phonemeCodePoints": 510,
74
+ "inputTokens": 512,
75
+ "outputSamples": 307200
76
+ }
77
+ ]
78
+ }
validation/web-de.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "language": "de",
3
+ "onnxRuntimeWebVersion": "1.29.0",
4
+ "executionProvider": "wasm",
5
+ "cases": [
6
+ {
7
+ "id": "de-dynamic",
8
+ "inputTokens": 71,
9
+ "outputSamples": 84000,
10
+ "peakAbsoluteAmplitude": 0.35137680172920227
11
+ },
12
+ {
13
+ "id": "max-phoneme-boundary",
14
+ "inputTokens": 512,
15
+ "outputSamples": 307200,
16
+ "peakAbsoluteAmplitude": 0.32699844241142273
17
+ }
18
+ ]
19
+ }
validation/web-pl.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "language": "pl",
3
+ "onnxRuntimeWebVersion": "1.29.0",
4
+ "executionProvider": "wasm",
5
+ "cases": [
6
+ {
7
+ "id": "pl-dynamic",
8
+ "inputTokens": 44,
9
+ "outputSamples": 60000,
10
+ "peakAbsoluteAmplitude": 0.2551245093345642
11
+ },
12
+ {
13
+ "id": "max-phoneme-boundary",
14
+ "inputTokens": 512,
15
+ "outputSamples": 616800,
16
+ "peakAbsoluteAmplitude": 0.16947251558303833
17
+ }
18
+ ]
19
+ }
voices/de/df_anna.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d583ccff3cdca2f7fae535cb998ac07e9fcb90f09737b9a41fa2734ec44a8f0b
3
+ size 522240
voices/pl/pm_mateusz.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dc8f2919ede945e6962310b5204b912e64689d5698c571822bf75ad704870cb4
3
+ size 522240