mirror of
https://github.com/ndarilek/tts-rs.git
synced 2024-11-17 12:19:37 +00:00
Add Web backend and refactor API.
This commit is contained in:
parent
7cab9aa3ac
commit
4fadad9d36
|
@ -8,6 +8,7 @@ license = "MIT"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
failure = "0.1"
|
||||
log = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -15,3 +16,7 @@ env_logger = "0.6"
|
|||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
speech-dispatcher = "0.2"
|
||||
|
||||
[target.wasm32-unknown-unknown.dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = ["SpeechSynthesis", "SpeechSynthesisUtterance", "Window", ] }
|
||||
|
|
|
@ -2,28 +2,29 @@ use std::u8;
|
|||
|
||||
use tts::TTS;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
env_logger::init();
|
||||
let tts: TTS = Default::default();
|
||||
tts.speak("Hello, world.", false);
|
||||
let original_rate = tts.get_rate();
|
||||
tts.speak(format!("Current rate: {}", original_rate), false);
|
||||
tts.set_rate(u8::MAX);
|
||||
tts.speak("This is very fast.", false);
|
||||
tts.set_rate(0);
|
||||
tts.speak("This is very slow.", false);
|
||||
tts.set_rate(original_rate);
|
||||
let original_pitch = tts.get_pitch();
|
||||
tts.set_pitch(u8::MAX);
|
||||
tts.speak("This is high-pitch.", false);
|
||||
tts.set_pitch(0);
|
||||
tts.speak("This is low pitch.", false);
|
||||
tts.set_pitch(original_pitch);
|
||||
let original_volume = tts.get_volume();
|
||||
tts.set_volume(u8::MAX);
|
||||
tts.speak("This is loud!", false);
|
||||
tts.set_volume(0);
|
||||
tts.speak("This is quiet.", false);
|
||||
tts.set_volume(original_volume);
|
||||
tts.speak("Goodbye.", false);
|
||||
let mut tts = TTS::default()?;
|
||||
tts.speak("Hello, world.", false)?;
|
||||
let original_rate = tts.get_rate()?;
|
||||
tts.speak(format!("Current rate: {}", original_rate), false)?;
|
||||
tts.set_rate(u8::MAX)?;
|
||||
tts.speak("This is very fast.", false)?;
|
||||
tts.set_rate(0)?;
|
||||
tts.speak("This is very slow.", false)?;
|
||||
tts.set_rate(original_rate)?;
|
||||
let original_pitch = tts.get_pitch()?;
|
||||
tts.set_pitch(u8::MAX)?;
|
||||
tts.speak("This is high-pitch.", false)?;
|
||||
tts.set_pitch(0)?;
|
||||
tts.speak("This is low pitch.", false)?;
|
||||
tts.set_pitch(original_pitch)?;
|
||||
let original_volume = tts.get_volume()?;
|
||||
tts.set_volume(u8::MAX)?;
|
||||
tts.speak("This is loud!", false)?;
|
||||
tts.set_volume(0)?;
|
||||
tts.speak("This is quiet.", false)?;
|
||||
tts.set_volume(original_volume)?;
|
||||
tts.speak("Goodbye.", false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
#[cfg(target_os = "linux")]
|
||||
mod speech_dispatcher;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod web;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use self::speech_dispatcher::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use self::web::*;
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::u8;
|
|||
use log::{info, trace};
|
||||
use speech_dispatcher::*;
|
||||
|
||||
use crate::Backend;
|
||||
use crate::{Backend, Error};
|
||||
|
||||
pub struct SpeechDispatcher(Connection);
|
||||
|
||||
|
@ -30,40 +30,45 @@ fn i32_to_u8(v: i32) -> u8 {
|
|||
}
|
||||
|
||||
impl Backend for SpeechDispatcher {
|
||||
fn speak(&self, text: &str, interrupt: bool) {
|
||||
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error> {
|
||||
trace!("speak({}, {})", text, interrupt);
|
||||
if interrupt {
|
||||
self.0.cancel();
|
||||
self.stop()?;
|
||||
}
|
||||
self.0.say(Priority::Important, text);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
fn stop(&self) -> Result<(), Error> {
|
||||
trace!("stop()");
|
||||
self.0.cancel();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_rate(&self) -> u8 {
|
||||
i32_to_u8(self.0.get_voice_rate())
|
||||
fn get_rate(&self) -> Result<u8, Error> {
|
||||
Ok(i32_to_u8(self.0.get_voice_rate()))
|
||||
}
|
||||
|
||||
fn set_rate(&self, rate: u8) {
|
||||
fn set_rate(&mut self, rate: u8) -> Result<(), Error> {
|
||||
self.0.set_voice_rate(u8_to_i32(rate));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_pitch(&self) -> u8 {
|
||||
i32_to_u8(self.0.get_voice_pitch())
|
||||
fn get_pitch(&self) -> Result<u8, Error> {
|
||||
Ok(i32_to_u8(self.0.get_voice_pitch()))
|
||||
}
|
||||
|
||||
fn set_pitch(&self, pitch: u8) {
|
||||
fn set_pitch(&mut self, pitch: u8) -> Result<(), Error> {
|
||||
self.0.set_voice_pitch(u8_to_i32(pitch));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_volume(&self) -> u8 {
|
||||
i32_to_u8(self.0.get_volume())
|
||||
fn get_volume(&self) -> Result<u8, Error> {
|
||||
Ok(i32_to_u8(self.0.get_volume()))
|
||||
}
|
||||
|
||||
fn set_volume(&self, volume: u8) {
|
||||
fn set_volume(&mut self, volume: u8) -> Result<(), Error> {
|
||||
self.0.set_volume(u8_to_i32(volume));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
85
src/backends/web.rs
Normal file
85
src/backends/web.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
|
||||
use std::u8;
|
||||
|
||||
use log::{info, trace};
|
||||
use web_sys::SpeechSynthesisUtterance;
|
||||
|
||||
use crate::{Backend, Error};
|
||||
|
||||
pub struct Web {
|
||||
rate: u8,
|
||||
pitch: u8,
|
||||
volume: u8,
|
||||
}
|
||||
|
||||
impl Web {
|
||||
pub fn new() -> Result<impl Backend, Error> {
|
||||
info!("Initializing Web backend");
|
||||
Ok(Web {
|
||||
rate: 25,
|
||||
pitch: 127,
|
||||
volume: u8::MAX,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for Web {
|
||||
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error> {
|
||||
trace!("speak({}, {})", text, interrupt);
|
||||
let utterance = SpeechSynthesisUtterance::new_with_text(text).unwrap();
|
||||
let mut rate: f32 = self.rate as f32;
|
||||
rate = rate / u8::MAX as f32 * 10.;
|
||||
utterance.set_rate(rate);
|
||||
let mut pitch: f32 = self.pitch as f32;
|
||||
pitch = pitch / u8::MAX as f32 * 2.;
|
||||
utterance.set_pitch(pitch);
|
||||
let mut volume: f32 = self.volume as f32;
|
||||
volume = volume / u8::MAX as f32 * 1.;
|
||||
utterance.set_volume(volume);
|
||||
if interrupt {
|
||||
self.stop()?;
|
||||
}
|
||||
if let Some(window) = web_sys::window() {
|
||||
let speech_synthesis = window.speech_synthesis().unwrap();
|
||||
speech_synthesis.speak(&utterance);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), Error> {
|
||||
trace!("stop()");
|
||||
if let Some(window) = web_sys::window() {
|
||||
let speech_synthesis = window.speech_synthesis().unwrap();
|
||||
speech_synthesis.cancel();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_rate(&self) -> Result<u8, Error> {
|
||||
Ok(self.rate)
|
||||
}
|
||||
|
||||
fn set_rate(&mut self, rate: u8) -> Result<(), Error> {
|
||||
self.rate = rate;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_pitch(&self) -> Result<u8, Error> {
|
||||
Ok(self.pitch)
|
||||
}
|
||||
|
||||
fn set_pitch(&mut self, pitch: u8) -> Result<(), Error> {
|
||||
self.pitch = pitch;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_volume(&self) -> Result<u8, Error> {
|
||||
Ok(self.volume)
|
||||
}
|
||||
|
||||
fn set_volume(&mut self, volume: u8) -> Result<(), Error> {
|
||||
self.volume = volume;
|
||||
Ok(())
|
||||
}
|
||||
}
|
99
src/lib.rs
99
src/lib.rs
|
@ -4,23 +4,46 @@
|
|||
*/
|
||||
|
||||
use std::boxed::Box;
|
||||
use std::convert;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
use failure::Fail;
|
||||
|
||||
mod backends;
|
||||
|
||||
pub enum Backends {
|
||||
#[cfg(target_os = "linux")]
|
||||
SpeechDispatcher,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Web,
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
||||
trait Backend {
|
||||
fn speak(&self, text: &str, interrupt: bool);
|
||||
fn stop(&self);
|
||||
fn get_rate(&self) -> u8;
|
||||
fn set_rate(&self, rate: u8);
|
||||
fn get_pitch(&self) -> u8;
|
||||
fn set_pitch(&self, pitch: u8);
|
||||
fn get_volume(&self) -> u8;
|
||||
fn set_volume(&self, volume: u8);
|
||||
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error>;
|
||||
fn stop(&self) -> Result<(), Error>;
|
||||
fn get_rate(&self) -> Result<u8, Error>;
|
||||
fn set_rate(&mut self, rate: u8) -> Result<(), Error>;
|
||||
fn get_pitch(&self) -> Result<u8, Error>;
|
||||
fn set_pitch(&mut self, pitch: u8) -> Result<(), Error>;
|
||||
fn get_volume(&self) -> Result<u8, Error>;
|
||||
fn set_volume(&mut self, volume: u8) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub struct TTS(Box<Backend>);
|
||||
|
@ -30,78 +53,84 @@ impl TTS {
|
|||
/**
|
||||
* Create a new `TTS` instance with the specified backend.
|
||||
*/
|
||||
pub fn new(backend: Backends) -> TTS {
|
||||
pub fn new(backend: Backends) -> Result<TTS, Error> {
|
||||
match backend {
|
||||
#[cfg(target_os = "linux")]
|
||||
Backends::SpeechDispatcher => TTS(Box::new(backends::SpeechDispatcher::new())),
|
||||
Backends::SpeechDispatcher => Ok(TTS(Box::new(backends::SpeechDispatcher::new()))),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
Backends::Web => {
|
||||
let tts = backends::Web::new()?;
|
||||
Ok(TTS(Box::new(tts)))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default() -> Result<TTS, Error> {
|
||||
#[cfg(target_os = "linux")]
|
||||
let tts = TTS::new(Backends::SpeechDispatcher);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let tts = TTS::new(Backends::Web);
|
||||
tts
|
||||
}
|
||||
|
||||
/**
|
||||
* Speaks the specified text, optionally interrupting current speech.
|
||||
*/
|
||||
pub fn speak<S: Into<String>>(&self, text: S, interrupt: bool) -> &Self {
|
||||
self.0.speak(text.into().as_str(), interrupt);
|
||||
self
|
||||
pub fn speak<S: Into<String>>(&self, text: S, interrupt: bool) -> Result<&Self, Error> {
|
||||
self.0.speak(text.into().as_str(), interrupt)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops current speech.
|
||||
*/
|
||||
pub fn stop(&self) -> &Self {
|
||||
self.0.stop();
|
||||
self
|
||||
pub fn stop(&self) -> Result<&Self, Error> {
|
||||
self.0.stop()?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current speech rate.
|
||||
*/
|
||||
pub fn get_rate(&self) -> u8 {
|
||||
pub fn get_rate(&self) -> Result<u8, Error> {
|
||||
self.0.get_rate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired speech rate.
|
||||
*/
|
||||
pub fn set_rate(&self, rate: u8) -> &Self {
|
||||
self.0.set_rate(rate);
|
||||
self
|
||||
pub fn set_rate(&mut self, rate: u8) -> Result<&Self, Error> {
|
||||
self.0.set_rate(rate)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current speech pitch.
|
||||
*/
|
||||
pub fn get_pitch(&self) -> u8 {
|
||||
pub fn get_pitch(&self) -> Result<u8, Error> {
|
||||
self.0.get_pitch()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired speech pitch.
|
||||
*/
|
||||
pub fn set_pitch(&self, pitch: u8) -> &Self {
|
||||
self.0.set_pitch(pitch);
|
||||
self
|
||||
pub fn set_pitch(&mut self, pitch: u8) -> Result<&Self, Error> {
|
||||
self.0.set_pitch(pitch)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current speech volume.
|
||||
*/
|
||||
pub fn get_volume(&self) -> u8 {
|
||||
pub fn get_volume(&self) -> Result<u8, Error> {
|
||||
self.0.get_volume()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired speech volume.
|
||||
*/
|
||||
pub fn set_volume(&self, volume: u8) -> &Self {
|
||||
self.0.set_volume(volume);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TTS {
|
||||
fn default() -> TTS {
|
||||
#[cfg(target_os = "linux")]
|
||||
TTS::new(Backends::SpeechDispatcher)
|
||||
pub fn set_volume(&mut self, volume: u8) -> Result<&Self, Error> {
|
||||
self.0.set_volume(volume)?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user