Add support for listing voices
Rebased on top of the result changes.
This commit is contained in:
parent
2879284030
commit
143147036c
29
speech-dispatcher/examples/list_voices.rs
Normal file
29
speech-dispatcher/examples/list_voices.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use speech_dispatcher::*;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let connection = Connection::open("list_voices", "list_voices", "list_voices", Mode::Threaded)?;
|
||||||
|
|
||||||
|
let modules = connection.list_output_modules()?;
|
||||||
|
println!("Modules available: {:?}", modules);
|
||||||
|
for module in modules.into_iter() {
|
||||||
|
if connection.set_output_module(&module).is_ok() {
|
||||||
|
println!("Listing voices for module {}", module);
|
||||||
|
} else {
|
||||||
|
println!("Failed to set output module to {}", module);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let voices = connection.list_synthesis_voices()?;
|
||||||
|
for voice in voices.into_iter() {
|
||||||
|
if let Some(variant) = voice.variant.as_deref() {
|
||||||
|
println!(
|
||||||
|
" Name: {} / Language: {} / Variant: {}",
|
||||||
|
voice.name, voice.language, variant
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!(" Name: {} / Language: {}", voice.name, voice.language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Use connection.set_synthesis_voice(voice.name) to set the voice to use.
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -42,6 +42,38 @@ pub enum VoiceType {
|
||||||
ChildFemale = SPDVoiceType::SPD_CHILD_FEMALE,
|
ChildFemale = SPDVoiceType::SPD_CHILD_FEMALE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||||
|
pub struct Voice {
|
||||||
|
/// The name of this voice. Unique with regards to the output module it came from. Pass this to
|
||||||
|
/// [`Connection::set_synthesis_voice`] to use this voice.
|
||||||
|
pub name: String,
|
||||||
|
/// The language of this voice. Probably a BCP 47 language tag.
|
||||||
|
pub language: String,
|
||||||
|
/// The variant of this language, if present. Loosely defined.
|
||||||
|
pub variant: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Voice {
|
||||||
|
/// Convert a SPDVoice to a Voice. Only fails if the fields are non-Unicode.
|
||||||
|
/// Does not check that the pointers are non-null, caller must ensure that.
|
||||||
|
unsafe fn try_from(v: &SPDVoice) -> Result<Self, std::str::Utf8Error> {
|
||||||
|
// SPDVoice fields appear to all be ASCII.
|
||||||
|
let name = CStr::from_ptr(v.name).to_str()?.to_owned();
|
||||||
|
let language = CStr::from_ptr(v.language).to_str()?.to_owned();
|
||||||
|
let variant = CStr::from_ptr(v.variant).to_str()?;
|
||||||
|
let variant = if variant == "none" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(variant.to_owned())
|
||||||
|
};
|
||||||
|
Ok(Self {
|
||||||
|
name,
|
||||||
|
language,
|
||||||
|
variant,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Connection(pub *mut SPDConnection, u64);
|
pub struct Connection(pub *mut SPDConnection, u64);
|
||||||
|
|
||||||
|
@ -705,11 +737,65 @@ impl Connection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_synthesis_voices(&self) -> Result<Vec<Voice>, Error> {
|
||||||
|
let start = unsafe { spd_list_synthesis_voices(self.0) };
|
||||||
|
let slice = unsafe { null_term_array_ptr_to_slice(start) }.ok_or(Error::OperationFailed)?;
|
||||||
|
let voices = unsafe {
|
||||||
|
slice
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.read())
|
||||||
|
.flat_map(|v| {
|
||||||
|
if v.name.is_null() || v.language.is_null() || v.variant.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Voice::try_from(&v).ok()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
free_spd_voices(start);
|
||||||
|
}
|
||||||
|
Ok(voices)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_output_modules(&self) -> Result<Vec<String>, Error> {
|
||||||
|
let start = unsafe { spd_list_modules(self.0) };
|
||||||
|
let slice = unsafe { null_term_array_ptr_to_slice(start) }.ok_or(Error::OperationFailed)?;
|
||||||
|
let modules = unsafe {
|
||||||
|
slice
|
||||||
|
.iter()
|
||||||
|
.flat_map(|v| CStr::from_ptr(*v).to_str().ok().map(String::from))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
free_spd_modules(start);
|
||||||
|
}
|
||||||
|
Ok(modules)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn client_id(&self) -> u64 {
|
pub fn client_id(&self) -> u64 {
|
||||||
self.1
|
self.1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Interpret a null-terminated array of pointers as a slice of pointers.
|
||||||
|
/// None of the pointers will be null if this returns Some.
|
||||||
|
unsafe fn null_term_array_ptr_to_slice<'a, T>(start: *mut *mut T) -> Option<&'a [*mut T]> {
|
||||||
|
if start.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut current = start;
|
||||||
|
let mut len = 0;
|
||||||
|
|
||||||
|
while !current.read().is_null() {
|
||||||
|
len += 1;
|
||||||
|
current = current.add(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(std::slice::from_raw_parts(start, len))
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl Send for Connection {}
|
unsafe impl Send for Connection {}
|
||||||
|
|
||||||
impl Drop for Connection {
|
impl Drop for Connection {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user