mirror of
https://github.com/ndarilek/tts-rs.git
synced 2024-11-17 13:49:36 +00:00
Merge branch 'v0.7' of https://github.com/ndarilek/tts-rs into v0.7
This commit is contained in:
commit
36a12597de
|
@ -12,6 +12,18 @@ use tts::*;
|
|||
fn main() -> Result<(), Error> {
|
||||
env_logger::init();
|
||||
let mut tts = TTS::default()?;
|
||||
let Features {
|
||||
utterance_callbacks,
|
||||
..
|
||||
} = tts.supported_features();
|
||||
if utterance_callbacks {
|
||||
tts.on_utterance_begin(Some(|utterance| {
|
||||
println!("Started speaking {:?}", utterance)
|
||||
}))?;
|
||||
tts.on_utterance_end(Some(|utterance| {
|
||||
println!("Finished speaking {:?}", utterance)
|
||||
}))?;
|
||||
}
|
||||
tts.speak("Hello, world.", false)?;
|
||||
let Features { rate, .. } = tts.supported_features();
|
||||
if rate {
|
||||
|
@ -49,6 +61,8 @@ fn main() -> Result<(), Error> {
|
|||
}
|
||||
tts.speak("Goodbye.", false)?;
|
||||
let mut _input = String::new();
|
||||
// The below is only needed to make the example run on MacOS because there is no NSRunLoop in this context.
|
||||
// It shouldn't be needed in an app or game that almost certainly has one already.
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let run_loop: id = unsafe { NSRunLoop::currentRunLoop() };
|
||||
|
|
|
@ -17,7 +17,7 @@ mod appkit;
|
|||
mod av_foundation;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use self::speech_dispatcher::*;
|
||||
pub(crate) use self::speech_dispatcher::*;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use self::tolk::*;
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
#[cfg(target_os = "linux")]
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use lazy_static::*;
|
||||
use log::{info, trace};
|
||||
use speech_dispatcher::*;
|
||||
|
||||
use crate::{Backend, Error, Features, UtteranceId};
|
||||
use crate::{Backend, BackendId, Error, Features, UtteranceId, CALLBACKS};
|
||||
|
||||
pub struct SpeechDispatcher(Connection);
|
||||
pub(crate) struct SpeechDispatcher(Connection);
|
||||
|
||||
lazy_static! {
|
||||
static ref SPEAKING: Mutex<HashMap<u64, bool>> = {
|
||||
|
@ -18,19 +19,33 @@ lazy_static! {
|
|||
}
|
||||
|
||||
impl SpeechDispatcher {
|
||||
pub fn new() -> Self {
|
||||
pub(crate) fn new() -> Self {
|
||||
info!("Initializing SpeechDispatcher backend");
|
||||
let connection = speech_dispatcher::Connection::open("tts", "tts", "tts", Mode::Threaded);
|
||||
let sd = SpeechDispatcher(connection);
|
||||
let mut speaking = SPEAKING.lock().unwrap();
|
||||
speaking.insert(sd.0.client_id(), false);
|
||||
sd.0.on_begin(Some(|_msg_id, client_id| {
|
||||
sd.0.on_begin(Some(|msg_id, client_id| {
|
||||
let mut speaking = SPEAKING.lock().unwrap();
|
||||
speaking.insert(client_id, true);
|
||||
let callbacks = CALLBACKS.lock().unwrap();
|
||||
let backend_id = BackendId::SpeechDispatcher(client_id);
|
||||
let cb = callbacks.get(&backend_id).unwrap();
|
||||
let utterance_id = UtteranceId::SpeechDispatcher(msg_id);
|
||||
if let Some(f) = cb.utterance_begin {
|
||||
f(utterance_id);
|
||||
}
|
||||
}));
|
||||
sd.0.on_end(Some(|_msg_id, client_id| {
|
||||
sd.0.on_end(Some(|msg_id, client_id| {
|
||||
let mut speaking = SPEAKING.lock().unwrap();
|
||||
speaking.insert(client_id, false);
|
||||
let callbacks = CALLBACKS.lock().unwrap();
|
||||
let backend_id = BackendId::SpeechDispatcher(client_id);
|
||||
let cb = callbacks.get(&backend_id).unwrap();
|
||||
let utterance_id = UtteranceId::SpeechDispatcher(msg_id);
|
||||
if let Some(f) = cb.utterance_end {
|
||||
f(utterance_id);
|
||||
}
|
||||
}));
|
||||
sd.0.on_cancel(Some(|_msg_id, client_id| {
|
||||
let mut speaking = SPEAKING.lock().unwrap();
|
||||
|
@ -49,6 +64,10 @@ impl SpeechDispatcher {
|
|||
}
|
||||
|
||||
impl Backend for SpeechDispatcher {
|
||||
fn id(&self) -> Option<BackendId> {
|
||||
Some(BackendId::SpeechDispatcher(self.0.client_id()))
|
||||
}
|
||||
|
||||
fn supported_features(&self) -> Features {
|
||||
Features {
|
||||
stop: true,
|
||||
|
@ -56,6 +75,7 @@ impl Backend for SpeechDispatcher {
|
|||
pitch: true,
|
||||
volume: true,
|
||||
is_speaking: true,
|
||||
utterance_callbacks: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +93,7 @@ impl Backend for SpeechDispatcher {
|
|||
self.0.set_punctuation(Punctuation::None);
|
||||
}
|
||||
if let Some(id) = id {
|
||||
Ok(Some(UtteranceId::SpeechDispatcher(id)))
|
||||
Ok(Some(UtteranceId::SpeechDispatcher(id.try_into().unwrap())))
|
||||
} else {
|
||||
Err(Error::NoneError)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
use log::{info, trace};
|
||||
use tolk::Tolk as TolkPtr;
|
||||
|
||||
use crate::{Backend, Error, Features, UtteranceId};
|
||||
use crate::{Backend, BackendId, Error, Features, UtteranceId};
|
||||
|
||||
pub struct Tolk(TolkPtr);
|
||||
|
||||
|
@ -19,6 +19,10 @@ impl Tolk {
|
|||
}
|
||||
|
||||
impl Backend for Tolk {
|
||||
fn id(&self) -> Option<BackendId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn supported_features(&self) -> Features {
|
||||
Features {
|
||||
stop: true,
|
||||
|
|
|
@ -10,7 +10,7 @@ use tts_winrt_bindings::windows::media::playback::{
|
|||
};
|
||||
use tts_winrt_bindings::windows::media::speech_synthesis::SpeechSynthesizer;
|
||||
|
||||
use crate::{Backend, Error, Features, UtteranceId};
|
||||
use crate::{Backend, BackendId, Error, Features, UtteranceId};
|
||||
|
||||
impl From<winrt::Error> for Error {
|
||||
fn from(e: winrt::Error) -> Self {
|
||||
|
@ -19,13 +19,14 @@ impl From<winrt::Error> for Error {
|
|||
}
|
||||
|
||||
pub struct WinRT {
|
||||
id: BackendId,
|
||||
synth: SpeechSynthesizer,
|
||||
player: MediaPlayer,
|
||||
playback_list: MediaPlaybackList,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref NEXT_UTTERANCE_ID: Mutex<u64> = Mutex::new(0);
|
||||
static ref NEXT_BACKEND_ID: Mutex<u64> = Mutex::new(0);
|
||||
}
|
||||
|
||||
impl WinRT {
|
||||
|
@ -35,11 +36,15 @@ impl WinRT {
|
|||
let player = MediaPlayer::new()?;
|
||||
player.set_auto_play(true)?;
|
||||
player.set_source(&playback_list)?;
|
||||
Ok(Self {
|
||||
let mut backend_id = NEXT_BACKEND_ID.lock().unwrap();
|
||||
let rv = Ok(Self {
|
||||
id: BackendId::WinRT(*backend_id),
|
||||
synth: SpeechSynthesizer::new()?,
|
||||
player: player,
|
||||
playback_list: playback_list,
|
||||
})
|
||||
});
|
||||
*backend_id += 1;
|
||||
rv
|
||||
}
|
||||
|
||||
fn reinit_player(&mut self) -> std::result::Result<(), Error> {
|
||||
|
@ -52,6 +57,10 @@ impl WinRT {
|
|||
}
|
||||
|
||||
impl Backend for WinRT {
|
||||
fn id(&self) -> Option<BackendId> {
|
||||
Some(self.id)
|
||||
}
|
||||
|
||||
fn supported_features(&self) -> Features {
|
||||
Features {
|
||||
stop: true,
|
||||
|
@ -59,6 +68,7 @@ impl Backend for WinRT {
|
|||
pitch: true,
|
||||
volume: true,
|
||||
is_speaking: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,13 +93,11 @@ impl Backend for WinRT {
|
|||
self.reinit_player()?;
|
||||
}
|
||||
}
|
||||
self.playback_list.items()?.append(item)?;
|
||||
self.playback_list.items()?.append(&item)?;
|
||||
if !self.is_speaking()? {
|
||||
self.player.play()?;
|
||||
}
|
||||
let mut utterance_id = NEXT_UTTERANCE_ID.lock().unwrap();
|
||||
*utterance_id += 1;
|
||||
Ok(Some(UtteranceId::WinRT(*utterance_id)))
|
||||
Ok(Some(UtteranceId::WinRT(item)))
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> std::result::Result<(), Error> {
|
||||
|
|
97
src/lib.rs
97
src/lib.rs
|
@ -12,17 +12,23 @@
|
|||
*/
|
||||
|
||||
use std::boxed::Box;
|
||||
use std::collections::HashMap;
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::ffi::CStr;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
use cocoa_foundation::base::id;
|
||||
use lazy_static::lazy_static;
|
||||
#[cfg(target_os = "macos")]
|
||||
use libc::c_char;
|
||||
#[cfg(target_os = "macos")]
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(windows)]
|
||||
use tts_winrt_bindings::windows::media::playback::MediaPlaybackItem;
|
||||
|
||||
mod backends;
|
||||
|
||||
pub enum Backends {
|
||||
|
@ -40,10 +46,10 @@ pub enum Backends {
|
|||
AvFoundation,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum UtteranceId {
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum BackendId {
|
||||
#[cfg(target_os = "linux")]
|
||||
SpeechDispatcher(i32),
|
||||
SpeechDispatcher(u64),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Web(u64),
|
||||
#[cfg(windows)]
|
||||
|
@ -52,12 +58,23 @@ pub enum UtteranceId {
|
|||
AvFoundation(id),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum UtteranceId {
|
||||
#[cfg(target_os = "linux")]
|
||||
SpeechDispatcher(u64),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Web(u64),
|
||||
#[cfg(windows)]
|
||||
WinRT(MediaPlaybackItem),
|
||||
}
|
||||
|
||||
pub struct Features {
|
||||
pub stop: bool,
|
||||
pub rate: bool,
|
||||
pub pitch: bool,
|
||||
pub volume: bool,
|
||||
pub is_speaking: bool,
|
||||
pub utterance_callbacks: bool,
|
||||
}
|
||||
|
||||
impl Default for Features {
|
||||
|
@ -68,6 +85,7 @@ impl Default for Features {
|
|||
pitch: false,
|
||||
volume: false,
|
||||
is_speaking: false,
|
||||
utterance_callbacks: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +109,7 @@ pub enum Error {
|
|||
}
|
||||
|
||||
pub trait Backend {
|
||||
fn id(&self) -> Option<BackendId>;
|
||||
fn supported_features(&self) -> Features;
|
||||
fn speak(&mut self, text: &str, interrupt: bool) -> Result<Option<UtteranceId>, Error>;
|
||||
fn stop(&mut self) -> Result<(), Error>;
|
||||
|
@ -112,6 +131,19 @@ pub trait Backend {
|
|||
fn is_speaking(&self) -> Result<bool, Error>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Callbacks {
|
||||
utterance_begin: Option<fn(UtteranceId)>,
|
||||
utterance_end: Option<fn(UtteranceId)>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CALLBACKS: Mutex<HashMap<BackendId, Callbacks>> = {
|
||||
let m: HashMap<BackendId, Callbacks> = HashMap::new();
|
||||
Mutex::new(m)
|
||||
};
|
||||
}
|
||||
|
||||
pub struct TTS(Box<dyn Backend>);
|
||||
|
||||
unsafe impl std::marker::Send for TTS {}
|
||||
|
@ -123,7 +155,7 @@ impl TTS {
|
|||
* Create a new `TTS` instance with the specified backend.
|
||||
*/
|
||||
pub fn new(backend: Backends) -> Result<TTS, Error> {
|
||||
match backend {
|
||||
let backend = match backend {
|
||||
#[cfg(target_os = "linux")]
|
||||
Backends::SpeechDispatcher => Ok(TTS(Box::new(backends::SpeechDispatcher::new()))),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -149,6 +181,16 @@ impl TTS {
|
|||
Backends::AppKit => Ok(TTS(Box::new(backends::AppKit::new()))),
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
Backends::AvFoundation => Ok(TTS(Box::new(backends::AvFoundation::new()))),
|
||||
};
|
||||
let mut callbacks = CALLBACKS.lock().unwrap();
|
||||
if backend.is_ok() {
|
||||
let backend = backend.unwrap();
|
||||
if let Some(id) = backend.0.id() {
|
||||
callbacks.insert(id, Callbacks::default());
|
||||
}
|
||||
Ok(backend)
|
||||
} else {
|
||||
backend
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -386,4 +428,51 @@ impl TTS {
|
|||
Err(Error::UnsupportedFeature)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this speech synthesizer begins speaking an utterance.
|
||||
*/
|
||||
pub fn on_utterance_begin(&self, callback: Option<fn(UtteranceId)>) -> Result<(), Error> {
|
||||
let Features {
|
||||
utterance_callbacks,
|
||||
..
|
||||
} = self.supported_features();
|
||||
if utterance_callbacks {
|
||||
let mut callbacks = CALLBACKS.lock().unwrap();
|
||||
let id = self.0.id().unwrap();
|
||||
let mut callbacks = callbacks.get_mut(&id).unwrap();
|
||||
callbacks.utterance_begin = callback;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::UnsupportedFeature)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this speech synthesizer finishes speaking an utterance.
|
||||
*/
|
||||
pub fn on_utterance_end(&self, callback: Option<fn(UtteranceId)>) -> Result<(), Error> {
|
||||
let Features {
|
||||
utterance_callbacks,
|
||||
..
|
||||
} = self.supported_features();
|
||||
if utterance_callbacks {
|
||||
let mut callbacks = CALLBACKS.lock().unwrap();
|
||||
let id = self.0.id().unwrap();
|
||||
let mut callbacks = callbacks.get_mut(&id).unwrap();
|
||||
callbacks.utterance_end = callback;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::UnsupportedFeature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TTS {
|
||||
fn drop(&mut self) {
|
||||
if let Some(id) = self.0.id() {
|
||||
let mut callbacks = CALLBACKS.lock().unwrap();
|
||||
callbacks.remove(&id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user