speakeronnx 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- speakeronnx-0.0.1/PKG-INFO +163 -0
- speakeronnx-0.0.1/README.md +141 -0
- speakeronnx-0.0.1/examples/basic_embedding.py +30 -0
- speakeronnx-0.0.1/examples/batch_enrollment.py +84 -0
- speakeronnx-0.0.1/examples/compare_models.py +52 -0
- speakeronnx-0.0.1/examples/custom_model.py +36 -0
- speakeronnx-0.0.1/examples/gpu_inference.py +37 -0
- speakeronnx-0.0.1/examples/verify_speakers.py +47 -0
- speakeronnx-0.0.1/pyproject.toml +53 -0
- speakeronnx-0.0.1/setup.cfg +4 -0
- speakeronnx-0.0.1/speakeronnx/__init__.py +86 -0
- speakeronnx-0.0.1/speakeronnx/__main__.py +73 -0
- speakeronnx-0.0.1/speakeronnx/__pycache__/version.cpython-314.pyc +0 -0
- speakeronnx-0.0.1/speakeronnx/embedder.py +605 -0
- speakeronnx-0.0.1/speakeronnx/version.py +8 -0
- speakeronnx-0.0.1/speakeronnx.egg-info/PKG-INFO +163 -0
- speakeronnx-0.0.1/speakeronnx.egg-info/SOURCES.txt +26 -0
- speakeronnx-0.0.1/speakeronnx.egg-info/dependency_links.txt +1 -0
- speakeronnx-0.0.1/speakeronnx.egg-info/entry_points.txt +2 -0
- speakeronnx-0.0.1/speakeronnx.egg-info/requires.txt +11 -0
- speakeronnx-0.0.1/speakeronnx.egg-info/top_level.txt +5 -0
- speakeronnx-0.0.1/tests/test_audio.py +179 -0
- speakeronnx-0.0.1/tests/test_cli.py +110 -0
- speakeronnx-0.0.1/tests/test_e2e.py +153 -0
- speakeronnx-0.0.1/tests/test_embedder.py +99 -0
- speakeronnx-0.0.1/tests/test_frontend.py +131 -0
- speakeronnx-0.0.1/tests/test_model_registry.py +132 -0
- speakeronnx-0.0.1/tests/test_unit.py +329 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: speakeronnx
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Pure-onnxruntime speaker embedding library — no torch at runtime
|
|
5
|
+
Author-email: JarbasAI <jarbasai@mailfence.com>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/TigreGotico/speakeronnx
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: onnxruntime
|
|
15
|
+
Requires-Dist: huggingface_hub
|
|
16
|
+
Provides-Extra: soxr
|
|
17
|
+
Requires-Dist: soxr; extra == "soxr"
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: pytest; extra == "test"
|
|
20
|
+
Requires-Dist: edge-tts; extra == "test"
|
|
21
|
+
Requires-Dist: requests; extra == "test"
|
|
22
|
+
|
|
23
|
+
# speakeronnx
|
|
24
|
+
|
|
25
|
+
Pure-onnxruntime speaker embedding library — no torch at runtime.
|
|
26
|
+
|
|
27
|
+
Extract speaker embeddings, compute cosine similarity, and verify speaker identity
|
|
28
|
+
using ONNX-exported models downloaded automatically from HuggingFace.
|
|
29
|
+
|
|
30
|
+
**Model collection:** [OpenVoiceOS/speaker-embeddings-onnx](https://huggingface.co/collections/OpenVoiceOS/speaker-embeddings-onnx)
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install speakeronnx
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Optional high-quality resampling:
|
|
39
|
+
```bash
|
|
40
|
+
pip install speakeronnx soxr
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from speakeronnx import SpeakerEmbedder, cosine, verify
|
|
47
|
+
|
|
48
|
+
embedder = SpeakerEmbedder(model="wespeaker-resnet34")
|
|
49
|
+
|
|
50
|
+
alice1 = embedder.embed("alice_clip1.wav")
|
|
51
|
+
alice2 = embedder.embed("alice_clip2.wav")
|
|
52
|
+
bob = embedder.embed("bob_clip1.wav")
|
|
53
|
+
|
|
54
|
+
print(cosine(alice1, alice2)) # e.g. 0.82 — same speaker
|
|
55
|
+
print(cosine(alice1, bob)) # e.g. 0.21 — different speaker
|
|
56
|
+
|
|
57
|
+
ok, score = verify(alice1, alice2, threshold=0.45)
|
|
58
|
+
print(ok, score) # True 0.82
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
More examples in [`examples/`](examples/).
|
|
62
|
+
|
|
63
|
+
## CLI
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
speakeronnx list # list available models
|
|
67
|
+
speakeronnx embed clip.wav # extract embedding
|
|
68
|
+
speakeronnx verify a.wav b.wav # same-speaker check (exit 0/1)
|
|
69
|
+
speakeronnx verify a.wav b.wav --threshold 0.5
|
|
70
|
+
speakeronnx embed clip.wav --model wespeaker-ecapa512
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Full CLI reference in [`docs/cli.md`](docs/cli.md).
|
|
74
|
+
|
|
75
|
+
## Models
|
|
76
|
+
|
|
77
|
+
All 9 models are registered in `MODEL_REGISTRY` and downloaded on first use:
|
|
78
|
+
|
|
79
|
+
| Alias | Embed dim | Frontend | License |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| `wespeaker-resnet34` | 256 | fbank80 | cc-by-4.0 |
|
|
82
|
+
| `wespeaker-ecapa512` | 192 | fbank80 | cc-by-4.0 |
|
|
83
|
+
| `wespeaker-resnet293` | 256 | fbank80 | cc-by-4.0 |
|
|
84
|
+
| `campplus` | 512 | fbank80 | cc-by-4.0 |
|
|
85
|
+
| `campplus-zh-en` | 192 | fbank80 | apache-2.0 |
|
|
86
|
+
| `eres2net` | 192 | fbank80 | apache-2.0 |
|
|
87
|
+
| `titanet-small` | 192 | fbank80 | cc-by-4.0 |
|
|
88
|
+
| `titanet-large` | 192 | fbank80 | cc-by-4.0 |
|
|
89
|
+
| `redimnet-b2` | 192 | raw | apache-2.0 |
|
|
90
|
+
|
|
91
|
+
Full model comparison and selection guide in [`docs/models.md`](docs/models.md).
|
|
92
|
+
|
|
93
|
+
## Documentation
|
|
94
|
+
|
|
95
|
+
| Document | Description |
|
|
96
|
+
|---|---|
|
|
97
|
+
| [`docs/index.md`](docs/index.md) | Full getting-started guide |
|
|
98
|
+
| [`docs/models.md`](docs/models.md) | Model comparison, selection, frontend/layout details |
|
|
99
|
+
| [`docs/api.md`](docs/api.md) | Complete API reference |
|
|
100
|
+
| [`docs/cli.md`](docs/cli.md) | CLI usage reference |
|
|
101
|
+
| [`docs/frontend.md`](docs/frontend.md) | Feature frontend (fbank80 vs raw) technical details |
|
|
102
|
+
| [`docs/advanced.md`](docs/advanced.md) | Custom models, GPU, threshold tuning |
|
|
103
|
+
|
|
104
|
+
## Examples
|
|
105
|
+
|
|
106
|
+
| Script | Description |
|
|
107
|
+
|---|---|
|
|
108
|
+
| [`examples/basic_embedding.py`](examples/basic_embedding.py) | Extract embedding from a single WAV |
|
|
109
|
+
| [`examples/verify_speakers.py`](examples/verify_speakers.py) | Verify two clips, try multiple thresholds |
|
|
110
|
+
| [`examples/compare_models.py`](examples/compare_models.py) | Compare all models on same utterances |
|
|
111
|
+
| [`examples/batch_enrollment.py`](examples/batch_enrollment.py) | Enroll speakers from directories, match unknown |
|
|
112
|
+
| [`examples/custom_model.py`](examples/custom_model.py) | Load a custom ONNX model from disk |
|
|
113
|
+
| [`examples/gpu_inference.py`](examples/gpu_inference.py) | CUDA / CoreML inference |
|
|
114
|
+
|
|
115
|
+
## Tests
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Unit tests (mocked, no downloads, no network)
|
|
119
|
+
pytest tests/test_unit.py tests/test_audio.py tests/test_frontend.py \
|
|
120
|
+
tests/test_embedder.py tests/test_cli.py tests/test_model_registry.py -v
|
|
121
|
+
| Alias | HF repo | License | Embed dim | Description |
|
|
122
|
+
|---|---|---|---|---|
|
|
123
|
+
| `wespeaker-resnet34` | [Wespeaker/wespeaker-voxceleb-resnet34-LM](https://huggingface.co/Wespeaker/wespeaker-voxceleb-resnet34-LM) | cc-by-4.0 | 256 | ResNet34 r-vector, VoxCeleb2 Dev — **recommended default** |
|
|
124
|
+
| `wespeaker-ecapa512` | [Wespeaker/wespeaker-ecapa-tdnn512-LM](https://huggingface.co/Wespeaker/wespeaker-ecapa-tdnn512-LM) | cc-by-4.0 | 192 | ECAPA-TDNN-512 x-vector, VoxCeleb2 Dev |
|
|
125
|
+
| `wespeaker-resnet293` | [Wespeaker/wespeaker-voxceleb-resnet293-LM](https://huggingface.co/Wespeaker/wespeaker-voxceleb-resnet293-LM) | cc-by-4.0 | 256 | ResNet293 r-vector — highest accuracy, 28M params |
|
|
126
|
+
| `campplus` | [csukuangfj/speaker-embedding-models](https://huggingface.co/csukuangfj/speaker-embedding-models) | cc-by-4.0 | 512 | CAM++ (D-TDNN backbone), VoxCeleb2 Dev |
|
|
127
|
+
| `campplus-zh-en` | [csukuangfj/speaker-embedding-models](https://huggingface.co/csukuangfj/speaker-embedding-models) | apache-2.0 | 192 | 3D-Speaker CAM++ multilingual (zh+en) |
|
|
128
|
+
| `eres2net` | [csukuangfj/speaker-embedding-models](https://huggingface.co/csukuangfj/speaker-embedding-models) | apache-2.0 | 192 | ERes2Net, VoxCeleb |
|
|
129
|
+
| `titanet-small` | [csukuangfj/speaker-embedding-models](https://huggingface.co/csukuangfj/speaker-embedding-models) | cc-by-4.0 | 192 | NVIDIA NeMo TitaNet-small (~40 MB) |
|
|
130
|
+
| `titanet-large` | [csukuangfj/speaker-embedding-models](https://huggingface.co/csukuangfj/speaker-embedding-models) | cc-by-4.0 | 192 | NVIDIA NeMo TitaNet-large (~101 MB) |
|
|
131
|
+
| `redimnet-b2` | [OpenVoiceOS/redimnet-b2-vox2-onnx](https://huggingface.co/OpenVoiceOS/redimnet-b2-vox2-onnx) | apache-2.0 | 192 | ReDimNet b2 (1.8M params), raw audio input |
|
|
132
|
+
|
|
133
|
+
# End-to-end tests (downloads models + generates TTS audio)
|
|
134
|
+
pytest tests/test_e2e.py -v -s
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Use `speakeronnx list` to print descriptions and metadata for all registered models.
|
|
138
|
+
|
|
139
|
+
## Feature frontend
|
|
140
|
+
|
|
141
|
+
- **fbank80** models: 80-dim log-Mel filterbank with per-utterance CMN,
|
|
142
|
+
implemented in pure numpy. See [`docs/frontend.md`](docs/frontend.md).
|
|
143
|
+
- **raw** models (redimnet-b2): raw 16 kHz waveform passed directly
|
|
144
|
+
to ONNX (internal MelSpectrogram in the model).
|
|
145
|
+
|
|
146
|
+
## Audio requirements
|
|
147
|
+
|
|
148
|
+
- Mono PCM WAV, any bit depth (8/16/24/32-bit int, 32-bit float)
|
|
149
|
+
- Any sample rate (resampled internally to 16 kHz)
|
|
150
|
+
- Stereo files are downmixed to mono
|
|
151
|
+
- Minimum ~1 second; recommended enrollment 5–30 seconds per speaker
|
|
152
|
+
|
|
153
|
+
## Dependencies
|
|
154
|
+
|
|
155
|
+
- `onnxruntime`
|
|
156
|
+
- `numpy`
|
|
157
|
+
- `huggingface_hub`
|
|
158
|
+
- `soxr` (optional, for high-quality resampling)
|
|
159
|
+
|
|
160
|
+
## Project links
|
|
161
|
+
|
|
162
|
+
- **GitHub:** [TigreGotico/speakeronnx](https://github.com/TigreGotico/speakeronnx)
|
|
163
|
+
- **PyPI:** `pip install speakeronnx`
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# speakeronnx
|
|
2
|
+
|
|
3
|
+
Pure-onnxruntime speaker embedding library — no torch at runtime.
|
|
4
|
+
|
|
5
|
+
Extract speaker embeddings, compute cosine similarity, and verify speaker identity
|
|
6
|
+
using ONNX-exported models downloaded automatically from HuggingFace.
|
|
7
|
+
|
|
8
|
+
**Model collection:** [OpenVoiceOS/speaker-embeddings-onnx](https://huggingface.co/collections/OpenVoiceOS/speaker-embeddings-onnx)
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install speakeronnx
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Optional high-quality resampling:
|
|
17
|
+
```bash
|
|
18
|
+
pip install speakeronnx soxr
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from speakeronnx import SpeakerEmbedder, cosine, verify
|
|
25
|
+
|
|
26
|
+
embedder = SpeakerEmbedder(model="wespeaker-resnet34")
|
|
27
|
+
|
|
28
|
+
alice1 = embedder.embed("alice_clip1.wav")
|
|
29
|
+
alice2 = embedder.embed("alice_clip2.wav")
|
|
30
|
+
bob = embedder.embed("bob_clip1.wav")
|
|
31
|
+
|
|
32
|
+
print(cosine(alice1, alice2)) # e.g. 0.82 — same speaker
|
|
33
|
+
print(cosine(alice1, bob)) # e.g. 0.21 — different speaker
|
|
34
|
+
|
|
35
|
+
ok, score = verify(alice1, alice2, threshold=0.45)
|
|
36
|
+
print(ok, score) # True 0.82
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
More examples in [`examples/`](examples/).
|
|
40
|
+
|
|
41
|
+
## CLI
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
speakeronnx list # list available models
|
|
45
|
+
speakeronnx embed clip.wav # extract embedding
|
|
46
|
+
speakeronnx verify a.wav b.wav # same-speaker check (exit 0/1)
|
|
47
|
+
speakeronnx verify a.wav b.wav --threshold 0.5
|
|
48
|
+
speakeronnx embed clip.wav --model wespeaker-ecapa512
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Full CLI reference in [`docs/cli.md`](docs/cli.md).
|
|
52
|
+
|
|
53
|
+
## Models
|
|
54
|
+
|
|
55
|
+
All 9 models are registered in `MODEL_REGISTRY` and downloaded on first use:
|
|
56
|
+
|
|
57
|
+
| Alias | Embed dim | Frontend | License |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `wespeaker-resnet34` | 256 | fbank80 | cc-by-4.0 |
|
|
60
|
+
| `wespeaker-ecapa512` | 192 | fbank80 | cc-by-4.0 |
|
|
61
|
+
| `wespeaker-resnet293` | 256 | fbank80 | cc-by-4.0 |
|
|
62
|
+
| `campplus` | 512 | fbank80 | cc-by-4.0 |
|
|
63
|
+
| `campplus-zh-en` | 192 | fbank80 | apache-2.0 |
|
|
64
|
+
| `eres2net` | 192 | fbank80 | apache-2.0 |
|
|
65
|
+
| `titanet-small` | 192 | fbank80 | cc-by-4.0 |
|
|
66
|
+
| `titanet-large` | 192 | fbank80 | cc-by-4.0 |
|
|
67
|
+
| `redimnet-b2` | 192 | raw | apache-2.0 |
|
|
68
|
+
|
|
69
|
+
Full model comparison and selection guide in [`docs/models.md`](docs/models.md).
|
|
70
|
+
|
|
71
|
+
## Documentation
|
|
72
|
+
|
|
73
|
+
| Document | Description |
|
|
74
|
+
|---|---|
|
|
75
|
+
| [`docs/index.md`](docs/index.md) | Full getting-started guide |
|
|
76
|
+
| [`docs/models.md`](docs/models.md) | Model comparison, selection, frontend/layout details |
|
|
77
|
+
| [`docs/api.md`](docs/api.md) | Complete API reference |
|
|
78
|
+
| [`docs/cli.md`](docs/cli.md) | CLI usage reference |
|
|
79
|
+
| [`docs/frontend.md`](docs/frontend.md) | Feature frontend (fbank80 vs raw) technical details |
|
|
80
|
+
| [`docs/advanced.md`](docs/advanced.md) | Custom models, GPU, threshold tuning |
|
|
81
|
+
|
|
82
|
+
## Examples
|
|
83
|
+
|
|
84
|
+
| Script | Description |
|
|
85
|
+
|---|---|
|
|
86
|
+
| [`examples/basic_embedding.py`](examples/basic_embedding.py) | Extract embedding from a single WAV |
|
|
87
|
+
| [`examples/verify_speakers.py`](examples/verify_speakers.py) | Verify two clips, try multiple thresholds |
|
|
88
|
+
| [`examples/compare_models.py`](examples/compare_models.py) | Compare all models on same utterances |
|
|
89
|
+
| [`examples/batch_enrollment.py`](examples/batch_enrollment.py) | Enroll speakers from directories, match unknown |
|
|
90
|
+
| [`examples/custom_model.py`](examples/custom_model.py) | Load a custom ONNX model from disk |
|
|
91
|
+
| [`examples/gpu_inference.py`](examples/gpu_inference.py) | CUDA / CoreML inference |
|
|
92
|
+
|
|
93
|
+
## Tests
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Unit tests (mocked, no downloads, no network)
|
|
97
|
+
pytest tests/test_unit.py tests/test_audio.py tests/test_frontend.py \
|
|
98
|
+
tests/test_embedder.py tests/test_cli.py tests/test_model_registry.py -v
|
|
99
|
+
| Alias | HF repo | License | Embed dim | Description |
|
|
100
|
+
|---|---|---|---|---|
|
|
101
|
+
| `wespeaker-resnet34` | [Wespeaker/wespeaker-voxceleb-resnet34-LM](https://huggingface.co/Wespeaker/wespeaker-voxceleb-resnet34-LM) | cc-by-4.0 | 256 | ResNet34 r-vector, VoxCeleb2 Dev — **recommended default** |
|
|
102
|
+
| `wespeaker-ecapa512` | [Wespeaker/wespeaker-ecapa-tdnn512-LM](https://huggingface.co/Wespeaker/wespeaker-ecapa-tdnn512-LM) | cc-by-4.0 | 192 | ECAPA-TDNN-512 x-vector, VoxCeleb2 Dev |
|
|
103
|
+
| `wespeaker-resnet293` | [Wespeaker/wespeaker-voxceleb-resnet293-LM](https://huggingface.co/Wespeaker/wespeaker-voxceleb-resnet293-LM) | cc-by-4.0 | 256 | ResNet293 r-vector — highest accuracy, 28M params |
|
|
104
|
+
| `campplus` | [csukuangfj/speaker-embedding-models](https://huggingface.co/csukuangfj/speaker-embedding-models) | cc-by-4.0 | 512 | CAM++ (D-TDNN backbone), VoxCeleb2 Dev |
|
|
105
|
+
| `campplus-zh-en` | [csukuangfj/speaker-embedding-models](https://huggingface.co/csukuangfj/speaker-embedding-models) | apache-2.0 | 192 | 3D-Speaker CAM++ multilingual (zh+en) |
|
|
106
|
+
| `eres2net` | [csukuangfj/speaker-embedding-models](https://huggingface.co/csukuangfj/speaker-embedding-models) | apache-2.0 | 192 | ERes2Net, VoxCeleb |
|
|
107
|
+
| `titanet-small` | [csukuangfj/speaker-embedding-models](https://huggingface.co/csukuangfj/speaker-embedding-models) | cc-by-4.0 | 192 | NVIDIA NeMo TitaNet-small (~40 MB) |
|
|
108
|
+
| `titanet-large` | [csukuangfj/speaker-embedding-models](https://huggingface.co/csukuangfj/speaker-embedding-models) | cc-by-4.0 | 192 | NVIDIA NeMo TitaNet-large (~101 MB) |
|
|
109
|
+
| `redimnet-b2` | [OpenVoiceOS/redimnet-b2-vox2-onnx](https://huggingface.co/OpenVoiceOS/redimnet-b2-vox2-onnx) | apache-2.0 | 192 | ReDimNet b2 (1.8M params), raw audio input |
|
|
110
|
+
|
|
111
|
+
# End-to-end tests (downloads models + generates TTS audio)
|
|
112
|
+
pytest tests/test_e2e.py -v -s
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Use `speakeronnx list` to print descriptions and metadata for all registered models.
|
|
116
|
+
|
|
117
|
+
## Feature frontend
|
|
118
|
+
|
|
119
|
+
- **fbank80** models: 80-dim log-Mel filterbank with per-utterance CMN,
|
|
120
|
+
implemented in pure numpy. See [`docs/frontend.md`](docs/frontend.md).
|
|
121
|
+
- **raw** models (redimnet-b2): raw 16 kHz waveform passed directly
|
|
122
|
+
to ONNX (internal MelSpectrogram in the model).
|
|
123
|
+
|
|
124
|
+
## Audio requirements
|
|
125
|
+
|
|
126
|
+
- Mono PCM WAV, any bit depth (8/16/24/32-bit int, 32-bit float)
|
|
127
|
+
- Any sample rate (resampled internally to 16 kHz)
|
|
128
|
+
- Stereo files are downmixed to mono
|
|
129
|
+
- Minimum ~1 second; recommended enrollment 5–30 seconds per speaker
|
|
130
|
+
|
|
131
|
+
## Dependencies
|
|
132
|
+
|
|
133
|
+
- `onnxruntime`
|
|
134
|
+
- `numpy`
|
|
135
|
+
- `huggingface_hub`
|
|
136
|
+
- `soxr` (optional, for high-quality resampling)
|
|
137
|
+
|
|
138
|
+
## Project links
|
|
139
|
+
|
|
140
|
+
- **GitHub:** [TigreGotico/speakeronnx](https://github.com/TigreGotico/speakeronnx)
|
|
141
|
+
- **PyPI:** `pip install speakeronnx`
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Basic speaker embedding extraction.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python examples/basic_embedding.py path/to/speaker.wav
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
import numpy as np
|
|
8
|
+
from speakeronnx import SpeakerEmbedder, cosine
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
if len(sys.argv) < 2:
|
|
13
|
+
print("Usage: python basic_embedding.py <wav_file> [model_alias]")
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
wav_path = sys.argv[1]
|
|
17
|
+
model_alias = sys.argv[2] if len(sys.argv) > 2 else "wespeaker-resnet34"
|
|
18
|
+
|
|
19
|
+
embedder = SpeakerEmbedder(model=model_alias)
|
|
20
|
+
embedding = embedder.embed(wav_path)
|
|
21
|
+
|
|
22
|
+
print(f"Model : {model_alias}")
|
|
23
|
+
print(f"Dim : {len(embedding)}")
|
|
24
|
+
print(f"Norm : {np.linalg.norm(embedding):.6f}")
|
|
25
|
+
print(f"First 8 values: {embedding[:8].tolist()}")
|
|
26
|
+
print(f"Self-cosine : {cosine(embedding, embedding):.6f} (should be 1.0)")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
main()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Batch speaker enrollment and verification.
|
|
2
|
+
|
|
3
|
+
Enroll multiple speakers from enrollment directories, then verify
|
|
4
|
+
unknown clips against the enrolled gallery.
|
|
5
|
+
|
|
6
|
+
Directory structure:
|
|
7
|
+
enrollments/
|
|
8
|
+
alice/
|
|
9
|
+
clip1.wav
|
|
10
|
+
clip2.wav
|
|
11
|
+
bob/
|
|
12
|
+
clip1.wav
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
python examples/batch_enrollment.py enrollments/ test_clip.wav
|
|
17
|
+
"""
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import numpy as np
|
|
21
|
+
from speakeronnx import SpeakerEmbedder, cosine
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def enroll_speakers(enroll_dir: str, embedder: SpeakerEmbedder):
|
|
25
|
+
speaker_embs = {}
|
|
26
|
+
|
|
27
|
+
for speaker in sorted(os.listdir(enroll_dir)):
|
|
28
|
+
spk_dir = os.path.join(enroll_dir, speaker)
|
|
29
|
+
if not os.path.isdir(spk_dir):
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
embeddings = []
|
|
33
|
+
for fname in sorted(os.listdir(spk_dir)):
|
|
34
|
+
if not fname.endswith(".wav"):
|
|
35
|
+
continue
|
|
36
|
+
path = os.path.join(spk_dir, fname)
|
|
37
|
+
emb = embedder.embed(path)
|
|
38
|
+
embeddings.append(emb)
|
|
39
|
+
print(f" Enrolled {speaker}/{fname} dim={len(emb)}")
|
|
40
|
+
|
|
41
|
+
if embeddings:
|
|
42
|
+
# Average enrollment embeddings (L2-norm the mean)
|
|
43
|
+
mean_emb = np.mean(embeddings, axis=0)
|
|
44
|
+
mean_emb /= np.linalg.norm(mean_emb)
|
|
45
|
+
speaker_embs[speaker] = mean_emb
|
|
46
|
+
print(f" → {speaker}: {len(embeddings)} clips, mean emb shape={mean_emb.shape}")
|
|
47
|
+
|
|
48
|
+
return speaker_embs
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def main():
|
|
52
|
+
if len(sys.argv) < 3:
|
|
53
|
+
print("Usage: python batch_enrollment.py <enroll_dir/> <test_clip.wav> [model_alias]")
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
enroll_dir = sys.argv[1]
|
|
57
|
+
test_wav = sys.argv[2]
|
|
58
|
+
model_alias = sys.argv[3] if len(sys.argv) > 3 else "wespeaker-resnet34"
|
|
59
|
+
|
|
60
|
+
embedder = SpeakerEmbedder(model=model_alias)
|
|
61
|
+
|
|
62
|
+
print(f"Enrolling speakers from {enroll_dir}/ ...")
|
|
63
|
+
gallery = enroll_speakers(enroll_dir, embedder)
|
|
64
|
+
|
|
65
|
+
print(f"\nVerifying {test_wav} against gallery ...")
|
|
66
|
+
test_emb = embedder.embed(test_wav)
|
|
67
|
+
|
|
68
|
+
results = []
|
|
69
|
+
for speaker, enroll_emb in gallery.items():
|
|
70
|
+
score = cosine(test_emb, enroll_emb)
|
|
71
|
+
results.append((score, speaker))
|
|
72
|
+
|
|
73
|
+
results.sort(key=lambda x: -x[0])
|
|
74
|
+
print(f"\n{'Speaker':<20} {'Score':>10}")
|
|
75
|
+
print("-" * 32)
|
|
76
|
+
for score, speaker in results:
|
|
77
|
+
print(f"{speaker:<20} {score:>10.6f}")
|
|
78
|
+
|
|
79
|
+
best = results[0]
|
|
80
|
+
print(f"\nBest match: {best[1]} (score={best[0]:.4f})")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Compare multiple models on the same audio files.
|
|
2
|
+
|
|
3
|
+
Shows how different models perform on the same pair of utterances.
|
|
4
|
+
Useful for selecting the right model for your data.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python examples/compare_models.py <same1.wav> <same2.wav> <diff1.wav>
|
|
8
|
+
"""
|
|
9
|
+
import sys
|
|
10
|
+
import numpy as np
|
|
11
|
+
from speakeronnx import SpeakerEmbedder, cosine, MODEL_REGISTRY
|
|
12
|
+
|
|
13
|
+
# Skip models that take raw audio (need different handling)
|
|
14
|
+
SKIP_RAW = True
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
if len(sys.argv) < 4:
|
|
19
|
+
print("Usage: python compare_models.py <same1.wav> <same2.wav> <diff1.wav>")
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
same1, same2, diff = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
23
|
+
|
|
24
|
+
print(f"{'Model':<22} {'Same-spk':>10} {'Diff-spk':>10} {'Margin':>10} {'Dim':>4}")
|
|
25
|
+
print("-" * 60)
|
|
26
|
+
|
|
27
|
+
results = []
|
|
28
|
+
for alias, entry in MODEL_REGISTRY.items():
|
|
29
|
+
if SKIP_RAW and entry.frontend == "raw":
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
emb = SpeakerEmbedder(model=alias)
|
|
33
|
+
|
|
34
|
+
e_same1 = emb.embed(same1)
|
|
35
|
+
e_same2 = emb.embed(same2)
|
|
36
|
+
e_diff = emb.embed(diff)
|
|
37
|
+
|
|
38
|
+
same_score = cosine(e_same1, e_same2)
|
|
39
|
+
diff_score = cosine(e_same1, e_diff)
|
|
40
|
+
margin = same_score - diff_score
|
|
41
|
+
|
|
42
|
+
results.append((alias, same_score, diff_score, margin, entry.embed_dim))
|
|
43
|
+
|
|
44
|
+
results.sort(key=lambda r: -r[3]) # sort by margin descending
|
|
45
|
+
for alias, same, diff, margin, dim in results:
|
|
46
|
+
print(f"{alias:<22} {same:>10.4f} {diff:>10.4f} {margin:>10.4f} {dim:>4}")
|
|
47
|
+
|
|
48
|
+
print("\nModels sorted by same-vs-different margin (higher = better separation)")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
main()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Load a custom ONNX model from disk.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python examples/custom_model.py /path/to/model.onnx clip.wav
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
import numpy as np
|
|
8
|
+
from speakeronnx import SpeakerEmbedder, cosine
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
if len(sys.argv) < 3:
|
|
13
|
+
print("Usage: python custom_model.py <model.onnx> <wav_file> [wav_file2]")
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
onnx_path = sys.argv[1]
|
|
17
|
+
wav1_path = sys.argv[2]
|
|
18
|
+
|
|
19
|
+
embedder = SpeakerEmbedder(model=onnx_path)
|
|
20
|
+
print(f"Model : {onnx_path}")
|
|
21
|
+
print(f"Sample rate : {embedder.sample_rate} Hz")
|
|
22
|
+
print(f"Embed dim : {embedder.embed_dim}")
|
|
23
|
+
print(f"Is registered: {embedder.entry is not None}")
|
|
24
|
+
|
|
25
|
+
emb1 = embedder.embed(wav1_path)
|
|
26
|
+
print(f"Embedding : dim={len(emb1)} norm={np.linalg.norm(emb1):.6f}")
|
|
27
|
+
|
|
28
|
+
if len(sys.argv) >= 4:
|
|
29
|
+
wav2_path = sys.argv[3]
|
|
30
|
+
emb2 = embedder.embed(wav2_path)
|
|
31
|
+
score = cosine(emb1, emb2)
|
|
32
|
+
print(f"Cosine({wav1_path}, {wav2_path}): {score:.6f}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""GPU inference example (requires onnxruntime-gpu).
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python examples/gpu_inference.py clip.wav
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
import numpy as np
|
|
8
|
+
from speakeronnx import SpeakerEmbedder
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
if len(sys.argv) < 2:
|
|
13
|
+
print("Usage: python gpu_inference.py <wav_file>")
|
|
14
|
+
sys.exit(1)
|
|
15
|
+
|
|
16
|
+
wav_path = sys.argv[1]
|
|
17
|
+
|
|
18
|
+
# Try CUDA, fall back to CPU
|
|
19
|
+
providers = [
|
|
20
|
+
"CUDAExecutionProvider",
|
|
21
|
+
"CoreMLExecutionProvider",
|
|
22
|
+
"CPUExecutionProvider",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
embedder = SpeakerEmbedder(
|
|
26
|
+
model="wespeaker-resnet34",
|
|
27
|
+
providers=providers,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
embedding = embedder.embed(wav_path)
|
|
31
|
+
print(f"Embedding dim : {len(embedding)}")
|
|
32
|
+
print(f"L2 norm : {np.linalg.norm(embedding):.6f}")
|
|
33
|
+
print(f"Provider used : {embedder._session.get_providers()}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
main()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Speaker verification with threshold tuning.
|
|
2
|
+
|
|
3
|
+
Demonstrates how to verify whether two audio files are from the same
|
|
4
|
+
speaker and tune the decision threshold.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python examples/verify_speakers.py <enroll.wav> <test.wav>
|
|
8
|
+
"""
|
|
9
|
+
import sys
|
|
10
|
+
import numpy as np
|
|
11
|
+
from speakeronnx import SpeakerEmbedder, cosine, verify
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
if len(sys.argv) < 3:
|
|
16
|
+
print("Usage: python verify_speakers.py <enroll.wav> <test.wav> [model_alias]")
|
|
17
|
+
sys.exit(1)
|
|
18
|
+
|
|
19
|
+
enroll_wav = sys.argv[1]
|
|
20
|
+
test_wav = sys.argv[2]
|
|
21
|
+
model_alias = sys.argv[3] if len(sys.argv) > 3 else "wespeaker-resnet34"
|
|
22
|
+
|
|
23
|
+
embedder = SpeakerEmbedder(model=model_alias)
|
|
24
|
+
|
|
25
|
+
enroll_emb = embedder.embed(enroll_wav)
|
|
26
|
+
test_emb = embedder.embed(test_wav)
|
|
27
|
+
|
|
28
|
+
score = cosine(enroll_emb, test_emb)
|
|
29
|
+
print(f"Model : {model_alias}")
|
|
30
|
+
print(f"Score : {score:.6f}")
|
|
31
|
+
|
|
32
|
+
# Try multiple thresholds
|
|
33
|
+
for threshold in [0.3, 0.4, 0.45, 0.5, 0.6]:
|
|
34
|
+
ok, _ = verify(enroll_emb, test_emb, threshold=threshold)
|
|
35
|
+
print(f" threshold={threshold:.2f} → {'SAME' if ok else 'DIFFERENT'}")
|
|
36
|
+
|
|
37
|
+
# Recommendation
|
|
38
|
+
if score > 0.5:
|
|
39
|
+
print("\nLikely SAME speaker (score > 0.5)")
|
|
40
|
+
elif score < 0.3:
|
|
41
|
+
print("\nLikely DIFFERENT speaker (score < 0.3)")
|
|
42
|
+
else:
|
|
43
|
+
print("\nAmbiguous — tune threshold on held-out data")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
main()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "speakeronnx"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Pure-onnxruntime speaker embedding library — no torch at runtime"
|
|
9
|
+
license = {text = "Apache-2.0"}
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"License :: OSI Approved :: Apache Software License",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
]
|
|
15
|
+
authors = [{name = "JarbasAI", email = "jarbasai@mailfence.com"}]
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
requires-python = ">=3.9"
|
|
18
|
+
dependencies = [
|
|
19
|
+
"numpy",
|
|
20
|
+
"onnxruntime",
|
|
21
|
+
"huggingface_hub",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
soxr = ["soxr"]
|
|
26
|
+
test = [
|
|
27
|
+
"pytest",
|
|
28
|
+
"edge-tts",
|
|
29
|
+
"requests",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
speakeronnx = "speakeronnx.__main__:main"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/TigreGotico/speakeronnx"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.dynamic]
|
|
39
|
+
version = {attr = "speakeronnx.version.__version__"}
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["."]
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.package-data]
|
|
45
|
+
"speakeronnx" = ["**/*"]
|
|
46
|
+
|
|
47
|
+
[tool.mypy]
|
|
48
|
+
python_version = "3.9"
|
|
49
|
+
ignore_missing_imports = true
|
|
50
|
+
no_site_packages = true
|
|
51
|
+
strict = false
|
|
52
|
+
warn_unused_ignores = true
|
|
53
|
+
warn_return_any = false
|