feat: Add transcription worker and state management
Introduce an `AppState` struct to hold shared application state, including configuration and the transcription sender. Modify `create_router` and `create_router_with_state` to use `AppState`. Update `main.rs` to initialize the transcription worker and pass the `AppState` to the router. Implement the transcription worker logic in `server/src/transcribe/worker.rs`, handling job queues, FFmpeg remuxing, and Whisper transcription. Update `server/src/auth.rs` to use `Arc<Config>` from the shared state. Modify `server/src/routes/upload.rs` to enqueue transcription jobs and handle potential errors. Add `TranscribeJob` and channel types in `server/src/transcribe/mod.rs`.
This commit is contained in:
@@ -1,2 +1,21 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
pub mod ffmpeg;
|
||||
pub mod whisper;
|
||||
pub mod worker;
|
||||
|
||||
pub const QUEUE_DEPTH: usize = 64;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TranscribeJob {
|
||||
pub audio_path: PathBuf,
|
||||
}
|
||||
|
||||
pub type TranscribeSender = mpsc::Sender<TranscribeJob>;
|
||||
pub type TranscribeReceiver = mpsc::Receiver<TranscribeJob>;
|
||||
|
||||
pub fn channel() -> (TranscribeSender, TranscribeReceiver) {
|
||||
mpsc::channel(QUEUE_DEPTH)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use super::{ffmpeg, whisper, TranscribeReceiver};
|
||||
use crate::config::Config;
|
||||
|
||||
/// Consume transcription jobs sequentially. Whisper holds the GPU, so parallelism
|
||||
/// here would only cause contention. One job in flight at a time.
|
||||
pub async fn run(mut rx: TranscribeReceiver, config: Arc<Config>, client: reqwest::Client) {
|
||||
info!("Transcription worker started");
|
||||
let timeout = Duration::from_secs(config.whisper_timeout_seconds);
|
||||
|
||||
while let Some(job) = rx.recv().await {
|
||||
let audio_path = job.audio_path;
|
||||
let transcript_path = audio_path.with_extension("transcript.txt");
|
||||
|
||||
if transcript_path.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
info!(audio = %audio_path.display(), "Transcribing");
|
||||
|
||||
let remuxed = match ffmpeg::remux_faststart(&audio_path).await {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
error!(audio = %audio_path.display(), error = %e, "ffmpeg remux failed");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let text = match whisper::transcribe(&client, &config.whisper_url, remuxed.path(), timeout).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
error!(audio = %audio_path.display(), error = %e, "whisper call failed");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = tokio::fs::write(&transcript_path, &text).await {
|
||||
error!(transcript = %transcript_path.display(), error = %e, "writing transcript failed");
|
||||
continue;
|
||||
}
|
||||
|
||||
info!(
|
||||
audio = %audio_path.display(),
|
||||
bytes = text.len(),
|
||||
"Transcript written"
|
||||
);
|
||||
}
|
||||
|
||||
warn!("Transcription worker stopped (channel closed)");
|
||||
}
|
||||
Reference in New Issue
Block a user