mirror of https://github.com/ndarilek/tts-rs.git
Initial WinRT backend.
* Add WinRT backend * Refactor to use thiserror and unify error-handling * If a screen reader is detected. use Tolk. Otherwise, use the WinRT backend.
This commit is contained in:
parent
5fbc0fd8f0
commit
3198a537f0
|
@ -10,11 +10,12 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
failure = "0.1"
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
thiserror = "1"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
tolk = "0.2"
|
tolk = "0.2"
|
||||||
|
winrt = { git = "https://github.com/microsoft/winrt-rs", rev = "ed46a71f506c343b3eb4fa6c15a4d9db1397ebcf" }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
speech-dispatcher = "0.4"
|
speech-dispatcher = "0.4"
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::u8;
|
||||||
|
|
||||||
use tts::*;
|
use tts::*;
|
||||||
|
|
||||||
fn main() -> Result<(), std::io::Error> {
|
fn main() -> Result<(), Error> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
let mut tts = TTS::default()?;
|
let mut tts = TTS::default()?;
|
||||||
tts.speak("Hello, world.", false)?;
|
tts.speak("Hello, world.", false)?;
|
||||||
|
|
|
@ -4,6 +4,9 @@ mod speech_dispatcher;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod tolk;
|
mod tolk;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub(crate) mod winrt;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
mod web;
|
mod web;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
#[cfg(windows)]
|
||||||
|
use winrt::*;
|
||||||
|
|
||||||
|
import!(
|
||||||
|
dependencies
|
||||||
|
os
|
||||||
|
modules
|
||||||
|
"windows.media.core"
|
||||||
|
"windows.media.playback"
|
||||||
|
"windows.media.speechsynthesis"
|
||||||
|
);
|
||||||
|
|
||||||
|
use log::{info, trace};
|
||||||
|
use windows::media::core::MediaSource;
|
||||||
|
use windows::media::playback::{MediaPlaybackItem, MediaPlaybackList, MediaPlayer};
|
||||||
|
use windows::media::speech_synthesis::SpeechSynthesizer;
|
||||||
|
|
||||||
|
use crate::{Backend, Error, Features};
|
||||||
|
|
||||||
|
impl From<winrt::Error> for Error {
|
||||||
|
fn from(e: winrt::Error) -> Self {
|
||||||
|
Error::WinRT(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WinRT {
|
||||||
|
synth: SpeechSynthesizer,
|
||||||
|
player: MediaPlayer,
|
||||||
|
playback_list: MediaPlaybackList,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WinRT {
|
||||||
|
pub fn new() -> std::result::Result<Self, Error> {
|
||||||
|
info!("Initializing WinRT backend");
|
||||||
|
let player = MediaPlayer::new()?;
|
||||||
|
player.set_auto_play(true)?;
|
||||||
|
let playback_list = MediaPlaybackList::new()?;
|
||||||
|
player.set_source(&playback_list)?;
|
||||||
|
Ok(Self {
|
||||||
|
synth: SpeechSynthesizer::new()?,
|
||||||
|
player: player,
|
||||||
|
playback_list: playback_list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Backend for WinRT {
|
||||||
|
fn supported_features(&self) -> Features {
|
||||||
|
Features {
|
||||||
|
stop: true,
|
||||||
|
rate: false,
|
||||||
|
pitch: false,
|
||||||
|
volume: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn speak(&self, text: &str, interrupt: bool) -> std::result::Result<(), Error> {
|
||||||
|
trace!("speak({}, {})", text, interrupt);
|
||||||
|
let stream = self.synth.synthesize_text_to_stream_async(text)?.get()?;
|
||||||
|
let content_type = stream.content_type()?;
|
||||||
|
let source = MediaSource::create_from_stream(stream, content_type)?;
|
||||||
|
let item = MediaPlaybackItem::create(source)?;
|
||||||
|
self.playback_list.items()?.append(item)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self) -> std::result::Result<(), Error> {
|
||||||
|
trace!("stop()");
|
||||||
|
self.player.close()?;
|
||||||
|
self.playback_list.items()?.clear()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rate(&self) -> std::result::Result<u8, Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_rate(&mut self, _rate: u8) -> std::result::Result<(), Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pitch(&self) -> std::result::Result<u8, Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_pitch(&mut self, _pitch: u8) -> std::result::Result<(), Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_volume(&self) -> std::result::Result<u8, Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_volume(&mut self, _volume: u8) -> std::result::Result<(), Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
65
src/lib.rs
65
src/lib.rs
|
@ -1,16 +1,14 @@
|
||||||
/*!
|
/*!
|
||||||
* a Text-To-Speech (TTS) library providing high-level interfaces to a variety of backends.
|
* a Text-To-Speech (TTS) library providing high-level interfaces to a variety of backends.
|
||||||
* Currently supported backends are:
|
* Currently supported backends are:
|
||||||
* * [Speech Dispatcher](https://freebsoft.org/speechd) (Linux)
|
* * [Speech Dispatcher](https://freebsoft.org/speechd) (Linux)
|
||||||
|
* * Windows screen readers and SAPI via [Tolk](https://github.com/dkager/tolk/)
|
||||||
* * WebAssembly
|
* * WebAssembly
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
use std::convert;
|
|
||||||
use std::fmt;
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use failure::Fail;
|
use thiserror::Error;
|
||||||
|
|
||||||
mod backends;
|
mod backends;
|
||||||
|
|
||||||
|
@ -21,22 +19,8 @@ pub enum Backends {
|
||||||
Web,
|
Web,
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
Tolk,
|
Tolk,
|
||||||
}
|
#[cfg(windows)]
|
||||||
|
WinRT,
|
||||||
#[derive(Debug, Fail)]
|
|
||||||
pub struct Error(String);
|
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
||||||
write!(f, "{}", self.0)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl convert::From<Error> for io::Error {
|
|
||||||
fn from(e: Error) -> io::Error {
|
|
||||||
io::Error::new(io::ErrorKind::Other, e.0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Features {
|
pub struct Features {
|
||||||
|
@ -46,6 +30,17 @@ pub struct Features {
|
||||||
pub volume: bool,
|
pub volume: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("IO error: {0}")]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[error("WinRT error")]
|
||||||
|
WinRT(winrt::Error),
|
||||||
|
#[error("Unsupported feature")]
|
||||||
|
UnsupportedFeature,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Backend {
|
pub trait Backend {
|
||||||
fn supported_features(&self) -> Features;
|
fn supported_features(&self) -> Features;
|
||||||
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error>;
|
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error>;
|
||||||
|
@ -82,6 +77,11 @@ impl TTS {
|
||||||
let tts = backends::Tolk::new();
|
let tts = backends::Tolk::new();
|
||||||
Ok(TTS(Box::new(tts)))
|
Ok(TTS(Box::new(tts)))
|
||||||
}
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
Backends::WinRT => {
|
||||||
|
let tts = backends::winrt::WinRT::new()?;
|
||||||
|
Ok(TTS(Box::new(tts)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,14 @@ impl TTS {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let tts = TTS::new(Backends::SpeechDispatcher);
|
let tts = TTS::new(Backends::SpeechDispatcher);
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let tts = TTS::new(Backends::Tolk);
|
let tts = {
|
||||||
|
let tolk = tolk::Tolk::new();
|
||||||
|
if tolk.detect_screen_reader().is_some() {
|
||||||
|
TTS::new(Backends::Tolk)
|
||||||
|
} else {
|
||||||
|
TTS::new(Backends::WinRT)
|
||||||
|
}
|
||||||
|
};
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
let tts = TTS::new(Backends::Web);
|
let tts = TTS::new(Backends::Web);
|
||||||
tts
|
tts
|
||||||
|
@ -119,7 +126,7 @@ impl TTS {
|
||||||
self.0.stop()?;
|
self.0.stop()?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
} else {
|
} else {
|
||||||
Err(Error("Feature not supported".to_string()))
|
Err(Error::UnsupportedFeature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +138,7 @@ impl TTS {
|
||||||
if rate {
|
if rate {
|
||||||
self.0.get_rate()
|
self.0.get_rate()
|
||||||
} else {
|
} else {
|
||||||
Err(Error("Feature not supported".to_string()))
|
Err(Error::UnsupportedFeature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +153,7 @@ impl TTS {
|
||||||
self.0.set_rate(rate)?;
|
self.0.set_rate(rate)?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
} else {
|
} else {
|
||||||
Err(Error("Unsupported feature".to_string()))
|
Err(Error::UnsupportedFeature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +165,7 @@ impl TTS {
|
||||||
if pitch {
|
if pitch {
|
||||||
self.0.get_pitch()
|
self.0.get_pitch()
|
||||||
} else {
|
} else {
|
||||||
Err(Error("Feature not supported".to_string()))
|
Err(Error::UnsupportedFeature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +181,7 @@ impl TTS {
|
||||||
self.0.set_pitch(pitch)?;
|
self.0.set_pitch(pitch)?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
} else {
|
} else {
|
||||||
Err(Error("Unsupported feature".to_string()))
|
Err(Error::UnsupportedFeature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +193,7 @@ impl TTS {
|
||||||
if volume {
|
if volume {
|
||||||
self.0.get_volume()
|
self.0.get_volume()
|
||||||
} else {
|
} else {
|
||||||
Err(Error("Unsupported feature".to_string()))
|
Err(Error::UnsupportedFeature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +209,7 @@ impl TTS {
|
||||||
self.0.set_volume(volume)?;
|
self.0.set_volume(volume)?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
} else {
|
} else {
|
||||||
Err(Error("Unsupported feature".to_string()))
|
Err(Error::UnsupportedFeature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue