use crate::engine::TtsEngine;
use crate::types::EngineDescriptor;
use std::sync::Arc;
#[cfg(all(feature = "avsynth", target_os = "macos"))]
use crate::avsynth_engine::AvSynthEngine;
#[cfg(feature = "cloud")]
use crate::cloud_engine;
#[cfg(feature = "qwen3-local")]
use crate::qwen3_local_engine::Qwen3LocalEngine;
#[cfg(all(feature = "sapi", target_os = "windows"))]
use crate::sapi_engine::SapiEngine;
#[cfg(feature = "sherpaonnx")]
use crate::sherpaonnx_engine::SherpaOnnxEngine;
#[cfg(all(feature = "system", target_os = "linux"))]
use crate::system_engine::SystemEngine;
#[must_use]
#[allow(unused_variables)]
#[allow(clippy::too_many_lines)]
pub fn create_engine(engine_id: &str, credentials_json: &str) -> Option<Arc<dyn TtsEngine>> {
match engine_id {
"system" => {
#[cfg(all(feature = "system", target_os = "linux"))]
{
return Some(Arc::new(SystemEngine::new()));
}
#[cfg(not(all(feature = "system", target_os = "linux")))]
{
eprintln!(
"Engine 'system' requires the 'system' feature on Linux. \
Current build does not satisfy these conditions."
);
return None;
}
}
"avsynth" => {
#[cfg(all(feature = "avsynth", target_os = "macos"))]
{
return Some(Arc::new(AvSynthEngine::new()));
}
#[cfg(not(all(feature = "avsynth", target_os = "macos")))]
{
eprintln!(
"Engine 'avsynth' requires the 'avsynth' feature and macOS. \
Current build does not satisfy these conditions."
);
return None;
}
}
"sapi" => {
#[cfg(all(feature = "sapi", target_os = "windows"))]
{
return Some(Arc::new(SapiEngine::new()));
}
#[cfg(not(all(feature = "sapi", target_os = "windows")))]
{
eprintln!(
"Engine 'sapi' requires the 'sapi' feature and Windows. \
Current build does not satisfy these conditions."
);
return None;
}
}
"sherpaonnx" => {
#[cfg(feature = "sherpaonnx")]
{
return Some(Arc::new(SherpaOnnxEngine::new(credentials_json)));
}
#[cfg(not(feature = "sherpaonnx"))]
{
eprintln!(
"Engine 'sherpaonnx' is not enabled in this build. \
Rebuild with --features sherpaonnx."
);
return None;
}
}
"qwen3-local" => {
#[cfg(feature = "qwen3-local")]
{
let creds: std::collections::HashMap<String, String> =
if credentials_json.is_empty() {
std::collections::HashMap::new()
} else {
serde_json::from_str(credentials_json).unwrap_or_default()
};
return match Qwen3LocalEngine::new(&creds) {
Ok(engine) => Some(Arc::new(engine)),
Err(e) => {
eprintln!("Engine 'qwen3-local' failed to load: {e}");
None
}
};
}
#[cfg(not(feature = "qwen3-local"))]
{
eprintln!(
"Engine 'qwen3-local' is not enabled in this build. \
Rebuild with --features qwen3-local."
);
return None;
}
}
"floravox" => {
#[cfg(feature = "floravox")]
{
return Some(Arc::new(crate::floravox_engine::FloravoxEngine::new(
credentials_json,
)));
}
#[cfg(not(feature = "floravox"))]
{
eprintln!(
"Engine 'floravox' is not enabled in this build. \
Rebuild with --features floravox."
);
return None;
}
}
_ => {}
}
#[cfg(feature = "cloud")]
{
let result = cloud_engine::create_cloud_engine(engine_id, credentials_json);
if result.is_none() {
eprintln!(
"Unknown engine '{engine_id}'. Available engines: {}",
engine_list()
.iter()
.map(|e| e.id.as_str())
.collect::<Vec<_>>()
.join(", ")
);
}
result
}
#[cfg(not(feature = "cloud"))]
{
eprintln!(
"Unknown engine '{engine_id}' (cloud feature is disabled; only \
built-in engines are available). Available engines: {}",
engine_list()
.iter()
.map(|e| e.id.as_str())
.collect::<Vec<_>>()
.join(", ")
);
None
}
}
#[must_use]
pub fn engine_count() -> usize {
engine_list().len()
}
#[must_use]
#[allow(clippy::vec_init_then_push)]
#[allow(clippy::too_many_lines)]
pub fn engine_list() -> Vec<EngineDescriptor> {
#[allow(unused_mut)]
let mut engines = Vec::new();
#[cfg(all(feature = "system", target_os = "linux"))]
engines.push(EngineDescriptor {
id: "system".into(),
name: "System (Speech Dispatcher)".into(),
needs_credentials: false,
credential_keys_json: "[]".into(),
});
#[cfg(all(feature = "avsynth", target_os = "macos"))]
engines.push(EngineDescriptor {
id: "avsynth".into(),
name: "macOS AVSpeechSynthesizer".into(),
needs_credentials: false,
credential_keys_json: "[]".into(),
});
#[cfg(all(feature = "sapi", target_os = "windows"))]
engines.push(EngineDescriptor {
id: "sapi".into(),
name: "Windows SAPI".into(),
needs_credentials: false,
credential_keys_json: "[]".into(),
});
#[cfg(feature = "sherpaonnx")]
engines.push(EngineDescriptor {
id: "sherpaonnx".into(),
name: "Sherpa-ONNX".into(),
needs_credentials: false,
credential_keys_json: "[]".into(),
});
#[cfg(feature = "qwen3-local")]
engines.push(EngineDescriptor {
id: "qwen3-local".into(),
name: "Qwen3-TTS local (qwen3-tts.cpp)".into(),
needs_credentials: true,
credential_keys_json: r#"["modelsDir","threads","temperature","topK","maxTokens"]"#.into(),
});
#[cfg(feature = "floravox")]
engines.push(EngineDescriptor {
id: "floravox".into(),
name: "floravox".into(),
needs_credentials: true,
credential_keys_json: r#"["modelsDir","modelId","misaki","chars","speaker"]"#.into(),
});
#[cfg(feature = "cloud")]
{
let cloud = [
("openai", "OpenAI", true, r#"["apiKey"]"#),
(
"elevenlabs",
"ElevenLabs",
true,
r#"["apiKey","modelId","voiceId","language"]"#,
),
("azure", "Azure", true, r#"["subscriptionKey","region"]"#),
("edge", "Microsoft Edge (Read Aloud)", false, "[]"),
("google", "Google Cloud", true, r#"["apiKey"]"#),
(
"gemini",
"Google Gemini (3.8 TTS)",
true,
r#"["apiKey","modelId","voice","style"]"#,
),
(
"qwen",
"Qwen (Alibaba Cloud Model Studio)",
true,
r#"["apiKey","modelId","region","instruction","wsUrl","customizationUrl"]"#,
),
(
"polly",
"Amazon Polly",
true,
r#"["accessKeyId","secretAccessKey","region"]"#,
),
("cartesia", "Cartesia", true, r#"["apiKey"]"#),
("deepgram", "Deepgram", true, r#"["apiKey"]"#),
("playht", "PlayHT", true, r#"["apiKey","userId"]"#),
("fishaudio", "Fish Audio", true, r#"["apiKey"]"#),
("hume", "Hume AI", true, r#"["apiKey"]"#),
("mistral", "Mistral", true, r#"["apiKey"]"#),
("murf", "Murf", true, r#"["apiKey"]"#),
("resemble", "Resemble AI", true, r#"["apiKey"]"#),
("unrealspeech", "Unreal Speech", true, r#"["apiKey"]"#),
("upliftai", "UpliftAI", true, r#"["apiKey"]"#),
(
"watson",
"IBM Watson",
true,
r#"["apiKey","region","instanceId"]"#,
),
("witai", "Wit.ai", true, r#"["token"]"#),
("xai", "xAI", true, r#"["apiKey"]"#),
("modelslab", "ModelsLab", true, r#"["apiKey"]"#),
];
for (id, name, creds, keys) in &cloud {
engines.push(EngineDescriptor {
id: (*id).into(),
name: (*name).into(),
needs_credentials: *creds,
credential_keys_json: (*keys).into(),
});
}
}
engines
}