From 9c3faf6ee5c3f62f7b0e673855854009837678b7 Mon Sep 17 00:00:00 2001 From: houssemko Date: Fri, 18 Sep 2026 09:20:01 +0100 Subject: [PATCH 1/2] Drop StreamSel headers, share yt-dlp identity argv --- src/video.rs | 64 +++++++++++++++++++++++++++------------------- src/video_tests.rs | 23 +++++++++++++++++ 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/src/video.rs b/src/video.rs index ac493a16..b142ee40 100644 --- a/src/video.rs +++ b/src/video.rs @@ -916,6 +916,31 @@ pub(crate) fn cookies_browser_spec(value: &str) -> Option { Some(value.to_string()) } +/// Shared trailing argv for every yt-dlp spawn: browser cookies, user +/// agent, then the page URL behind `--`. One helper so identity flags +/// can never drift between extraction, parts, HLS and live-resolve +/// spawns (or let a hostile URL parse as a flag). `None` user agent +/// keeps today's extraction behavior (yt-dlp default UA there). +pub(crate) fn ytdlp_identity_args( + cookies_browser: &str, + user_agent: Option<&str>, + page_url: &str, +) -> Vec { + let mut args = Vec::new(); + if let Some(spec) = cookies_browser_spec(cookies_browser) { + args.push(format!("--cookies-from-browser={spec}")); + } + if let Some(ua) = user_agent.map(str::trim).filter(|s| !s.is_empty()) { + args.push("--user-agent".to_string()); + args.push(ua.to_string()); + } + // `--` before the page URL: option parsing ends here, so a hostile + // or malformed URL can never be read as a flag. + args.push("--".to_string()); + args.push(page_url.to_string()); + args +} + /// Shared root for extraction scratch space. pub fn staging_root() -> PathBuf { std::env::temp_dir().join("grab-video") @@ -1054,13 +1079,7 @@ async fn fetch_video_page( "--no-progress".to_string(), "--dump-single-json".to_string(), ]; - if let Some(spec) = cookies_browser_spec(cookies_browser) { - args.push(format!("--cookies-from-browser={spec}")); - } - // `--` before the page URL: option parsing ends here, so a hostile - // or malformed URL can never be read as a flag. - args.push("--".to_string()); - args.push(url.to_string()); + args.extend(ytdlp_identity_args(cookies_browser, None, url)); // Spawned directly (tokio + timeout) rather than through the // crate's executor: same semantics — concurrent pipe drain, // timeout kill, nonzero exit as error — with the failure detail @@ -2018,7 +2037,6 @@ struct StreamSel { format_id: String, ext: String, url: String, - headers: HttpHeaders, size: Option, /// Whether the stream carries an audio track (muxed files do). has_audio: bool, @@ -2045,7 +2063,6 @@ impl StreamSel { .url .clone() .ok_or_else(VideoError::unavailable)?, - headers: f.download_info.http_headers.clone(), size: filesize_of(f), has_audio: f .codec_info @@ -2881,15 +2898,11 @@ fn part_download_argv(job: &VideoJob, spec: &str, out: &Path) -> Vec { "--retries".to_string(), job.tries.max(1).to_string(), ]; - if let Some(spec) = cookies_browser_spec(&job.cookies_browser) { - args.push(format!("--cookies-from-browser={spec}")); - } - if !job.user_agent.is_empty() { - args.push("--user-agent".to_string()); - args.push(job.user_agent.clone()); - } - args.push("--".to_string()); - args.push(job.page_url.clone()); + args.extend(ytdlp_identity_args( + &job.cookies_browser, + Some(job.user_agent.as_str()), + &job.page_url, + )); args } @@ -3401,16 +3414,13 @@ async fn run_hls_ytdlp( } else { cmd.arg("--merge-output-format").arg("mp4"); } - if let Some(spec) = cookies_browser_spec(&job.cookies_browser) { - cmd.arg(format!("--cookies-from-browser={spec}")); - } - if !job.user_agent.is_empty() { - cmd.arg("--user-agent").arg(&job.user_agent); + for arg in ytdlp_identity_args( + &job.cookies_browser, + Some(job.user_agent.as_str()), + &job.page_url, + ) { + cmd.arg(arg); } - // `--` before the page URL: option parsing ends here, so a hostile - // or malformed URL can never be read as a flag. - cmd.arg("--"); - cmd.arg(&job.page_url); cmd.stdin(std::process::Stdio::null()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()); diff --git a/src/video_tests.rs b/src/video_tests.rs index ad39b745..47acabe9 100644 --- a/src/video_tests.rs +++ b/src/video_tests.rs @@ -2395,3 +2395,26 @@ fn part_binary_retries_stale_id_with_fallback_spec() { assert!(attempts[1].contains("-f ba/b"), "{logged}"); let _ = std::fs::remove_dir_all(&dir); } + +// ── shared yt-dlp identity argv ────────────────────────────────────── + +#[test] +fn identity_args_order_and_trim() { + // Cookies, trimmed UA, then `--` + page: identical for every spawn. + let argv = ytdlp_identity_args("none", Some(" Grab/1 "), "https://x.com/u/status/1"); + assert_eq!( + argv, + vec![ + "--user-agent".to_string(), + "Grab/1".to_string(), + "--".to_string(), + "https://x.com/u/status/1".to_string(), + ] + ); + // Blank UA is omitted, never sent empty. + let argv = ytdlp_identity_args("none", None, "https://x.com/u/status/1"); + assert_eq!( + argv, + vec!["--".to_string(), "https://x.com/u/status/1".to_string()] + ); +} From ae52380c4bb3c4e1c1434d9546764cc4dc58a935 Mon Sep 17 00:00:00 2001 From: houssemko Date: Fri, 18 Sep 2026 09:32:13 +0100 Subject: [PATCH 2/2] Repair web-merge damage on tidy-argv, adopt identity helper in resolve --- src/video.rs | 10 +--------- src/video_tests.rs | 2 ++ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/video.rs b/src/video.rs index fe0342df..443b773d 100644 --- a/src/video.rs +++ b/src/video.rs @@ -1906,15 +1906,7 @@ async fn resolve_hls_input_ytdlp( "--no-progress".to_string(), "--dump-single-json".to_string(), ]; - if let Some(spec) = cookies_browser_spec(cookies_browser) { - args.push(format!("--cookies-from-browser={spec}")); - } - if !user_agent.trim().is_empty() { - args.push("--user-agent".to_string()); - args.push(user_agent.trim().to_string()); - } - args.push("--".to_string()); - args.push(url.to_string()); + args.extend(ytdlp_identity_args(cookies_browser, Some(user_agent), url)); let mut cmd = tokio::process::Command::new(youtube_bin); cmd.args(&args); cmd.stdin(std::process::Stdio::null()) diff --git a/src/video_tests.rs b/src/video_tests.rs index 83a91f13..992515cd 100644 --- a/src/video_tests.rs +++ b/src/video_tests.rs @@ -2426,6 +2426,8 @@ fn identity_args_order_and_trim() { argv, vec!["--".to_string(), "https://x.com/u/status/1".to_string()] ); +} + // ── live resolution via yt-dlp dump ────────────────────────────────── /// Canned `--dump-single-json` over an HLS master: two variants, a