use std::collections::HashMap;
use std::ffi::{CStr, CString, c_char};
use std::path::Path;
use crate::model::KittenTtsOnnx;
use crate::phonemize;
pub struct KittenTtsHandle {
model: KittenTtsOnnx,
}
unsafe fn cstr_to_string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned())
}
fn to_c_str(s: &str) -> *const c_char {
match CString::new(s) {
Ok(cs) => cs.into_raw(),
Err(_) => std::ptr::null(),
}
}
#[no_mangle]
pub unsafe extern "C" fn kittentts_set_espeak_data_path(path: *const c_char) {
if let Some(s) = unsafe { cstr_to_string(path) } {
phonemize::set_data_path(Path::new(&s));
}
}
#[no_mangle]
pub unsafe extern "C" fn kittentts_model_load(
onnx_path: *const c_char,
voices_path: *const c_char,
) -> *mut KittenTtsHandle {
let (Some(onnx), Some(voices)) = (
unsafe { cstr_to_string(onnx_path) },
unsafe { cstr_to_string(voices_path) },
) else {
eprintln!("[kittentts] kittentts_model_load: null argument");
return std::ptr::null_mut();
};
match KittenTtsOnnx::load(
Path::new(&onnx),
Path::new(&voices),
HashMap::new(), HashMap::new(), ) {
Ok(model) => Box::into_raw(Box::new(KittenTtsHandle { model })),
Err(e) => {
eprintln!("[kittentts] load error: {e:#}");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn kittentts_model_voices(
model: *const KittenTtsHandle,
) -> *const c_char {
if model.is_null() {
return std::ptr::null();
}
let h = unsafe { &*model };
let quoted: Vec<String> = h
.model
.available_voices
.iter()
.map(|v| format!("\"{}\"", v.replace('"', "\\\"")))
.collect();
let json = format!("[{}]", quoted.join(","));
to_c_str(&json)
}
#[cfg(feature = "espeak")]
#[no_mangle]
pub unsafe extern "C" fn kittentts_synthesize_to_file(
model: *const KittenTtsHandle,
text: *const c_char,
voice: *const c_char,
speed: f32,
output_path: *const c_char,
) -> *const c_char {
macro_rules! bail {
($msg:literal) => {
return to_c_str($msg);
};
($fmt:expr, $($arg:tt)*) => {
return to_c_str(&format!($fmt, $($arg)*));
};
}
if model.is_null() {
bail!("null model handle");
}
let (Some(txt), Some(vox), Some(out)) = (
unsafe { cstr_to_string(text) },
unsafe { cstr_to_string(voice) },
unsafe { cstr_to_string(output_path) },
) else {
bail!("null argument (text, voice, or output_path)");
};
let h = unsafe { &*model };
match h.model.generate_to_file(
&txt,
Path::new(&out),
&vox,
speed,
true,
) {
Ok(()) => std::ptr::null(),
Err(e) => to_c_str(&format!("{e:#}")),
}
}
#[no_mangle]
pub unsafe extern "C" fn kittentts_free_string(s: *const c_char) {
if !s.is_null() {
drop(unsafe { CString::from_raw(s as *mut c_char) });
}
}
#[no_mangle]
pub unsafe extern "C" fn kittentts_free_error(s: *const c_char) {
unsafe { kittentts_free_string(s) };
}
#[no_mangle]
pub unsafe extern "C" fn kittentts_model_free(model: *mut KittenTtsHandle) {
if !model.is_null() {
drop(unsafe { Box::from_raw(model) });
}
}