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

  1. 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).
  2. Verify the VoiceStudio API key/base URL configuration used to build the reqwest client.
  3. For 4xx, fix the request: confirm profile_id exists, audio format/size is accepted, and parameters are valid.
  4. For 5xx or 429, retry with backoff and check VoiceStudio service health.
  5. 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

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)