tonhowtf/omniget · error · anyhow::Error

HTTP fetching playlist

Error message

HTTP {} fetching playlist

What it means

When download_media_playlist fetches a media (non-master) playlist directly, it applies referer/User-Agent headers, sends the request, and checks the HTTP status. Any non-success status is turned into this bail, embedding the status code. The library does not retry non-success statuses here, so a failed playlist fetch aborts the media-playlist download path.

Solutions

  1. Check the status code in the error message: 404/410 means the playlist is gone — re-fetch a fresh playlist URL from the page or master playlist before retrying.
  2. For 403, supply the correct Referer, User-Agent, and/or cookies (import via the cookies mechanism) so the CDN accepts the request.
  3. For 429/5xx, retry with backoff after a delay, or re-run the download later once the server recovers.
  4. Verify the URL with curl -I using the same headers to confirm the failure is server-side versus a missing-header problem in your client configuration.

Example fix

// before: no referer/cookies on a protected stream
download_media_playlist(url, None, ...).await?; // HTTP 403 Forbidden fetching playlist
// after: provide referer and imported cookies
let referer = Some("https://example.com/watch/123".to_string());
// ensure cookies were imported via import_cookies_file beforehand
download_media_playlist(url, referer, ...).await?;
Defensive patterns

Strategy: retry

Validate before calling

let resp = reqwest::Client::new().get(url)
    .header("Referer", referer)
    .send().await?;
if !resp.status().is_success() {
    eprintln!("playlist endpoint returned {} — fix headers/URL first", resp.status());
}

Try / catch

// retry with backoff on transient statuses
for attempt in 0..3 {
    match download_media_playlist(url, referer.as_deref(), ...).await {
        Ok(r) => return Ok(r),
        Err(e) if is_retryable(&e.to_string()) => tokio::time::sleep(
            std::time::Duration::from_secs(2u64.pow(attempt))).await,
        Err(e) => return Err(e),
    }
}
fn is_retryable(msg: &str) -> bool {
    ["429", "500", "502", "503", "504"].iter().any(|s| msg.contains(s))
}

Prevention

When it happens

Trigger: download_with_quality resolves the input to a media playlist and calls download_media_playlist, whose GET via apply_referer_headers(self.client.get(m3u8_url)) returns a non-2xx status: 403 (blocked/expired link, missing referer/cookies), 404 (playlist removed or rotated), 401 (auth required), 5xx (origin/CDN failure), or 429 (rate limiting).

Common situations: HLS stream URLs expire quickly so a copied playlist link returns 403/404 moments later; origin requires a Referer or cookies the client didn't send; CDN under load returns 503; scraping too aggressively triggers 429.

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/c3fe767cf6f0f37e. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/hls_downloader.rs:270

        // The media playlist is fetched a second time here, independently of
        // `fetch_m3u8_with_retry`. Skipping this branch would throw the
        // prefetched text away and hit the network anyway.
        let text = match self.prefetched_for(m3u8_url) {
            Some(text) => {
                tracing::info!(
                    "[hls] using prefetched media playlist text ({} bytes)",
                    text.len()
                );
                text.to_string()
            }
            None => {
                let resp = apply_referer_headers(self.client.get(m3u8_url), referer)
                    .header("User-Agent", self.effective_user_agent())
                    .send()
                    .await?;

                if !resp.status().is_success() {
                    anyhow::bail!("HTTP {} fetching playlist", resp.status());
                }

                resp.text().await?
            }
        };

        let (_, playlist) = parse_media_playlist(text.as_bytes())
            .map_err(|e| anyhow::anyhow!("Parse media playlist: {:?}", e))?;

        let total_segments = playlist.segments.len();

        let encryption = self
            .fetch_encryption_info(&playlist, m3u8_url, referer)
            .await?;

        let output = PathBuf::from(output_path);
        let part_path = {
            let mut p = output.as_os_str().to_owned();

View on GitHub (pinned to 8600b91f42)