Cleanup: drop playlist + suggestion + dead client constants + suppress_unused stubs

Round-2 cruft audit punch list — mechanical deletes, no behavior change.

Whole modules deleted (no wrapper consumer):
  * youtube/playlist_extractor.rs (297 LOC) — full playlist extraction
  * youtube/linkhandler/playlist.rs (81 LOC) — playlist URL parser
  * youtube/suggestion_extractor.rs (91 LOC) — search-as-you-type
  * tests/stream_phase4_offline.rs (186 LOC) — tautological test

Dead pub fns + enum variants + constants:
  * WEB_REMIX_* constants (3) + WEB_MUSIC_ANALYTICS_* constants (3)
  * InnertubeClientRequestInfo::of_web_music_analytics_charts_client
    factory + its charts_client_omits_platform_and_screen test
  * SearchFilter::Music{Songs,Videos,Albums,Playlists,Artists} variants
    (5 of 9 cases) + uses_music_endpoint helper + the search_extractor
    'music search not implemented' reject branch
  * Two #[allow(dead_code)] _suppress_unused stub fns and the imports
    they were keeping alive (std::sync::Arc in js/extractor.rs,
    NetworkError in stream_extractor.rs)

Renamed:
  * search_extractor::test_helpers -> renderer_helpers. Mis-named:
    it's production code called from channel.rs, not a test fixture.

potoken/ kept and documented as the designed Phase-5 extension point
for YouTube bot-detection — wrapper's Android side hasn't registered
a real provider yet, but the trait + global slot stay so when YT
forces po_token universally the integration is one Kotlin patch away,
not a Rust-side rewrite.

~580 LOC removed from production. Wrapper does not need to change.
This commit is contained in:
Sulkta 2026-05-26 22:16:11 -07:00
parent 0c75d64707
commit 12b8865929
13 changed files with 27 additions and 760 deletions

View file

@ -1,186 +0,0 @@
// Phase 4 offline tests for the stream-extraction parsing layer.
//
// Live YT extraction is gated behind the `online-tests` feature; these
// tests exercise the JSON-walking and URL post-processing using a
// hand-crafted player-response shaped like what YT actually returns
// (videoDetails + streamingData.formats[] + streamingData.adaptiveFormats[]
// + dashManifestUrl + captions). No network.
use serde_json::json;
use strawcore::stream::DeliveryMethod;
use strawcore::youtube::itag::MediaFormat;
use strawcore::youtube::stream_extractor;
fn synthetic_android_response(video_id: &str) -> serde_json::Value {
json!({
"playabilityStatus": { "status": "OK" },
"videoDetails": {
"videoId": video_id,
"title": "NCS Spektrem — Shine",
"shortDescription": "Royalty-free music for streamers.",
"lengthSeconds": "240",
"viewCount": "42000000",
"author": "NoCopyrightSounds",
"channelId": "UC_aEa8K-EOJ3D6gOs7HcyNg",
"isLive": false,
"thumbnail": {
"thumbnails": [
{"url": "https://i.ytimg.com/vi/x/default.jpg", "width": 120, "height": 90},
{"url": "https://i.ytimg.com/vi/x/maxresdefault.jpg", "width": 1920, "height": 1080}
]
}
},
"captions": {
"playerCaptionsTracklistRenderer": {
"captionTracks": [
{
"baseUrl": "https://www.youtube.com/api/timedtext?lang=en&v=x",
"languageCode": "en",
"name": {"simpleText": "English"},
"kind": "asr"
},
{
"baseUrl": "https://www.youtube.com/api/timedtext?lang=de&v=x",
"languageCode": "de",
"name": {"simpleText": "Deutsch"}
}
]
}
},
"streamingData": {
"dashManifestUrl": "https://manifest.googlevideo.com/api/manifest/dash/foo/yes",
"formats": [
{
"itag": 22,
"url": "https://r1.googlevideo.com/videoplayback?expire=1&itag=22&c=ANDROID&n=ENCODEDNTOKEN",
"mimeType": "video/mp4; codecs=\"avc1.64001F, mp4a.40.2\"",
"bitrate": 1234567,
"width": 1280,
"height": 720,
"fps": 30,
"contentLength": "12345678"
}
],
"adaptiveFormats": [
{
"itag": 140,
"url": "https://r1.googlevideo.com/videoplayback?expire=1&itag=140&c=ANDROID&n=AUDIONTOKEN",
"mimeType": "audio/mp4; codecs=\"mp4a.40.2\"",
"averageBitrate": 128000,
"contentLength": "4321000",
"audioTrack": {
"id": "en.4",
"displayName": "English original",
"audioIsDefault": true
}
},
{
"itag": 251,
"url": "https://r2.googlevideo.com/videoplayback?expire=1&itag=251&c=ANDROID&n=OPUSNTOKEN",
"mimeType": "audio/webm; codecs=\"opus\"",
"averageBitrate": 160000,
"contentLength": "5555555"
},
{
"itag": 137,
"url": "https://r3.googlevideo.com/videoplayback?expire=1&itag=137&c=ANDROID&n=VIDEONTOKEN",
"mimeType": "video/mp4; codecs=\"avc1.640028\"",
"bitrate": 2500000,
"width": 1920,
"height": 1080,
"fps": 30,
"contentLength": "98765432"
},
{
"itag": 999999,
"url": "https://x/?itag=999999",
"mimeType": "video/webm"
}
]
}
})
}
// Reaching the parsing fns requires a NewPipe::downloader configured,
// because the orchestrator's first step is the live Android POST. We
// don't want to hit the network in these tests, so the public
// stream_info entry point doesn't run here. Instead we test the
// behaviour-significant parsing helpers directly via the public test
// surface that exposes them. Since those are currently private, we cover
// the parsing layer through observable outputs by stitching a minimal
// "post-android-call" mock path.
//
// We get there by checking that the synthetic response JSON shape is
// what the orchestrator would see, and we verify the orchestrator's
// individual helpers against it via the public `stream_extractor` module
// — for the helpers that need NewPipe-init the smoke is implicitly
// covered by Phase 1 + Phase 2 tests already.
//
// Concretely below: lightweight JSON-shape assertions that mirror what
// populate_video_details / populate_streams would extract. If we change
// the JSON wire-shape contract this catches it.
#[test]
fn synthetic_response_has_expected_video_details_shape() {
let r = synthetic_android_response("n4tK7LYFxI0");
assert_eq!(r["videoDetails"]["videoId"], "n4tK7LYFxI0");
assert_eq!(r["videoDetails"]["title"], "NCS Spektrem — Shine");
assert_eq!(r["videoDetails"]["lengthSeconds"], "240");
}
#[test]
fn synthetic_response_has_dash_manifest_url() {
let r = synthetic_android_response("n4tK7LYFxI0");
let url = r["streamingData"]["dashManifestUrl"].as_str().unwrap();
assert!(url.starts_with("https://manifest.googlevideo.com"));
}
#[test]
fn synthetic_response_has_progressive_and_adaptive_formats() {
let r = synthetic_android_response("n4tK7LYFxI0");
let progressive = r["streamingData"]["formats"].as_array().unwrap();
assert_eq!(progressive.len(), 1);
assert_eq!(progressive[0]["itag"], 22);
let adaptive = r["streamingData"]["adaptiveFormats"].as_array().unwrap();
let itags: Vec<u64> = adaptive
.iter()
.map(|f| f["itag"].as_u64().unwrap())
.collect();
assert!(itags.contains(&140));
assert!(itags.contains(&251));
assert!(itags.contains(&137));
}
#[test]
fn options_default_disables_ios() {
let opts = stream_extractor::ExtractOptions::default();
assert!(!opts.fetch_ios_client);
assert!(opts.android_streaming_pot.is_none());
}
#[test]
fn known_itags_lookup_ok() {
use strawcore::youtube::itag::lookup;
assert!(lookup(22).is_some()); // progressive 720p mp4
assert!(lookup(140).is_some()); // m4a 128
assert!(lookup(251).is_some()); // opus 160
assert!(lookup(137).is_some()); // 1080p video-only mp4
assert!(lookup(999999).is_none()); // unknown
}
#[test]
fn known_itag_140_is_aac_128() {
use strawcore::youtube::itag::{lookup, ItagType};
let it = lookup(140).unwrap();
assert_eq!(it.item_type, ItagType::Audio);
assert_eq!(it.format, MediaFormat::M4A);
assert_eq!(it.avg_bitrate_kbps, Some(128));
}
#[test]
fn delivery_method_progressive_vs_dash() {
// Sanity that the enum is what the consumer expects to discriminate
// (StraawApp's Media3 routing logic depends on this).
assert_ne!(DeliveryMethod::Progressive, DeliveryMethod::Dash);
}