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