mirror of
https://github.com/ndarilek/tts-rs.git
synced 2024-11-17 05:19:38 +00:00
Add AVFoundation backend, used automatically on MacOS 10.14 and above.
This commit is contained in:
parent
2d0ab8889a
commit
c5b1ff1944
|
@ -25,6 +25,7 @@ speech-dispatcher = "0.4"
|
|||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa-foundation = "0.1"
|
||||
libc = "0.2"
|
||||
objc = "0.2"
|
||||
|
||||
[target.wasm32-unknown-unknown.dependencies]
|
||||
|
|
1
build.rs
1
build.rs
|
@ -10,5 +10,6 @@
|
|||
fn main() {
|
||||
if std::env::var("TARGET").unwrap().contains("-apple") {
|
||||
println!("cargo:rustc-link-lib=framework=AppKit");
|
||||
println!("cargo:rustc-link-lib=framework=AVFoundation");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ pub struct AppKit(*mut Object, *mut Object);
|
|||
|
||||
impl AppKit {
|
||||
pub fn new() -> Self {
|
||||
info!("Initializing NSSpeechSynthesizer backend");
|
||||
info!("Initializing AppKit backend");
|
||||
unsafe {
|
||||
let obj: *mut Object = msg_send![class!(NSSpeechSynthesizer), new];
|
||||
let mut decl =
|
||||
|
|
145
src/backends/av_foundation.rs
Normal file
145
src/backends/av_foundation.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
#[cfg(target_os = "macos")]
|
||||
#[link(name = "AVFoundation", kind = "framework")]
|
||||
use cocoa_foundation::base::{id, nil};
|
||||
use cocoa_foundation::foundation::NSString;
|
||||
use log::{info, trace};
|
||||
use objc::runtime::*;
|
||||
use objc::*;
|
||||
|
||||
use crate::{Backend, Error, Features};
|
||||
|
||||
pub struct AvFoundation {
|
||||
synth: *mut Object,
|
||||
rate: f32,
|
||||
volume: f32,
|
||||
pitch: f32,
|
||||
}
|
||||
|
||||
impl AvFoundation {
|
||||
pub fn new() -> Self {
|
||||
info!("Initializing AVFoundation backend");
|
||||
unsafe {
|
||||
let synth: *mut Object = msg_send![class!(AVSpeechSynthesizer), new];
|
||||
AvFoundation {
|
||||
synth: synth,
|
||||
rate: 0.5,
|
||||
volume: 1.,
|
||||
pitch: 1.,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for AvFoundation {
|
||||
fn supported_features(&self) -> Features {
|
||||
Features {
|
||||
stop: true,
|
||||
rate: true,
|
||||
pitch: true,
|
||||
volume: true,
|
||||
is_speaking: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn speak(&mut self, text: &str, interrupt: bool) -> Result<(), Error> {
|
||||
trace!("speak({}, {})", text, interrupt);
|
||||
if interrupt {
|
||||
self.stop()?;
|
||||
}
|
||||
unsafe {
|
||||
let str = NSString::alloc(nil).init_str(text);
|
||||
let utterance: id = msg_send![class!(AVSpeechUtterance), alloc];
|
||||
let _: () = msg_send![utterance, initWithString: str];
|
||||
let _: () = msg_send![utterance, setRate: self.rate];
|
||||
let _: () = msg_send![utterance, setVolume: self.volume];
|
||||
let _: () = msg_send![utterance, setPitchMultiplier: self.pitch];
|
||||
let _: () = msg_send![self.synth, speakUtterance: utterance];
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> Result<(), Error> {
|
||||
trace!("stop()");
|
||||
unsafe {
|
||||
let _: () = msg_send![self.synth, stopSpeakingAtBoundary: 0];
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn min_rate(&self) -> f32 {
|
||||
0.1
|
||||
}
|
||||
|
||||
fn max_rate(&self) -> f32 {
|
||||
2.
|
||||
}
|
||||
|
||||
fn normal_rate(&self) -> f32 {
|
||||
0.5
|
||||
}
|
||||
|
||||
fn get_rate(&self) -> Result<f32, Error> {
|
||||
Ok(self.rate)
|
||||
}
|
||||
|
||||
fn set_rate(&mut self, rate: f32) -> Result<(), Error> {
|
||||
trace!("set_rate({})", rate);
|
||||
self.rate = rate;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn min_pitch(&self) -> f32 {
|
||||
0.5
|
||||
}
|
||||
|
||||
fn max_pitch(&self) -> f32 {
|
||||
2.0
|
||||
}
|
||||
|
||||
fn normal_pitch(&self) -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
fn get_pitch(&self) -> Result<f32, Error> {
|
||||
Ok(self.pitch)
|
||||
}
|
||||
|
||||
fn set_pitch(&mut self, pitch: f32) -> Result<(), Error> {
|
||||
self.pitch = pitch;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn min_volume(&self) -> f32 {
|
||||
0.
|
||||
}
|
||||
|
||||
fn max_volume(&self) -> f32 {
|
||||
1.
|
||||
}
|
||||
|
||||
fn normal_volume(&self) -> f32 {
|
||||
1.
|
||||
}
|
||||
|
||||
fn get_volume(&self) -> Result<f32, Error> {
|
||||
Ok(self.volume)
|
||||
}
|
||||
|
||||
fn set_volume(&mut self, volume: f32) -> Result<(), Error> {
|
||||
self.volume = volume;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_speaking(&self) -> Result<bool, Error> {
|
||||
let is_speaking: i8 = unsafe { msg_send![self.synth, isSpeaking] };
|
||||
Ok(is_speaking == YES)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AvFoundation {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _: Object = msg_send![self.synth, release];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,9 @@ mod web;
|
|||
#[cfg(target_os = "macos")]
|
||||
mod appkit;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod av_foundation;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use self::speech_dispatcher::*;
|
||||
|
||||
|
@ -24,3 +27,6 @@ pub use self::web::*;
|
|||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use self::appkit::*;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use self::av_foundation::*;
|
||||
|
|
30
src/lib.rs
30
src/lib.rs
|
@ -8,8 +8,14 @@
|
|||
* * WebAssembly
|
||||
*/
|
||||
|
||||
use std::boxed::Box;
|
||||
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;
|
||||
|
||||
mod backends;
|
||||
|
@ -25,6 +31,8 @@ pub enum Backends {
|
|||
WinRT,
|
||||
#[cfg(target_os = "macos")]
|
||||
AppKit,
|
||||
#[cfg(target_os = "macos")]
|
||||
AvFoundation,
|
||||
}
|
||||
|
||||
pub struct Features {
|
||||
|
@ -110,6 +118,8 @@ impl 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()))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +135,23 @@ impl TTS {
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
let tts = TTS::new(Backends::Web);
|
||||
#[cfg(target_os = "macos")]
|
||||
let tts = TTS::new(Backends::AppKit);
|
||||
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)
|
||||
}
|
||||
};
|
||||
tts
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user