Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 35 additions & 5 deletions src/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,35 @@ pub fn quality_for_height(height: u32) -> &'static str {
.unwrap_or("1080p")
}

/// Best muxed (audio+video) file for a height cap: smallest height at
/// or above the cap, else the tallest available. Same semantics as
/// [`select_hls_format`]. Only directly fetchable files qualify, so a
/// DRM or link-less entry can never shadow a playable one — and the
/// quality cap survives: callers used to take the crate's
/// `best_audio_video_format`, which is first-in-extractor-order (lowest
/// first on x.com) regardless of the requested height.
fn select_muxed_format(formats: &[Format], want: Option<u32>) -> Option<StreamSel> {
let mut cands: Vec<(&Format, u32)> = formats
.iter()
.filter(|f| f.format_type().is_audio_and_video())
.filter_map(|f| {
let h = f.video_resolution.height.filter(|&h| h > 0)?;
StreamSel::from_format(f).ok().map(|_| (f, h))
})
.collect();
cands.sort_by_key(|(_, h)| *h);
match want {
Some(cap) => cands
.iter()
.find(|(_, h)| *h >= cap)
.or_else(|| cands.last())
.and_then(|(f, _)| StreamSel::from_format(f).ok()),
None => cands
.last()
.and_then(|(f, _)| StreamSel::from_format(f).ok()),
}
}

/// Stored quality value to a height cap: `None` (Best) takes the
/// tallest variant available.
fn quality_height(value: &str) -> Option<u32> {
Expand Down Expand Up @@ -1789,7 +1818,8 @@ struct StreamPlan {
/// dropped and then shadowed by the muxed adoption below (x.com
/// VODs: direct mp4s are muxed, so the combo lists HLS only).
/// 2. Direct splits: pinned HTTPS id, else the quality preset.
/// 3. Single-part adoption for a still-missing side: muxed files, then
/// 3. Single-part adoption for a still-missing side: muxed files (at
/// the requested height, not first-in-extractor-order), then
/// Unclassified video containers (TikTok-style sparse extractors).
/// Gated on the missing side — not on audio absence — because those
/// pages also list a separate audio track, which used to skip both
Expand Down Expand Up @@ -1858,10 +1888,10 @@ fn plan_streams(
&& (!audio_only_request || audio_sel.is_none())
{
let muxed = video_sel.take_if(|v| v.has_audio).or_else(|| {
video
.best_audio_video_format()
.ok()
.and_then(|m| StreamSel::from_format(m).ok())
// Height-aware: a bare "first muxed file" pick is lowest
// first on extractors that list ascending (x.com), ignoring
// the requested quality entirely.
select_muxed_format(&video.formats, quality_height(quality))
});
if let Some(m) = muxed {
audio_sel = Some(m);
Expand Down
60 changes: 56 additions & 4 deletions src/video_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1848,21 +1848,73 @@ fn plan_pinned_hls_wins_over_muxed_adoption() {
#[test]
fn plan_muxed_only_still_adopts_without_pin() {
// No pin, no splits: the muxed file adopts as before (precedence
// over the HLS preset is unchanged).
// over the HLS preset is unchanged) — but at the requested height,
// not first-in-extractor-order.
let video = x_like_video();
let plan = plan_streams(&video, "1080p", false, None, true, 1);
assert!(plan.video_sel.is_none());
assert_eq!(plan.audio_sel.expect("adopted").format_id, "http-320");
assert_eq!(plan.audio_sel.expect("adopted").format_id, "http-720");
assert!(plan.audio_only);
assert!(plan.hls_sel.is_none());
}

#[test]
fn plan_muxed_adoption_honors_height_cap() {
// First-in-order used to win regardless of quality (x.com lists
// ascending, so Best match downloaded the lowest). Now the cap
// picks smallest-at-or-above, tallest when nothing qualifies.
let video = test_video(serde_json::json!([
test_format_full(
"m320",
"avc1.64001f",
"mp4a.40.2",
Some(320),
None,
"https",
false
),
test_format_full(
"m720",
"avc1.64001f",
"mp4a.40.2",
Some(720),
None,
"https",
false
),
test_format_full(
"m1080",
"avc1.64001f",
"mp4a.40.2",
Some(1080),
None,
"https",
false
),
]));
for (quality, want) in [
("best", "m1080"),
("1080p", "m1080"),
("720p", "m720"),
("480p", "m720"),
] {
let plan = plan_streams(&video, quality, false, None, true, 1);
assert_eq!(
plan.audio_sel.expect("adopted").format_id,
want,
"quality {quality}"
);
assert!(plan.audio_only, "quality {quality}");
}
}

#[test]
fn plan_stale_hls_pin_degrades_to_muxed_adoption() {
// A vanished HLS pin behaves like no pin: preset, then adoption.
// A vanished HLS pin behaves like no pin: preset, then adoption
// (at the dialog-picked height, not the lowest listing).
let video = x_like_video();
let plan = plan_streams(&video, "1080p", false, Some("gone"), true, 1);
assert_eq!(plan.audio_sel.expect("adopted").format_id, "http-320");
assert_eq!(plan.audio_sel.expect("adopted").format_id, "http-720");
assert!(plan.audio_only);
assert!(plan.hls_sel.is_none());
}
Expand Down
Loading