basic program

This commit is contained in:
Lauren Kaviak
2022-12-28 10:01:52 +00:00
parent 52e7261ddc
commit fbb3c5c0e0
5 changed files with 1722 additions and 0 deletions

View 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
View File

@@ -0,0 +1 @@
/target

1559
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

20
Cargo.toml Normal file
View 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
View 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(())
}