tonhowtf/omniget · error
VoiceStudio HTTP
Error message
VoiceStudio HTTP {}: {} What it means
wav_from checks the HTTP status of a VoiceStudio TTS/voice-API response and, on any non-success status, aborts by formatting the status code plus up to 300 chars of the response body into an anyhow error. It surfaces server-side rejections (auth, rate limits, bad payloads) from the VoiceStudio backend to the caller.
Solutions
- Read the status and body text in the error message to identify the server-side cause (401/403 = credentials, 404 = bad profile/endpoint, 413 = file too large, 5xx = server issue).
- Verify the VoiceStudio API key/base URL configuration used to build the reqwest client.
- For 4xx, fix the request: confirm profile_id exists, audio format/size is accepted, and parameters are valid.
- For 5xx or 429, retry with backoff and check VoiceStudio service health.
- Log the full body (not just 300 chars) in server logs if the truncated message is insufficient.
Example fix
// before
let text = resp.text().await.unwrap_or_default();
return Err(anyhow!("VoiceStudio HTTP {}: {}", status.as_u16(), text.chars().take(300).collect::<String>()));
// after
let status_code = status.as_u16();
let text = resp.text().await.unwrap_or_default();
if status_code == 401 || status_code == 403 {
anyhow::bail!("VoiceStudio authentication failed (HTTP {}): check your API key", status_code);
}
anyhow::bail!("VoiceStudio HTTP {}: {}", status_code, text.chars().take(300).collect::<String>()); Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check connectivity/auth cheaply before long synthesis jobs
let health = reqwest::get(format!("{}/health", base_url)).await;
if let Ok(r) = &health {
if !r.status().is_success() {
return Err(format!("VoiceStudio unavailable: HTTP {}", r.status()));
}
} Try / catch
match clone_speak(&opts).await {
Err(e) if e.to_string().contains("VoiceStudio HTTP") => {
let msg = e.to_string();
if msg.contains("401") || msg.contains("403") {
// refresh credentials and retry once
} else if msg.contains("429") || msg.contains("5") {
// backoff and retry
} else {
// surface msg (status + body) to the user
}
}
Err(e) => eprintln!("unexpected: {e}"),
Ok(path) => println!("saved to {}", path.display()),
} Prevention
- Validate the API key and base URL with a cheap health/auth call before long jobs.
- Check audio file size/format limits client-side before upload.
- Implement retry with exponential backoff for 429/5xx responses.
- Verify profile ids against the profiles list API before use.
When it happens
Trigger: Any HTTP call made by clone_speak, design_speak, or isolate whose response status is not 2xx: invalid API key, expired token, oversized or malformed audio, unknown profile_id, or server-side 5xx during synthesis/isolation.
Common situations: Expired or wrong VoiceStudio credentials, uploading a voice sample that exceeds server limits, referencing a deleted voice profile, or the VoiceStudio service being overloaded and returning 500/503.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/0b53fdd57fa3c454.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/omniget-core/src/core/tools/voicestudio.rs:194
/// Abre o app do VoiceStudio (que sobe o backend).
pub async fn launch() -> anyhow::Result<()> {
let app = find_app().ok_or_else(|| anyhow!("VoiceStudio nao encontrado"))?;
if cfg!(target_os = "macos") {
crate::core::process::command("open")
.arg(&app)
.output()
.await?;
} else {
crate::core::process::command(&app).spawn()?;
}
Ok(())
}
async fn wav_from(resp: reqwest::Response, output: &Path) -> anyhow::Result<PathBuf> {
let status = resp.status();
if !status.is_success() {
let text = resp.text().await.unwrap_or_default();
return Err(anyhow!(
"VoiceStudio HTTP {}: {}",
status.as_u16(),
text.chars().take(300).collect::<String>()
));
}
let bytes = resp.bytes().await?;
if bytes.len() < 100 {
return Err(anyhow!("resposta vazia"));
}
if let Some(parent) = output.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(output, &bytes)?;
Ok(output.to_path_buf())
}
fn stamp() -> String {
chrono::Local::now().format("%Y%m%d-%H%M%S").to_string()View on GitHub (pinned to 8600b91f42)