1
0
mirror of https://github.com/ndarilek/tts-rs.git synced 2024-11-17 12:39:36 +00:00

Add voices feature

Implemented for AVFoundation backend but set_voice has no effect for now
Warning: does not build on Linux or windows for now
This commit is contained in:
François Caddet 2020-09-03 16:50:11 +02:00
parent 6c091f3284
commit 5b0d1b6621
5 changed files with 101 additions and 0 deletions

View File

@ -29,6 +29,7 @@ speech-dispatcher = "0.6"
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
cocoa-foundation = "0.1"
core-foundation = "0.9"
libc = "0.2"
objc = "0.2"

View File

@ -195,6 +195,18 @@ impl Backend for AppKit {
let is_speaking: i8 = unsafe { msg_send![self.0, isSpeaking] };
Ok(is_speaking == YES)
}
fn voice(&self) -> Result<String,Error> {
unimplemented!()
}
fn list_voices(&self) -> Vec<String> {
unimplemented!()
}
fn set_voice(&self, voice: String) -> Result<(),Error> {
unimplemented!()
}
}
impl Drop for AppKit {

View File

@ -8,11 +8,15 @@ use objc::*;
use crate::{Backend, Error, Features};
mod voices;
use voices::AVSpeechSynthesisVoice;
pub struct AvFoundation {
synth: *mut Object,
rate: f32,
volume: f32,
pitch: f32,
voice: AVSpeechSynthesisVoice,
}
impl AvFoundation {
@ -25,6 +29,7 @@ impl AvFoundation {
rate: 0.5,
volume: 1.,
pitch: 1.,
voice: AVSpeechSynthesisVoice::new(),
}
}
}
@ -38,6 +43,7 @@ impl Backend for AvFoundation {
pitch: true,
volume: true,
is_speaking: true,
voices: true,
}
}
@ -134,6 +140,18 @@ impl Backend for AvFoundation {
let is_speaking: i8 = unsafe { msg_send![self.synth, isSpeaking] };
Ok(is_speaking == 1)
}
fn voice(&self) -> Result<String,Error> {
Ok(self.voice.identifier())
}
fn list_voices(&self) -> Vec<String> {
AVSpeechSynthesisVoice::list().iter().map(|v| {v.identifier()}).collect()
}
fn set_voice(&self, voice: String) -> Result<(),Error> {
Ok(())
}
}
impl Drop for AvFoundation {

View File

@ -0,0 +1,31 @@
use objc::runtime::*;
use objc::*;
use core_foundation::array::CFArray;
use core_foundation::string::CFString;
#[derive(Copy,Clone)]
pub struct AVSpeechSynthesisVoice(*const Object);
impl AVSpeechSynthesisVoice {
pub fn new() -> Self {
Self::list()[0]
}
pub fn list() -> Vec<Self> {
let voices: CFArray = unsafe{msg_send![class!(AVSpeechSynthesisVoice), speechVoices]};
voices.iter().map(|v| {
AVSpeechSynthesisVoice{0: *v as *mut Object}
}).collect()
}
pub fn name(self) -> String {
let name: CFString = unsafe{msg_send![self.0, name]};
name.to_string()
}
pub fn identifier(self) -> String {
let identifier: CFString = unsafe{msg_send![self.0, identifier]};
identifier.to_string()
}
}

View File

@ -46,6 +46,7 @@ pub struct Features {
pub pitch: bool,
pub volume: bool,
pub is_speaking: bool,
pub voices: bool,
}
impl Default for Features {
@ -56,6 +57,7 @@ impl Default for Features {
pitch: false,
volume: false,
is_speaking: false,
voices: false,
}
}
}
@ -98,6 +100,9 @@ pub trait Backend {
fn get_volume(&self) -> Result<f32, Error>;
fn set_volume(&mut self, volume: f32) -> Result<(), Error>;
fn is_speaking(&self) -> Result<bool, Error>;
fn voice(&self) -> Result<String, Error>;
fn list_voices(&self) -> Vec<String>;
fn set_voice(&self, voice: String) -> Result<(),Error>;
}
pub struct TTS(Box<dyn Backend>);
@ -371,4 +376,38 @@ impl TTS {
Err(Error::UnsupportedFeature)
}
}
/**
* Returns list of available voices.
*/
pub fn list_voices(&self) -> Vec<String> {
self.0.list_voices()
}
/**
* Return the current speaking voice.
*/
pub fn voice(&self) -> Result<String,Error> {
let Features { voices, .. } = self.supported_features();
if voices {
self.0.voice()
} else {
Err(Error::UnsupportedFeature)
}
}
/**
* Set speaking voice.
*/
pub fn set_voice(&self, voice: String) -> Result<(),Error> {
let Features {
voices: voices_feature,
..
} = self.0.supported_features();
if voices_feature {
self.0.set_voice(voice)
} else {
Err(Error::UnsupportedFeature)
}
}
}