#[cfg(not(windows))]
pub mod auto_select;
pub mod local_agreement;
pub mod mock;
pub mod models;
#[cfg(not(windows))]
pub mod nemotron_engine;
#[cfg(not(windows))]
pub mod refinement_engine;
pub mod voicebird_engine;
pub mod whisper_kit_engine;
#[cfg(not(windows))]
pub mod whisper_rs_engine;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use tokio::sync::{broadcast, mpsc, oneshot};
use crate::session::writer::WrittenSegment;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Token {
pub text: String,
pub t_start_ms: u64,
pub t_end_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Segment {
pub t_start: Duration,
pub t_end: Duration,
pub text: String,
pub tokens: Vec<Token>,
}
impl From<&Segment> for WrittenSegment {
fn from(s: &Segment) -> Self {
WrittenSegment {
t_start_ms: s.t_start.as_millis() as u64,
t_end_ms: s.t_end.as_millis() as u64,
text: s.text.clone(),
}
}
}
#[derive(Debug, Clone)]
pub enum EngineEvent {
ModelLoaded { name: String },
Committed(Segment),
Tentative(String),
Error(String),
}
#[derive(Debug, Clone)]
pub enum EngineConfig {
Local {
model_path: std::path::PathBuf,
language: Option<String>,
sample_rate: u32, hop_ms: u32, min_window_ms: u32, },
Cloud {
api_key: String,
language: Option<String>,
sample_rate: u32,
server_url: String,
device_name: String,
app_name: String,
},
}
pub struct EngineHandle {
pub pcm_tx: mpsc::Sender<Vec<f32>>,
pub events_rx: broadcast::Receiver<EngineEvent>,
pub shutdown: oneshot::Sender<()>,
}
pub trait TranscriptionEngine: Send {
fn start(&mut self, cfg: EngineConfig) -> anyhow::Result<EngineHandle>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EngineKind {
WhisperRs,
WhisperKit,
Nemotron,
VoiceBirdWeb,
}
pub fn try_select_engine(
prefer: &str,
cloud_broadcast_enabled: bool,
voicebird_api_key: &str,
voicebird_server_url: &str,
sidecar_path: Option<&std::path::Path>,
) -> Result<(EngineKind, Box<dyn TranscriptionEngine>), String> {
if cloud_broadcast_enabled {
if voicebird_api_key.is_empty() {
return Err(
"Live broadcast enabled but no Voice Bird API key — open settings (press ',')"
.into(),
);
}
if voicebird_server_url.is_empty() {
return Err(
"Live broadcast enabled but no Voice Bird server URL — open settings (press ',')"
.into(),
);
}
return Ok((
EngineKind::VoiceBirdWeb,
Box::new(voicebird_engine::VoiceBirdEngine::new(
voicebird_api_key.to_string(),
voicebird_server_url.to_string(),
)),
));
}
#[cfg(windows)]
{
let _ = (prefer, sidecar_path);
Err("Local transcription is not supported on Windows — Windows is cloud-only. \
Press 'c' to set your Voice Bird API key."
.into())
}
#[cfg(not(windows))]
{
#[cfg(target_os = "macos")]
{
if prefer == "whisperkit" || prefer == "auto" {
if let Some(path) = sidecar_path {
if path.exists() {
return Ok((
EngineKind::WhisperKit,
Box::new(whisper_kit_engine::WhisperKitEngine::new(
path.to_path_buf(),
)),
));
}
}
}
}
let _ = (prefer, sidecar_path);
Ok((
EngineKind::WhisperRs,
Box::<whisper_rs_engine::WhisperRsEngine>::default(),
))
}
}
pub fn sidecar_path() -> Option<std::path::PathBuf> {
let exe = std::env::current_exe().ok()?;
let dir = exe.parent()?;
let bundle = dir.join("../Resources/voice-bird-whisperkit");
if bundle.exists() {
return Some(bundle);
}
let sibling = dir.join("voice-bird-whisperkit");
if sibling.exists() {
return Some(sibling);
}
let dev = dir.join("../../whisperkit-helper/.build/release/voice-bird-whisperkit");
if dev.exists() {
return Some(dev);
}
None
}
#[cfg(test)]
mod select_tests {
use super::*;
const TEST_URL: &str = "wss://example.test/api/audio/stream";
#[test]
fn broadcast_with_key_returns_cloud_engine() {
let res = try_select_engine("auto", true, "vb-fake", TEST_URL, None);
let (kind, _engine) = res.expect("expected Ok");
assert_eq!(kind, EngineKind::VoiceBirdWeb);
}
#[test]
fn broadcast_without_key_returns_err() {
let err = try_select_engine("auto", true, "", TEST_URL, None)
.err()
.unwrap();
assert!(err.to_lowercase().contains("api key"));
}
#[test]
fn broadcast_without_url_returns_err() {
let err = try_select_engine("auto", true, "vb-fake", "", None)
.err()
.unwrap();
assert!(err.to_lowercase().contains("server url"));
}
#[cfg(not(windows))]
#[test]
fn local_path_ignores_credentials() {
let (kind, _) = try_select_engine("whisper_rs", false, "", "", None).unwrap();
assert_eq!(kind, EngineKind::WhisperRs);
}
#[cfg(not(windows))]
#[test]
fn local_path_when_broadcast_off_even_with_creds() {
let (kind, _) = try_select_engine("whisper_rs", false, "vb-fake", TEST_URL, None).unwrap();
assert_eq!(kind, EngineKind::WhisperRs);
}
#[cfg(windows)]
#[test]
fn windows_without_cloud_returns_err() {
let err = try_select_engine("auto", false, "", "", None).err().unwrap();
assert!(err.to_lowercase().contains("cloud-only"));
}
}