diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef0f86b..973b9bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,6 @@ on: - "v*" jobs: - build_linux: name: Build Linux runs-on: ubuntu-latest @@ -15,6 +14,7 @@ jobs: - run: | sudo apt-get update sudo apt-get install -y libspeechd-dev + rustup update cargo build --release rustup target add wasm32-unknown-unknown cargo build --release --target wasm32-unknown-unknown @@ -26,6 +26,7 @@ jobs: - uses: actions/checkout@v2 - run: | choco install -y llvm + rustup update cargo build --release build_macos: @@ -34,8 +35,20 @@ jobs: steps: - uses: actions/checkout@v2 - run: | + rustup update cargo build --release + build_ios: + name: Build iOS + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: | + rustup update + rustup target add aarch64-apple-ios x86_64-apple-ios + cargo install cargo-lipo + cargo lipo --release + publish_winrt_bindings: name: Publish winrt_bindings runs-on: windows-latest @@ -46,10 +59,11 @@ jobs: - uses: actions/checkout@v2 - run: | choco install -y llvm + rustup update cargo login $CARGO_TOKEN cd winrt_bindings cargo package - cargo publish + cargo publish || true publish: name: Publish @@ -62,5 +76,6 @@ jobs: - run: | sudo apt-get update sudo apt-get install -y libspeechd-dev + rustup update cargo login $CARGO_TOKEN cargo publish diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 00245b3..d49d480 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,6 @@ on: pull_request: jobs: - build_linux: name: Build Linux runs-on: ubuntu-latest @@ -14,6 +13,7 @@ jobs: - run: | sudo apt-get update sudo apt-get install -y libspeechd-dev + rustup update cargo build --release rustup target add wasm32-unknown-unknown cargo build --release --target wasm32-unknown-unknown @@ -25,6 +25,7 @@ jobs: - uses: actions/checkout@v2 - run: | choco install -y llvm + rustup update cargo build --release build_macos: @@ -33,7 +34,16 @@ jobs: steps: - uses: actions/checkout@v2 - run: | + rustup update cargo build --release + + build_ios: + name: Build iOS + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: | + rustup update + rustup target add aarch64-apple-ios x86_64-apple-ios cargo install cargo-lipo - rustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios cargo lipo --release diff --git a/Cargo.toml b/Cargo.toml index 4a2f44e..af088f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tts" -version = "0.5.0" +version = "0.6.2" authors = ["Nolan Darilek "] repository = "https://github.com/ndarilek/tts-rs" description = "High-level Text-To-Speech (TTS) interface" @@ -12,6 +12,7 @@ edition = "2018" crate-type = ["lib", "staticlib"] [dependencies] +lazy_static = "1" log = "0.4" thiserror = "1" @@ -24,7 +25,7 @@ winrt = "0.7" tts_winrt_bindings = { version = "0.1", path="winrt_bindings" } [target.'cfg(target_os = "linux")'.dependencies] -speech-dispatcher = "0.4" +speech-dispatcher = "0.6" [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] cocoa-foundation = "0.1" diff --git a/README.md b/README.md index 921b598..c76fe23 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,5 @@ This library provides a high-level Text-To-Speech (TTS) interface supporting var * Linux via [Speech Dispatcher](https://freebsoft.org/speechd) * MacOS * AppKit on MacOS 10.13 and below - * AVFoundation on MacOS 10.14 and, eventually, iDevices -* WebAssembly \ No newline at end of file + * AVFoundation on MacOS 10.14 and above, and iOS +* WebAssembly diff --git a/src/backends/appkit.rs b/src/backends/appkit.rs index 6bfbf06..8584188 100644 --- a/src/backends/appkit.rs +++ b/src/backends/appkit.rs @@ -95,9 +95,9 @@ impl Backend for AppKit { Features { stop: true, rate: true, - pitch: false, volume: true, is_speaking: true, + ..Default::default() } } diff --git a/src/backends/speech_dispatcher.rs b/src/backends/speech_dispatcher.rs index 6a36d6c..009a8fb 100644 --- a/src/backends/speech_dispatcher.rs +++ b/src/backends/speech_dispatcher.rs @@ -1,4 +1,8 @@ #[cfg(target_os = "linux")] +use std::collections::HashMap; +use std::sync::Mutex; + +use lazy_static::*; use log::{info, trace}; use speech_dispatcher::*; @@ -6,11 +10,41 @@ use crate::{Backend, Error, Features}; pub struct SpeechDispatcher(Connection); +lazy_static! { + static ref SPEAKING: Mutex> = { + let m: HashMap = HashMap::new(); + Mutex::new(m) + }; +} + impl SpeechDispatcher { pub fn new() -> Self { info!("Initializing SpeechDispatcher backend"); - let connection = speech_dispatcher::Connection::open("tts", "tts", "tts", Mode::Single); - SpeechDispatcher(connection) + let connection = speech_dispatcher::Connection::open("tts", "tts", "tts", Mode::Threaded); + let sd = SpeechDispatcher(connection); + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(sd.0.client_id(), false); + sd.0.on_begin(Some(|_msg_id, client_id| { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(client_id, true); + })); + sd.0.on_end(Some(|_msg_id, client_id| { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(client_id, false); + })); + sd.0.on_cancel(Some(|_msg_id, client_id| { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(client_id, false); + })); + sd.0.on_pause(Some(|_msg_id, client_id| { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(client_id, false); + })); + sd.0.on_resume(Some(|_msg_id, client_id| { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(client_id, true); + })); + sd } } @@ -21,7 +55,7 @@ impl Backend for SpeechDispatcher { rate: true, pitch: true, volume: true, - is_speaking: false, + is_speaking: true, } } @@ -98,7 +132,7 @@ impl Backend for SpeechDispatcher { } fn normal_volume(&self) -> f32 { - 0. + 100. } fn get_volume(&self) -> Result { @@ -111,6 +145,15 @@ impl Backend for SpeechDispatcher { } fn is_speaking(&self) -> Result { - unimplemented!() + let speaking = SPEAKING.lock().unwrap(); + let is_speaking = speaking.get(&self.0.client_id()).unwrap(); + Ok(*is_speaking) + } +} + +impl Drop for SpeechDispatcher { + fn drop(&mut self) { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.remove(&self.0.client_id()); } } diff --git a/src/backends/tolk.rs b/src/backends/tolk.rs index 176fdb7..c71cdfa 100644 --- a/src/backends/tolk.rs +++ b/src/backends/tolk.rs @@ -22,16 +22,31 @@ impl Backend for Tolk { fn supported_features(&self) -> Features { Features { stop: true, - rate: false, - pitch: false, - volume: false, - is_speaking: false, + ..Default::default() } } fn speak(&mut self, text: &str, interrupt: bool) -> Result<(), Error> { trace!("speak({}, {})", text, interrupt); - self.0.speak(text, interrupt); + const BUFFER_LENGTH: usize = 300; + if text.len() <= BUFFER_LENGTH { + self.0.speak(text, interrupt); + } else { + if interrupt { + self.stop()?; + } + let tokens = text.split_whitespace(); + let mut buffer = String::new(); + for token in tokens { + if buffer.len() + token.len() > BUFFER_LENGTH { + self.0.speak(buffer, false); + buffer = String::new(); + } else { + buffer.push_str(token); + buffer.push(' '); + } + } + } Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index d1dda48..56b9f32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ * * Linux via [Speech Dispatcher](https://freebsoft.org/speechd) * * MacOS * * AppKit on MacOS 10.13 and below - * * AVFoundation on MacOS 10.14 and, eventually, iDevices + * * AVFoundation on MacOS 10.14 and above, and iOS * * WebAssembly */ @@ -48,6 +48,18 @@ pub struct Features { pub is_speaking: bool, } +impl Default for Features { + fn default() -> Self { + Self { + stop: false, + rate: false, + pitch: false, + volume: false, + is_speaking: false, + } + } +} + #[derive(Debug, Error)] pub enum Error { #[error("IO error: {0}")]