1use std::marker::PhantomData;
6
7use super::{
8 Language, Layer, Mode, Operation, Streaming, Transcriber, Transcript, TranscriptSegment,
9 Transcription, TranscriptionError,
10};
11use crate::audio::RecordingReader;
12
13#[cfg(not(target_family = "wasm"))]
14use crate::audio::Stopper;
15#[cfg(all(feature = "microphone", not(target_family = "wasm")))]
16use crate::audio::{Microphone, RecordingMode};
17#[cfg(all(feature = "silero", not(target_family = "wasm")))]
18use crate::speaker::{Diarizer, Embedder, Heard, SegmentLabeler, SegmentReader, Verdict};
19#[cfg(all(feature = "silero", not(target_family = "wasm")))]
20use crate::voice_activity::speech_gate;
21
22pub struct Session<'a, T, M: Mode, L = ()> {
28 operation: Operation<'a, T, L>,
29 recording: RecordingReader,
30 #[cfg(all(feature = "microphone", not(target_family = "wasm")))]
32 _microphone: Option<Microphone>,
33 #[cfg(not(target_family = "wasm"))]
34 stopper: Option<Stopper>,
35 mode: PhantomData<M>,
36}
37
38impl<'a, T: Transcriber, M: Mode, L: Layer> Session<'a, T, M, L> {
39 pub fn over(
42 transcription: &'a Transcription<T, M, L>,
43 language: &'a Language,
44 recording: RecordingReader,
45 ) -> Result<Self, TranscriptionError> {
46 Ok(Self {
47 operation: transcription.start(language)?,
48 recording,
49 #[cfg(all(feature = "microphone", not(target_family = "wasm")))]
50 _microphone: None,
51 #[cfg(not(target_family = "wasm"))]
52 stopper: None,
53 mode: PhantomData,
54 })
55 }
56
57 pub fn stop(mut self) -> Result<Transcript, TranscriptionError> {
61 #[cfg(not(target_family = "wasm"))]
62 if let Some(stopper) = &self.stopper {
63 stopper.stop();
64 }
65 self.operation.finish(&mut self.recording)
66 }
67}
68
69#[cfg(all(feature = "microphone", not(target_family = "wasm")))]
70impl<'a, T: Transcriber, M: Mode, L: Layer> Session<'a, T, M, L> {
71 pub fn microphone(
75 transcription: &'a Transcription<T, M, L>,
76 language: &'a Language,
77 ) -> Result<Self, TranscriptionError> {
78 let mut microphone = Microphone::open().map_err(TranscriptionError::Capture)?;
81 let (recording, stopper) = microphone
82 .record(RecordingMode::UntilStopped)
83 .map_err(TranscriptionError::Capture)?;
84 let operation = match transcription.start(language) {
85 Ok(operation) => operation,
86 Err(error) => {
87 stopper.stop();
88 return Err(error);
89 }
90 };
91 Ok(Self {
92 operation,
93 recording,
94 _microphone: Some(microphone),
95 stopper: Some(stopper),
96 mode: PhantomData,
97 })
98 }
99}
100
101impl<'a, T: Transcriber, L: Layer> Session<'a, T, Streaming, L> {
102 pub fn next_finalized(&mut self) -> Result<Option<TranscriptSegment>, TranscriptionError> {
105 self.operation.next_finalized(&mut self.recording)
106 }
107
108 #[cfg(all(feature = "silero", not(target_family = "wasm")))]
111 pub fn hearing<E: Embedder>(self, voices: Diarizer<E>) -> HeardSession<'a, T, E, L> {
112 self.with_reader(SegmentReader::new(SegmentLabeler::new(
113 speech_gate(),
114 Some(voices),
115 )))
116 }
117
118 #[cfg(all(feature = "silero", not(target_family = "wasm")))]
121 pub fn with_reader<E: Embedder>(self, reader: SegmentReader<E>) -> HeardSession<'a, T, E, L> {
122 HeardSession {
123 session: self,
124 reader,
125 kept: Vec::new(),
126 }
127 }
128}
129
130#[cfg(all(feature = "silero", not(target_family = "wasm")))]
133pub struct HeardSession<'a, T, E, L = ()> {
134 session: Session<'a, T, Streaming, L>,
135 reader: SegmentReader<E>,
136 kept: Vec<String>,
139}
140
141#[cfg(all(feature = "silero", not(target_family = "wasm")))]
142impl<T: Transcriber, E: Embedder, L: Layer> HeardSession<'_, T, E, L> {
143 pub fn only_own_voice(mut self) -> Self {
145 self.reader.labeler_mut().set_only_own_voice(true);
146 self
147 }
148
149 pub fn next_heard(&mut self) -> Result<Option<Heard>, TranscriptionError> {
152 let heard = self
153 .reader
154 .next(&mut self.session.operation, &mut self.session.recording)?;
155 if let Some(heard) = &heard
156 && matches!(heard.verdict, Verdict::Keep { .. })
157 {
158 self.kept.push(heard.segment.as_str().to_owned());
159 }
160 Ok(heard)
161 }
162
163 pub fn reader(&self) -> &SegmentReader<E> {
164 &self.reader
165 }
166
167 pub fn stop(self) -> Result<Transcript, TranscriptionError> {
171 let transcript = self.session.stop();
172 if !self.reader.labeler().only_own_voice() {
173 return transcript;
174 }
175 transcript?;
178 Transcript::new(self.kept.join(" "))
179 }
180}
181
182#[cfg(not(target_family = "wasm"))]
183impl<T, M: Mode, L> Drop for Session<'_, T, M, L> {
184 fn drop(&mut self) {
185 if let Some(stopper) = &self.stopper {
189 stopper.stop();
190 }
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use std::collections::VecDeque;
197 use std::sync::Mutex;
198
199 use super::*;
200 use crate::audio::CANONICAL_SAMPLE_RATE;
201 use crate::audio::testing::{Pace, paced, recorded};
202 use crate::cancel::CancelToken;
203 use crate::transcription::{Cadence, TranscriptionAudio};
204
205 const STRIDE_MS: usize = 100;
206 const STRIDE: usize = STRIDE_MS * CANONICAL_SAMPLE_RATE as usize / 1000;
207
208 struct Scripted {
211 answers: Mutex<VecDeque<&'static str>>,
212 pace: Option<Pace>,
213 }
214
215 impl Scripted {
216 fn new(answers: &[&'static str]) -> Self {
217 Self {
218 answers: Mutex::new(answers.iter().copied().collect()),
219 pace: None,
220 }
221 }
222
223 fn paced(answers: &[&'static str], pace: Pace) -> Self {
224 Self {
225 answers: Mutex::new(answers.iter().copied().collect()),
226 pace: Some(pace),
227 }
228 }
229 }
230
231 impl Transcriber for Scripted {
232 fn validate_language(
233 &self,
234 _language: &Language,
235 _cancel: &CancelToken,
236 ) -> Result<(), TranscriptionError> {
237 Ok(())
238 }
239
240 fn transcribe_with(
241 &self,
242 _audio: TranscriptionAudio<'_>,
243 _language: &Language,
244 _cancel: &CancelToken,
245 ) -> Result<super::super::Hypothesis, TranscriptionError> {
246 if let Some(pace) = &self.pace {
247 pace.advance();
248 }
249 let answer = self
250 .answers
251 .lock()
252 .unwrap_or_else(|poisoned| poisoned.into_inner())
253 .pop_front()
254 .expect("the script covers every decode");
255 Ok(super::super::Hypothesis::new(answer))
256 }
257 }
258
259 fn stride_batch() -> Vec<i16> {
260 vec![8_192; STRIDE]
261 }
262
263 #[test]
264 fn a_streaming_session_commits_words_as_their_agreement_holds() {
265 let (_directory, recording, pace) = paced(vec![stride_batch(); 4]);
266 let cadence = Cadence::from_millis(STRIDE_MS, 20_000);
267 let transcription = Transcription::agreement(
268 cadence,
269 Scripted::paced(&["one", "one two", "one two three", "one two three"], pace),
270 );
271 let language = Language::Automatic;
272 let mut session =
273 Session::over(&transcription, &language, recording).expect("the session starts");
274
275 let mut segments = Vec::new();
276 while let Some(segment) = session.next_finalized().expect("every step succeeds") {
277 segments.push(segment.into_text());
278 }
279 let transcript = session.stop().expect("the transcript completes");
280
281 assert_eq!(segments, ["one", "two", "three"]);
282 assert_eq!(transcript.as_str(), "one two three");
283 }
284
285 #[cfg(feature = "silero")]
286 #[test]
287 fn a_session_with_a_reader_says_whose_voice_each_segment_is_in() {
288 use crate::speaker::{Diarizer, Embedder, SegmentLabeler, SpeakerError, Verdict};
289
290 const OWN: [f32; 2] = [1.0, 0.0];
291 const OTHER: [f32; 2] = [0.0, 1.0];
292
293 struct Voice([f32; 2]);
294
295 impl Embedder for Voice {
296 fn embed(&mut self, _window: &[f32]) -> Result<Option<Vec<f32>>, SpeakerError> {
297 Ok(Some(self.0.to_vec()))
298 }
299
300 fn dimension(&self) -> usize {
301 2
302 }
303 }
304
305 struct Timed;
307
308 impl Transcriber for Timed {
309 fn validate_language(
310 &self,
311 _language: &Language,
312 _cancel: &CancelToken,
313 ) -> Result<(), TranscriptionError> {
314 Ok(())
315 }
316
317 fn transcribe_with(
318 &self,
319 audio: TranscriptionAudio<'_>,
320 _language: &Language,
321 _cancel: &CancelToken,
322 ) -> Result<super::super::Hypothesis, TranscriptionError> {
323 let TranscriptionAudio::Samples(samples) = audio else {
324 unreachable!("the session hands over samples");
325 };
326 let end = samples.len() as f32 / CANONICAL_SAMPLE_RATE as f32;
327 Ok(super::super::Hypothesis::with_words(
328 "one",
329 vec![super::super::TimedWord { start: 0.0, end }],
330 ))
331 }
332 }
333
334 let judged = |voice: [f32; 2]| {
337 let (_directory, recording) = recorded(&vec![8_192; 34 * STRIDE]);
338 let cadence = Cadence::from_millis(STRIDE_MS, 20_000);
339 let transcription = Transcription::agreement(cadence, Timed);
340 let language = Language::Automatic;
341 let enrolled = vec![crate::speaker::enrolled_voice(
342 "alex",
343 crate::speaker::Role::Owner,
344 OWN.to_vec(),
345 )];
346 let labeler = SegmentLabeler::new(None, Some(Diarizer::new(Voice(voice), enrolled)));
347 let mut session = Session::over(&transcription, &language, recording)
348 .expect("the session starts")
349 .with_reader(SegmentReader::new(labeler))
350 .only_own_voice();
351
352 let mut heard = Vec::new();
353 while let Some(segment) = session.next_heard().expect("every step succeeds") {
354 let stranger = matches!(segment.verdict, Verdict::Stranger);
355 heard.push((segment.segment.into_text(), stranger));
356 }
357 (heard, session.stop().map(Transcript::into_text))
358 };
359
360 let (heard, transcript) = judged(OTHER);
361 assert_eq!(heard, [("one".to_owned(), true)]);
362 assert!(matches!(transcript, Err(TranscriptionError::NoSpeech)));
363
364 let (heard, transcript) = judged(OWN);
365 assert_eq!(heard, [("one".to_owned(), false)]);
366 assert_eq!(transcript.expect("the kept words complete"), "one");
367 }
368
369 #[test]
370 fn a_whole_session_has_only_its_end() {
371 let (_directory, recording) = recorded(&[0; 64]);
372 let transcription = Transcription::complete_samples(Scripted::new(&["hello world"]));
373 let language = Language::Automatic;
374
375 let transcript = Session::over(&transcription, &language, recording)
376 .expect("the session starts")
377 .stop()
378 .expect("the transcript completes");
379
380 assert_eq!(transcript.as_str(), "hello world");
381 }
382}