use crate::audio::RecordingReader;
use crate::transcription::{
Layer, Operation, Transcriber, TranscriptSegment, TranscriptUpdate, TranscriptionError,
};
pub fn next_heard<T: Transcriber, L: Layer>(
operation: &mut Operation<'_, T, L>,
recording: &mut RecordingReader,
) -> Result<Option<Heard>, TranscriptionError> {
loop {
match next_heard_update(operation, recording, false)? {
Some(HeardUpdate::Finalized(heard)) => return Ok(Some(heard)),
Some(HeardUpdate::Provisional { .. }) => {}
None => return Ok(None),
}
}
}
pub fn next_heard_update<T: Transcriber, L: Layer>(
operation: &mut Operation<'_, T, L>,
recording: &mut RecordingReader,
previews: bool,
) -> Result<Option<HeardUpdate>, TranscriptionError> {
let segment = match operation.next(recording, previews)? {
Some(TranscriptUpdate::Finalized(segment)) => segment,
Some(TranscriptUpdate::Provisional {
text,
source_window,
}) => {
return Ok(Some(HeardUpdate::Provisional {
text,
source_window,
}));
}
None => return Ok(None),
};
let span = (segment.word_spans().is_some() || segment.source_window().is_some())
.then(|| segment.span())
.flatten();
let window = span
.clone()
.or_else(|| segment.source_window())
.unwrap_or(0..operation.read_to());
recording.release_before(operation.retained_from());
Ok(Some(HeardUpdate::Finalized(Heard {
segment,
span,
window,
})))
}
pub enum HeardUpdate {
Finalized(Heard),
Provisional {
text: String,
source_window: Option<std::ops::Range<u64>>,
},
}
pub struct Heard {
pub segment: TranscriptSegment,
pub span: Option<std::ops::Range<u64>>,
pub window: std::ops::Range<u64>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::audio::CANONICAL_SAMPLE_RATE;
#[test]
fn preview_reads_preserve_finalized_segments_and_completion() {
use crate::audio::testing::{Pace, paced};
use crate::cancel::CancelToken;
use crate::transcription::{
Cadence, Hypothesis, Language, TimedWord, Transcription, TranscriptionAudio,
};
use std::sync::Mutex;
struct Script {
pace: Pace,
calls: Mutex<usize>,
timed: bool,
}
impl Transcriber for Script {
fn validate_language(
&self,
_: &Language,
_: &CancelToken,
) -> Result<(), TranscriptionError> {
Ok(())
}
fn transcribe_with(
&self,
audio: TranscriptionAudio<'_>,
_: &Language,
_: &CancelToken,
) -> Result<Hypothesis, TranscriptionError> {
let mut calls = self.calls.lock().expect("no poison");
let text = ["one two", "one two three", "three four", "three four five"][*calls];
*calls += 1;
self.pace.advance();
if self.timed {
let duration = audio.require_samples("script")?.len() as f32
/ CANONICAL_SAMPLE_RATE as f32;
let words = text.split_whitespace().count();
Ok(Hypothesis::with_words(
text,
(0..words)
.map(|index| TimedWord {
start: duration * index as f32 / words as f32,
end: duration * (index + 1) as f32 / words as f32,
})
.collect(),
))
} else {
Ok(Hypothesis::new(text))
}
}
}
let run = |previews, timed| {
let (_directory, mut recording, pace) = paced(vec![vec![8192_i16; 1600]; 4]);
let transcription = Transcription::agreement(
Cadence::from_millis(100, 200),
Script {
pace,
calls: Mutex::new(0),
timed,
},
);
let mut operation = transcription.start(&Language::Automatic).expect("starts");
let mut rows = Vec::new();
let mut preview_count = 0;
loop {
let update = if previews {
next_heard_update(&mut operation, &mut recording, true)
} else {
next_heard(&mut operation, &mut recording)
.map(|heard| heard.map(HeardUpdate::Finalized))
}
.expect("reads");
match update {
Some(HeardUpdate::Finalized(heard)) => {
rows.push((heard.segment.into_text(), heard.window));
}
Some(HeardUpdate::Provisional { .. }) => preview_count += 1,
None => break,
}
}
let final_text = operation
.finish(&mut recording)
.expect("transcript")
.into_text();
(rows, final_text, preview_count)
};
for timed in [false, true] {
let (baseline, baseline_text, _) = run(false, timed);
let (previewed, preview_text, count) = run(true, timed);
assert!(count > 0);
assert_eq!(previewed, baseline);
assert_eq!(preview_text, baseline_text);
}
}
#[test]
fn delayed_untimed_words_keep_the_entire_decode_window() {
use crate::audio::testing::{Pace, paced};
use crate::cancel::CancelToken;
use crate::transcription::{
Cadence, Hypothesis, Language, Transcription, TranscriptionAudio,
};
use std::sync::Mutex;
struct Script {
pace: Pace,
calls: Mutex<usize>,
}
impl Transcriber for Script {
fn validate_language(
&self,
_: &Language,
_: &CancelToken,
) -> Result<(), TranscriptionError> {
Ok(())
}
fn transcribe_with(
&self,
_: TranscriptionAudio<'_>,
_: &Language,
_: &CancelToken,
) -> Result<Hypothesis, TranscriptionError> {
let mut calls = self.calls.lock().unwrap();
let answers = [
"one two",
"one two",
"one two three four",
"one two three four",
"one two three four five six",
"one two three four five six",
"one two three four five six guest phrase",
"one two three four five six guest phrase",
];
let text = answers[*calls];
*calls += 1;
self.pace.advance();
Ok(Hypothesis::new(text))
}
}
let second = CANONICAL_SAMPLE_RATE as usize;
let (_directory, mut recording, pace) = paced(vec![vec![8192_i16; second]; 8]);
let transcription = Transcription::agreement(
Cadence::from_millis(1_000, 20_000),
Script {
pace,
calls: Mutex::new(0),
},
);
let mut operation = transcription.start(&Language::Automatic).unwrap();
let mut heard = Vec::new();
while let Some(segment) = next_heard(&mut operation, &mut recording).unwrap() {
heard.push(segment);
}
assert_eq!(
heard
.iter()
.map(|heard| heard.segment.as_str())
.collect::<Vec<_>>(),
["one two", "three four", "five six", "guest phrase"]
);
let guest = heard.last().unwrap();
assert_eq!(guest.segment.span(), None);
assert_eq!(guest.segment.word_spans(), None);
assert_eq!(guest.segment.source_window(), Some(0..8 * second as u64));
assert_eq!(guest.window, 0..8 * second as u64);
}
}