Compare commits

...

10 Commits

Author SHA1 Message Date
Bear_03 4ae7b18677
Merge 4f4ab53252 into 5c528f1d8e 2023-09-24 18:00:37 -07:00
Nolan Darilek 5c528f1d8e Bump version. 2023-09-09 12:44:17 -05:00
Nolan Darilek 9fb8107acf Eliminate some warnings. 2023-09-09 12:43:43 -05:00
Nolan Darilek 8dabcc99c4 Bump windows dependency. 2023-09-09 12:41:36 -05:00
Bear-03 4f4ab53252
Add Tts::synthesize method 2022-07-23 14:04:15 +02:00
Bear-03 91a0f03f1a
Remove InMemoryRandomAccessStream in WinRt::speak 2022-07-23 14:03:45 +02:00
Bear-03 87cf05f78e
Fix errors on stable toolchain 2022-07-22 18:03:01 +02:00
Bear_03 9cd66c0358
Merge branch 'master' into synthesize 2022-07-22 17:42:04 +02:00
Bear-03 b85ffc80d4
Add synthesis support to WinRT 2022-07-22 17:31:14 +02:00
Bear-03 bf522b42a7
Prepare Backend and Features for synthesis 2022-07-22 17:29:48 +02:00
9 changed files with 98 additions and 19 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "tts"
version = "0.25.5"
version = "0.25.6"
authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
repository = "https://github.com/ndarilek/tts-rs"
description = "High-level Text-To-Speech (TTS) interface"
@ -30,7 +30,14 @@ env_logger = "0.10"
[target.'cfg(windows)'.dependencies]
tolk = { version = "0.5", optional = true }
windows = { version = "0.48", features = ["Foundation", "Foundation_Collections", "Media_Core", "Media_Playback", "Media_SpeechSynthesis", "Storage_Streams"] }
windows = { version = "0.51", features = [
"Foundation",
"Foundation_Collections",
"Media_Core",
"Media_Playback",
"Media_SpeechSynthesis",
"Storage_Streams",
] }
[target.'cfg(target_os = "linux")'.dependencies]
speech-dispatcher = { version = "0.16", default-features = false }
@ -43,7 +50,16 @@ objc = { version = "0.2", features = ["exception"] }
[target.wasm32-unknown-unknown.dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["EventTarget", "SpeechSynthesis", "SpeechSynthesisErrorCode", "SpeechSynthesisErrorEvent", "SpeechSynthesisEvent", "SpeechSynthesisUtterance", "SpeechSynthesisVoice", "Window", ] }
web-sys = { version = "0.3", features = [
"EventTarget",
"SpeechSynthesis",
"SpeechSynthesisErrorCode",
"SpeechSynthesisErrorEvent",
"SpeechSynthesisEvent",
"SpeechSynthesisUtterance",
"SpeechSynthesisVoice",
"Window",
] }
[target.'cfg(target_os="android")'.dependencies]
jni = "0.21"
@ -52,4 +68,4 @@ ndk-glue = "0.7"
[package.metadata.docs.rs]
no-default-features = true
features = ["speech_dispatcher_0_11"]
features = ["speech_dispatcher_0_11"]

View File

@ -250,6 +250,7 @@ impl Backend for Android {
utterance_callbacks: true,
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> {
let vm = Self::vm()?;
let mut env = vm.get_env()?;

View File

@ -125,6 +125,10 @@ impl Backend for AppKit {
Ok(None)
}
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error> {
unimplemented!();
}
fn stop(&mut self) -> Result<(), Error> {
trace!("stop()");
unsafe {

View File

@ -170,6 +170,7 @@ impl Backend for AvFoundation {
voice: true,
get_voice: false,
utterance_callbacks: true,
synthesize: false,
}
}
@ -206,6 +207,10 @@ impl Backend for AvFoundation {
Ok(Some(UtteranceId::AvFoundation(utterance)))
}
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error> {
unimplemented!();
}
fn stop(&mut self) -> Result<(), Error> {
trace!("stop()");
unsafe {

View File

@ -85,6 +85,7 @@ impl Backend for SpeechDispatcher {
voice: true,
get_voice: false,
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> {
trace!("stop()");
self.0.cancel()?;

View File

@ -39,6 +39,10 @@ impl Backend for Tolk {
Ok(None)
}
fn synthesize(&mut self, text: &str) -> Result<Vec<u8>, Error> {
unimplemented!();
}
fn stop(&mut self) -> Result<(), Error> {
trace!("stop()");
self.0.silence();

View File

@ -59,6 +59,7 @@ impl Backend for Web {
voice: true,
get_voice: 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> {
trace!("stop()");
if let Some(window) = web_sys::window() {

View File

@ -12,8 +12,11 @@ use windows::{
Media::{
Core::MediaSource,
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};
@ -138,6 +141,20 @@ impl WinRt {
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 {
@ -155,6 +172,7 @@ impl Backend for WinRt {
voice: true,
get_voice: true,
utterance_callbacks: true,
synthesize: true,
}
}
@ -189,18 +207,12 @@ impl Backend for WinRt {
}
}
if no_utterances {
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()?;
let content_type = stream.ContentType()?;
let source = MediaSource::CreateFromStream(&stream, &content_type)?;
self.player.SetSource(&source)?;
let stream = self.create_synthesis_stream(text)?;
let media_source = MediaSource::CreateFromStream(&stream, &stream.ContentType()?)?;
self.player.SetSource(&media_source)?;
self.player.Play()?;
let mut callbacks = CALLBACKS.lock().unwrap();
let callbacks = callbacks.get_mut(&self.id).unwrap();
if let Some(callback) = callbacks.utterance_begin.as_mut() {
@ -210,6 +222,18 @@ impl Backend for WinRt {
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> {
trace!("stop()");
if !self.is_speaking()? {

View File

@ -162,6 +162,7 @@ unsafe impl Sync for UtteranceId {}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Features {
pub is_speaking: bool,
pub synthesize: bool,
pub pitch: bool,
pub rate: bool,
pub stop: bool,
@ -217,6 +218,7 @@ pub trait Backend: Clone {
fn id(&self) -> Option<BackendId>;
fn supported_features(&self) -> Features;
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 min_rate(&self) -> f32;
fn max_rate(&self) -> f32;
@ -374,6 +376,15 @@ impl Tts {
.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.
pub fn stop(&mut self) -> Result<&Self, Error> {
let Features { stop, .. } = self.supported_features();
@ -571,7 +582,7 @@ impl Tts {
if utterance_callbacks {
let mut callbacks = CALLBACKS.lock().unwrap();
let id = self.0.read().unwrap().id().unwrap();
let mut callbacks = callbacks.get_mut(&id).unwrap();
let callbacks = callbacks.get_mut(&id).unwrap();
callbacks.utterance_begin = callback;
Ok(())
} else {
@ -591,7 +602,7 @@ impl Tts {
if utterance_callbacks {
let mut callbacks = CALLBACKS.lock().unwrap();
let id = self.0.read().unwrap().id().unwrap();
let mut callbacks = callbacks.get_mut(&id).unwrap();
let callbacks = callbacks.get_mut(&id).unwrap();
callbacks.utterance_end = callback;
Ok(())
} else {
@ -611,7 +622,7 @@ impl Tts {
if utterance_callbacks {
let mut callbacks = CALLBACKS.lock().unwrap();
let id = self.0.read().unwrap().id().unwrap();
let mut callbacks = callbacks.get_mut(&id).unwrap();
let callbacks = callbacks.get_mut(&id).unwrap();
callbacks.utterance_stop = callback;
Ok(())
} else {