use async_trait::async_trait;
use crate::error::{LlmError, LlmResult};
const SUPPORTED_AUDIO_FORMATS: &[&str] = &["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"];
pub fn validate_audio_format(format: &str) -> LlmResult<()> {
let lower = format.to_ascii_lowercase();
if SUPPORTED_AUDIO_FORMATS.contains(&lower.as_str()) {
Ok(())
} else {
Err(LlmError::InvalidAudioFormat(format.to_string()))
}
}
#[derive(Debug, Clone)]
pub struct TranscriptionOutput {
pub text: String,
pub language: Option<String>,
pub duration: Option<f32>,
}
#[async_trait]
pub trait Transcriber: Send + Sync {
async fn transcribe_audio(
&self,
audio: &[u8],
format: &str,
language_hint: Option<&str>,
prompt_hint: Option<&str>,
) -> LlmResult<TranscriptionOutput>;
fn transcription_model(&self) -> &str;
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test code — panics are acceptable"
)]
use super::*;
#[test]
fn test_valid_formats() {
for fmt in &["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"] {
assert!(
validate_audio_format(fmt).is_ok(),
"Expected {fmt} to be valid"
);
}
}
#[test]
fn test_invalid_formats() {
for fmt in &["mid", "aiff", "amr", "ogg", "flac", "aac", "wma"] {
let result = validate_audio_format(fmt);
assert!(result.is_err(), "Expected {fmt} to be invalid");
assert!(
matches!(result.unwrap_err(), LlmError::InvalidAudioFormat(_)),
"Expected InvalidAudioFormat for {fmt}"
);
}
}
#[test]
fn test_format_case_insensitive() {
assert!(validate_audio_format("MP3").is_ok());
assert!(validate_audio_format("Mp3").is_ok());
assert!(validate_audio_format("WAV").is_ok());
assert!(validate_audio_format("WebM").is_ok());
}
}