feat: Forward user settings to Whisper API
Include per-user hotwords and initial prompts in the multipart form data sent to the Whisper API. The language parameter is now dynamically set based on user configuration, defaulting to "de" if not specified.
This commit is contained in:
@@ -4,6 +4,8 @@ use std::time::Duration;
|
||||
use reqwest::multipart::{Form, Part};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::config::WhisperUserSettings;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WhisperError {
|
||||
Io(std::io::Error),
|
||||
@@ -27,15 +29,21 @@ impl std::error::Error for WhisperError {}
|
||||
|
||||
/// POST the audio file to whisper-asr-webservice and return the transcript text.
|
||||
///
|
||||
/// `output=txt` → plain-text response. `language=de` pins German so the model
|
||||
/// skips language detection. We send AAC-in-mp4 and let the service decode it
|
||||
/// internally (the default `encode=true`) — passing `encode=false` would tell
|
||||
/// the service "this is already raw PCM", which ours isn't.
|
||||
/// `output=txt` → plain-text response. `language` pins the language so the model
|
||||
/// skips detection (defaults to `de` when the user has no override). We send
|
||||
/// AAC-in-mp4 and let the service decode it internally (the default
|
||||
/// `encode=true`) — passing `encode=false` would tell the service "this is
|
||||
/// already raw PCM", which ours isn't.
|
||||
///
|
||||
/// Per-user `hotwords` and `initial_prompt` are forwarded as multipart form
|
||||
/// fields when non-empty; our `whisper/`-service passes them straight into
|
||||
/// `faster_whisper.WhisperModel.transcribe()`.
|
||||
pub async fn transcribe(
|
||||
client: &reqwest::Client,
|
||||
whisper_url: &str,
|
||||
audio: &Path,
|
||||
timeout: Duration,
|
||||
settings: &WhisperUserSettings,
|
||||
) -> Result<String, WhisperError> {
|
||||
let filename = audio
|
||||
.file_name()
|
||||
@@ -48,9 +56,25 @@ pub async fn transcribe(
|
||||
.file_name(filename)
|
||||
.mime_str("audio/mp4")
|
||||
.map_err(WhisperError::Http)?;
|
||||
let form = Form::new().part("audio_file", part);
|
||||
let mut form = Form::new().part("audio_file", part);
|
||||
|
||||
let url = format!("{}/asr?output=txt&language=de", whisper_url.trim_end_matches('/'));
|
||||
if let Some(hotwords) = settings.hotwords.as_deref().filter(|s| !s.is_empty()) {
|
||||
form = form.part("hotwords", Part::text(hotwords.to_owned()));
|
||||
}
|
||||
if let Some(prompt) = settings.initial_prompt.as_deref().filter(|s| !s.is_empty()) {
|
||||
form = form.part("initial_prompt", Part::text(prompt.to_owned()));
|
||||
}
|
||||
|
||||
let language = settings
|
||||
.language
|
||||
.as_deref()
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or("de");
|
||||
let url = format!(
|
||||
"{}/asr?output=txt&language={}",
|
||||
whisper_url.trim_end_matches('/'),
|
||||
language
|
||||
);
|
||||
debug!(%url, "posting to whisper");
|
||||
|
||||
let response = client
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::time::Duration;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use super::{ffmpeg, ollama, whisper, TranscribeReceiver};
|
||||
use crate::config::Config;
|
||||
use crate::config::{Config, WhisperUserSettings};
|
||||
|
||||
const ONELINER_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
@@ -23,7 +23,17 @@ pub async fn run(mut rx: TranscribeReceiver, config: Arc<Config>, client: reqwes
|
||||
continue;
|
||||
}
|
||||
|
||||
info!(audio = %audio_path.display(), "Transcribing");
|
||||
// Look up per-user Whisper settings live from the shared config so that
|
||||
// edits to users.toml (on next restart) reach in-flight jobs naturally.
|
||||
// Unknown slug → empty settings (service falls back to language=de).
|
||||
let settings: WhisperUserSettings = config
|
||||
.users
|
||||
.iter()
|
||||
.find(|u| u.slug == job.user_slug)
|
||||
.map(|u| u.whisper.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
info!(audio = %audio_path.display(), user = %job.user_slug, "Transcribing");
|
||||
|
||||
let remuxed = match ffmpeg::remux_faststart(&audio_path).await {
|
||||
Ok(f) => f,
|
||||
@@ -33,7 +43,7 @@ pub async fn run(mut rx: TranscribeReceiver, config: Arc<Config>, client: reqwes
|
||||
}
|
||||
};
|
||||
|
||||
let text = match whisper::transcribe(&client, &config.whisper_url, remuxed.path(), timeout).await {
|
||||
let text = match whisper::transcribe(&client, &config.whisper_url, remuxed.path(), timeout, &settings).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!(audio = %audio_path.display(), error = %e, "whisper call failed");
|
||||
|
||||
@@ -5,6 +5,7 @@ use doctate_server::transcribe;
|
||||
use doctate_server::transcribe::ffmpeg::remux_faststart;
|
||||
use doctate_server::transcribe::ollama::{generate_oneliner, OllamaError};
|
||||
use doctate_server::transcribe::recovery::scan_and_enqueue;
|
||||
use doctate_server::config::WhisperUserSettings;
|
||||
use doctate_server::transcribe::whisper::{transcribe, WhisperError};
|
||||
use serde_json::json;
|
||||
use wiremock::matchers::{body_partial_json, method, path, query_param};
|
||||
@@ -67,7 +68,13 @@ async fn whisper_client_posts_multipart_and_returns_text() {
|
||||
.await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let text = transcribe(&client, &server.uri(), &fixture("sample.m4a"), Duration::from_secs(10))
|
||||
let text = transcribe(
|
||||
&client,
|
||||
&server.uri(),
|
||||
&fixture("sample.m4a"),
|
||||
Duration::from_secs(10),
|
||||
&WhisperUserSettings::default(),
|
||||
)
|
||||
.await
|
||||
.expect("transcribe failed");
|
||||
|
||||
@@ -85,7 +92,13 @@ async fn whisper_client_returns_status_error_on_500() {
|
||||
.await;
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let err = transcribe(&client, &server.uri(), &fixture("sample.m4a"), Duration::from_secs(10))
|
||||
let err = transcribe(
|
||||
&client,
|
||||
&server.uri(),
|
||||
&fixture("sample.m4a"),
|
||||
Duration::from_secs(10),
|
||||
&WhisperUserSettings::default(),
|
||||
)
|
||||
.await
|
||||
.expect_err("expected error");
|
||||
|
||||
@@ -114,6 +127,7 @@ async fn whisper_client_times_out() {
|
||||
&server.uri(),
|
||||
&fixture("sample.m4a"),
|
||||
Duration::from_millis(100),
|
||||
&WhisperUserSettings::default(),
|
||||
)
|
||||
.await
|
||||
.expect_err("expected timeout");
|
||||
|
||||
Reference in New Issue
Block a user