use ::buffa::bytes::Bytes;
use mediaframe::lang::Language;
use smol_str::SmolStr;
use crate::{
buffa::{
error::BuffaError,
voice_fingerprint::{voice_fingerprint_from_wire, voice_fingerprint_to_wire},
},
domain::{
aggregates::audio::segment::{AudioSegment, Word},
vo::LocalizedText,
Uuid7,
},
generated::media::v1 as wire,
};
#[cfg(all(not(feature = "std"), feature = "alloc"))]
#[allow(unused_imports)]
use std::{
borrow::ToOwned,
string::{String, ToString},
};
impl From<&LocalizedText> for wire::LocalizedText {
fn from(d: &LocalizedText) -> Self {
wire::LocalizedText {
src: d.src().to_owned().into(),
translated: d.translated().to_owned().into(),
__buffa_unknown_fields: Default::default(),
}
}
}
impl From<&wire::LocalizedText> for LocalizedText {
fn from(w: &wire::LocalizedText) -> Self {
LocalizedText::from_src_translated(
SmolStr::from(w.src.as_str()),
SmolStr::from(w.translated.as_str()),
)
}
}
fn localized_text_from_wire(w: &::buffa::MessageField<wire::LocalizedText>) -> LocalizedText {
match w.as_option() {
Some(v) => LocalizedText::from(v),
None => LocalizedText::new(),
}
}
impl From<&Language> for wire::Language {
fn from(d: &Language) -> Self {
wire::Language {
bcp47: d.to_bcp47().into(),
__buffa_unknown_fields: Default::default(),
}
}
}
impl TryFrom<&wire::Language> for Language {
type Error = BuffaError;
fn try_from(w: &wire::Language) -> Result<Self, Self::Error> {
if w.bcp47.is_empty() {
return Ok(Language::new());
}
Language::from_bcp47(w.bcp47.as_str())
.map_err(|_| BuffaError::LanguageMalformed(SmolStr::from(w.bcp47.as_str())))
}
}
fn language_to_wire(v: Option<Language>) -> ::buffa::MessageField<wire::Language> {
match v {
Some(v) => ::buffa::MessageField::some(wire::Language::from(&v)),
None => ::buffa::MessageField::none(),
}
}
fn language_from_wire(
w: &::buffa::MessageField<wire::Language>,
) -> Result<Option<Language>, BuffaError> {
match w.as_option() {
Some(v) => Language::try_from(v).map(Some),
None => Ok(None),
}
}
impl From<&Word> for wire::Word {
fn from(d: &Word) -> Self {
wire::Word {
text: d.text().to_owned().into(),
span: ::buffa::MessageField::some(*d.span_ref()),
score: d.score(),
language: language_to_wire(d.language()),
__buffa_unknown_fields: Default::default(),
}
}
}
impl TryFrom<&wire::Word> for Word {
type Error = BuffaError;
fn try_from(w: &wire::Word) -> Result<Self, Self::Error> {
let span = w
.span
.as_option()
.cloned()
.ok_or(BuffaError::MissingRequiredField("Word.span"))?;
let language = language_from_wire(&w.language)?;
Word::try_from_parts(SmolStr::from(w.text.as_str()), span, w.score, language).map_err(|_| {
BuffaError::MissingRequiredField("Word.score")
})
}
}
impl From<&AudioSegment<Uuid7>> for wire::AudioSegment {
fn from(d: &AudioSegment<Uuid7>) -> Self {
wire::AudioSegment {
id: Bytes::copy_from_slice(d.id_ref().as_bytes()),
audio_track_id: Bytes::copy_from_slice(d.audio_track_id_ref().as_bytes()),
index: d.index(),
span: ::buffa::MessageField::some(*d.span_ref()),
speaker_id: d
.speaker_id_ref()
.map(|id| Bytes::copy_from_slice(id.as_bytes())),
text: ::buffa::MessageField::some(wire::LocalizedText::from(d.text_ref())),
language: language_to_wire(d.language()),
words: d.words_slice().iter().map(wire::Word::from).collect(),
no_speech_prob: d.no_speech_prob(),
avg_logprob: d.avg_logprob(),
temperature: d.temperature(),
voice_fingerprint: voice_fingerprint_to_wire(d.voice_fingerprint_ref()),
__buffa_unknown_fields: Default::default(),
}
}
}
impl TryFrom<&wire::AudioSegment> for AudioSegment<Uuid7> {
type Error = BuffaError;
fn try_from(w: &wire::AudioSegment) -> Result<Self, Self::Error> {
let id = id_from_bytes(&w.id)?;
let parent = id_from_bytes(&w.audio_track_id)?;
let span = w
.span
.as_option()
.cloned()
.ok_or(BuffaError::MissingRequiredField("AudioSegment.span"))?;
let speaker = match &w.speaker_id {
Some(b) => Some(id_from_bytes(b)?),
None => None,
};
let text = localized_text_from_wire(&w.text);
let language = language_from_wire(&w.language)?;
let words: std::vec::Vec<Word> = w
.words
.iter()
.map(Word::try_from)
.collect::<Result<_, _>>()?;
let voice_fingerprint = voice_fingerprint_from_wire(&w.voice_fingerprint)?;
let mut seg = AudioSegment::try_new(id, parent, w.index, span)
.map_err(audio_segment_error_as_buffa)?
.with_speaker_id(speaker)
.with_text(text)
.with_language(language)
.with_avg_logprob(w.avg_logprob)
.with_temperature(w.temperature)
.with_voice_fingerprint(voice_fingerprint);
if !words.is_empty() {
seg = seg
.try_with_words(words)
.map_err(audio_segment_error_as_buffa)?;
}
if w.no_speech_prob.is_some() {
seg = seg
.try_with_no_speech_prob(w.no_speech_prob)
.map_err(audio_segment_error_as_buffa)?;
}
Ok(seg)
}
}
fn id_from_bytes(b: &Bytes) -> Result<Uuid7, BuffaError> {
let arr: [u8; 16] = b
.as_ref()
.try_into()
.map_err(|_| BuffaError::IdWrongLength(b.len()))?;
Uuid7::try_from_bytes(arr).map_err(BuffaError::from)
}
fn audio_segment_error_as_buffa(
e: crate::domain::aggregates::audio::segment::AudioSegmentError,
) -> BuffaError {
use crate::domain::aggregates::audio::segment::AudioSegmentError as E;
match e {
E::NilId => BuffaError::MissingRequiredField("AudioSegment.id"),
E::NilAudioTrackId => BuffaError::MissingRequiredField("AudioSegment.audio_track_id"),
E::InvertedSpan => BuffaError::MissingRequiredField("AudioSegment.span"),
E::WordSpanOutOfSegment | E::InvertedWordSpan => {
BuffaError::MissingRequiredField("AudioSegment.words")
}
E::NoSpeechProbOutOfRange => BuffaError::MissingRequiredField("AudioSegment.no_speech_prob"),
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::domain::vo::{Provenance, VoiceFingerprint};
use core::num::NonZeroU32;
use jiff::Timestamp as JiffTimestamp;
use mediatime::{TimeRange, Timebase};
fn tb() -> Timebase {
Timebase::new(1, NonZeroU32::new(1000).expect("nonzero"))
}
fn span(start: i64, end: i64) -> TimeRange {
TimeRange::new(start, end, tb())
}
fn vfp() -> VoiceFingerprint<Uuid7> {
VoiceFingerprint::try_new(
Uuid7::new(),
192,
JiffTimestamp::from_millisecond(1_700_000_000_000).expect("valid ts"),
Some(0.83),
Provenance::from_parts("ecapa-tdnn", "v1.0.0", "", "findit-indexer-0.1.0"),
)
.expect("valid voiceprint")
}
#[test]
fn localized_text_roundtrip_both_populated() {
let d = LocalizedText::from_src_translated("hola", "hello");
let w: wire::LocalizedText = (&d).into();
let d2: LocalizedText = (&w).into();
assert_eq!(d, d2);
}
#[test]
fn localized_text_roundtrip_empty() {
let d = LocalizedText::new();
let w: wire::LocalizedText = (&d).into();
let d2: LocalizedText = (&w).into();
assert_eq!(d, d2);
assert!(d2.is_empty());
}
#[test]
fn language_roundtrip_concrete_tag() {
let d = Language::from_bcp47("zh-Hant-TW").unwrap();
let w: wire::Language = (&d).into();
assert_eq!(w.bcp47, "zh-Hant-TW");
let d2 = Language::try_from(&w).unwrap();
assert_eq!(d, d2);
}
#[test]
fn language_empty_bcp47_decodes_as_undetermined() {
let w = wire::Language {
bcp47: SmolStr::default(),
__buffa_unknown_fields: Default::default(),
};
let d = Language::try_from(&w).unwrap();
assert!(d.is_undetermined());
}
#[test]
fn language_und_roundtrip() {
let d = Language::from_bcp47("und").unwrap();
let w: wire::Language = (&d).into();
let d2 = Language::try_from(&w).unwrap();
assert_eq!(d, d2);
assert!(d2.is_undetermined());
}
#[test]
fn language_malformed_bcp47_errors() {
let w = wire::Language {
bcp47: SmolStr::from("xx-yy-zz-bogus"),
__buffa_unknown_fields: Default::default(),
};
let err = Language::try_from(&w).unwrap_err();
assert!(matches!(err, BuffaError::LanguageMalformed(ref s) if s == "xx-yy-zz-bogus"));
}
#[test]
fn word_roundtrip_without_language() {
let w = Word::try_new("hi", span(0, 100), 0.9).unwrap();
let wire: wire::Word = (&w).into();
assert!(wire.language.as_option().is_none());
let w2 = Word::try_from(&wire).unwrap();
assert_eq!(w, w2);
}
#[test]
fn word_roundtrip_with_language() {
let fr = Language::from_bcp47("fr").unwrap();
let w = Word::try_from_parts("bon", span(0, 200), 0.95, Some(fr)).unwrap();
let wire: wire::Word = (&w).into();
let w2 = Word::try_from(&wire).unwrap();
assert_eq!(w, w2);
}
#[test]
fn word_missing_span_errors() {
let mut wire = wire::Word::from(&Word::try_new("x", span(0, 100), 0.5).unwrap());
wire.span = ::buffa::MessageField::none();
let err = Word::try_from(&wire).unwrap_err();
assert!(err.is_missing_required_field());
}
#[test]
fn audio_segment_minimal_roundtrip() {
let parent = Uuid7::new();
let d = AudioSegment::try_new(Uuid7::new(), parent, 0, span(0, 1500)).unwrap();
let w: wire::AudioSegment = (&d).into();
let d2 = AudioSegment::try_from(&w).unwrap();
assert_eq!(d, d2);
assert_eq!(d2.audio_track_id_ref(), &parent);
assert!(d2.speaker_id_ref().is_none());
assert!(d2.voice_fingerprint_ref().is_none());
assert!(d2.words_slice().is_empty());
}
#[test]
fn audio_segment_full_roundtrip_with_words_and_fingerprint() {
let speaker = Uuid7::new();
let es = Language::from_bcp47("es").unwrap();
let w1 = Word::try_new("hola", span(0, 200), 0.95)
.unwrap()
.with_language(Some(es));
let w2 = Word::try_new("mundo", span(200, 400), 0.93).unwrap();
let d = AudioSegment::try_new(Uuid7::new(), Uuid7::new(), 0, span(0, 400))
.unwrap()
.with_speaker_id(Some(speaker))
.with_text(LocalizedText::from_src_translated(
"hola mundo",
"hello world",
))
.with_language(Some(es))
.try_with_words(std::vec![w1, w2])
.unwrap()
.try_with_no_speech_prob(Some(0.05))
.unwrap()
.with_avg_logprob(Some(-0.4))
.with_temperature(Some(0.0))
.with_voice_fingerprint(Some(vfp()));
let w: wire::AudioSegment = (&d).into();
let d2 = AudioSegment::try_from(&w).unwrap();
assert_eq!(d, d2);
}
#[test]
fn audio_segment_voice_fingerprint_absent_roundtrip() {
let d = AudioSegment::try_new(Uuid7::new(), Uuid7::new(), 0, span(0, 500)).unwrap();
let w: wire::AudioSegment = (&d).into();
assert!(w.voice_fingerprint.as_option().is_none());
let d2 = AudioSegment::try_from(&w).unwrap();
assert!(d2.voice_fingerprint_ref().is_none());
}
#[test]
fn audio_segment_missing_span_errors() {
let d = AudioSegment::try_new(Uuid7::new(), Uuid7::new(), 0, span(0, 500)).unwrap();
let mut w: wire::AudioSegment = (&d).into();
w.span = ::buffa::MessageField::none();
let err = AudioSegment::try_from(&w).unwrap_err();
assert!(err.is_missing_required_field());
}
#[test]
fn audio_segment_wrong_length_id_errors() {
let d = AudioSegment::try_new(Uuid7::new(), Uuid7::new(), 0, span(0, 500)).unwrap();
let mut w: wire::AudioSegment = (&d).into();
w.id = Bytes::copy_from_slice(&[0u8; 8]);
let err = AudioSegment::try_from(&w).unwrap_err();
assert!(err.is_id_wrong_length());
}
#[test]
fn audio_segment_nil_id_errors() {
let d = AudioSegment::try_new(Uuid7::new(), Uuid7::new(), 0, span(0, 500)).unwrap();
let mut w: wire::AudioSegment = (&d).into();
w.id = Bytes::copy_from_slice(&[0u8; 16]);
let err = AudioSegment::try_from(&w).unwrap_err();
assert!(err.is_id_invalid());
}
#[test]
fn audio_segment_wrong_length_speaker_fk_errors() {
let d = AudioSegment::try_new(Uuid7::new(), Uuid7::new(), 0, span(0, 500))
.unwrap()
.with_speaker_id(Some(Uuid7::new()));
let mut w: wire::AudioSegment = (&d).into();
w.speaker_id = Some(Bytes::copy_from_slice(&[0u8; 7]));
let err = AudioSegment::try_from(&w).unwrap_err();
assert!(err.is_id_wrong_length());
}
}