tonhowtf/omniget · error · anyhow::Error

Parse media playlist

Error message

Parse media playlist: {:?}

What it means

After downloading the media playlist text, download_media_playlist parses it with parse_media_playlist. If the bytes are not a valid HLS media playlist, the parse error is wrapped with this message. It usually means the served content is not the expected m3u8 (e.g., an HTML error page or a master playlist where a media playlist was required).

Solutions

  1. Log the first ~200 bytes of `text` — if it's HTML, the URL/token is wrong or expired; re-fetch a fresh URL.
  2. If the content is a master playlist (#EXT-X-STREAM-INF), select a variant and re-run with that media playlist URL.
  3. Check Content-Encoding handling; ensure the client decompresses gzip responses.
  4. Retry with a fresh playlist after a short delay; live playlists can be temporarily inconsistent.

Example fix

// before
let (_, playlist) = parse_media_playlist(text.as_bytes())
    .map_err(|e| anyhow::anyhow!("Parse media playlist: {:?}", e))?;
// after
if text.trim_start().starts_with("<") {
    anyhow::bail!("playlist URL returned HTML (token expired or blocked)");
}
let (_, playlist) = parse_media_playlist(text.as_bytes())
    .map_err(|e| anyhow::anyhow!("Parse media playlist: {e:?}; head: {:.120}", text))?;
Defensive patterns

Strategy: validation

Validate before calling

fn looks_like_media_playlist(text: &str) -> bool {
    let t = text.trim_start();
    t.starts_with("#EXTM3U") && !t.contains("#EXT-X-STREAM-INF")
}
// call before parsing:
if !looks_like_media_playlist(&text) {
    anyhow::bail!("not a media playlist (HTML or master playlist?), head: {:.120}", text);
}

Try / catch

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

Prevention

When it happens

Trigger: URL returns HTML (error/login page), returns a master playlist to a media-playlist parser, or the m3u8 content is truncated/malformed or gzip-encoded but not decoded.

Common situations: Tokenized URL expired and server returned an HTML 200 page; wrong playlist level passed (EXT-X-STREAM-INF vs segments); CDN returning an interstitial; mid-stream playlist variant changes.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/23f8a065ab468edb. Report an issue: GitHub.

Appendix: source

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

                );
                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();
            p.push(".part");
            PathBuf::from(p)
        };
        if let Some(parent) = output.parent() {
            std::fs::create_dir_all(parent)?;
        }

        let (seg_tx, seg_rx) = mpsc::channel::<(usize, Vec<u8>)>(max_concurrent as usize);

View on GitHub (pinned to 8600b91f42)