pub mod local;
pub mod openrouter;
use crate::audio::AudioInput;
use crate::error::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BackendKind {
Asr,
LlmAssisted,
}
#[derive(Debug, Clone)]
pub struct TranscriptionOptions {
pub model: String,
pub language: String,
pub timestamps: bool,
pub cancel: Option<crate::cancel::CancelFlag>,
}
impl Default for TranscriptionOptions {
fn default() -> Self {
Self {
model: crate::config::DEFAULT_LOCAL_MODEL.to_string(),
language: crate::config::DEFAULT_LANGUAGE.to_string(),
timestamps: false,
cancel: None,
}
}
}
impl TranscriptionOptions {
pub fn with_cancel(mut self, flag: crate::cancel::CancelFlag) -> Self {
self.cancel = Some(flag);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Segment {
pub start: f64,
pub end: f64,
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TranscriptionResult {
pub text: String,
pub segments: Vec<Segment>,
pub language: Option<String>,
pub model: String,
pub provider: String,
pub duration_secs: f64,
#[serde(default = "default_backend_kind")]
pub backend_kind: BackendKind,
#[serde(default = "default_true")]
pub timestamps_reliable: bool,
#[serde(default)]
pub cleanup_style: crate::cleanup::CleanupStyle,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cleanup_provider: Option<crate::cleanup::CleanupProviderKind>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub original_text: Option<String>,
}
fn default_backend_kind() -> BackendKind {
BackendKind::Asr
}
fn default_true() -> bool {
true
}
impl TranscriptionResult {
pub fn local(
text: String,
segments: Vec<Segment>,
language: Option<String>,
model: String,
duration_secs: f64,
) -> Self {
Self {
text,
segments,
language,
model,
provider: "local".into(),
duration_secs,
backend_kind: BackendKind::Asr,
timestamps_reliable: true,
cleanup_style: crate::cleanup::CleanupStyle::Raw,
cleanup_provider: None,
original_text: None,
}
}
pub fn openrouter(
text: String,
segments: Vec<Segment>,
language: Option<String>,
model: String,
duration_secs: f64,
_timestamps_requested: bool,
) -> Self {
Self {
text,
segments,
language,
model,
provider: "openrouter".into(),
duration_secs,
backend_kind: BackendKind::LlmAssisted,
timestamps_reliable: false,
cleanup_style: crate::cleanup::CleanupStyle::Raw,
cleanup_provider: None,
original_text: None,
}
}
}
#[async_trait]
pub trait TranscriptionProvider: Send + Sync {
fn name(&self) -> &'static str;
fn backend_kind(&self) -> BackendKind;
fn timestamps_reliable(&self) -> bool {
matches!(self.backend_kind(), BackendKind::Asr)
}
async fn transcribe(
&self,
input: &AudioInput,
options: &TranscriptionOptions,
) -> Result<TranscriptionResult>;
}
pub use local::LocalWhisperProvider;
pub use openrouter::OpenRouterProvider;