tonhowtf/omniget · warning

Unsupported media type

Error message

Unsupported media type

What it means

Thrown in BlueskyDownloader::native_get_media_info when extract_media(embed) returns None. The post's embed exists but its `$type` is not one of the supported embed views (video, images, external Tenor GIF, recordWithMedia), or required fields (playlist, images.fullsize, external.uri) are missing/empty. It means the native parser cannot handle this post's media shape.

Solutions

  1. Rely on the yt-dlp fallback: get_media_info already falls back to yt-dlp when native parsing fails, so ensure yt-dlp is installed/up to date (crate::core::ytdlp::ensure_ytdlp).
  2. Verify the post actually contains downloadable media (video, images, or Tenor GIF) in a browser before treating it as a bug.
  3. Update the library/embed match arms in extract_media to handle the new $type.
  4. Surface a user-facing 'this post has no supported media' message instead of retrying.

Example fix

// before
let media = extract_media(embed).ok_or_else(|| anyhow!("Unsupported media type"))?;
// after
let Some(media) = extract_media(embed) else {
    tracing::debug!(embed = ?embed, "unhandled bluesky embed; falling back");
    return Err(anyhow!("Unsupported media type")); // get_media_info will try yt-dlp
};
Defensive patterns

Strategy: fallback

Validate before calling

let embed = json.pointer("/thread/post/embed");
let supported = embed
    .and_then(|e| e.get("$type"))
    .and_then(|t| t.as_str())
    .map(|t| matches!(t, "app.bsky.embed.video#view" | "app.bsky.embed.images#view" | "app.bsky.embed.external#view" | "app.bsky.embed.recordWithMedia#view"))
    .unwrap_or(false);

Type guard

fn has_supported_embed(json: &serde_json::Value) -> bool {
    json.pointer("/thread/post/embed/$type")
        .and_then(|t| t.as_str())
        .map(|t| t.ends_with("video#view") || t.ends_with("images#view") || t.ends_with("external#view") || t.ends_with("recordWithMedia#view"))
        .unwrap_or(false)
}

Try / catch

match downloader.get_media_info(url).await {
    Ok(info) => download(info),
    Err(e) if e.to_string().contains("Unsupported media type") => {
        // post has no supported embed; inform user, don't retry
    }
    Err(e) => report(e),
}

Prevention

When it happens

Trigger: Calling get_media_info on a bsky.app post whose embed is e.g. app.bsky.embed.record#view (quote post without media), an unknown/new embed $type, an images embed whose fullsize URLs are absent, or an external embed pointing to a non-Tenor URI.

Common situations: Users paste links to quote-only posts, posts whose external link card is not Tenor, or new embed types Bluesky introduces that the extractor doesn't know; also posts where the CDN returns embed objects with unexpected fields.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at src-tauri/omniget-core/src/platforms/bluesky.rs:41

impl BlueskyDownloader {
    async fn fallback_ytdlp(&self, url: &str) -> anyhow::Result<MediaInfo> {
        let ytdlp_path = crate::core::ytdlp::ensure_ytdlp().await?;
        let json = crate::core::ytdlp::get_video_info(&ytdlp_path, url, &[]).await?;
        crate::platforms::generic_ytdlp::GenericYtdlpDownloader::parse_video_info(&json)
    }

    async fn native_get_media_info(&self, url: &str) -> anyhow::Result<MediaInfo> {
        let (user, post_id) = Self::extract_user_and_post(url)
            .ok_or_else(|| anyhow!("Could not extract user and post_id from URL"))?;

        let json = self.fetch_post(&user, &post_id).await?;

        let embed = json
            .pointer("/thread/post/embed")
            .ok_or_else(|| anyhow!("Post does not contain media"))?;

        let media = extract_media(embed).ok_or_else(|| anyhow!("Unsupported media type"))?;

        let filename_base = format!("bluesky_{}_{}", sanitize_filename::sanitize(&user), post_id);

        match media {
            BlueskyMedia::Video { hls_url } => Ok(MediaInfo {
                title: filename_base,
                author: user,
                platform: "bluesky".to_string(),
                duration_seconds: None,
                thumbnail_url: None,
                available_qualities: vec![VideoQuality {
                    label: "best".to_string(),
                    width: 0,
                    height: 0,
                    url: hls_url,
                    format: "hls".to_string(),
                }],
                media_type: MediaType::Video,

View on GitHub (pinned to 8600b91f42)