1pub mod local;
4pub mod openrouter;
5
6use crate::audio::AudioInput;
7use crate::error::Result;
8use async_trait::async_trait;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum BackendKind {
15 Asr,
17 LlmAssisted,
19}
20
21#[derive(Debug, Clone)]
23pub struct TranscriptionOptions {
24 pub model: String,
26 pub language: String,
28 pub timestamps: bool,
30 pub cancel: Option<crate::cancel::CancelFlag>,
32}
33
34impl Default for TranscriptionOptions {
35 fn default() -> Self {
36 Self {
37 model: crate::config::DEFAULT_LOCAL_MODEL.to_string(),
38 language: crate::config::DEFAULT_LANGUAGE.to_string(),
39 timestamps: false,
40 cancel: None,
41 }
42 }
43}
44
45impl TranscriptionOptions {
46 pub fn with_cancel(mut self, flag: crate::cancel::CancelFlag) -> Self {
47 self.cancel = Some(flag);
48 self
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54pub struct Segment {
55 pub start: f64,
57 pub end: f64,
59 pub text: String,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct TranscriptionResult {
65 pub text: String,
66 pub segments: Vec<Segment>,
67 pub language: Option<String>,
68 pub model: String,
69 pub provider: String,
70 pub duration_secs: f64,
71 #[serde(default = "default_backend_kind")]
73 pub backend_kind: BackendKind,
74 #[serde(default = "default_true")]
76 pub timestamps_reliable: bool,
77 #[serde(default)]
79 pub cleanup_style: crate::cleanup::CleanupStyle,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub cleanup_provider: Option<crate::cleanup::CleanupProviderKind>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub original_text: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub original_segments: Option<Vec<Segment>>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub cleanup_segment_policy: Option<crate::cleanup::SegmentCleanupPolicy>,
92}
93
94fn default_backend_kind() -> BackendKind {
95 BackendKind::Asr
96}
97fn default_true() -> bool {
98 true
99}
100
101impl TranscriptionResult {
102 pub fn local(
103 text: String,
104 segments: Vec<Segment>,
105 language: Option<String>,
106 model: String,
107 duration_secs: f64,
108 ) -> Self {
109 Self {
110 text,
111 segments,
112 language,
113 model,
114 provider: "local".into(),
115 duration_secs,
116 backend_kind: BackendKind::Asr,
117 timestamps_reliable: true,
118 cleanup_style: crate::cleanup::CleanupStyle::Raw,
119 cleanup_provider: None,
120 original_text: None,
121 original_segments: None,
122 cleanup_segment_policy: None,
123 }
124 }
125
126 pub fn openrouter(
127 text: String,
128 segments: Vec<Segment>,
129 language: Option<String>,
130 model: String,
131 duration_secs: f64,
132 _timestamps_requested: bool,
133 ) -> Self {
134 Self {
135 text,
136 segments,
137 language,
138 model,
139 provider: "openrouter".into(),
140 duration_secs,
141 backend_kind: BackendKind::LlmAssisted,
142 timestamps_reliable: false,
144 cleanup_style: crate::cleanup::CleanupStyle::Raw,
145 cleanup_provider: None,
146 original_text: None,
147 original_segments: None,
148 cleanup_segment_policy: None,
149 }
150 }
151}
152
153#[async_trait]
155pub trait TranscriptionProvider: Send + Sync {
156 fn name(&self) -> &'static str;
158
159 fn backend_kind(&self) -> BackendKind;
161
162 fn timestamps_reliable(&self) -> bool {
164 matches!(self.backend_kind(), BackendKind::Asr)
165 }
166
167 async fn transcribe(
169 &self,
170 input: &AudioInput,
171 options: &TranscriptionOptions,
172 ) -> Result<TranscriptionResult>;
173}
174
175pub use local::LocalWhisperProvider;
176pub use openrouter::{OpenRouterProvider, OpenRouterSttMode, SttPath};