{"record":{"id":"3baf877bf33629b9","repo":"tonhowtf/omniget","slug":"playlist-empty-or-unavailable-youtube","errorCode":null,"errorMessage":"Playlist empty or unavailable","messagePattern":"Playlist empty or unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/platforms/youtube.rs","lineNumber":87,"sourceCode":"                if key == \"v\" && !value.is_empty() {\n                    has_video = true;\n                }\n            }\n\n            return has_list && !has_video;\n        }\n        false\n    }\n\n    pub async fn fetch_with_ytdlp(\n        url: &str,\n        ytdlp_path: &std::path::Path,\n    ) -> anyhow::Result<MediaInfo> {\n        if Self::is_playlist_url(url) {\n            let (playlist_title, entries) = ytdlp::get_playlist_info(ytdlp_path, url, &[]).await?;\n\n            if entries.is_empty() {\n                return Err(anyhow!(\"Playlist empty or unavailable\"));\n            }\n\n            let qualities: Vec<MediaVideoQuality> = entries\n                .into_iter()\n                .enumerate()\n                .map(|(i, entry)| MediaVideoQuality {\n                    label: format!(\"{}. {}\", i + 1, entry.title),\n                    width: 0,\n                    height: 0,\n                    url: entry.url,\n                    format: \"ytdlp_playlist\".to_string(),\n                })\n                .collect();\n\n            return Ok(MediaInfo {\n                title: sanitize_filename::sanitize(&playlist_title),\n                author: playlist_title,\n                platform: \"youtube\".to_string(),","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/platforms/youtube.rs#L69-L105","documentation":"In YouTube's `fetch_with_ytdlp` (src-tauri/omniget-core/src/platforms/youtube.rs:87), when the URL is detected as a playlist via `is_playlist_url`, the code fetches entries with `ytdlp::get_playlist_info` and throws 'Playlist empty or unavailable' if the returned entry list is empty. It signals that although the URL looked like a playlist, yt-dlp produced no playlist entries — the playlist is private, deleted, region-blocked, or the fetch silently failed.","triggerScenarios":"Calling native_get_media_info (via fetch_with_ytdlp) with a playlist URL (e.g., youtube.com/playlist?list=...) where get_playlist_info returns zero entries: deleted playlist, private/unlisted playlist without credentials, yt-dlp parse failure swallowed into an empty list, or a `list=` parameter pointing at an inaccessible mix.","commonSituations":"Stale playlist URLs whose videos were all removed, private playlists of the requesting user without passing cookies to yt-dlp, region-restricted playlists, YouTube mixes/radio lists yt-dlp can't enumerate, or an outdated yt-dlp after a YouTube API/markup change.","solutions":["Update yt-dlp to the latest release — YouTube playlist extraction breaks frequently with old versions.","Open the playlist URL in a browser to confirm it exists, is public, and still contains videos.","Pass authentication cookies to yt-dlp if the playlist is private or age/region restricted.","Test manually with `yt-dlp --flat-playlist -J <url>` to see whether yt-dlp itself returns entries or an error.","Verify is_playlist_url isn't misclassifying a single-video URL with a stray `list=` parameter; strip the param or handle it as a single video."],"exampleFix":"// before: empty entries abort with a generic error\nlet (playlist_title, entries) = ytdlp::get_playlist_info(ytdlp_path, url, &[]).await?;\nif entries.is_empty() {\n    return Err(anyhow!(\"Playlist empty or unavailable\"));\n}\n\n// after: retry once with fresh cookies and single-video fallback\nlet (playlist_title, entries) = ytdlp::get_playlist_info(ytdlp_path, url, &[]).await?;\nlet entries = if entries.is_empty() {\n    tracing::warn!(\"[youtube] playlist empty; retrying with auth cookies\");\n    let (_t, retried) = ytdlp::get_playlist_info(ytdlp_path, url, &[\"--cookies-from-browser\", \"firefox\"]).await?;\n    if retried.is_empty() && !Self::is_single_video_url(url) {\n        return Err(anyhow!(\"Playlist empty or unavailable\"));\n    }\n    retried\n} else { entries };","handlingStrategy":"validation","validationCode":"// Validate the playlist is resolvable before calling the library\nlet probe = tokio::process::Command::new(\"yt-dlp\")\n    .args([\"--flat-playlist\", \"--print\", \"id\", \"--playlist-items\", \"1\", url])\n    .output().await?;\nif probe.stdout.is_empty() {\n    anyhow::bail!(\"playlist is empty, private, or deleted — check it in a browser first\");\n}","typeGuard":"fn is_playlist_id(url: &str) -> bool {\n    url.contains(\"playlist?list=\")\n        && url.split(\"list=\").nth(1)\n            .map(|id| !id.is_empty() && !id.starts_with(\"RD\")) // RD* are mixes yt-dlp may not enumerate\n            .unwrap_or(false)\n}","tryCatchPattern":"match youtube.get_media_info(playlist_url).await {\n    Ok(info) => use_media(info),\n    Err(e) if e.to_string().contains(\"Playlist empty or unavailable\") => {\n        eprintln!(\"Playlist is empty/private/deleted; verify URL or pass cookies to yt-dlp\");\n        // fall back to treating the URL as a single video if a v= param is present\n        if let Some(video_url) = extract_single_video_url(playlist_url) {\n            youtube.get_media_info(&video_url).await\n        } else {\n            Err(e)\n        }\n    }\n    Err(e) => report(e),\n}","preventionTips":["Confirm the playlist exists, is public, and contains videos in a browser before processing.","Pass authentication cookies to yt-dlp for private or restricted playlists.","Update yt-dlp regularly — YouTube playlist extraction changes frequently.","Strip or ignore the `list=` parameter when the URL also targets a single video (v=...).","Treat YouTube mixes (RD-prefixed list ids) as non-deterministic and avoid batch-fetching them."],"tags":["youtube","playlist","yt-dlp","empty-result"],"backgroundTag":"empty-result-set","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}