basic program
This commit is contained in:
21
.devcontainer/devcontainer.json
Normal file
21
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,21 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
|
||||
{
|
||||
"name": "Rust",
|
||||
"image": "mcr.microsoft.com/devcontainers/rust:1-bullseye"
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "rustc --version",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
1559
Cargo.lock
generated
Normal file
1559
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "mimic3-keyboard"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
about = "Sends line-terminated input to a Mimic3 API host then plays the resulting audio on the default audio device."
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
urlencoding = "2.1.2"
|
||||
clap = {version="4.0.29", features=["derive"]}
|
||||
rodio="0.16.0"
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
121
src/main.rs
Normal file
121
src/main.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use std::io::{self, Cursor};
|
||||
use rodio::{Source, OutputStream, OutputStreamHandle};
|
||||
use hyper::{Client, Body, Method, Request, StatusCode};
|
||||
use clap::{Parser, ArgAction};
|
||||
use urlencoding::encode;
|
||||
|
||||
const MIMIC3_HOST : &str = "http://tts.local:59125";
|
||||
const MIMIC3_VOICE : &str = "en_US/vctk_low#p236";
|
||||
const MIMIC3_NOISE : f32 = 0.33;
|
||||
const MIMIC3_LENGTH : f32 = 1.2;
|
||||
|
||||
struct MimicConfig {
|
||||
host: String,
|
||||
voice: String,
|
||||
noise: f32,
|
||||
length: f32,
|
||||
ssml: bool
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about=None)]
|
||||
struct Args {
|
||||
#[arg(short = None, long = "host", default_value_t = String::from(MIMIC3_HOST))]
|
||||
host: String,
|
||||
#[arg(short = 'v', long = "voice", default_value_t = String::from(MIMIC3_VOICE))]
|
||||
voice: String,
|
||||
#[arg(short, long, action = ArgAction::SetTrue)]
|
||||
ssml: bool,
|
||||
#[arg(short = 'r', long = "rate", default_value_t = MIMIC3_LENGTH)]
|
||||
length: f32,
|
||||
#[arg(short = 'n', long = "noise", default_value_t = MIMIC3_NOISE)]
|
||||
noise: f32
|
||||
}
|
||||
|
||||
async fn process_line(data: &str, output: &OutputStreamHandle, config: &MimicConfig) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let client = Client::new();
|
||||
let url = format!("{}/api/tts?voice={}&noiseScale={}&lengthScale={}&ssml={}&audioTarget=client", config.host, config.voice, config.noise, config.length, config.ssml);
|
||||
let ssml_body_text = format!("<speak>{}</speak>", data);
|
||||
let req = Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(url.clone())
|
||||
.body(Body::from(match config.ssml {
|
||||
true => ssml_body_text.clone(),
|
||||
false => String::from(data)
|
||||
}))?;
|
||||
|
||||
match client.request(req).await {
|
||||
Ok(response) => {
|
||||
if response.status()==StatusCode::OK {
|
||||
match hyper::body::to_bytes(response.into_body()).await {
|
||||
Ok(buff) => {
|
||||
let cursor = Cursor::new(buff);
|
||||
let source = rodio::Decoder::new_wav(cursor).unwrap();
|
||||
let duration = source.total_duration().expect("Unable to determine length of audio sample.");
|
||||
match output.play_raw(source.convert_samples()) {
|
||||
Ok(()) => std::thread::sleep(duration),
|
||||
Err(error) => {
|
||||
println!("Error playing audio clip: {}", error);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
Err(error) => Err(Box::new(error))
|
||||
}
|
||||
} else {
|
||||
eprintln!("API Unable to process line: {}", match config.ssml {
|
||||
true => ssml_body_text,
|
||||
false => String::from(data)
|
||||
});
|
||||
eprintln!("URL used: {}", url);
|
||||
eprintln!("Response: {}", response.status());
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
Err(Box::new(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let cli = Args::parse();
|
||||
|
||||
let config : MimicConfig = MimicConfig {
|
||||
host: cli.host,
|
||||
voice: encode(&cli.voice).into_owned(),
|
||||
noise: cli.noise,
|
||||
length: cli.length,
|
||||
ssml: cli.ssml
|
||||
};
|
||||
|
||||
// let matches = App::new("Mimic3 Keyboard")
|
||||
// .version("0.1.0")
|
||||
// .author("Lauren Kaviak <lauren@laurayco.me>")
|
||||
// .about("Sends stdin to a mimic3 API instance and plays the result on the default audio device")
|
||||
// .arg(Arg::new("host").short("h").long("host").takes_value(true).help("Mimic3 hostname (including protocol, eg: http://mimic3.com)"))
|
||||
// .arg(Arg::new("voice").short("v").long("voice").takes_value(true).help("Mimic3 voice to use"))
|
||||
// .arg(Arg::new("ssml").short("s").long("ssml").takes_value(false).help("enable ssml support"))
|
||||
// .get_matches();
|
||||
|
||||
let stdin = io::stdin();
|
||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
loop {
|
||||
let mut user_input = String::new();
|
||||
stdin.read_line(&mut user_input)?;
|
||||
let user_input = user_input.trim();
|
||||
|
||||
if user_input.to_lowercase()=="goodbye" {
|
||||
break;
|
||||
}
|
||||
|
||||
match process_line(user_input, &stream_handle, &config).await {
|
||||
Ok(..) => {},
|
||||
Err(error) => {
|
||||
println!("Error: {}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user