1mod diarizer;
13#[cfg(not(target_family = "wasm"))]
14mod enrollment;
15mod label;
16#[cfg(all(feature = "silero", not(target_family = "wasm")))]
17mod labeler;
18mod profile;
19#[cfg(not(target_family = "wasm"))]
20mod store;
21mod voices;
22
23use std::path::PathBuf;
24
25use crate::backend::{BackendError, ModelDownload, ModelError};
26
27pub use diarizer::{CLUSTER_THRESHOLD, Diarizer, ENROLLED_THRESHOLD, settle};
28#[cfg(not(target_family = "wasm"))]
29pub use enrollment::{Enrolled, Enrollment, EnrollmentError, MIN_SAMPLES, centroid, consistency};
30pub(crate) use label::AnonymousNumbering;
31pub use label::Label;
32#[cfg(all(feature = "silero", not(target_family = "wasm")))]
33pub use labeler::{
34 Heard, HeardUpdate, OWN_VOICE_THRESHOLD, SegmentLabeler, SegmentReader, Verdict,
35};
36#[cfg(any(test, feature = "test-support"))]
37pub use profile::enrolled_voice;
38pub use profile::{Profile, Role, SpeakerName, SpeakerNameError};
39#[cfg(not(target_family = "wasm"))]
40pub use store::{ProfileError, ProfileStore, Replace, StoredProfiles};
41pub use voices::{
42 Chunk, Grouping, Merged, PyannoteVoices, Revision, Spoken, Standing, Voiceprint, Voices,
43 attribute_spans,
44};
45
46pub trait Embedder {
52 fn embed(&mut self, window: &[f32]) -> Result<Option<Vec<f32>>, SpeakerError>;
59
60 fn dimension(&self) -> usize;
63}
64
65pub trait Engine: Embedder + Sized {
69 const NAME: &'static str;
70
71 const MODEL: &'static [ModelDownload];
75
76 type Config;
77
78 fn check_from(config: &Self::Config) -> Result<PathBuf, SpeakerError>;
80
81 fn load_from(config: Self::Config) -> Result<Self, SpeakerError>;
82}
83
84pub fn normalized(mut values: Vec<f32>) -> Option<Vec<f32>> {
87 let norm = values.iter().map(|value| value * value).sum::<f32>().sqrt();
88 if !norm.is_normal() {
89 return None;
90 }
91 for value in &mut values {
92 *value /= norm;
93 }
94 Some(values)
95}
96
97#[cfg(not(target_family = "wasm"))]
99pub fn validate_model_file(path: PathBuf, backend: &'static str) -> Result<PathBuf, SpeakerError> {
100 crate::backend::validate_model_file(path)
101 .map_err(|source| SpeakerError::Model { backend, source })
102}
103
104#[derive(Debug, thiserror::Error)]
105pub enum SpeakerError {
106 #[error("the {backend} model cannot be used")]
107 Model {
108 backend: &'static str,
109 #[source]
110 source: ModelError,
111 },
112
113 #[error("failed to load the {backend} model {model}")]
114 Load {
115 backend: &'static str,
116 model: String,
117 #[source]
118 source: BackendError,
119 },
120
121 #[error("{backend} inference failed")]
122 Inference {
123 backend: &'static str,
124 #[source]
125 source: BackendError,
126 },
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn normalizing_leaves_a_unit_vector_pointing_the_same_way() {
135 let unit = normalized(vec![3.0, 4.0]).expect("the vector has a direction");
136 assert!((unit[0] - 0.6).abs() < 1e-6);
137 assert!((unit[1] - 0.8).abs() < 1e-6);
138 }
139
140 #[test]
141 fn a_vector_without_direction_cannot_be_normalized() {
142 assert!(normalized(vec![0.0, 0.0]).is_none());
143 }
144}