use crate::error::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
pub const DEFAULT_SAMPLE_RATE_HZ: u32 = 24_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BackendKind {
Local,
Remote,
}
impl BackendKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Local => "local",
Self::Remote => "remote",
}
}
}
#[derive(Debug, Clone)]
pub struct SynthesisOptions {
pub model: String,
pub voice: String,
pub language: String,
pub sample_rate_hz: Option<u32>,
pub speaking_rate: f32,
pub timeout_ms: u64,
pub cancel: Option<crate::cancel::CancelFlag>,
pub op: Option<crate::runtime::OpContext>,
pub local_only: bool,
pub pack_dir: Option<std::path::PathBuf>,
pub allow_unverified: bool,
}
impl Default for SynthesisOptions {
fn default() -> Self {
Self {
model: crate::tts::DEFAULT_TTS_MODEL.to_string(),
voice: crate::tts::DEFAULT_TTS_VOICE.to_string(),
language: "en".to_string(),
sample_rate_hz: None,
speaking_rate: 1.0,
timeout_ms: crate::tts::DEFAULT_TIMEOUT_MS,
cancel: None,
op: None,
local_only: false,
pack_dir: None,
allow_unverified: false,
}
}
}
impl SynthesisOptions {
pub fn resolve_op_context(&self) -> crate::runtime::OpContext {
use std::time::{Duration, Instant};
let mut op = if let Some(ref op) = self.op {
op.clone()
} else {
crate::runtime::OpContext::from_optional_cancel(self.cancel.clone())
};
let timeout_ms = if self.timeout_ms == 0 {
crate::tts::DEFAULT_TIMEOUT_MS
} else {
self.timeout_ms
};
let from_timeout = Instant::now() + Duration::from_millis(timeout_ms);
match op.deadline() {
Some(existing) if existing <= from_timeout => {}
_ => {
op = op.with_absolute_deadline(from_timeout);
}
}
op
}
}
#[derive(Debug, Clone, Serialize)]
pub struct SynthesisResult {
#[serde(skip)]
pub pcm_i16_mono: Vec<i16>,
pub sample_rate_hz: u32,
pub channels: u16,
pub backend_kind: BackendKind,
pub provider: String,
pub model: String,
pub voice: String,
pub language: String,
pub duration_ms: u64,
pub text_chars: usize,
pub text_truncated: bool,
pub chunk_count: usize,
pub synthesized_chars: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub adapter: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trust: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provenance: Option<String>,
}
#[async_trait]
pub trait SynthesisProvider: Send + Sync {
fn name(&self) -> &'static str;
async fn synthesize(&self, text: &str, opts: &SynthesisOptions) -> Result<SynthesisResult>;
async fn preload(&self, model: &str, voice: &str) -> Result<()>;
}