seyk_core/audio/
extraction.rs1use crate::extractor::{ExtractionProvenance, SpeechProvenance};
3use crate::{
4 ExtractedContent, ResourceId, ResourceLocator, ResourceMetadata, ResourceProvider, Result,
5};
6use std::sync::Arc;
7
8pub struct Transcript {
10 pub text: String,
11 pub language: Option<String>,
12 pub no_speech: bool,
13}
14
15pub trait SpeechToText: Send + Sync {
18 fn implementation(&self) -> &'static str;
19 fn manifest_sha256(&self) -> String;
20 fn transcribe(&self, samples: &[f32], language: Option<&str>) -> Result<Transcript>;
22}
23
24#[derive(Clone)]
26pub struct AudioExtractor {
27 speech: Arc<dyn SpeechToText>,
28 language: Option<String>,
29}
30impl AudioExtractor {
31 pub fn new(speech: impl SpeechToText + 'static) -> Self {
32 Self {
33 speech: Arc::new(speech),
34 language: None,
35 }
36 }
37 pub fn language(mut self, language: impl Into<String>) -> Self {
39 self.language = Some(language.into());
40 self
41 }
42 pub fn extract(
43 &self,
44 provider: &dyn ResourceProvider,
45 source: &ResourceId,
46 locator: &ResourceLocator,
47 metadata: &ResourceMetadata,
48 max_duration_secs: u64,
49 ) -> Result<ExtractedContent> {
50 let samples =
52 super::extract_samples(provider, source, locator, metadata, max_duration_secs)?;
53 let ResourceLocator::Time { start_ms, .. } = locator else {
54 unreachable!("slice validates locator")
55 };
56 let sample_start = start_ms * 16; let sample_end = sample_start + samples.len() as u64;
58 let result = self.speech.transcribe(&samples, self.language.as_deref())?;
59 let (text, language_detected) = if result.no_speech {
60 (String::new(), None)
61 } else {
62 (result.text, result.language)
63 };
64 Ok(ExtractedContent {
65 text,
66 source: source.clone(),
67 locator: locator.clone(),
68 metadata: metadata.clone(),
69 provenance: Some(ExtractionProvenance {
70 method: "speech_to_text".into(),
71 implementation: self.speech.implementation().into(),
72 speech: Some(SpeechProvenance {
73 fidelity: "machine_transcription".into(),
74 requested_locator: locator.clone(),
75 sample_start,
76 sample_end,
77 sample_rate: 16_000,
78 manifest_sha256: self.speech.manifest_sha256(),
79 language_requested: self.language.clone(),
80 language_detected,
81 no_speech: result.no_speech,
82 }),
83 }),
84 })
85 }
86}