mirror of https://github.com/ndarilek/tts-rs.git
Compare commits
20 Commits
4ae7b18677
...
eede49b0a6
Author | SHA1 | Date |
---|---|---|
Bear_03 | eede49b0a6 | |
Nolan Darilek | 3c8ae0ae42 | |
Nolan Darilek | 07edc20861 | |
Nolan Darilek | 96a5209a9f | |
Nolan Darilek | 20b18949e2 | |
Nolan Darilek | f29de0aede | |
Nolan Darilek | 9e1476fd36 | |
Nolan Darilek | 3032fe0fb3 | |
Nolan Darilek | edd09c24e7 | |
Nolan Darilek | b7b4e7dc85 | |
Nolan Darilek | f593340051 | |
Enyium | 12d8e1f532 | |
Marijn Suijten | 2a81dc9b70 | |
Esther Alter | a0c6cbaf6a | |
Bear-03 | 4f4ab53252 | |
Bear-03 | 91a0f03f1a | |
Bear-03 | 87cf05f78e | |
Bear_03 | 9cd66c0358 | |
Bear-03 | b85ffc80d4 | |
Bear-03 | bf522b42a7 |
|
@ -12,7 +12,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }}
|
CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: |
|
- run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libspeechd-dev
|
sudo apt-get install -y libspeechd-dev
|
||||||
|
|
|
@ -5,6 +5,17 @@ on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
check_formatting:
|
||||||
|
name: Check Formatting
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: |
|
||||||
|
rustup toolchain install stable
|
||||||
|
cargo fmt --all --check
|
||||||
|
cd examples/web
|
||||||
|
cargo fmt --all --check
|
||||||
|
|
||||||
check:
|
check:
|
||||||
name: Check
|
name: Check
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -12,41 +23,38 @@ jobs:
|
||||||
os: [windows-latest, ubuntu-22.04, macos-latest]
|
os: [windows-latest, ubuntu-22.04, macos-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: sudo apt-get update; sudo apt-get install -y libspeechd-dev
|
- run: sudo apt-get update; sudo apt-get install -y libspeechd-dev
|
||||||
if: ${{ runner.os == 'Linux' }}
|
if: ${{ runner.os == 'Linux' }}
|
||||||
- run: |
|
- run: |
|
||||||
rustup toolchain install stable
|
rustup toolchain install stable
|
||||||
cargo fmt --check
|
cargo clippy --all-targets
|
||||||
cargo clippy
|
|
||||||
|
|
||||||
check_web:
|
check_web:
|
||||||
name: Check Web
|
name: Check Web
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: |
|
- run: |
|
||||||
rustup target add wasm32-unknown-unknown
|
rustup target add wasm32-unknown-unknown
|
||||||
rustup toolchain install stable
|
rustup toolchain install stable
|
||||||
cargo fmt --all --check
|
cargo clippy --all-targets --target wasm32-unknown-unknown
|
||||||
cargo clippy --target wasm32-unknown-unknown
|
|
||||||
|
|
||||||
check_android:
|
check_android:
|
||||||
name: Check Android
|
name: Check Android
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: |
|
- run: |
|
||||||
|
rustup target add aarch64-linux-android
|
||||||
rustup toolchain install stable
|
rustup toolchain install stable
|
||||||
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
|
cargo clippy --all-targets --target aarch64-linux-android
|
||||||
cargo install -f cargo-apk
|
|
||||||
cargo apk build
|
|
||||||
|
|
||||||
check_web_example:
|
check_web_example:
|
||||||
name: Check Web Example
|
name: Check Web Example
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: |
|
- run: |
|
||||||
rustup target add wasm32-unknown-unknown
|
rustup target add wasm32-unknown-unknown
|
||||||
rustup toolchain install stable
|
rustup toolchain install stable
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tts"
|
name = "tts"
|
||||||
version = "0.25.6"
|
version = "0.26.0"
|
||||||
authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
|
authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
|
||||||
repository = "https://github.com/ndarilek/tts-rs"
|
repository = "https://github.com/ndarilek/tts-rs"
|
||||||
description = "High-level Text-To-Speech (TTS) interface"
|
description = "High-level Text-To-Speech (TTS) interface"
|
||||||
|
documentation = "https://docs.rs/tts"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
exclude = ["*.cfg", "*.yml"]
|
exclude = ["*.cfg", "*.yml"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -26,11 +27,11 @@ serde = { version = "1", optional = true, features = ["derive"] }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.10"
|
env_logger = "0.11"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
tolk = { version = "0.5", optional = true }
|
tolk = { version = "0.5", optional = true }
|
||||||
windows = { version = "0.51", features = [
|
windows = { version = "0.52", features = [
|
||||||
"Foundation",
|
"Foundation",
|
||||||
"Foundation_Collections",
|
"Foundation_Collections",
|
||||||
"Media_Core",
|
"Media_Core",
|
||||||
|
@ -64,7 +65,6 @@ web-sys = { version = "0.3", features = [
|
||||||
[target.'cfg(target_os="android")'.dependencies]
|
[target.'cfg(target_os="android")'.dependencies]
|
||||||
jni = "0.21"
|
jni = "0.21"
|
||||||
ndk-context = "0.1"
|
ndk-context = "0.1"
|
||||||
ndk-glue = "0.7"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
no-default-features = true
|
no-default-features = true
|
||||||
|
|
|
@ -250,6 +250,7 @@ impl Backend for Android {
|
||||||
utterance_callbacks: true,
|
utterance_callbacks: true,
|
||||||
voice: false,
|
voice: false,
|
||||||
get_voice: false,
|
get_voice: false,
|
||||||
|
synthesize: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,6 +285,10 @@ impl Backend for Android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
fn stop(&mut self) -> Result<(), Error> {
|
fn stop(&mut self) -> Result<(), Error> {
|
||||||
let vm = Self::vm()?;
|
let vm = Self::vm()?;
|
||||||
let mut env = vm.get_env()?;
|
let mut env = vm.get_env()?;
|
||||||
|
|
|
@ -125,6 +125,10 @@ impl Backend for AppKit {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
fn stop(&mut self) -> Result<(), Error> {
|
fn stop(&mut self) -> Result<(), Error> {
|
||||||
trace!("stop()");
|
trace!("stop()");
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
@ -170,6 +170,7 @@ impl Backend for AvFoundation {
|
||||||
voice: true,
|
voice: true,
|
||||||
get_voice: false,
|
get_voice: false,
|
||||||
utterance_callbacks: true,
|
utterance_callbacks: true,
|
||||||
|
synthesize: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,6 +207,10 @@ impl Backend for AvFoundation {
|
||||||
Ok(Some(UtteranceId::AvFoundation(utterance)))
|
Ok(Some(UtteranceId::AvFoundation(utterance)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
fn stop(&mut self) -> Result<(), Error> {
|
fn stop(&mut self) -> Result<(), Error> {
|
||||||
trace!("stop()");
|
trace!("stop()");
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
@ -85,6 +85,7 @@ impl Backend for SpeechDispatcher {
|
||||||
voice: true,
|
voice: true,
|
||||||
get_voice: false,
|
get_voice: false,
|
||||||
utterance_callbacks: true,
|
utterance_callbacks: true,
|
||||||
|
synthesize: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +109,10 @@ impl Backend for SpeechDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
fn stop(&mut self) -> Result<(), Error> {
|
fn stop(&mut self) -> Result<(), Error> {
|
||||||
trace!("stop()");
|
trace!("stop()");
|
||||||
self.0.cancel()?;
|
self.0.cancel()?;
|
||||||
|
@ -188,6 +193,7 @@ impl Backend for SpeechDispatcher {
|
||||||
.0
|
.0
|
||||||
.list_synthesis_voices()?
|
.list_synthesis_voices()?
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|v| LanguageTag::parse(v.language.clone()).is_ok())
|
||||||
.map(|v| Voice {
|
.map(|v| Voice {
|
||||||
id: v.name.clone(),
|
id: v.name.clone(),
|
||||||
name: v.name.clone(),
|
name: v.name.clone(),
|
||||||
|
|
|
@ -39,6 +39,10 @@ impl Backend for Tolk {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
fn stop(&mut self) -> Result<(), Error> {
|
fn stop(&mut self) -> Result<(), Error> {
|
||||||
trace!("stop()");
|
trace!("stop()");
|
||||||
self.0.silence();
|
self.0.silence();
|
||||||
|
|
|
@ -59,6 +59,7 @@ impl Backend for Web {
|
||||||
voice: true,
|
voice: true,
|
||||||
get_voice: true,
|
get_voice: true,
|
||||||
utterance_callbacks: true,
|
utterance_callbacks: true,
|
||||||
|
synthesize: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +122,10 @@ impl Backend for Web {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
fn stop(&mut self) -> Result<(), Error> {
|
fn stop(&mut self) -> Result<(), Error> {
|
||||||
trace!("stop()");
|
trace!("stop()");
|
||||||
if let Some(window) = web_sys::window() {
|
if let Some(window) = web_sys::window() {
|
||||||
|
|
|
@ -12,8 +12,11 @@ use windows::{
|
||||||
Media::{
|
Media::{
|
||||||
Core::MediaSource,
|
Core::MediaSource,
|
||||||
Playback::{MediaPlayer, MediaPlayerAudioCategory},
|
Playback::{MediaPlayer, MediaPlayerAudioCategory},
|
||||||
SpeechSynthesis::{SpeechSynthesizer, VoiceGender, VoiceInformation},
|
SpeechSynthesis::{
|
||||||
|
SpeechSynthesisStream, SpeechSynthesizer, VoiceGender, VoiceInformation,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
Storage::Streams::DataReader,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Backend, BackendId, Error, Features, Gender, UtteranceId, Voice, CALLBACKS};
|
use crate::{Backend, BackendId, Error, Features, Gender, UtteranceId, Voice, CALLBACKS};
|
||||||
|
@ -138,6 +141,20 @@ impl WinRt {
|
||||||
voice: SpeechSynthesizer::DefaultVoice()?,
|
voice: SpeechSynthesizer::DefaultVoice()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_synthesis_stream(&mut self, text: &str) -> Result<SpeechSynthesisStream, Error> {
|
||||||
|
self.synth.Options()?.SetSpeakingRate(self.rate.into())?;
|
||||||
|
self.synth.Options()?.SetAudioPitch(self.pitch.into())?;
|
||||||
|
self.synth.Options()?.SetAudioVolume(self.volume.into())?;
|
||||||
|
|
||||||
|
self.synth.SetVoice(&self.voice)?;
|
||||||
|
let stream = self
|
||||||
|
.synth
|
||||||
|
.SynthesizeTextToStreamAsync(&text.into())?
|
||||||
|
.get()?;
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend for WinRt {
|
impl Backend for WinRt {
|
||||||
|
@ -155,6 +172,7 @@ impl Backend for WinRt {
|
||||||
voice: true,
|
voice: true,
|
||||||
get_voice: true,
|
get_voice: true,
|
||||||
utterance_callbacks: true,
|
utterance_callbacks: true,
|
||||||
|
synthesize: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,18 +207,12 @@ impl Backend for WinRt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if no_utterances {
|
if no_utterances {
|
||||||
self.synth.Options()?.SetSpeakingRate(self.rate.into())?;
|
let stream = self.create_synthesis_stream(text)?;
|
||||||
self.synth.Options()?.SetAudioPitch(self.pitch.into())?;
|
|
||||||
self.synth.Options()?.SetAudioVolume(self.volume.into())?;
|
let media_source = MediaSource::CreateFromStream(&stream, &stream.ContentType()?)?;
|
||||||
self.synth.SetVoice(&self.voice)?;
|
self.player.SetSource(&media_source)?;
|
||||||
let stream = self
|
|
||||||
.synth
|
|
||||||
.SynthesizeTextToStreamAsync(&text.into())?
|
|
||||||
.get()?;
|
|
||||||
let content_type = stream.ContentType()?;
|
|
||||||
let source = MediaSource::CreateFromStream(&stream, &content_type)?;
|
|
||||||
self.player.SetSource(&source)?;
|
|
||||||
self.player.Play()?;
|
self.player.Play()?;
|
||||||
|
|
||||||
let mut callbacks = CALLBACKS.lock().unwrap();
|
let mut callbacks = CALLBACKS.lock().unwrap();
|
||||||
let callbacks = callbacks.get_mut(&self.id).unwrap();
|
let callbacks = callbacks.get_mut(&self.id).unwrap();
|
||||||
if let Some(callback) = callbacks.utterance_begin.as_mut() {
|
if let Some(callback) = callbacks.utterance_begin.as_mut() {
|
||||||
|
@ -210,6 +222,18 @@ impl Backend for WinRt {
|
||||||
Ok(Some(utterance_id))
|
Ok(Some(utterance_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error> {
|
||||||
|
let stream = self.create_synthesis_stream(text)?;
|
||||||
|
|
||||||
|
let size = stream.Size()?;
|
||||||
|
let data_reader = DataReader::CreateDataReader(&stream.GetInputStreamAt(0)?)?;
|
||||||
|
let mut bytes = vec![0; size as usize];
|
||||||
|
data_reader.LoadAsync(size as u32)?;
|
||||||
|
data_reader.ReadBytes(&mut bytes)?;
|
||||||
|
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
fn stop(&mut self) -> std::result::Result<(), Error> {
|
fn stop(&mut self) -> std::result::Result<(), Error> {
|
||||||
trace!("stop()");
|
trace!("stop()");
|
||||||
if !self.is_speaking()? {
|
if !self.is_speaking()? {
|
||||||
|
|
36
src/lib.rs
36
src/lib.rs
|
@ -14,9 +14,10 @@ use std::collections::HashMap;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::rc::Rc;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::string::FromUtf16Error;
|
use std::string::FromUtf16Error;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Mutex;
|
||||||
use std::{boxed::Box, sync::RwLock};
|
use std::{boxed::Box, sync::RwLock};
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
|
@ -162,6 +163,7 @@ unsafe impl Sync for UtteranceId {}
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Features {
|
pub struct Features {
|
||||||
pub is_speaking: bool,
|
pub is_speaking: bool,
|
||||||
|
pub synthesize: bool,
|
||||||
pub pitch: bool,
|
pub pitch: bool,
|
||||||
pub rate: bool,
|
pub rate: bool,
|
||||||
pub stop: bool,
|
pub stop: bool,
|
||||||
|
@ -217,6 +219,7 @@ pub trait Backend: Clone {
|
||||||
fn id(&self) -> Option<BackendId>;
|
fn id(&self) -> Option<BackendId>;
|
||||||
fn supported_features(&self) -> Features;
|
fn supported_features(&self) -> Features;
|
||||||
fn speak(&mut self, text: &str, interrupt: bool) -> Result<Option<UtteranceId>, Error>;
|
fn speak(&mut self, text: &str, interrupt: bool) -> Result<Option<UtteranceId>, Error>;
|
||||||
|
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error>;
|
||||||
fn stop(&mut self) -> Result<(), Error>;
|
fn stop(&mut self) -> Result<(), Error>;
|
||||||
fn min_rate(&self) -> f32;
|
fn min_rate(&self) -> f32;
|
||||||
fn max_rate(&self) -> f32;
|
fn max_rate(&self) -> f32;
|
||||||
|
@ -258,7 +261,7 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Tts(Arc<RwLock<Box<dyn Backend>>>);
|
pub struct Tts(Rc<RwLock<Box<dyn Backend>>>);
|
||||||
|
|
||||||
unsafe impl Send for Tts {}
|
unsafe impl Send for Tts {}
|
||||||
|
|
||||||
|
@ -271,18 +274,18 @@ impl Tts {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
Backends::SpeechDispatcher => {
|
Backends::SpeechDispatcher => {
|
||||||
let tts = backends::SpeechDispatcher::new()?;
|
let tts = backends::SpeechDispatcher::new()?;
|
||||||
Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
|
Ok(Tts(Rc::new(RwLock::new(Box::new(tts)))))
|
||||||
}
|
}
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
Backends::Web => {
|
Backends::Web => {
|
||||||
let tts = backends::Web::new()?;
|
let tts = backends::Web::new()?;
|
||||||
Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
|
Ok(Tts(Rc::new(RwLock::new(Box::new(tts)))))
|
||||||
}
|
}
|
||||||
#[cfg(all(windows, feature = "tolk"))]
|
#[cfg(all(windows, feature = "tolk"))]
|
||||||
Backends::Tolk => {
|
Backends::Tolk => {
|
||||||
let tts = backends::Tolk::new();
|
let tts = backends::Tolk::new();
|
||||||
if let Some(tts) = tts {
|
if let Some(tts) = tts {
|
||||||
Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
|
Ok(Tts(Rc::new(RwLock::new(Box::new(tts)))))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::NoneError)
|
Err(Error::NoneError)
|
||||||
}
|
}
|
||||||
|
@ -290,20 +293,20 @@ impl Tts {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
Backends::WinRt => {
|
Backends::WinRt => {
|
||||||
let tts = backends::WinRt::new()?;
|
let tts = backends::WinRt::new()?;
|
||||||
Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
|
Ok(Tts(Rc::new(RwLock::new(Box::new(tts)))))
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
Backends::AppKit => Ok(Tts(Arc::new(RwLock::new(Box::new(
|
Backends::AppKit => Ok(Tts(Rc::new(RwLock::new(
|
||||||
backends::AppKit::new()?
|
Box::new(backends::AppKit::new()?),
|
||||||
))))),
|
)))),
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
Backends::AvFoundation => Ok(Tts(Arc::new(RwLock::new(Box::new(
|
Backends::AvFoundation => Ok(Tts(Rc::new(RwLock::new(Box::new(
|
||||||
backends::AvFoundation::new()?,
|
backends::AvFoundation::new()?,
|
||||||
))))),
|
))))),
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
Backends::Android => {
|
Backends::Android => {
|
||||||
let tts = backends::Android::new()?;
|
let tts = backends::Android::new()?;
|
||||||
Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
|
Ok(Tts(Rc::new(RwLock::new(Box::new(tts)))))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Ok(backend) = backend {
|
if let Ok(backend) = backend {
|
||||||
|
@ -374,6 +377,15 @@ impl Tts {
|
||||||
.speak(text.into().as_str(), interrupt)
|
.speak(text.into().as_str(), interrupt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn synthesize<S: Into<String>>(&mut self, text: S) -> Result<Vec<u8>, Error> {
|
||||||
|
let Features { synthesize, .. } = self.supported_features();
|
||||||
|
if synthesize {
|
||||||
|
self.0.write().unwrap().synthesize(text.into().as_str())
|
||||||
|
} else {
|
||||||
|
Err(Error::UnsupportedFeature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Stops current speech.
|
/// Stops current speech.
|
||||||
pub fn stop(&mut self) -> Result<&Self, Error> {
|
pub fn stop(&mut self) -> Result<&Self, Error> {
|
||||||
let Features { stop, .. } = self.supported_features();
|
let Features { stop, .. } = self.supported_features();
|
||||||
|
@ -640,7 +652,7 @@ impl Tts {
|
||||||
|
|
||||||
impl Drop for Tts {
|
impl Drop for Tts {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if Arc::strong_count(&self.0) <= 1 {
|
if Rc::strong_count(&self.0) <= 1 {
|
||||||
if let Some(id) = self.0.read().unwrap().id() {
|
if let Some(id) = self.0.read().unwrap().id() {
|
||||||
let mut callbacks = CALLBACKS.lock().unwrap();
|
let mut callbacks = CALLBACKS.lock().unwrap();
|
||||||
callbacks.remove(&id);
|
callbacks.remove(&id);
|
||||||
|
|
Loading…
Reference in New Issue