#![allow(clippy::wildcard_imports)]
use crate::types::WordBoundary;
#[must_use]
pub fn build_google_request(
text: &str,
voice: &str,
add_marks: bool,
input_ssml: Option<&str>,
) -> (serde_json::Value, Vec<String>) {
let voice_obj = if voice.len() >= 5 && voice.as_bytes().get(2) == Some(&b'-') {
let lang = &voice[..5];
serde_json::json!({ "languageCode": lang, "name": voice })
} else {
serde_json::json!({ "name": voice })
};
let mut words_list = Vec::new();
let input = if let Some(ssml) = input_ssml {
serde_json::json!({ "ssml": ssml })
} else if add_marks {
let words: Vec<&str> = text.split_whitespace().filter(|w| !w.is_empty()).collect();
let mut ssml = String::from("<speak>");
for (i, w) in words.iter().enumerate() {
if i > 0 {
ssml.push(' ');
}
let _ = std::fmt::Write::write_fmt(&mut ssml, format_args!("<mark name=\"{i}\"/>{w}"));
words_list = words.iter().map(|w| (*w).to_string()).collect();
}
ssml.push_str("</speak>");
serde_json::json!({ "ssml": ssml })
} else {
serde_json::json!({ "text": text })
};
let mut body = serde_json::json!({
"input": input,
"voice": voice_obj,
"audioConfig": { "audioEncoding": "MP3", "sampleRateHertz": 24000 }
});
if add_marks {
body["enableTimePointing"] = serde_json::json!(["SSML_MARK"]);
}
(body, words_list)
}
pub(crate) fn parse_google_timepoints(
timepoints: &[serde_json::Value],
words: &[String],
) -> Vec<WordBoundary> {
#[derive(Clone)]
struct RawTp {
index: usize,
time_ms: u64,
}
let mut raw: Vec<RawTp> = Vec::new();
for tp in timepoints {
let mark = tp.get("markName").and_then(|v| v.as_str()).unwrap_or("");
let idx: usize = mark.parse().unwrap_or(usize::MAX);
let secs = tp
.get("timeSeconds")
.and_then(serde_json::Value::as_f64)
.unwrap_or(0.0);
if idx < words.len() {
raw.push(RawTp {
index: idx,
time_ms: (secs * 1000.0) as u64,
});
}
}
raw.sort_by_key(|r| r.time_ms);
let mut boundaries = Vec::with_capacity(raw.len());
for (i, tp) in raw.iter().enumerate() {
let word = &words[tp.index];
let duration = if i + 1 < raw.len() {
raw[i + 1].time_ms.saturating_sub(tp.time_ms)
} else {
((word.len() as u64) * 80).max(50)
};
boundaries.push(WordBoundary {
text: word.clone(),
offset: tp.time_ms,
duration,
estimated: false,
});
}
boundaries
}