use crate::error::{DrivenError, Result};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WhisperModel {
Tiny,
Base,
Small,
Medium,
Large,
}
impl WhisperModel {
pub fn filename(&self) -> &'static str {
match self {
Self::Tiny => "whisper-tiny.bin",
Self::Base => "whisper-base.bin",
Self::Small => "whisper-small.bin",
Self::Medium => "whisper-medium.bin",
Self::Large => "whisper-large.bin",
}
}
pub fn size_mb(&self) -> u32 {
match self {
Self::Tiny => 75,
Self::Base => 142,
Self::Small => 466,
Self::Medium => 1500,
Self::Large => 2900,
}
}
}
#[derive(Debug, Clone)]
pub struct TranscriptionOptions {
pub language: String,
pub translate: bool,
pub threads: u32,
pub temperature: f32,
}
impl Default for TranscriptionOptions {
fn default() -> Self {
Self {
language: "en".to_string(),
translate: false,
threads: 4,
temperature: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct TranscriptionSegment {
pub text: String,
pub start_ms: u64,
pub end_ms: u64,
pub confidence: f32,
}
pub struct WhisperClient {
model_path: PathBuf,
options: TranscriptionOptions,
}
impl WhisperClient {
pub fn new(model_path: impl AsRef<Path>) -> Result<Self> {
let model_path = model_path.as_ref().to_path_buf();
if !model_path.exists() {
return Err(DrivenError::NotFound(format!(
"Whisper model not found: {}",
model_path.display()
)));
}
Ok(Self {
model_path,
options: TranscriptionOptions::default(),
})
}
pub fn with_options(mut self, options: TranscriptionOptions) -> Self {
self.options = options;
self
}
pub fn with_language(mut self, language: impl Into<String>) -> Self {
self.options.language = language.into();
self
}
pub async fn transcribe(&self, samples: &[f32]) -> Result<(String, f32)> {
self.transcribe_internal(samples).await
}
pub async fn transcribe_with_timestamps(&self, samples: &[f32]) -> Result<Vec<TranscriptionSegment>> {
let (text, confidence) = self.transcribe(samples).await?;
Ok(vec![TranscriptionSegment {
text,
start_ms: 0,
end_ms: (samples.len() as f32 / 16.0) as u64, confidence,
}])
}
pub async fn transcribe_file(&self, path: impl AsRef<Path>) -> Result<(String, f32)> {
let samples = self.load_audio_file(path).await?;
self.transcribe(&samples).await
}
async fn transcribe_internal(&self, _samples: &[f32]) -> Result<(String, f32)> {
tracing::debug!(
"Whisper transcription using model: {}",
self.model_path.display()
);
Ok(("".to_string(), 0.0))
}
async fn load_audio_file(&self, path: impl AsRef<Path>) -> Result<Vec<f32>> {
let path = path.as_ref();
tracing::debug!("Loading audio file: {}", path.display());
Ok(Vec::new())
}
pub fn model_info(&self) -> ModelInfo {
ModelInfo {
path: self.model_path.clone(),
language: self.options.language.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct ModelInfo {
pub path: PathBuf,
pub language: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_model_sizes() {
assert_eq!(WhisperModel::Tiny.size_mb(), 75);
assert_eq!(WhisperModel::Large.size_mb(), 2900);
}
#[test]
fn test_default_options() {
let opts = TranscriptionOptions::default();
assert_eq!(opts.language, "en");
assert!(!opts.translate);
}
}