Add transcribator page: audio recording + Whisper STT proxy
Some checks failed
ci/woodpecker/push/deploy Pipeline failed
Some checks failed
ci/woodpecker/push/deploy Pipeline failed
Browser records audio via MediaRecorder API, bcard proxies it to Whisper STT service and returns transcription as JSON. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
19
src/main.rs
19
src/main.rs
@@ -1,9 +1,14 @@
|
||||
use axum::{response::Html, routing::get, Router};
|
||||
mod transcribe;
|
||||
|
||||
use axum::{response::Html, routing::{get, post}, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let app = Router::new().route("/", get(handler));
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.route("/transcribator", get(transcribator_page))
|
||||
.route("/api/transcribe", post(transcribe::transcribe));
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
|
||||
println!("listening on {}", addr);
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
@@ -14,6 +19,10 @@ async fn handler() -> Html<&'static str> {
|
||||
Html("<h1>Mikhail Kilin</h1>")
|
||||
}
|
||||
|
||||
async fn transcribator_page() -> Html<&'static str> {
|
||||
Html(include_str!("../static/transcribator.html"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -23,4 +32,10 @@ mod tests {
|
||||
let response = handler().await;
|
||||
assert!(response.0.contains("Mikhail Kilin"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_transcribator_page() {
|
||||
let response = transcribator_page().await;
|
||||
assert!(response.0.contains("Transcribator"));
|
||||
}
|
||||
}
|
||||
|
||||
78
src/transcribe.rs
Normal file
78
src/transcribe.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use axum::{extract::Multipart, http::StatusCode, Json};
|
||||
use serde::Serialize;
|
||||
|
||||
const WHISPER_URL: &str = "http://whisper.whisper.svc:8000/v1/audio/transcriptions";
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct TranscribeResponse {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
pub async fn transcribe(
|
||||
mut multipart: Multipart,
|
||||
) -> Result<Json<TranscribeResponse>, (StatusCode, String)> {
|
||||
let mut audio_data: Option<(Vec<u8>, String)> = None;
|
||||
|
||||
while let Some(field) = multipart
|
||||
.next_field()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?
|
||||
{
|
||||
if field.name() == Some("audio") {
|
||||
let file_name = field
|
||||
.file_name()
|
||||
.unwrap_or("recording.webm")
|
||||
.to_string();
|
||||
let data = field
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
|
||||
audio_data = Some((data.to_vec(), file_name));
|
||||
}
|
||||
}
|
||||
|
||||
let (data, file_name) = audio_data
|
||||
.ok_or((StatusCode::BAD_REQUEST, "No audio field".to_string()))?;
|
||||
|
||||
let mime = if file_name.ends_with(".m4a") || file_name.ends_with(".mp4") {
|
||||
"audio/mp4"
|
||||
} else {
|
||||
"audio/webm"
|
||||
};
|
||||
|
||||
let part = reqwest::multipart::Part::bytes(data)
|
||||
.file_name(file_name)
|
||||
.mime_str(mime)
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let form = reqwest::multipart::Form::new()
|
||||
.part("file", part)
|
||||
.text("model", "Systran/faster-whisper-medium")
|
||||
.text("language", "ru");
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let resp = client
|
||||
.post(WHISPER_URL)
|
||||
.multipart(form)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_GATEWAY, format!("Whisper unavailable: {e}")))?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
let body = resp.text().await.unwrap_or_default();
|
||||
return Err((StatusCode::BAD_GATEWAY, format!("Whisper {status}: {body}")));
|
||||
}
|
||||
|
||||
let whisper_resp: serde_json::Value = resp
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_GATEWAY, format!("Invalid Whisper response: {e}")))?;
|
||||
|
||||
let text = whisper_resp["text"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
Ok(Json(TranscribeResponse { text }))
|
||||
}
|
||||
Reference in New Issue
Block a user