feat: Add Whisper transcription client
Introduces a new module for interacting with the Whisper ASR service. This includes an async function to POST audio files and retrieve transcripts. Error handling for network and HTTP status codes is also implemented. New tests are added to cover successful transcription, HTTP error responses, and request timeouts.
This commit is contained in:
@@ -1 +1,2 @@
|
||||
pub mod ffmpeg;
|
||||
pub mod whisper;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::multipart::{Form, Part};
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WhisperError {
|
||||
Io(std::io::Error),
|
||||
Http(reqwest::Error),
|
||||
Status { status: u16, body: String },
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WhisperError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Io(e) => write!(f, "whisper io error: {e}"),
|
||||
Self::Http(e) => write!(f, "whisper http error: {e}"),
|
||||
Self::Status { status, body } => {
|
||||
write!(f, "whisper returned {status}: {body}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for WhisperError {}
|
||||
|
||||
/// POST the audio file to whisper-asr-webservice and return the transcript text.
|
||||
///
|
||||
/// Uses `output=txt` for a plain-text response and `encode=false` to skip
|
||||
/// whisper's internal ffmpeg pass — we remux ourselves upstream.
|
||||
pub async fn transcribe(
|
||||
client: &reqwest::Client,
|
||||
whisper_url: &str,
|
||||
audio: &Path,
|
||||
timeout: Duration,
|
||||
) -> Result<String, WhisperError> {
|
||||
let filename = audio
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| "audio.m4a".to_string());
|
||||
|
||||
let bytes = tokio::fs::read(audio).await.map_err(WhisperError::Io)?;
|
||||
|
||||
let part = Part::bytes(bytes)
|
||||
.file_name(filename)
|
||||
.mime_str("audio/mp4")
|
||||
.map_err(WhisperError::Http)?;
|
||||
let form = Form::new().part("audio_file", part);
|
||||
|
||||
let url = format!("{}/asr?output=txt&encode=false", whisper_url.trim_end_matches('/'));
|
||||
debug!(%url, "posting to whisper");
|
||||
|
||||
let response = client
|
||||
.post(&url)
|
||||
.timeout(timeout)
|
||||
.multipart(form)
|
||||
.send()
|
||||
.await
|
||||
.map_err(WhisperError::Http)?;
|
||||
|
||||
let status = response.status();
|
||||
let body = response.text().await.map_err(WhisperError::Http)?;
|
||||
|
||||
if !status.is_success() {
|
||||
return Err(WhisperError::Status {
|
||||
status: status.as_u16(),
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
Reference in New Issue
Block a user