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.
@@ -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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+