{"record":{"id":"6ef694e62d882532","repo":"tonhowtf/omniget","slug":"no-url-available-douyin","errorCode":null,"errorMessage":"No URL available","messagePattern":"No URL available","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/platforms/douyin.rs","lineNumber":264,"sourceCode":"        opts: &DownloadOptions,\n        progress: mpsc::Sender<ProgressUpdate>,\n    ) -> anyhow::Result<DownloadResult> {\n        let _ = progress.send(ProgressUpdate::percent(0.0)).await;\n\n        let ytdlp_path = match &opts.ytdlp_path {\n            Some(p) => p.clone(),\n            None => ytdlp::find_ytdlp_cached()\n                .await\n                .ok_or_else(|| anyhow!(\"yt-dlp not found\"))?,\n        };\n\n        let canonical = info\n            .available_qualities\n            .first()\n            .map(|q| q.url.as_str())\n            .unwrap_or(\"\");\n        if canonical.is_empty() {\n            return Err(anyhow!(\"No URL available\"));\n        }\n        let canonical = Self::resolve_url(canonical).await;\n\n        let quality_height = opts\n            .quality\n            .as_ref()\n            .and_then(|q| q.trim_end_matches('p').parse::<u32>().ok());\n\n        let extra = Self::extra_flags();\n\n        ytdlp::download_video(\n            &ytdlp_path,\n            &canonical,\n            &opts.output_dir,\n            quality_height,\n            progress,\n            opts.download_mode.as_deref(),\n            opts.format_id.as_deref(),","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/platforms/douyin.rs#L246-L282","documentation":"After get_media_info succeeds, download picks the first entry of info.available_qualities as the canonical media URL. If that list is empty or the first entry has an empty url string, it fails with 'No URL available' because there is nothing to download.","triggerScenarios":"Calling download(opts) with a MediaInfo whose available_qualities is empty, or whose first quality entry has url == \"\".","commonSituations":"Douyin pages where extraction returned metadata but no direct media URL (region-locked or removed posts); a partially populated MediaInfo constructed by the caller instead of returned by get_media_info; upstream HTML/API layout changes that break URL extraction in the parser.","solutions":["Re-run get_media_info and check available_qualities is non-empty before calling download.","Log/inspect the raw yt-dlp JSON (ytdlp::get_video_info result) to see whether the source actually exposes a media URL; if not, the post is likely private, deleted, or geo-blocked.","Update yt-dlp to the latest version, since extractor fixes often restore missing URL extraction.","Guard the caller: validate !info.available_qualities.is_empty() and first.url is a non-empty http(s) URL before download."],"exampleFix":"// before\nplatform.download(opts).await?;\n// after\nanyhow::ensure!(!info.available_qualities.is_empty(), \"no downloadable URL for {url}\");\nanyhow::ensure!(info.available_qualities[0].url.starts_with(\"http\"), \"quality entry has no URL\");\nplatform.download(opts).await?;","handlingStrategy":"validation","validationCode":"fn has_downloadable_url(info: &MediaInfo) -> bool {\n    info.available_qualities\n        .first()\n        .map(|q| q.url.starts_with(\"http\"))\n        .unwrap_or(false)\n}\n// usage: anyhow::ensure!(has_downloadable_url(&info), \"media info has no URL\");","typeGuard":"fn first_quality(info: &MediaInfo) -> Option<&Quality> {\n    info.available_qualities\n        .first()\n        .filter(|q| !q.url.is_empty() && q.url.starts_with(\"http\"))\n}","tryCatchPattern":"match platform.download(opts).await {\n    Err(e) if e.to_string().contains(\"No URL available\") => {\n        let info = platform.get_media_info(url).await?; // re-extract\n        if info.available_qualities.is_empty() {\n            Err(anyhow!(\"post has no downloadable media (removed/private/geo-blocked?)\"))\n        } else {\n            platform.download(opts).await\n        }\n    }\n    other => other,\n}","preventionTips":["Always call get_media_info immediately before download and inspect available_qualities.","Never hand-construct MediaInfo with placeholder URLs.","Keep yt-dlp updated so URL extraction keeps working against Douyin page changes."],"tags":["empty-list","media-extraction","rust","douyin"],"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"}