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:
Nolan Darilek 2020-05-18 15:01:28 -05:00
parent 5fbc0fd8f0
commit 3198a537f0
5 changed files with 139 additions and 31 deletions

View File

@ -10,11 +10,12 @@ edition = "2018"
[dependencies]
env_logger = "0.7"
failure = "0.1"
log = "0.4"
thiserror = "1"
[target.'cfg(windows)'.dependencies]
tolk = "0.2"
winrt = { git = "https://github.com/microsoft/winrt-rs", rev = "ed46a71f506c343b3eb4fa6c15a4d9db1397ebcf" }
[target.'cfg(target_os = "linux")'.dependencies]
speech-dispatcher = "0.4"

View File

@ -3,7 +3,7 @@ use std::u8;
use tts::*;
fn main() -> Result<(), std::io::Error> {
fn main() -> Result<(), Error> {
env_logger::init();
let mut tts = TTS::default()?;
tts.speak("Hello, world.", false)?;

View File

@ -4,6 +4,9 @@ mod speech_dispatcher;
#[cfg(windows)]
mod tolk;
#[cfg(windows)]
pub(crate) mod winrt;
#[cfg(target_arch = "wasm32")]
mod web;

97
src/backends/winrt.rs Normal file
View File

@ -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!();
}
}

View File

@ -1,16 +1,14 @@
/*!
* a Text-To-Speech (TTS) library providing high-level interfaces to a variety of backends.
* 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
*/
use std::boxed::Box;
use std::convert;
use std::fmt;
use std::io;
use failure::Fail;
use thiserror::Error;
mod backends;
@ -21,22 +19,8 @@ pub enum Backends {
Web,
#[cfg(windows)]
Tolk,
}
#[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)
}
#[cfg(windows)]
WinRT,
}
pub struct Features {
@ -46,6 +30,17 @@ pub struct Features {
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 {
fn supported_features(&self) -> Features;
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error>;
@ -82,6 +77,11 @@ impl TTS {
let tts = backends::Tolk::new();
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")]
let tts = TTS::new(Backends::SpeechDispatcher);
#[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")]
let tts = TTS::new(Backends::Web);
tts
@ -119,7 +126,7 @@ impl TTS {
self.0.stop()?;
Ok(self)
} else {
Err(Error("Feature not supported".to_string()))
Err(Error::UnsupportedFeature)
}
}
@ -131,7 +138,7 @@ impl TTS {
if rate {
self.0.get_rate()
} else {
Err(Error("Feature not supported".to_string()))
Err(Error::UnsupportedFeature)
}
}
@ -146,7 +153,7 @@ impl TTS {
self.0.set_rate(rate)?;
Ok(self)
} else {
Err(Error("Unsupported feature".to_string()))
Err(Error::UnsupportedFeature)
}
}
@ -158,7 +165,7 @@ impl TTS {
if pitch {
self.0.get_pitch()
} else {
Err(Error("Feature not supported".to_string()))
Err(Error::UnsupportedFeature)
}
}
@ -174,7 +181,7 @@ impl TTS {
self.0.set_pitch(pitch)?;
Ok(self)
} else {
Err(Error("Unsupported feature".to_string()))
Err(Error::UnsupportedFeature)
}
}
@ -186,7 +193,7 @@ impl TTS {
if volume {
self.0.get_volume()
} else {
Err(Error("Unsupported feature".to_string()))
Err(Error::UnsupportedFeature)
}
}
@ -202,7 +209,7 @@ impl TTS {
self.0.set_volume(volume)?;
Ok(self)
} else {
Err(Error("Unsupported feature".to_string()))
Err(Error::UnsupportedFeature)
}
}
}