tts-rs/src/lib.rs

357 lines
9.8 KiB
Rust
Raw Normal View History

2018-12-28 15:39:50 +00:00
/*!
* a Text-To-Speech (TTS) library providing high-level interfaces to a variety of backends.
2018-12-30 17:20:03 +00:00
* Currently supported backends are:
* * [Speech Dispatcher](https://freebsoft.org/speechd) (Linux)
* * Windows screen readers and SAPI via [Tolk](https://github.com/dkager/tolk/)
* * Windows WinRT
* * MacOS `NSSpeechSynthesizer`
2018-12-30 17:20:03 +00:00
* * WebAssembly
2018-12-28 15:39:50 +00:00
*/
2020-08-13 16:11:38 +00:00
#[cfg(target_os = "macos")]
use std::{boxed::Box, ffi::CStr};
#[cfg(target_os = "macos")]
use cocoa_foundation::base::id;
#[cfg(target_os = "macos")]
use libc::c_char;
#[cfg(target_os = "macos")]
use objc::{class, msg_send, sel, sel_impl};
use thiserror::Error;
2018-12-14 19:35:49 +00:00
mod backends;
pub enum Backends {
#[cfg(target_os = "linux")]
SpeechDispatcher,
2018-12-30 17:13:48 +00:00
#[cfg(target_arch = "wasm32")]
Web,
2019-03-25 19:15:08 +00:00
#[cfg(windows)]
Tolk,
#[cfg(windows)]
WinRT,
#[cfg(target_os = "macos")]
AppKit,
#[cfg(target_os = "macos")]
AvFoundation,
2018-12-14 19:35:49 +00:00
}
pub struct Features {
pub stop: bool,
pub rate: bool,
pub pitch: bool,
pub volume: bool,
pub is_speaking: bool,
}
#[derive(Debug, Error)]
pub enum Error {
#[error("IO error: {0}")]
IO(#[from] std::io::Error),
#[error("Value not received")]
NoneError,
#[cfg(target_arch = "wasm32")]
#[error("JavaScript error: [0])]")]
JavaScriptError(wasm_bindgen::JsValue),
#[cfg(windows)]
#[error("WinRT error")]
WinRT(winrt::Error),
#[error("Unsupported feature")]
UnsupportedFeature,
#[error("Out of range")]
OutOfRange,
}
pub trait Backend {
fn supported_features(&self) -> Features;
fn speak(&mut self, text: &str, interrupt: bool) -> Result<(), Error>;
fn stop(&mut self) -> Result<(), Error>;
fn min_rate(&self) -> f32;
fn max_rate(&self) -> f32;
fn normal_rate(&self) -> f32;
fn get_rate(&self) -> Result<f32, Error>;
fn set_rate(&mut self, rate: f32) -> Result<(), Error>;
fn min_pitch(&self) -> f32;
fn max_pitch(&self) -> f32;
fn normal_pitch(&self) -> f32;
fn get_pitch(&self) -> Result<f32, Error>;
fn set_pitch(&mut self, pitch: f32) -> Result<(), Error>;
fn min_volume(&self) -> f32;
fn max_volume(&self) -> f32;
fn normal_volume(&self) -> f32;
fn get_volume(&self) -> Result<f32, Error>;
fn set_volume(&mut self, volume: f32) -> Result<(), Error>;
fn is_speaking(&self) -> Result<bool, Error>;
2018-12-14 19:35:49 +00:00
}
2019-09-30 15:36:20 +00:00
pub struct TTS(Box<dyn Backend>);
2018-12-14 19:35:49 +00:00
2019-01-03 17:20:04 +00:00
unsafe impl std::marker::Send for TTS {}
unsafe impl std::marker::Sync for TTS {}
2018-12-14 19:35:49 +00:00
impl TTS {
2018-12-28 15:39:50 +00:00
/**
* Create a new `TTS` instance with the specified backend.
2019-01-03 16:16:54 +00:00
*/
2018-12-30 17:13:48 +00:00
pub fn new(backend: Backends) -> Result<TTS, Error> {
2018-12-14 19:35:49 +00:00
match backend {
#[cfg(target_os = "linux")]
2018-12-30 17:13:48 +00:00
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)))
2019-01-03 16:16:54 +00:00
}
2019-03-25 19:15:08 +00:00
#[cfg(windows)]
Backends::Tolk => {
let tts = backends::Tolk::new();
if let Some(tts) = tts {
Ok(TTS(Box::new(tts)))
} else {
Err(Error::NoneError)
}
2019-12-23 13:37:48 +00:00
}
#[cfg(windows)]
Backends::WinRT => {
let tts = backends::winrt::WinRT::new()?;
Ok(TTS(Box::new(tts)))
}
#[cfg(target_os = "macos")]
Backends::AppKit => Ok(TTS(Box::new(backends::AppKit::new()))),
#[cfg(target_os = "macos")]
Backends::AvFoundation => Ok(TTS(Box::new(backends::AvFoundation::new()))),
2018-12-14 19:35:49 +00:00
}
}
2018-12-30 17:13:48 +00:00
pub fn default() -> Result<TTS, Error> {
#[cfg(target_os = "linux")]
let tts = TTS::new(Backends::SpeechDispatcher);
2019-03-25 19:15:08 +00:00
#[cfg(windows)]
let tts = if let Some(tts) = TTS::new(Backends::Tolk).ok() {
Ok(tts)
} else {
TTS::new(Backends::WinRT)
};
2018-12-30 17:13:48 +00:00
#[cfg(target_arch = "wasm32")]
let tts = TTS::new(Backends::Web);
#[cfg(target_os = "macos")]
let tts = unsafe {
// Needed because the Rust NSProcessInfo structs report bogus values, and I don't want to pull in a full bindgen stack.
let pi: id = msg_send![class!(NSProcessInfo), new];
let version: id = msg_send![pi, operatingSystemVersionString];
let str: *const c_char = msg_send![version, UTF8String];
let str = CStr::from_ptr(str);
let str = str.to_string_lossy();
let version: Vec<&str> = str.split(" ").collect();
let version = version[1];
let version_parts: Vec<&str> = version.split(".").collect();
let minor_version: i8 = version_parts[1].parse().unwrap();
if minor_version >= 14 {
TTS::new(Backends::AvFoundation)
} else {
TTS::new(Backends::AppKit)
}
};
2018-12-30 17:13:48 +00:00
tts
}
/**
* Returns the features supported by this TTS engine
*/
pub fn supported_features(&self) -> Features {
self.0.supported_features()
}
2018-12-28 15:39:50 +00:00
/**
* Speaks the specified text, optionally interrupting current speech.
2019-01-03 16:16:54 +00:00
*/
pub fn speak<S: Into<String>>(&mut self, text: S, interrupt: bool) -> Result<&Self, Error> {
2018-12-30 17:13:48 +00:00
self.0.speak(text.into().as_str(), interrupt)?;
Ok(self)
2018-12-14 19:35:49 +00:00
}
2018-12-28 15:39:50 +00:00
/**
* Stops current speech.
2019-01-03 16:16:54 +00:00
*/
pub fn stop(&mut self) -> Result<&Self, Error> {
let Features { stop, .. } = self.supported_features();
if stop {
self.0.stop()?;
Ok(self)
} else {
Err(Error::UnsupportedFeature)
}
2018-12-28 14:49:02 +00:00
}
/**
* Returns the minimum rate for this speech synthesizer.
*/
pub fn min_rate(&self) -> f32 {
self.0.min_rate()
}
/**
* Returns the maximum rate for this speech synthesizer.
*/
pub fn max_rate(&self) -> f32 {
self.0.max_rate()
}
/**
* Returns the normal rate for this speech synthesizer.
*/
pub fn normal_rate(&self) -> f32 {
self.0.normal_rate()
}
2018-12-28 15:39:50 +00:00
/**
* Gets the current speech rate.
2019-01-03 16:16:54 +00:00
*/
pub fn get_rate(&self) -> Result<f32, Error> {
let Features { rate, .. } = self.supported_features();
if rate {
self.0.get_rate()
} else {
Err(Error::UnsupportedFeature)
}
2018-12-14 19:35:49 +00:00
}
2018-12-28 15:39:50 +00:00
/**
* Sets the desired speech rate.
2019-01-03 16:16:54 +00:00
*/
pub fn set_rate(&mut self, rate: f32) -> Result<&Self, Error> {
let Features {
rate: rate_feature, ..
} = self.supported_features();
if rate_feature {
if rate < self.0.min_rate() || rate > self.0.max_rate() {
Err(Error::OutOfRange)
} else {
self.0.set_rate(rate)?;
Ok(self)
}
} else {
Err(Error::UnsupportedFeature)
}
2018-12-14 19:35:49 +00:00
}
2018-12-15 15:56:13 +00:00
/**
* Returns the minimum pitch for this speech synthesizer.
*/
pub fn min_pitch(&self) -> f32 {
self.0.min_pitch()
}
/**
* Returns the maximum pitch for this speech synthesizer.
*/
pub fn max_pitch(&self) -> f32 {
self.0.max_pitch()
}
/**
* Returns the normal pitch for this speech synthesizer.
*/
pub fn normal_pitch(&self) -> f32 {
self.0.normal_pitch()
}
2018-12-28 15:39:50 +00:00
/**
* Gets the current speech pitch.
2019-01-03 16:16:54 +00:00
*/
pub fn get_pitch(&self) -> Result<f32, Error> {
let Features { pitch, .. } = self.supported_features();
if pitch {
self.0.get_pitch()
} else {
Err(Error::UnsupportedFeature)
}
2018-12-15 15:56:13 +00:00
}
2018-12-28 15:39:50 +00:00
/**
* Sets the desired speech pitch.
2019-01-03 16:16:54 +00:00
*/
pub fn set_pitch(&mut self, pitch: f32) -> Result<&Self, Error> {
let Features {
pitch: pitch_feature,
..
} = self.supported_features();
if pitch_feature {
if pitch < self.0.min_pitch() || pitch > self.0.max_pitch() {
Err(Error::OutOfRange)
} else {
self.0.set_pitch(pitch)?;
Ok(self)
}
} else {
Err(Error::UnsupportedFeature)
}
2018-12-15 15:56:13 +00:00
}
/**
* Returns the minimum volume for this speech synthesizer.
*/
pub fn min_volume(&self) -> f32 {
self.0.min_volume()
}
/**
* Returns the maximum volume for this speech synthesizer.
*/
pub fn max_volume(&self) -> f32 {
self.0.max_volume()
}
/**
* Returns the normal volume for this speech synthesizer.
*/
pub fn normal_volume(&self) -> f32 {
self.0.normal_volume()
}
2018-12-28 15:39:50 +00:00
/**
* Gets the current speech volume.
2019-01-03 16:16:54 +00:00
*/
pub fn get_volume(&self) -> Result<f32, Error> {
let Features { volume, .. } = self.supported_features();
if volume {
self.0.get_volume()
} else {
Err(Error::UnsupportedFeature)
}
2018-12-15 15:56:13 +00:00
}
2018-12-28 15:39:50 +00:00
/**
* Sets the desired speech volume.
2019-01-03 16:16:54 +00:00
*/
pub fn set_volume(&mut self, volume: f32) -> Result<&Self, Error> {
let Features {
volume: volume_feature,
..
} = self.supported_features();
if volume_feature {
if volume < self.0.min_volume() || volume > self.0.max_volume() {
Err(Error::OutOfRange)
} else {
self.0.set_volume(volume)?;
Ok(self)
}
} else {
Err(Error::UnsupportedFeature)
}
2018-12-14 19:35:49 +00:00
}
/**
* Returns whether this speech synthesizer is speaking.
*/
pub fn is_speaking(&self) -> Result<bool, Error> {
let Features { is_speaking, .. } = self.supported_features();
if is_speaking {
self.0.is_speaking()
} else {
Err(Error::UnsupportedFeature)
}
}
2018-12-14 19:35:49 +00:00
}