use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde_json::Value;
use crate::audio::WavAudio;
use crate::error::Error;
#[derive(Debug, Clone)]
pub enum AudioInput {
Path(String),
Buffer(WavAudio),
}
impl From<&str> for AudioInput {
fn from(s: &str) -> Self {
AudioInput::Path(s.to_owned())
}
}
impl From<String> for AudioInput {
fn from(s: String) -> Self {
AudioInput::Path(s)
}
}
impl From<&String> for AudioInput {
fn from(s: &String) -> Self {
AudioInput::Path(s.clone())
}
}
impl From<WavAudio> for AudioInput {
fn from(a: WavAudio) -> Self {
AudioInput::Buffer(a)
}
}
impl From<&Path> for AudioInput {
fn from(p: &Path) -> Self {
AudioInput::Path(p.to_string_lossy().into_owned())
}
}
impl From<PathBuf> for AudioInput {
fn from(p: PathBuf) -> Self {
AudioInput::Path(p.to_string_lossy().into_owned())
}
}
#[derive(Debug, Clone, Default)]
pub struct AudioRequest {
pub audio: Option<AudioInput>,
pub options: BTreeMap<String, Value>,
}
impl AudioRequest {
pub fn new(audio: impl Into<AudioInput>) -> Self {
Self {
audio: Some(audio.into()),
options: BTreeMap::new(),
}
}
pub fn option<V: Into<Value>>(mut self, key: impl Into<String>, value: V) -> Self {
self.options.insert(key.into(), value.into());
self
}
pub fn options<K, V>(mut self, opts: impl IntoIterator<Item = (K, V)>) -> Self
where
K: Into<String>,
V: Into<Value>,
{
self.options
.extend(opts.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
}
#[derive(Debug, Clone)]
pub struct TtsRequest {
pub text: String,
pub language: Option<String>,
pub reference_audio: Option<AudioInput>,
pub reference_text: Option<String>,
pub options: BTreeMap<String, Value>,
}
impl TtsRequest {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
language: None,
reference_audio: None,
reference_text: None,
options: BTreeMap::new(),
}
}
pub fn language(mut self, language: impl Into<String>) -> Self {
self.language = Some(language.into());
self
}
pub fn reference(mut self, audio: impl Into<AudioInput>) -> Self {
self.reference_audio = Some(audio.into());
self
}
pub fn reference_text(mut self, text: impl Into<String>) -> Self {
self.reference_text = Some(text.into());
self
}
pub fn option<V: Into<Value>>(mut self, key: impl Into<String>, value: V) -> Self {
self.options.insert(key.into(), value.into());
self
}
pub fn options<K, V>(mut self, opts: impl IntoIterator<Item = (K, V)>) -> Self
where
K: Into<String>,
V: Into<Value>,
{
self.options
.extend(opts.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
}
#[derive(Debug, Clone)]
pub enum Request {
Vad(AudioRequest),
Asr(AudioRequest),
Diar(AudioRequest),
SourceSeparation(AudioRequest),
Tts(TtsRequest),
Json(String),
}
impl Request {
pub fn vad(audio: impl Into<AudioInput>) -> Self {
Request::Vad(AudioRequest::new(audio))
}
pub fn asr(audio: impl Into<AudioInput>) -> Self {
Request::Asr(AudioRequest::new(audio))
}
pub fn diar(audio: impl Into<AudioInput>) -> Self {
Request::Diar(AudioRequest::new(audio))
}
pub fn source_separation(audio: impl Into<AudioInput>) -> Self {
Request::SourceSeparation(AudioRequest::new(audio))
}
pub fn tts(text: impl Into<String>) -> Self {
Request::Tts(TtsRequest::new(text))
}
pub fn json(s: impl Into<String>) -> Self {
Request::Json(s.into())
}
pub fn option<V: Into<Value>>(self, key: impl Into<String>, value: V) -> Self {
let key = key.into();
let value = value.into();
match self {
Request::Vad(r) => Request::Vad(r.option(key, value)),
Request::Asr(r) => Request::Asr(r.option(key, value)),
Request::Diar(r) => Request::Diar(r.option(key, value)),
Request::SourceSeparation(r) => Request::SourceSeparation(r.option(key, value)),
Request::Tts(r) => Request::Tts(r.option(key, value)),
Request::Json(_) => self,
}
}
pub fn options<K, V>(self, opts: impl IntoIterator<Item = (K, V)>) -> Self
where
K: Into<String>,
V: Into<Value>,
{
let opts: Vec<(String, Value)> = opts
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect();
match self {
Request::Vad(r) => Request::Vad(r.options(opts)),
Request::Asr(r) => Request::Asr(r.options(opts)),
Request::Diar(r) => Request::Diar(r.options(opts)),
Request::SourceSeparation(r) => Request::SourceSeparation(r.options(opts)),
Request::Tts(r) => Request::Tts(r.options(opts)),
Request::Json(_) => self,
}
}
pub fn reference(self, audio: impl Into<AudioInput>) -> Self {
match self {
Request::Tts(r) => Request::Tts(r.reference(audio)),
other => other,
}
}
pub fn reference_text(self, text: impl Into<String>) -> Self {
match self {
Request::Tts(r) => Request::Tts(r.reference_text(text)),
other => other,
}
}
pub fn language(self, language: impl Into<String>) -> Self {
match self {
Request::Tts(r) => Request::Tts(r.language(language)),
other => other,
}
}
pub fn to_json(&self) -> Result<String, Error> {
let mut obj = serde_json::Map::new();
match self {
Request::Json(s) => return Ok(s.clone()),
Request::Vad(r) | Request::Asr(r) | Request::Diar(r) | Request::SourceSeparation(r) => {
if let Some(audio) = &r.audio {
write_audio(&mut obj, audio);
}
if !r.options.is_empty() {
obj.insert(
"options".into(),
Value::Object(r.options.clone().into_iter().collect()),
);
}
}
Request::Tts(r) => {
obj.insert("text".into(), Value::String(r.text.clone()));
if let Some(lang) = &r.language {
obj.insert("language".into(), Value::String(lang.clone()));
}
if let Some(audio) = &r.reference_audio {
write_audio(&mut obj, audio);
}
let mut options = r.options.clone();
if let Some(rt) = &r.reference_text {
options.insert("reference_text".into(), Value::String(rt.clone()));
}
if !options.is_empty() {
obj.insert(
"options".into(),
Value::Object(options.into_iter().collect()),
);
}
}
}
Ok(serde_json::to_string(&Value::Object(obj))?)
}
}
fn write_audio(obj: &mut serde_json::Map<String, Value>, input: &AudioInput) {
match input {
AudioInput::Path(p) => {
obj.insert("audio_path".into(), Value::String(p.clone()));
}
AudioInput::Buffer(buf) => {
obj.insert(
"audio".into(),
serde_json::json!({
"sample_rate": buf.sample_rate,
"channels": buf.channels,
"samples": buf.samples,
}),
);
}
}
}
pub trait IntoRequest {
fn into_request(self) -> Result<Request, Error>;
}
impl IntoRequest for Request {
fn into_request(self) -> Result<Request, Error> {
Ok(self)
}
}
impl IntoRequest for &Request {
fn into_request(self) -> Result<Request, Error> {
Ok(self.clone())
}
}
impl IntoRequest for () {
fn into_request(self) -> Result<Request, Error> {
Ok(Request::Json("{}".into()))
}
}
impl IntoRequest for &str {
fn into_request(self) -> Result<Request, Error> {
Ok(Request::Json(self.to_string()))
}
}
impl IntoRequest for String {
fn into_request(self) -> Result<Request, Error> {
Ok(Request::Json(self))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
fn json(s: &str) -> serde_json::Value {
serde_json::from_str(s).expect("合法 JSON")
}
#[test]
fn vad_offline() {
let req = Request::vad("./a.wav").option("vad_threshold", 0.5);
assert_eq!(
json(&req.to_json().unwrap()),
json(r#"{"audio_path":"./a.wav","options":{"vad_threshold":0.5}}"#)
);
}
#[test]
fn asr_streaming_window() {
let req = Request::asr("./a.wav").option("audio_chunk_seconds", 3.0);
assert_eq!(
json(&req.to_json().unwrap()),
json(r#"{"audio_path":"./a.wav","options":{"audio_chunk_seconds":3.0}}"#)
);
}
#[test]
fn tts_text() {
let req = Request::tts("Hello!");
assert_eq!(json(&req.to_json().unwrap()), json(r#"{"text":"Hello!"}"#));
}
#[test]
fn tts_voice_clone() {
let req = Request::tts("Hi")
.reference("./ref.wav")
.reference_text("参考文本")
.language("zh");
assert_eq!(
json(&req.to_json().unwrap()),
json(
r#"{"text":"Hi","language":"zh","audio_path":"./ref.wav","options":{"reference_text":"参考文本"}}"#
)
);
}
#[test]
fn windows_path_no_manual_escaping() {
let req = Request::asr("C:\\dir\\spe\"ch.wav");
assert_eq!(
json(&req.to_json().unwrap()),
json(r#"{"audio_path":"C:\\dir\\spe\"ch.wav"}"#)
);
}
#[test]
fn embedded_audio_buffer() {
let buf = WavAudio {
sample_rate: 16000,
channels: 1,
samples: vec![0.0, 0.5, -0.5],
};
let req = Request::vad(AudioInput::Buffer(buf));
assert_eq!(
json(&req.to_json().unwrap()),
json(r#"{"audio":{"sample_rate":16000,"channels":1,"samples":[0.0,0.5,-0.5]}}"#)
);
}
#[test]
fn raw_json_pass_through() {
let s = r#"{"text":"hi","options":{"a":1}}"#;
assert_eq!(Request::json(s).to_json().unwrap(), s);
assert_eq!(
<&str as IntoRequest>::into_request(s)
.unwrap()
.to_json()
.unwrap(),
s
);
}
#[test]
fn empty_request() {
assert_eq!(
IntoRequest::into_request(()).unwrap().to_json().unwrap(),
"{}"
);
}
}