WIP: Reorganize, and try to get working with Speech Dispatcher.

This commit is contained in:
Nolan Darilek 2022-03-30 12:07:59 -05:00
parent 55f841d887
commit e56a0da2e5
3 changed files with 52 additions and 35 deletions

View File

@ -8,21 +8,11 @@ license = "MIT"
exclude = ["*.cfg", "*.yml"] exclude = ["*.cfg", "*.yml"]
edition = "2021" edition = "2021"
[package.metadata.patch.speech-dispatcher]
version = "0.7.0"
#patches = [
# "speech-dispatcher.patch"
#]
#[patch.crates-io]
#speech-dispatcher = { path = './target/patch/speech-dispatcher-0.7.0'}
[lib] [lib]
crate-type = ["lib", "cdylib", "staticlib"] crate-type = ["lib", "cdylib", "staticlib"]
[features] [features]
speech_dispatcher_0_10 = ["speech-dispatcher/0_10"] speech_dispatcher_0_10 = ["speech-dispatcher/0_10"]
default = ["speech_dispatcher_0_10"]
[dependencies] [dependencies]
dyn-clonable = "0.9" dyn-clonable = "0.9"

View File

@ -1,11 +1,15 @@
use std::str::FromStr;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::{collections::HashMap, sync::Mutex}; use std::{collections::HashMap, sync::Mutex};
use lazy_static::*; use lazy_static::*;
use log::{info, trace}; use log::{info, trace};
use speech_dispatcher::*; use speech_dispatcher::{Voice as SpdVoice, *};
use unic_langid::{LanguageIdentifier, LanguageIdentifierError};
use crate::{Backend, BackendId, Error, Features, UtteranceId, CALLBACKS}; use crate::{
Backend, BackendId, Error, Features, Gender, UtteranceId, Voice, VoiceImpl, CALLBACKS,
};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct SpeechDispatcher(Connection); pub(crate) struct SpeechDispatcher(Connection);
@ -17,6 +21,24 @@ lazy_static! {
}; };
} }
impl VoiceImpl for SpdVoice {
fn id(self) -> String {
self.name
}
fn name(self) -> String {
self.name
}
fn gender(self) -> Gender {
Gender::Other
}
fn language(self) -> Result<LanguageIdentifier, LanguageIdentifierError> {
LanguageIdentifier::from_str(&self.language)
}
}
impl SpeechDispatcher { impl SpeechDispatcher {
pub(crate) fn new() -> std::result::Result<Self, Error> { pub(crate) fn new() -> std::result::Result<Self, Error> {
info!("Initializing SpeechDispatcher backend"); info!("Initializing SpeechDispatcher backend");
@ -69,7 +91,7 @@ impl SpeechDispatcher {
} }
} }
impl Backend for SpeechDispatcher { impl<T: VoiceImpl> Backend<T> for SpeechDispatcher {
fn id(&self) -> Option<BackendId> { fn id(&self) -> Option<BackendId> {
Some(BackendId::SpeechDispatcher(self.0.client_id())) Some(BackendId::SpeechDispatcher(self.0.client_id()))
} }
@ -181,11 +203,18 @@ impl Backend for SpeechDispatcher {
Ok(*is_speaking) Ok(*is_speaking)
} }
fn voice(&self) -> Result<String, Error> { fn voices(&self) -> Result<Vec<Voice<T>>, Error> {
unimplemented!() let rv = self
.0
.list_synthesis_voices()?
.iter()
.cloned()
.map(|v| Voice(Box::new(v)))
.collect::<Vec<Voice<T>>>();
Ok(rv)
} }
fn list_voices(&self) -> Vec<String> { fn voice(&self) -> Result<String, Error> {
unimplemented!() unimplemented!()
} }

View File

@ -16,13 +16,13 @@ use std::collections::HashMap;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use std::ffi::CStr; use std::ffi::CStr;
use std::fmt; use std::fmt;
use std::marker::PhantomData;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::{boxed::Box, sync::RwLock}; use std::{boxed::Box, sync::RwLock};
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(any(target_os = "macos", target_os = "ios"))]
use cocoa_foundation::base::id; use cocoa_foundation::base::id;
use dyn_clonable::*; use dyn_clonable::*;
pub use unic_langid::LanguageIdentifier;
use lazy_static::lazy_static; use lazy_static::lazy_static;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use libc::c_char; use libc::c_char;
@ -33,6 +33,8 @@ use speech_dispatcher::Error as SpeechDispatcherError;
use thiserror::Error; use thiserror::Error;
#[cfg(all(windows, feature = "tolk"))] #[cfg(all(windows, feature = "tolk"))]
use tolk::Tolk; use tolk::Tolk;
pub use unic_langid::LanguageIdentifier;
use unic_langid::LanguageIdentifierError;
mod backends; mod backends;
@ -209,7 +211,7 @@ pub enum Error {
} }
#[clonable] #[clonable]
pub trait Backend: Clone { pub trait Backend<T: VoiceImpl>: Clone {
fn id(&self) -> Option<BackendId>; fn id(&self) -> Option<BackendId>;
fn supported_features(&self) -> Features; fn supported_features(&self) -> Features;
fn speak(&mut self, text: &str, interrupt: bool) -> Result<Option<UtteranceId>, Error>; fn speak(&mut self, text: &str, interrupt: bool) -> Result<Option<UtteranceId>, Error>;
@ -230,8 +232,8 @@ pub trait Backend: Clone {
fn get_volume(&self) -> Result<f32, Error>; fn get_volume(&self) -> Result<f32, Error>;
fn set_volume(&mut self, volume: f32) -> Result<(), Error>; fn set_volume(&mut self, volume: f32) -> Result<(), Error>;
fn is_speaking(&self) -> Result<bool, Error>; fn is_speaking(&self) -> Result<bool, Error>;
fn voices(&self) -> Result<Vec<Voice<T>>, Error>;
fn voice(&self) -> Result<String, Error>; fn voice(&self) -> Result<String, Error>;
fn list_voices(&self) -> Vec<String>;
fn set_voice(&mut self, voice: &str) -> Result<(), Error>; fn set_voice(&mut self, voice: &str) -> Result<(), Error>;
} }
@ -254,22 +256,22 @@ lazy_static! {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Tts(Arc<RwLock<Box<dyn Backend>>>); pub struct Tts<T: VoiceImpl>(Arc<RwLock<Box<dyn Backend<T>>>>, PhantomData<T>);
unsafe impl Send for Tts {} unsafe impl<T: VoiceImpl> Send for Tts<T> {}
unsafe impl Sync for Tts {} unsafe impl<T: VoiceImpl> Sync for Tts<T> {}
impl Tts { impl<T: VoiceImpl> Tts<T> {
/** /**
* Create a new `TTS` instance with the specified backend. * Create a new `TTS` instance with the specified backend.
*/ */
pub fn new(backend: Backends) -> Result<Tts, Error> { pub fn new(backend: Backends) -> Result<Tts<T>, Error> {
let backend = match backend { let backend = match backend {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
Backends::SpeechDispatcher => { Backends::SpeechDispatcher => {
let tts = backends::SpeechDispatcher::new()?; let tts = backends::SpeechDispatcher::new()?;
Ok(Tts(Arc::new(RwLock::new(Box::new(tts))))) Ok(Tts(Arc::new(RwLock::new(Box::new(tts))), PhantomData))
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
Backends::Web => { Backends::Web => {
@ -315,7 +317,7 @@ impl Tts {
} }
} }
pub fn default() -> Result<Tts, Error> { pub fn default() -> Result<Tts<T>, Error> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
let tts = Tts::new(Backends::SpeechDispatcher); let tts = Tts::new(Backends::SpeechDispatcher);
#[cfg(all(windows, feature = "tolk"))] #[cfg(all(windows, feature = "tolk"))]
@ -564,8 +566,8 @@ impl Tts {
/** /**
* Returns list of available voices. * Returns list of available voices.
*/ */
pub fn list_voices(&self) -> Vec<String> { pub fn voices(&self) -> Result<Vec<Voice<T>>, Error> {
self.0.read().unwrap().list_voices() self.0.read().unwrap().voices()
} }
/** /**
@ -680,7 +682,7 @@ impl Tts {
} }
} }
impl Drop for Tts { impl<T: VoiceImpl> Drop for Tts<T> {
fn drop(&mut self) { fn drop(&mut self) {
if Arc::strong_count(&self.0) <= 1 { if Arc::strong_count(&self.0) <= 1 {
if let Some(id) = self.0.read().unwrap().id() { if let Some(id) = self.0.read().unwrap().id() {
@ -698,14 +700,10 @@ pub enum Gender {
} }
pub trait VoiceImpl: Sized { pub trait VoiceImpl: Sized {
type Backend: crate::Backend; fn id(self) -> String;
fn from_id(id: String) -> Self;
fn from_language(lang: LanguageIdentifier) -> Self;
fn list() -> Vec<Self>;
fn name(self) -> String; fn name(self) -> String;
fn gender(self) -> Gender; fn gender(self) -> Gender;
fn id(self) -> String; fn language(self) -> Result<LanguageIdentifier, LanguageIdentifierError>;
fn language(self) -> LanguageIdentifier;
} }
pub struct Voice<T: VoiceImpl + Sized>(Box<T>); pub struct Voice<T: VoiceImpl + Sized>(Box<T>);