1pub mod bench;
9mod engine;
10mod error;
11mod model;
12mod split;
13
14pub use engine::{DEFAULT_VOICE, KokoroTts, SAMPLE_RATE, select_voice};
15pub use error::Error;
16pub use model::ModelFiles;
17pub use split::{sentences, within_bound};
18
19#[derive(Debug, Clone)]
21pub struct SpeakRequest {
22 pub text: String,
23 pub voice: Option<String>,
26}
27
28#[derive(Debug, Clone)]
30pub struct Chunk {
31 pub seq: u32,
32 pub sample_rate: u32,
33 pub pcm: Vec<i16>,
34}
35
36pub trait Engine: Send + Sync {
40 fn speak(&self, request: SpeakRequest) -> tokio::sync::mpsc::Receiver<Result<Chunk, Error>>;
44}
45
46#[cfg(test)]
47mod tests {
48 use super::Error;
49
50 #[test]
51 fn an_error_names_its_stage() {
52 let error = Error::ModelMissing("/models/kokoro.onnx".to_owned());
53 assert!(error.to_string().contains("kokoro.onnx"));
54 }
55
56 #[test]
59 fn the_qwen_engine_is_gone() {
60 let manifest = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"))
61 .expect("the crate's own manifest reads");
62 assert!(!manifest.contains("llama"), "forge-tts still depends on llama.cpp:\n{manifest}");
63 assert!(
64 !std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/engine/ffi.rs"))
65 .exists(),
66 "the Qwen FFI module is still in the crate"
67 );
68 }
69
70 #[test]
74 fn depends_on_no_forge_crate() {
75 let output = std::process::Command::new(option_env!("CARGO").unwrap_or("cargo"))
76 .args([
77 "metadata",
78 "--no-deps",
79 "--offline",
80 "--format-version",
81 "1",
82 "--manifest-path",
83 concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"),
84 ])
85 .output()
86 .expect("cargo metadata must run: a guard that cannot run is not a guard");
87 assert!(
88 output.status.success(),
89 "cargo metadata failed, so the invariant is unchecked: {}",
90 String::from_utf8_lossy(&output.stderr)
91 );
92
93 let metadata: serde_json::Value =
94 serde_json::from_slice(&output.stdout).expect("cargo metadata emits json");
95 let package = metadata["packages"]
96 .as_array()
97 .into_iter()
98 .flatten()
99 .find(|p| p["name"] == "forge-tts")
100 .expect("forge-tts must appear in its own metadata");
101
102 let forge: Vec<&str> = package["dependencies"]
103 .as_array()
104 .into_iter()
105 .flatten()
106 .filter_map(|dep| dep["name"].as_str())
107 .filter(|name| name.starts_with("forge-"))
108 .collect();
109 assert!(forge.is_empty(), "forge-tts must depend on no forge-* crate, found {forge:?}");
110 }
111}