mod grid;
mod pyannote;
mod timeline;
use std::ops::Range;
use super::Label;
pub use pyannote::{Chunk, Grouping, PyannoteVoices, Standing, Voiceprint};
pub trait Voices {
fn attribute(&self, spans: &[Range<u64>]) -> Vec<Option<Label>>;
}
pub fn attribute_spans(voices: &impl Voices, spans: &[Option<Range<u64>>]) -> Vec<Option<Label>> {
let timed: Vec<Range<u64>> = spans.iter().flatten().cloned().collect();
let mut answers = voices.attribute(&timed).into_iter();
spans
.iter()
.map(|span| span.as_ref().and_then(|_| answers.next().flatten()))
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Spoken {
pub span: Range<u64>,
pub speaker: Label,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Revision {
pub merges: Vec<Merged>,
}
impl Revision {
pub fn revised(&self) -> bool {
!self.merges.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Merged {
pub from: Label,
pub into: Label,
}
#[cfg(test)]
mod tests {
use super::*;
struct Positional(std::cell::RefCell<Vec<Range<u64>>>);
impl Voices for Positional {
fn attribute(&self, spans: &[Range<u64>]) -> Vec<Option<Label>> {
self.0.borrow_mut().extend_from_slice(spans);
spans
.iter()
.map(|span| Some(Label::Anonymous(span.start as u32)))
.collect()
}
}
#[test]
fn a_paragraph_with_no_span_is_never_asked_about() {
let voices = Positional(Vec::new().into());
let named = attribute_spans(&voices, &[Some(10..20), None, Some(30..40)]);
assert_eq!(
voices.0.borrow().as_slice(),
[10..20, 30..40],
"the track hears only the spans somebody measured"
);
assert_eq!(
named,
[Some(Label::Anonymous(10)), None, Some(Label::Anonymous(30))],
"and its answers land back on the paragraphs they were about"
);
}
#[test]
fn a_track_that_answers_short_leaves_the_rest_unnamed() {
struct Silent;
impl Voices for Silent {
fn attribute(&self, _spans: &[Range<u64>]) -> Vec<Option<Label>> {
Vec::new()
}
}
assert_eq!(attribute_spans(&Silent, &[Some(0..10), None]), [None, None]);
}
}