1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//! car-voice's `SttProvider` over the shared [`car_whisper`] crate.
//!
//! The whisper.cpp path — model resolve/download, context loading, resampling,
//! and transcription — moved to the leaf `car-whisper` crate so `car-inference`
//! can route its `car speech` catalog entry through the same code without a
//! circular dependency (car-voice depends on car-inference). This file is now a
//! thin adapter: it maps `VoiceConfig` → a loaded `car_whisper::WhisperStt` and
//! bridges the async `SttProvider` trait to the crate's synchronous transcribe
//! (on a blocking pool, as before).
use async_trait::async_trait;
use crate::stt::SttProvider;
use crate::{Result, VoiceConfig, VoiceError};
/// In-process Whisper STT (delegates to [`car_whisper::WhisperStt`]).
#[derive(Debug)]
pub struct WhisperCppSttProvider {
inner: car_whisper::WhisperStt,
}
impl WhisperCppSttProvider {
/// Build a provider by resolving the configured model file and loading it.
/// If the file isn't present at the cache path yet, this downloads it
/// synchronously — first-run cost is the model download (~600 MB for the
/// default quantized turbo model).
pub fn from_config(config: &VoiceConfig) -> Result<Self> {
let inner = car_whisper::WhisperStt::load(&config.whisper_cpp_model, &config.language)
.map_err(|e| VoiceError::Stt(e.to_string()))?;
Ok(Self { inner })
}
}
#[async_trait]
impl SttProvider for WhisperCppSttProvider {
async fn transcribe(&self, samples: &[f32], sample_rate: u32) -> Result<String> {
// whisper.cpp inference is synchronous and CPU/GPU-bound — move it off
// the tokio runtime onto a blocking pool. `WhisperStt` is cheap to clone
// (the context is behind an `Arc`).
let inner = self.inner.clone();
let samples = samples.to_vec();
tokio::task::spawn_blocking(move || inner.transcribe(&samples, sample_rate))
.await
.map_err(|e| VoiceError::Stt(format!("whisper join: {e}")))?
.map_err(|e| VoiceError::Stt(e.to_string()))
}
}