mirror of
https://github.com/ndarilek/tts-rs.git
synced 2024-11-17 07:49:37 +00:00
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]
|
||||
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"
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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
97
src/backends/winrt.rs
Normal 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!();
|
||||
}
|
||||
}
|
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.
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user