mirror of https://github.com/ndarilek/tts-rs.git
Merge f39f86aa13
into 919bc4249a
This commit is contained in:
commit
1dc9aea890
|
@ -12,6 +12,7 @@ edition = "2021"
|
|||
crate-type = ["lib", "cdylib", "staticlib"]
|
||||
|
||||
[features]
|
||||
ffi = ["cbindgen"]
|
||||
speech_dispatcher_0_10 = ["speech-dispatcher/0_10"]
|
||||
speech_dispatcher_0_11 = ["speech-dispatcher/0_11"]
|
||||
default = ["speech_dispatcher_0_11"]
|
||||
|
@ -19,6 +20,7 @@ default = ["speech_dispatcher_0_11"]
|
|||
[dependencies]
|
||||
dyn-clonable = "0.9"
|
||||
lazy_static = "1"
|
||||
libc = {version = "0.2", optional = true}
|
||||
log = "0.4"
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
thiserror = "1"
|
||||
|
@ -27,6 +29,9 @@ unic-langid = "0.9.0"
|
|||
[dev-dependencies]
|
||||
env_logger = "0.9"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = {version = "0.18.0", optional = true}
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
tolk = { version = "0.5", optional = true }
|
||||
windows = { version = "0.40", features = ["Foundation", "Foundation_Collections", "Media_Core", "Media_Playback", "Media_SpeechSynthesis", "Storage_Streams"] }
|
||||
|
@ -49,4 +54,4 @@ jni = "0.19"
|
|||
ndk-glue = "0.6"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
no-default-features = true
|
||||
no-default-features = true
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
[tasks.gen-c-bindings]
|
||||
install_crate = "cbindgen"
|
||||
command = "cbindgen"
|
||||
args = ["-c", "cbindgen.toml", "-o", "target/release/tts-rs.h"]
|
||||
|
||||
[tasks.build-android-example]
|
||||
script = [
|
||||
"cd examples/android",
|
||||
|
@ -30,4 +35,4 @@ args = ["build"]
|
|||
dependencies = ["install-trunk", "install-wasm-bindgen-cli"]
|
||||
cwd = "examples/web"
|
||||
command = "trunk"
|
||||
args = ["serve"]
|
||||
args = ["serve"]
|
||||
|
|
14
build.rs
14
build.rs
|
@ -8,4 +8,18 @@ fn main() {
|
|||
println!("cargo:rustc-link-lib=framework=AppKit");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
generate_c_bindings();
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
fn generate_c_bindings() {
|
||||
use std::path::PathBuf;
|
||||
let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let mut header_path: PathBuf = std::env::var("OUT_DIR").unwrap().into();
|
||||
header_path.push("tts.h");
|
||||
cbindgen::generate(crate_dir)
|
||||
.unwrap()
|
||||
.write_to_file(header_path);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
language = "c"
|
||||
cpp_compat = true
|
||||
include_guard = "TTS_RS_H"
|
||||
header = "/* SPDXLicenseIdentifier: MIT */"
|
||||
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
|
||||
|
||||
[enum]
|
||||
rename_variants = "QualifiedScreamingSnakeCase"
|
||||
|
||||
[defines]
|
||||
"target_os = android" = "__ANDROID__"
|
||||
"target_os = linux" = "__linux__"
|
||||
"target_os = macos" = "__APPLE__"
|
||||
"target_arch = wasm32" = "__EMSCRIPTEN__"
|
||||
windows = "__WIN32__"
|
|
@ -16,6 +16,7 @@ mod appkit;
|
|||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
mod av_foundation;
|
||||
|
||||
/// cbindgen:ignore
|
||||
#[cfg(target_os = "android")]
|
||||
mod android;
|
||||
|
||||
|
|
|
@ -0,0 +1,353 @@
|
|||
//! Bindings to this library to allow it to be called from C/C++.
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::{CStr, CString, NulError},
|
||||
os::raw::c_char,
|
||||
ptr,
|
||||
};
|
||||
|
||||
use crate::{Backends, Features, Tts, UtteranceId};
|
||||
|
||||
thread_local! {
|
||||
/// Stores the last reported error, so it can be retrieved at will from C
|
||||
static LAST_ERROR: RefCell<Option<CString>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
fn set_last_error<E: Into<Vec<u8>>>(err: E) -> Result<(), NulError> {
|
||||
LAST_ERROR.with(|last| {
|
||||
*last.borrow_mut() = Some(CString::new(err)?);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the last reported error as a const C string.
|
||||
/// This string will be valid until at least the next call to `tts_get_error`.
|
||||
/// It is never called internally by the library.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tts_get_error() -> *const c_char {
|
||||
LAST_ERROR.with(|err| match &*err.borrow() {
|
||||
Some(e) => e.as_ptr(),
|
||||
None => ptr::null(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Deallocate the last reported error (if any).
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tts_clear_error() {
|
||||
LAST_ERROR.with(|err| {
|
||||
*err.borrow_mut() = None;
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a new `Tts` instance with the specified backend.
|
||||
/// If an error occurs, returns a null pointer,
|
||||
/// Call `tts_get_error()` for more information about the specific error.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tts_new(backend: Backends) -> *mut Tts {
|
||||
match Tts::new(backend) {
|
||||
Ok(tts) => Box::into_raw(Box::new(tts)),
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new Tts object with the default backend.
|
||||
/// If an error occurs, returns a null pointer,
|
||||
/// Call `tts_get_error()` for more information about the specific error.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn tts_default() -> *mut Tts {
|
||||
match Tts::default() {
|
||||
Ok(tts) => Box::into_raw(Box::new(tts)),
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Free the memory associated with a Tts object.
|
||||
/// If `tts` is a null pointer, this function does nothing.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_free(tts: *mut Tts) {
|
||||
if tts.is_null() {
|
||||
return;
|
||||
}
|
||||
Box::from_raw(tts); // Goes out of scope and is dropped
|
||||
}
|
||||
|
||||
/// Returns the features supported by this Tts engine in the object specified by `features`.
|
||||
/// `tts` must be a valid pointer to a Tts object.
|
||||
/// `features` must be a valid pointer to an uninitialized `Features` object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_supported_features(tts: *const Tts, features: *mut Features) {
|
||||
*features = tts.as_ref().unwrap().supported_features()
|
||||
}
|
||||
|
||||
/// Speaks the specified text, optionally interrupting current speech.
|
||||
/// If `utterance` is not NULL, , fills it with a pointer to the returned UtteranceId (or NULL if
|
||||
/// the backend doesn't provide one).
|
||||
/// Returns true on success, false on error or if `tts` is NULL.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_speak(
|
||||
tts: *mut Tts,
|
||||
text: *const c_char,
|
||||
interrupt: bool,
|
||||
utterance: *mut *mut UtteranceId,
|
||||
) -> bool {
|
||||
if tts.is_null() {
|
||||
return false;
|
||||
}
|
||||
let text = CStr::from_ptr(text).to_string_lossy().into_owned();
|
||||
match tts.as_mut().unwrap().speak(text, interrupt) {
|
||||
Ok(u) => {
|
||||
if !utterance.is_null() {
|
||||
*utterance = match u {
|
||||
Some(u) => Box::into_raw(Box::new(u)),
|
||||
None => ptr::null_mut(),
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Free the memory associated with an `UtteranceId`.
|
||||
/// Does nothing if `utterance` is NULL.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_free_utterance(utterance: *mut UtteranceId) {
|
||||
if utterance.is_null() {
|
||||
return;
|
||||
}
|
||||
Box::from_raw(utterance);
|
||||
}
|
||||
|
||||
/// Stops current speech.
|
||||
/// Returns true on success, false on error or if `tts` is NULL.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_stop(tts: *mut Tts) -> bool {
|
||||
if tts.is_null() {
|
||||
return false;
|
||||
}
|
||||
match tts.as_mut().unwrap().stop() {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the minimum rate for this speech synthesizer.
|
||||
/// `tts` must be a valid pointer to a Tts object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_min_rate(tts: *const Tts) -> f32 {
|
||||
tts.as_ref().unwrap().min_rate()
|
||||
}
|
||||
|
||||
/// Returns the maximum rate for this speech synthesizer.
|
||||
/// `tts` must be a valid pointer to a Tts object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_max_rate(tts: *const Tts) -> f32 {
|
||||
tts.as_ref().unwrap().max_rate()
|
||||
}
|
||||
|
||||
/// Returns the normal rate for this speech synthesizer.
|
||||
/// `tts` must be a valid pointer to a Tts object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_normal_rate(tts: *const Tts) -> f32 {
|
||||
tts.as_ref().unwrap().normal_rate()
|
||||
}
|
||||
|
||||
/// Gets the current speech rate.
|
||||
/// Returns true on success, false on error (likely that the backend doesn't support rate changes)
|
||||
/// or if `tts` is NULL.
|
||||
/// Does nothing if `rate` is NULL.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_get_rate(tts: *const Tts, rate: *mut f32) -> bool {
|
||||
if tts.is_null() {
|
||||
return false;
|
||||
}
|
||||
match tts.as_ref().unwrap().get_rate() {
|
||||
Ok(r) => {
|
||||
if !rate.is_null() {
|
||||
*rate = r;
|
||||
}
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the desired speech rate.
|
||||
/// Returns true on success, false on error (likely that the backend doesn't support rate changes)
|
||||
/// or if `tts` is NULL.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_set_rate(tts: *mut Tts, rate: f32) -> bool {
|
||||
if tts.is_null() {
|
||||
return false;
|
||||
}
|
||||
match tts.as_mut().unwrap().set_rate(rate) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the minimum pitch for this speech synthesizer.
|
||||
/// `tts` must be a valid pointer to a Tts object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_min_pitch(tts: *const Tts) -> f32 {
|
||||
tts.as_ref().unwrap().min_pitch()
|
||||
}
|
||||
|
||||
/// Returns the maximum pitch for this speech synthesizer.
|
||||
/// `tts` must be a valid pointer to a Tts object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_max_pitch(tts: *const Tts) -> f32 {
|
||||
tts.as_ref().unwrap().max_pitch()
|
||||
}
|
||||
|
||||
/// Returns the normal pitch for this speech synthesizer.
|
||||
/// `tts` must be a valid pointer to a Tts object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_normal_pitch(tts: *const Tts) -> f32 {
|
||||
tts.as_ref().unwrap().normal_pitch()
|
||||
}
|
||||
|
||||
/// Gets the current speech pitch.
|
||||
/// Returns true on success, false on error (likely that the backend doesn't support pitch changes)
|
||||
/// or if `tts` is NULL.
|
||||
/// Does nothing if `pitch` is NULL.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_get_pitch(tts: *const Tts, pitch: *mut f32) -> bool {
|
||||
if tts.is_null() {
|
||||
return false;
|
||||
}
|
||||
match tts.as_ref().unwrap().get_pitch() {
|
||||
Ok(r) => {
|
||||
if !pitch.is_null() {
|
||||
*pitch = r;
|
||||
}
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the desired speech pitch.
|
||||
/// Returns true on success, false on error (likely that the backend doesn't support pitch changes)
|
||||
/// or if `tts` is NULL.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_set_pitch(tts: *mut Tts, pitch: f32) -> bool {
|
||||
if tts.is_null() {
|
||||
return false;
|
||||
}
|
||||
match tts.as_mut().unwrap().set_pitch(pitch) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the minimum volume for this speech synthesizer.
|
||||
/// `tts` must be a valid pointer to a Tts object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_min_volume(tts: *const Tts) -> f32 {
|
||||
tts.as_ref().unwrap().min_volume()
|
||||
}
|
||||
|
||||
/// Returns the maximum volume for this speech synthesizer.
|
||||
/// `tts` must be a valid pointer to a Tts object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_max_volume(tts: *const Tts) -> f32 {
|
||||
tts.as_ref().unwrap().max_volume()
|
||||
}
|
||||
|
||||
/// Returns the normal volume for this speech synthesizer.
|
||||
/// `tts` must be a valid pointer to a Tts object.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_normal_volume(tts: *const Tts) -> f32 {
|
||||
tts.as_ref().unwrap().normal_volume()
|
||||
}
|
||||
|
||||
/// Gets the current speech volume.
|
||||
/// Returns true on success, false on error (likely that the backend doesn't support volume changes)
|
||||
/// or if `tts` is NULL.
|
||||
/// Does nothing if `volume` is NULL.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_get_volume(tts: *const Tts, volume: *mut f32) -> bool {
|
||||
if tts.is_null() {
|
||||
return false;
|
||||
}
|
||||
match tts.as_ref().unwrap().get_volume() {
|
||||
Ok(r) => {
|
||||
if !volume.is_null() {
|
||||
*volume = r;
|
||||
}
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the desired speech volume.
|
||||
/// Returns true on success, false on error (likely that the backend doesn't support volume changes)
|
||||
/// or if `tts` is NULL.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_set_volume(tts: *mut Tts, volume: f32) -> bool {
|
||||
if tts.is_null() {
|
||||
return false;
|
||||
}
|
||||
match tts.as_mut().unwrap().set_volume(volume) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// fills `speaking` with a bool indicating whether this speech synthesizer is speaking.
|
||||
/// Returns true on success, false on error (likely that the backend doesn't support speaking
|
||||
/// status) or if `tts` is NULL.
|
||||
/// If `speaking` is NULL, returns this value instead, meaning you can't tell the difference
|
||||
/// between an error occuring and the synth not speaking.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tts_is_speaking(tts: *const Tts, speaking: *mut bool) -> bool {
|
||||
if tts.is_null() {
|
||||
return false;
|
||||
}
|
||||
match tts.as_ref().unwrap().is_speaking() {
|
||||
Ok(s) => {
|
||||
if speaking.is_null() {
|
||||
return s;
|
||||
}
|
||||
*speaking = s;
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
set_last_error(e.to_string()).unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,7 +35,10 @@ use tolk::Tolk;
|
|||
pub use unic_langid::LanguageIdentifier;
|
||||
|
||||
mod backends;
|
||||
#[cfg(feature = "ffi")]
|
||||
pub mod ffi;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Backends {
|
||||
|
@ -158,6 +161,7 @@ unsafe impl Send for UtteranceId {}
|
|||
|
||||
unsafe impl Sync for UtteranceId {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Features {
|
||||
|
|
Loading…
Reference in New Issue