feat: add search filter, refactor params

This commit is contained in:
ThetaDev 2022-10-10 21:08:21 +02:00
parent 44e14a4ae0
commit 25de7d678a
24 changed files with 374 additions and 72 deletions

855
src/param/locale.rs Normal file
View file

@ -0,0 +1,855 @@
// This file is automatically generated. DO NOT EDIT.
//! Languages and countries
use std::{fmt::Display, str::FromStr};
use serde::{Deserialize, Serialize};
/// Available languages
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "lowercase")]
pub enum Language {
/// Afrikaans
Af,
/// አማርኛ
Am,
/// العربية
Ar,
/// অসমীয়া
As,
/// Azərbaycan
Az,
/// Беларуская
Be,
/// Български
Bg,
/// বাংলা
Bn,
/// Bosanski
Bs,
/// Català
Ca,
/// Čeština
Cs,
/// Dansk
Da,
/// Deutsch
De,
/// Ελληνικά
El,
/// English (US)
En,
/// English (UK)
#[serde(rename = "en-GB")]
EnGb,
/// English (India)
#[serde(rename = "en-IN")]
EnIn,
/// Español (España)
Es,
/// Español (Latinoamérica)
#[serde(rename = "es-419")]
Es419,
/// Español (US)
#[serde(rename = "es-US")]
EsUs,
/// Eesti
Et,
/// Euskara
Eu,
/// فارسی
Fa,
/// Suomi
Fi,
/// Filipino
Fil,
/// Français
Fr,
/// Français (Canada)
#[serde(rename = "fr-CA")]
FrCa,
/// Galego
Gl,
/// ગુજરાતી
Gu,
/// हिन्दी
Hi,
/// Hrvatski
Hr,
/// Magyar
Hu,
/// Հայերեն
Hy,
/// Bahasa Indonesia
Id,
/// Íslenska
Is,
/// Italiano
It,
/// עברית
Iw,
/// 日本語
Ja,
/// ქართული
Ka,
/// Қазақ Тілі
Kk,
/// ខ្មែរ
Km,
/// ಕನ್ನಡ
Kn,
/// 한국어
Ko,
/// Кыргызча
Ky,
/// ລາວ
Lo,
/// Lietuvių
Lt,
/// Latviešu valoda
Lv,
/// Македонски
Mk,
/// മലയാളം
Ml,
/// Монгол
Mn,
/// मराठी
Mr,
/// Bahasa Malaysia
Ms,
/// ဗမာ
My,
/// नेपाली
Ne,
/// Nederlands
Nl,
/// Norsk
No,
/// ଓଡ଼ିଆ
Or,
/// ਪੰਜਾਬੀ
Pa,
/// Polski
Pl,
/// Português (Brasil)
Pt,
/// Português
#[serde(rename = "pt-PT")]
PtPt,
/// Română
Ro,
/// Русский
Ru,
/// සිංහල
Si,
/// Slovenčina
Sk,
/// Slovenščina
Sl,
/// Shqip
Sq,
/// Српски
Sr,
/// Srpski
#[serde(rename = "sr-Latn")]
SrLatn,
/// Svenska
Sv,
/// Kiswahili
Sw,
/// தமிழ்
Ta,
/// తెలుగు
Te,
/// ภาษาไทย
Th,
/// Türkçe
Tr,
/// Українська
Uk,
/// اردو
Ur,
/// Ozbek
Uz,
/// Tiếng Việt
Vi,
/// 中文 (简体)
#[serde(rename = "zh-CN")]
ZhCn,
/// 中文 (香港)
#[serde(rename = "zh-HK")]
ZhHk,
/// 中文 (繁體)
#[serde(rename = "zh-TW")]
ZhTw,
/// IsiZulu
Zu,
}
/// Available countries
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "UPPERCASE")]
pub enum Country {
/// United Arab Emirates
Ae,
/// Argentina
Ar,
/// Austria
At,
/// Australia
Au,
/// Azerbaijan
Az,
/// Bosnia and Herzegovina
Ba,
/// Bangladesh
Bd,
/// Belgium
Be,
/// Bulgaria
Bg,
/// Bahrain
Bh,
/// Bolivia
Bo,
/// Brazil
Br,
/// Belarus
By,
/// Canada
Ca,
/// Switzerland
Ch,
/// Chile
Cl,
/// Colombia
Co,
/// Costa Rica
Cr,
/// Cyprus
Cy,
/// Czechia
Cz,
/// Germany
De,
/// Denmark
Dk,
/// Dominican Republic
Do,
/// Algeria
Dz,
/// Ecuador
Ec,
/// Estonia
Ee,
/// Egypt
Eg,
/// Spain
Es,
/// Finland
Fi,
/// France
Fr,
/// United Kingdom
Gb,
/// Georgia
Ge,
/// Ghana
Gh,
/// Greece
Gr,
/// Guatemala
Gt,
/// Hong Kong
Hk,
/// Honduras
Hn,
/// Croatia
Hr,
/// Hungary
Hu,
/// Indonesia
Id,
/// Ireland
Ie,
/// Israel
Il,
/// India
In,
/// Iraq
Iq,
/// Iceland
Is,
/// Italy
It,
/// Jamaica
Jm,
/// Jordan
Jo,
/// Japan
Jp,
/// Kenya
Ke,
/// Cambodia
Kh,
/// South Korea
Kr,
/// Kuwait
Kw,
/// Kazakhstan
Kz,
/// Laos
La,
/// Lebanon
Lb,
/// Liechtenstein
Li,
/// Sri Lanka
Lk,
/// Lithuania
Lt,
/// Luxembourg
Lu,
/// Latvia
Lv,
/// Libya
Ly,
/// Morocco
Ma,
/// Montenegro
Me,
/// North Macedonia
Mk,
/// Malta
Mt,
/// Mexico
Mx,
/// Malaysia
My,
/// Nigeria
Ng,
/// Nicaragua
Ni,
/// Netherlands
Nl,
/// Norway
No,
/// Nepal
Np,
/// New Zealand
Nz,
/// Oman
Om,
/// Panama
Pa,
/// Peru
Pe,
/// Papua New Guinea
Pg,
/// Philippines
Ph,
/// Pakistan
Pk,
/// Poland
Pl,
/// Puerto Rico
Pr,
/// Portugal
Pt,
/// Paraguay
Py,
/// Qatar
Qa,
/// Romania
Ro,
/// Serbia
Rs,
/// Russia
Ru,
/// Saudi Arabia
Sa,
/// Sweden
Se,
/// Singapore
Sg,
/// Slovenia
Si,
/// Slovakia
Sk,
/// Senegal
Sn,
/// El Salvador
Sv,
/// Thailand
Th,
/// Tunisia
Tn,
/// Turkey
Tr,
/// Taiwan
Tw,
/// Tanzania
Tz,
/// Ukraine
Ua,
/// Uganda
Ug,
/// United States
Us,
/// Uruguay
Uy,
/// Venezuela
Ve,
/// Vietnam
Vn,
/// Yemen
Ye,
/// South Africa
Za,
/// Zimbabwe
Zw,
}
/// Array of all available languages
pub const LANGUAGES: [Language; 83] = [
Language::Af,
Language::Am,
Language::Ar,
Language::As,
Language::Az,
Language::Be,
Language::Bg,
Language::Bn,
Language::Bs,
Language::Ca,
Language::Cs,
Language::Da,
Language::De,
Language::El,
Language::En,
Language::EnGb,
Language::EnIn,
Language::Es,
Language::Es419,
Language::EsUs,
Language::Et,
Language::Eu,
Language::Fa,
Language::Fi,
Language::Fil,
Language::Fr,
Language::FrCa,
Language::Gl,
Language::Gu,
Language::Hi,
Language::Hr,
Language::Hu,
Language::Hy,
Language::Id,
Language::Is,
Language::It,
Language::Iw,
Language::Ja,
Language::Ka,
Language::Kk,
Language::Km,
Language::Kn,
Language::Ko,
Language::Ky,
Language::Lo,
Language::Lt,
Language::Lv,
Language::Mk,
Language::Ml,
Language::Mn,
Language::Mr,
Language::Ms,
Language::My,
Language::Ne,
Language::Nl,
Language::No,
Language::Or,
Language::Pa,
Language::Pl,
Language::Pt,
Language::PtPt,
Language::Ro,
Language::Ru,
Language::Si,
Language::Sk,
Language::Sl,
Language::Sq,
Language::Sr,
Language::SrLatn,
Language::Sv,
Language::Sw,
Language::Ta,
Language::Te,
Language::Th,
Language::Tr,
Language::Uk,
Language::Ur,
Language::Uz,
Language::Vi,
Language::ZhCn,
Language::ZhHk,
Language::ZhTw,
Language::Zu,
];
/// Array of all available countries
pub const COUNTRIES: [Country; 109] = [
Country::Ae,
Country::Ar,
Country::At,
Country::Au,
Country::Az,
Country::Ba,
Country::Bd,
Country::Be,
Country::Bg,
Country::Bh,
Country::Bo,
Country::Br,
Country::By,
Country::Ca,
Country::Ch,
Country::Cl,
Country::Co,
Country::Cr,
Country::Cy,
Country::Cz,
Country::De,
Country::Dk,
Country::Do,
Country::Dz,
Country::Ec,
Country::Ee,
Country::Eg,
Country::Es,
Country::Fi,
Country::Fr,
Country::Gb,
Country::Ge,
Country::Gh,
Country::Gr,
Country::Gt,
Country::Hk,
Country::Hn,
Country::Hr,
Country::Hu,
Country::Id,
Country::Ie,
Country::Il,
Country::In,
Country::Iq,
Country::Is,
Country::It,
Country::Jm,
Country::Jo,
Country::Jp,
Country::Ke,
Country::Kh,
Country::Kr,
Country::Kw,
Country::Kz,
Country::La,
Country::Lb,
Country::Li,
Country::Lk,
Country::Lt,
Country::Lu,
Country::Lv,
Country::Ly,
Country::Ma,
Country::Me,
Country::Mk,
Country::Mt,
Country::Mx,
Country::My,
Country::Ng,
Country::Ni,
Country::Nl,
Country::No,
Country::Np,
Country::Nz,
Country::Om,
Country::Pa,
Country::Pe,
Country::Pg,
Country::Ph,
Country::Pk,
Country::Pl,
Country::Pr,
Country::Pt,
Country::Py,
Country::Qa,
Country::Ro,
Country::Rs,
Country::Ru,
Country::Sa,
Country::Se,
Country::Sg,
Country::Si,
Country::Sk,
Country::Sn,
Country::Sv,
Country::Th,
Country::Tn,
Country::Tr,
Country::Tw,
Country::Tz,
Country::Ua,
Country::Ug,
Country::Us,
Country::Uy,
Country::Ve,
Country::Vn,
Country::Ye,
Country::Za,
Country::Zw,
];
impl Language {
/// Get the native name of the language
///
/// Examples: "English (US)", "Deutsch", "中文 (简体)"
pub fn name(&self) -> &str {
match self {
Language::Af => "Afrikaans",
Language::Am => "አማርኛ",
Language::Ar => "العربية",
Language::As => "অসমীয়া",
Language::Az => "Azərbaycan",
Language::Be => "Беларуская",
Language::Bg => "Български",
Language::Bn => "বাংলা",
Language::Bs => "Bosanski",
Language::Ca => "Català",
Language::Cs => "Čeština",
Language::Da => "Dansk",
Language::De => "Deutsch",
Language::El => "Ελληνικά",
Language::En => "English (US)",
Language::EnGb => "English (UK)",
Language::EnIn => "English (India)",
Language::Es => "Español (España)",
Language::Es419 => "Español (Latinoamérica)",
Language::EsUs => "Español (US)",
Language::Et => "Eesti",
Language::Eu => "Euskara",
Language::Fa => "فارسی",
Language::Fi => "Suomi",
Language::Fil => "Filipino",
Language::Fr => "Français",
Language::FrCa => "Français (Canada)",
Language::Gl => "Galego",
Language::Gu => "ગુજરાતી",
Language::Hi => "हिन्दी",
Language::Hr => "Hrvatski",
Language::Hu => "Magyar",
Language::Hy => "Հայերեն",
Language::Id => "Bahasa Indonesia",
Language::Is => "Íslenska",
Language::It => "Italiano",
Language::Iw => "עברית",
Language::Ja => "日本語",
Language::Ka => "ქართული",
Language::Kk => "Қазақ Тілі",
Language::Km => "ខ្មែរ",
Language::Kn => "ಕನ್ನಡ",
Language::Ko => "한국어",
Language::Ky => "Кыргызча",
Language::Lo => "ລາວ",
Language::Lt => "Lietuvių",
Language::Lv => "Latviešu valoda",
Language::Mk => "Македонски",
Language::Ml => "മലയാളം",
Language::Mn => "Монгол",
Language::Mr => "मराठी",
Language::Ms => "Bahasa Malaysia",
Language::My => "ဗမာ",
Language::Ne => "नेपाली",
Language::Nl => "Nederlands",
Language::No => "Norsk",
Language::Or => "ଓଡ଼ିଆ",
Language::Pa => "ਪੰਜਾਬੀ",
Language::Pl => "Polski",
Language::Pt => "Português (Brasil)",
Language::PtPt => "Português",
Language::Ro => "Română",
Language::Ru => "Русский",
Language::Si => "සිංහල",
Language::Sk => "Slovenčina",
Language::Sl => "Slovenščina",
Language::Sq => "Shqip",
Language::Sr => "Српски",
Language::SrLatn => "Srpski",
Language::Sv => "Svenska",
Language::Sw => "Kiswahili",
Language::Ta => "தமிழ்",
Language::Te => "తెలుగు",
Language::Th => "ภาษาไทย",
Language::Tr => "Türkçe",
Language::Uk => "Українська",
Language::Ur => "اردو",
Language::Uz => "Ozbek",
Language::Vi => "Tiếng Việt",
Language::ZhCn => "中文 (简体)",
Language::ZhHk => "中文 (香港)",
Language::ZhTw => "中文 (繁體)",
Language::Zu => "IsiZulu",
}
}
}
impl Country {
/// Get the English name of the country
///
/// Examples: "United States", "Germany"
pub fn name(&self) -> &str {
match self {
Country::Ae => "United Arab Emirates",
Country::Ar => "Argentina",
Country::At => "Austria",
Country::Au => "Australia",
Country::Az => "Azerbaijan",
Country::Ba => "Bosnia and Herzegovina",
Country::Bd => "Bangladesh",
Country::Be => "Belgium",
Country::Bg => "Bulgaria",
Country::Bh => "Bahrain",
Country::Bo => "Bolivia",
Country::Br => "Brazil",
Country::By => "Belarus",
Country::Ca => "Canada",
Country::Ch => "Switzerland",
Country::Cl => "Chile",
Country::Co => "Colombia",
Country::Cr => "Costa Rica",
Country::Cy => "Cyprus",
Country::Cz => "Czechia",
Country::De => "Germany",
Country::Dk => "Denmark",
Country::Do => "Dominican Republic",
Country::Dz => "Algeria",
Country::Ec => "Ecuador",
Country::Ee => "Estonia",
Country::Eg => "Egypt",
Country::Es => "Spain",
Country::Fi => "Finland",
Country::Fr => "France",
Country::Gb => "United Kingdom",
Country::Ge => "Georgia",
Country::Gh => "Ghana",
Country::Gr => "Greece",
Country::Gt => "Guatemala",
Country::Hk => "Hong Kong",
Country::Hn => "Honduras",
Country::Hr => "Croatia",
Country::Hu => "Hungary",
Country::Id => "Indonesia",
Country::Ie => "Ireland",
Country::Il => "Israel",
Country::In => "India",
Country::Iq => "Iraq",
Country::Is => "Iceland",
Country::It => "Italy",
Country::Jm => "Jamaica",
Country::Jo => "Jordan",
Country::Jp => "Japan",
Country::Ke => "Kenya",
Country::Kh => "Cambodia",
Country::Kr => "South Korea",
Country::Kw => "Kuwait",
Country::Kz => "Kazakhstan",
Country::La => "Laos",
Country::Lb => "Lebanon",
Country::Li => "Liechtenstein",
Country::Lk => "Sri Lanka",
Country::Lt => "Lithuania",
Country::Lu => "Luxembourg",
Country::Lv => "Latvia",
Country::Ly => "Libya",
Country::Ma => "Morocco",
Country::Me => "Montenegro",
Country::Mk => "North Macedonia",
Country::Mt => "Malta",
Country::Mx => "Mexico",
Country::My => "Malaysia",
Country::Ng => "Nigeria",
Country::Ni => "Nicaragua",
Country::Nl => "Netherlands",
Country::No => "Norway",
Country::Np => "Nepal",
Country::Nz => "New Zealand",
Country::Om => "Oman",
Country::Pa => "Panama",
Country::Pe => "Peru",
Country::Pg => "Papua New Guinea",
Country::Ph => "Philippines",
Country::Pk => "Pakistan",
Country::Pl => "Poland",
Country::Pr => "Puerto Rico",
Country::Pt => "Portugal",
Country::Py => "Paraguay",
Country::Qa => "Qatar",
Country::Ro => "Romania",
Country::Rs => "Serbia",
Country::Ru => "Russia",
Country::Sa => "Saudi Arabia",
Country::Se => "Sweden",
Country::Sg => "Singapore",
Country::Si => "Slovenia",
Country::Sk => "Slovakia",
Country::Sn => "Senegal",
Country::Sv => "El Salvador",
Country::Th => "Thailand",
Country::Tn => "Tunisia",
Country::Tr => "Turkey",
Country::Tw => "Taiwan",
Country::Tz => "Tanzania",
Country::Ua => "Ukraine",
Country::Ug => "Uganda",
Country::Us => "United States",
Country::Uy => "Uruguay",
Country::Ve => "Venezuela",
Country::Vn => "Vietnam",
Country::Ye => "Yemen",
Country::Za => "South Africa",
Country::Zw => "Zimbabwe",
}
}
}
impl Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
&serde_json::to_string(self).map_or("".to_owned(), |s| s[1..s.len() - 1].to_owned()),
)
}
}
impl Display for Country {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
&serde_json::to_string(self).map_or("".to_owned(), |s| s[1..s.len() - 1].to_owned()),
)
}
}
impl FromStr for Language {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(&format!("\"{}\"", s))
}
}
impl FromStr for Country {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(&format!("\"{}\"", s))
}
}

20
src/param/mod.rs Normal file
View file

@ -0,0 +1,20 @@
mod stream_filter;
pub mod locale;
pub mod search_filter;
pub use locale::{Country, Language};
pub use stream_filter::StreamFilter;
/// Channel video sort order
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ChannelOrder {
/// Output the latest videos first
#[default]
Latest,
/// Output the oldest videos first
Oldest,
/// Output the most viewed videos first
Popular,
}

229
src/param/search_filter.rs Normal file
View file

@ -0,0 +1,229 @@
use std::collections::BTreeSet;
use crate::util::{self, ProtoBuilder};
#[derive(Default, Debug)]
pub struct SearchFilter {
sort: Option<Order>,
features: BTreeSet<Feature>,
date: Option<Date>,
entity: Option<Entity>,
length: Option<Length>,
verbatim: bool,
}
/// Video feature to filter by
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Feature {
/// HD resolution
IsHd = 4,
/// Video with subtitles
Subtitles = 5,
/// Video published under the Creative Commons BY 3.0 license
CCommons = 6,
/// 3D Video
Is3d = 7,
/// Active livestream
IsLive = 8,
/// 4K resolution
Is4k = 14,
/// 360° Video
Is360 = 15,
/// 180° VR-Video
IsVr180 = 26,
/// HDR Video
IsHdr = 25,
}
/// Sort order of search results
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Order {
/// Sort by Like/Dislike ratio
Rating = 1,
/// Sort by upload date
Date = 2,
/// Sort by view count
Views = 3,
}
/// Upload date range to filter by
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Date {
/// 1 hour old or newer
Hour = 1,
/// 1 day old or newer
Day = 2,
/// 1 week old or newer
Week = 3,
/// 1 month old or newer
Month = 4,
/// 1 year old or newer
Year = 5,
}
/// YouTube entity type to filter by
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Entity {
Video = 1,
Channel = 2,
Playlist = 3,
}
/// Video length range to filter by
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Length {
/// < 4min
Short = 1,
/// 4-20min
Medium = 3,
/// > 20min
Long = 2,
}
impl SearchFilter {
pub fn new() -> Self {
Self::default()
}
/// Sort the search results
pub fn sort(mut self, sort: Order) -> Self {
self.sort = Some(sort);
self
}
/// Sort the search results
pub fn sort_opt(mut self, sort: Option<Order>) -> Self {
self.sort = sort;
self
}
/// Filter videos with specific features
pub fn feature(mut self, feature: Feature) -> Self {
self.features.insert(feature);
self
}
/// Filter videos with specific features
pub fn features(mut self, features: BTreeSet<Feature>) -> Self {
self.features = features;
self
}
/// Filter videos by upload date range
pub fn date(mut self, date: Date) -> Self {
self.date = Some(date);
self
}
/// Filter videos by upload date range
pub fn date_opt(mut self, date: Option<Date>) -> Self {
self.date = date;
self
}
/// Filter videos by entity type
pub fn entity(mut self, entity: Entity) -> Self {
self.entity = Some(entity);
self
}
/// Filter videos by entity type
pub fn entity_opt(mut self, entity: Option<Entity>) -> Self {
self.entity = entity;
self
}
/// Filter videos by length range
pub fn length(mut self, length: Length) -> Self {
self.length = Some(length);
self
}
/// Filter videos by length range
pub fn length_opt(mut self, length: Option<Length>) -> Self {
self.length = length;
self
}
/// Disable the automatic correction of mistyped search terms
pub fn verbatim(mut self) -> Self {
self.verbatim = true;
self
}
/// Disable the automatic correction of mistyped search terms
pub fn verbatim_set(mut self, verbatim: bool) -> Self {
self.verbatim = verbatim;
self
}
pub fn encode(&self) -> String {
let mut filters = ProtoBuilder::new();
if let Some(date) = self.date {
filters.varint(1, date as u64);
}
if let Some(entity) = self.entity {
filters.varint(2, entity as u64);
}
if let Some(length) = self.length {
filters.varint(3, length as u64);
}
self.features.iter().for_each(|feat| {
filters.varint(*feat as u32, 1);
});
let mut pb = ProtoBuilder::new();
if let Some(sort) = self.sort {
pb.varint(1, sort as u64);
}
if !filters.bytes.is_empty() {
pb.embedded(2, filters);
}
if self.verbatim {
let mut extras = ProtoBuilder::new();
extras.varint(1, 1);
pb.embedded(8, extras)
}
let b64 = base64::encode(pb.bytes);
util::urlencode(&b64)
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(SearchFilter::new().entity(Entity::Video), "EgIQAQ%253D%253D")]
#[case(SearchFilter::new().entity(Entity::Channel), "EgIQAg%253D%253D")]
#[case(SearchFilter::new().entity(Entity::Playlist), "EgIQAw%253D%253D")]
#[case(SearchFilter::new().date(Date::Hour), "EgIIAQ%253D%253D")]
#[case(SearchFilter::new().date(Date::Day), "EgIIAg%253D%253D")]
#[case(SearchFilter::new().date(Date::Week), "EgIIAw%253D%253D")]
#[case(SearchFilter::new().date(Date::Month), "EgIIBA%253D%253D")]
#[case(SearchFilter::new().date(Date::Year), "EgIIBQ%253D%253D")]
#[case(SearchFilter::new().length(Length::Short), "EgIYAQ%253D%253D")]
#[case(SearchFilter::new().length(Length::Medium), "EgIYAw%253D%253D")]
#[case(SearchFilter::new().length(Length::Long), "EgIYAg%253D%253D")]
#[case(SearchFilter::new().feature(Feature::IsLive), "EgJAAQ%253D%253D")]
#[case(SearchFilter::new().feature(Feature::Is4k), "EgJwAQ%253D%253D")]
#[case(SearchFilter::new().feature(Feature::IsHd), "EgIgAQ%253D%253D")]
#[case(SearchFilter::new().feature(Feature::Subtitles), "EgIoAQ%253D%253D")]
#[case(SearchFilter::new().feature(Feature::CCommons), "EgIwAQ%253D%253D")]
#[case(SearchFilter::new().feature(Feature::Is360), "EgJ4AQ%253D%253D")]
#[case(SearchFilter::new().feature(Feature::IsVr180), "EgPQAQE%253D")]
#[case(SearchFilter::new().feature(Feature::Is3d), "EgI4AQ%253D%253D")]
#[case(SearchFilter::new().feature(Feature::IsHdr), "EgPIAQE%253D")]
#[case(SearchFilter::new().sort(Order::Date), "CAI%253D")]
#[case(SearchFilter::new().sort(Order::Views), "CAM%253D")]
#[case(SearchFilter::new().sort(Order::Rating), "CAE%253D")]
fn t_filter(#[case] filter: SearchFilter, #[case] expect: &str) {
assert_eq!(util::urlencode(&filter.encode()), expect);
}
}

420
src/param/stream_filter.rs Normal file
View file

@ -0,0 +1,420 @@
//! Filters for selecting audio/video streams
use std::collections::HashSet;
use crate::model::{
AudioCodec, AudioFormat, AudioStream, VideoCodec, VideoFormat, VideoPlayer, VideoStream,
};
#[derive(Debug, Default, Clone)]
pub struct StreamFilter {
audio_max_bitrate: Option<u32>,
audio_formats: Option<HashSet<AudioFormat>>,
audio_codecs: Option<HashSet<AudioCodec>>,
audio_language: Option<String>,
video_max_res: Option<u32>,
video_max_fps: Option<u8>,
video_formats: Option<HashSet<VideoFormat>>,
video_codecs: Option<HashSet<VideoCodec>>,
video_hdr: bool,
video_none: bool,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
enum FilterResult {
Deny,
AllowLowest,
Allow,
Match,
}
impl FilterResult {
fn hard(val: bool) -> Self {
match val {
true => Self::Match,
false => Self::Deny,
}
}
fn soft(val: bool) -> Self {
match val {
true => Self::Match,
false => Self::AllowLowest,
}
}
fn allow(val: bool) -> Self {
match val {
true => Self::Allow,
false => Self::Deny,
}
}
fn join(self, other: Self) -> Self {
match self == Self::Deny {
true => Self::Deny,
false => self.min(other),
}
}
}
impl StreamFilter {
/// Set the maximum audio bitrate in bits per second.
///
/// This is a soft filter, so if there is no stream with a bitrate
/// <= the limit, the stream with the next higher bitrate is returned.
pub fn audio_max_bitrate(&mut self, max_bitrate: u32) -> &mut Self {
self.audio_max_bitrate = Some(max_bitrate);
self
}
fn apply_audio_max_bitrate(&self, stream: &AudioStream) -> FilterResult {
match self.audio_max_bitrate {
Some(max_bitrate) => FilterResult::soft(stream.average_bitrate <= max_bitrate),
None => FilterResult::Match,
}
}
/// Set the supported audio container formats
pub fn audio_formats(&mut self, formats: HashSet<AudioFormat>) -> &mut Self {
self.audio_formats = Some(formats);
self
}
fn apply_audio_formats(&self, stream: &AudioStream) -> FilterResult {
match &self.audio_formats {
Some(formats) => FilterResult::hard(formats.contains(&stream.format)),
None => FilterResult::Match,
}
}
/// Set the supported audio codecs
pub fn audio_codecs(&mut self, codecs: HashSet<AudioCodec>) -> &mut Self {
self.audio_codecs = Some(codecs);
self
}
fn apply_audio_codecs(&self, stream: &AudioStream) -> FilterResult {
match &self.audio_codecs {
Some(codecs) => FilterResult::hard(codecs.contains(&stream.codec)),
None => FilterResult::Match,
}
}
/// Set the preferred audio language (2 letter ISO 639-1 code, e.g. `en`, `fr`).
/// Some YouTube videos feature multiple audio streams in
/// different languages (e.g. <https://www.youtube.com/watch?v=tVWWp1PqDus>).
///
/// If this filter is unset or no stream matches,
/// the filter returns the default audio stream.
pub fn audio_language(&mut self, language: &str) -> &mut Self {
self.audio_language = Some(language.to_owned());
self
}
fn apply_audio_language(&self, stream: &AudioStream) -> FilterResult {
match &self.audio_language {
Some(language) => match &stream.track {
Some(track) => match &track.lang {
Some(track_lang) => match track_lang == language {
true => FilterResult::Match,
false => FilterResult::allow(track.is_default),
},
None => FilterResult::allow(track.is_default),
},
None => FilterResult::Match,
},
None => FilterResult::hard(stream.track.as_ref().map_or(true, |t| t.is_default)),
}
}
/// Set the maximum video resolution. Resolution is determined by the
/// pixel count of the shorter edge (e.g. 1080p).
///
/// This is a soft filter, so if there is no stream with a resolution
/// <= the limit, the stream with the next higher resolution is returned.
pub fn video_max_res(&mut self, max_res: u32) -> &mut Self {
self.video_max_res = Some(max_res);
self
}
fn apply_video_max_res(&self, stream: &VideoStream) -> FilterResult {
match self.video_max_res {
Some(max_res) => FilterResult::soft(stream.height.min(stream.width) <= max_res),
None => FilterResult::Match,
}
}
/// Set the maximum video framerate.
///
/// This is a soft filter, so if there is no stream with a framerate
/// <= the limit, the stream with the next higher framerate is returned.
pub fn video_max_fps(&mut self, max_fps: u8) -> &mut Self {
self.video_max_fps = Some(max_fps);
self
}
fn apply_video_max_fps(&self, stream: &VideoStream) -> FilterResult {
match self.video_max_fps {
Some(max_fps) => FilterResult::soft(stream.fps <= max_fps),
None => FilterResult::Match,
}
}
/// Set the supported video container formats
pub fn video_formats(&mut self, formats: HashSet<VideoFormat>) -> &mut Self {
self.video_formats = Some(formats);
self
}
fn apply_video_formats(&self, stream: &VideoStream) -> FilterResult {
match &self.video_formats {
Some(formats) => FilterResult::hard(formats.contains(&stream.format)),
None => FilterResult::Match,
}
}
/// Set the supported video codecs
pub fn video_codecs(&mut self, codecs: HashSet<VideoCodec>) -> &mut Self {
self.video_codecs = Some(codecs);
self
}
fn apply_video_codecs(&self, stream: &VideoStream) -> FilterResult {
match &self.video_codecs {
Some(codecs) => FilterResult::hard(codecs.contains(&stream.codec)),
None => FilterResult::Match,
}
}
/// Allow HDR videos
pub fn video_hdr(&mut self) -> &mut Self {
self.video_hdr = true;
self
}
fn apply_video_hdr(&self, stream: &VideoStream) -> FilterResult {
match &self.video_hdr {
true => FilterResult::Match,
false => FilterResult::hard(!&stream.hdr),
}
}
/// Output no video stream (audio only)
pub fn no_video(&mut self) -> &mut Self {
self.video_none = true;
self
}
fn apply_audio(&self, stream: &AudioStream) -> FilterResult {
self.apply_audio_max_bitrate(stream).join(
self.apply_audio_formats(stream).join(
self.apply_audio_codecs(stream)
.join(self.apply_audio_language(stream)),
),
)
}
fn apply_video(&self, stream: &VideoStream) -> FilterResult {
self.apply_video_max_res(stream).join(
self.apply_video_formats(stream).join(
self.apply_video_codecs(stream).join(
self.apply_video_hdr(stream)
.join(self.apply_video_max_fps(stream)),
),
),
)
}
}
impl VideoPlayer {
pub fn select_audio_stream(&self, filter: &StreamFilter) -> Option<&AudioStream> {
let mut fallback: Option<&AudioStream> = None;
self.audio_streams
.iter()
.rev()
.find(|s| match filter.apply_audio(s) {
FilterResult::Deny => false,
FilterResult::AllowLowest => {
fallback = Some(s);
false
}
FilterResult::Allow => {
if fallback.is_none() {
fallback = Some(s);
}
false
}
FilterResult::Match => true,
})
.or(fallback)
}
fn _select_video_stream<'a>(
streams: &'a [VideoStream],
filter: &StreamFilter,
) -> Option<&'a VideoStream> {
if filter.video_none {
return None;
}
let mut fallback: Option<&VideoStream> = None;
streams
.iter()
.rev()
.find(|s| match filter.apply_video(s) {
FilterResult::Deny => false,
FilterResult::AllowLowest => {
fallback = Some(s);
false
}
FilterResult::Allow => {
if fallback.is_none() {
fallback = Some(s);
}
false
}
FilterResult::Match => true,
})
.or(fallback)
}
pub fn select_video_stream(&self, filter: &StreamFilter) -> Option<&VideoStream> {
Self::_select_video_stream(&self.video_streams, filter)
}
pub fn select_video_only_stream(&self, filter: &StreamFilter) -> Option<&VideoStream> {
Self::_select_video_stream(&self.video_only_streams, filter)
}
pub fn select_video_audio_stream(
&self,
filter: &StreamFilter,
) -> (Option<&VideoStream>, Option<&AudioStream>) {
let video_stream = self.select_video_stream(filter);
let video_only_stream = self.select_video_only_stream(filter);
match (video_stream, video_only_stream) {
(None, None) => (None, self.select_audio_stream(filter)),
(None, Some(video_only_stream)) => {
(Some(video_only_stream), self.select_audio_stream(filter))
}
(Some(video_stream), None) => (Some(video_stream), None),
(Some(video_stream), Some(video_only_stream)) => match video_only_stream > video_stream
{
true => (
Some(video_only_stream),
Some(some_or_bail!(
self.select_audio_stream(filter),
(Some(video_stream), None)
)),
),
false => (Some(video_stream), None),
},
}
}
}
#[cfg(test)]
mod tests {
use std::{fs::File, io::BufReader, path::Path};
use super::*;
use once_cell::sync::Lazy;
use rstest::rstest;
use velcro::hash_set;
static PLAYER_ML: Lazy<VideoPlayer> = Lazy::new(|| {
let json_path = Path::new("testfiles/player_model/multilanguage.json");
let json_file = File::open(json_path).unwrap();
serde_json::from_reader(BufReader::new(json_file)).unwrap()
});
static PLAYER_HDR: Lazy<VideoPlayer> = Lazy::new(|| {
let json_path = Path::new("testfiles/player_model/hdr.json");
let json_file = File::open(json_path).unwrap();
serde_json::from_reader(BufReader::new(json_file)).unwrap()
});
#[rstest]
#[case::default(StreamFilter::default(), Some("https://rr4---sn-h0jeener.googlevideo.com/videoplayback?c=WEB&clen=16104134&dur=1012.661&ei=498HY6KvArqM6dsPiN6QgAE&expire=1661482051&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AK8GbbQovVxldcBz4pkXu4EA9N2sU-4yPPa5hFT3bXta&initcwndbps=1392500&ip=2003%3Ade%3Aaf1e%3A8a00%3A84c6%3A28f3%3A9de2%3A464&itag=251&keepalive=yes&lmt=1659767097097120&lsig=AG3C_xAwRAIgLFPuLqOoHoNQax15AE9Q2YIZ7pM7-olbGWgYGv1MDccCIADKSc_HeOdmD7CDs4AkY5ZtWF4gdZd4rw99Cqlzakbk&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=wB&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeener%2Csn-h0jeln7l&ms=au%2Crdu&mt=1661460078&mv=m&mvi=4&n=LwUYrFgbIVPzmA&ns=bBqoZtLH6lsaX8ke0xgRMM8H&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgW4IxGJJFRAwZefvDdDkJfjhN7y3bPmh96BCFuyFn6pwCIQDW6pVnk_DwMC3FcZy5rXNUULMNWLdadScxwuhFTFR84g%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-Khmjdu08WYG7DxAg_8xq0R2u5a6w&txp=4532434&vprv=1&xtags=lang%3Den"))]
#[case::bitrate(StreamFilter::default().audio_max_bitrate(100000).to_owned(), Some("https://rr4---sn-h0jeener.googlevideo.com/videoplayback?c=WEB&clen=8217507&dur=1012.661&ei=498HY6KvArqM6dsPiN6QgAE&expire=1661482051&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AK8GbbQovVxldcBz4pkXu4EA9N2sU-4yPPa5hFT3bXta&initcwndbps=1392500&ip=2003%3Ade%3Aaf1e%3A8a00%3A84c6%3A28f3%3A9de2%3A464&itag=250&keepalive=yes&lmt=1659767073159859&lsig=AG3C_xAwRAIgLFPuLqOoHoNQax15AE9Q2YIZ7pM7-olbGWgYGv1MDccCIADKSc_HeOdmD7CDs4AkY5ZtWF4gdZd4rw99Cqlzakbk&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=wB&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeener%2Csn-h0jeln7l&ms=au%2Crdu&mt=1661460078&mv=m&mvi=4&n=LwUYrFgbIVPzmA&ns=bBqoZtLH6lsaX8ke0xgRMM8H&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgM9loLjlUtgwrALqSek4vO8KljcCltFjLw1TGX0d9lZ4CICRiTJ8a_KgdafXVo2vKwgLPuH2B7t0hF-ln2k_MI3ds&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-Khmjdu08WYG7DxAg_8xq0R2u5a6w&txp=4532434&vprv=1&xtags=lang%3Den"))]
#[case::m4a_format(StreamFilter::default().audio_formats(hash_set!(AudioFormat::M4a)).to_owned(), Some("https://rr4---sn-h0jeener.googlevideo.com/videoplayback?c=WEB&clen=16390628&dur=1012.691&ei=498HY6KvArqM6dsPiN6QgAE&expire=1661482051&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AK8GbbQovVxldcBz4pkXu4EA9N2sU-4yPPa5hFT3bXta&initcwndbps=1392500&ip=2003%3Ade%3Aaf1e%3A8a00%3A84c6%3A28f3%3A9de2%3A464&itag=140&keepalive=yes&lmt=1659766154827884&lsig=AG3C_xAwRAIgLFPuLqOoHoNQax15AE9Q2YIZ7pM7-olbGWgYGv1MDccCIADKSc_HeOdmD7CDs4AkY5ZtWF4gdZd4rw99Cqlzakbk&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=wB&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jeener%2Csn-h0jeln7l&ms=au%2Crdu&mt=1661460078&mv=m&mvi=4&n=LwUYrFgbIVPzmA&ns=bBqoZtLH6lsaX8ke0xgRMM8H&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgc8oxPEHDO0cgO3ZcbPmv3nrkzfy52WchpV0HcBcUw24CIEFxLKBcM4vVqGeRkt581dFL2tetvHd93SHCTVEUnIn_&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-Khmjdu08WYG7DxAg_8xq0R2u5a6w&txp=4532434&vprv=1&xtags=lang%3Den"))]
#[case::m4a_codec(StreamFilter::default().audio_codecs(hash_set!(AudioCodec::Mp4a)).to_owned(), Some("https://rr4---sn-h0jeener.googlevideo.com/videoplayback?c=WEB&clen=16390628&dur=1012.691&ei=498HY6KvArqM6dsPiN6QgAE&expire=1661482051&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AK8GbbQovVxldcBz4pkXu4EA9N2sU-4yPPa5hFT3bXta&initcwndbps=1392500&ip=2003%3Ade%3Aaf1e%3A8a00%3A84c6%3A28f3%3A9de2%3A464&itag=140&keepalive=yes&lmt=1659766154827884&lsig=AG3C_xAwRAIgLFPuLqOoHoNQax15AE9Q2YIZ7pM7-olbGWgYGv1MDccCIADKSc_HeOdmD7CDs4AkY5ZtWF4gdZd4rw99Cqlzakbk&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=wB&mime=audio%2Fmp4&mm=31%2C29&mn=sn-h0jeener%2Csn-h0jeln7l&ms=au%2Crdu&mt=1661460078&mv=m&mvi=4&n=LwUYrFgbIVPzmA&ns=bBqoZtLH6lsaX8ke0xgRMM8H&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgc8oxPEHDO0cgO3ZcbPmv3nrkzfy52WchpV0HcBcUw24CIEFxLKBcM4vVqGeRkt581dFL2tetvHd93SHCTVEUnIn_&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-Khmjdu08WYG7DxAg_8xq0R2u5a6w&txp=4532434&vprv=1&xtags=lang%3Den"))]
#[case::french(StreamFilter::default().audio_language("fr").to_owned(), Some("https://rr4---sn-h0jeener.googlevideo.com/videoplayback?c=WEB&clen=16357630&dur=1012.721&ei=498HY6KvArqM6dsPiN6QgAE&expire=1661482051&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AK8GbbQovVxldcBz4pkXu4EA9N2sU-4yPPa5hFT3bXta&initcwndbps=1392500&ip=2003%3Ade%3Aaf1e%3A8a00%3A84c6%3A28f3%3A9de2%3A464&itag=251&keepalive=yes&lmt=1659767033119964&lsig=AG3C_xAwRAIgLFPuLqOoHoNQax15AE9Q2YIZ7pM7-olbGWgYGv1MDccCIADKSc_HeOdmD7CDs4AkY5ZtWF4gdZd4rw99Cqlzakbk&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=wB&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeener%2Csn-h0jeln7l&ms=au%2Crdu&mt=1661460078&mv=m&mvi=4&n=LwUYrFgbIVPzmA&ns=bBqoZtLH6lsaX8ke0xgRMM8H&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAKtzVVyoS46hkuKX31EyUE6X6Q5wotcToOCnYKswX3x_AiB0G2SUdVoso39bYgewd3zT8Pf77DrVtahXh4kVb46T9g%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-Khmjdu08WYG7DxAg_8xq0R2u5a6w&txp=4532434&vprv=1&xtags=lang%3Dfr"))]
#[case::br_fallback(StreamFilter::default().audio_max_bitrate(0).to_owned(), Some("https://rr4---sn-h0jeener.googlevideo.com/videoplayback?c=WEB&clen=6297404&dur=1012.661&ei=498HY6KvArqM6dsPiN6QgAE&expire=1661482051&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AK8GbbQovVxldcBz4pkXu4EA9N2sU-4yPPa5hFT3bXta&initcwndbps=1392500&ip=2003%3Ade%3Aaf1e%3A8a00%3A84c6%3A28f3%3A9de2%3A464&itag=249&keepalive=yes&lmt=1659767062297621&lsig=AG3C_xAwRAIgLFPuLqOoHoNQax15AE9Q2YIZ7pM7-olbGWgYGv1MDccCIADKSc_HeOdmD7CDs4AkY5ZtWF4gdZd4rw99Cqlzakbk&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=wB&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeener%2Csn-h0jeln7l&ms=au%2Crdu&mt=1661460078&mv=m&mvi=4&n=LwUYrFgbIVPzmA&ns=bBqoZtLH6lsaX8ke0xgRMM8H&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAPm10DeIvOt5Oc7e36cfhPC0ej2PslQqF3-CFVUl5TNfAiEAlgvwjlQK14e_-6j3W_hMvk9KHax8zd5shSVlYSR1P34%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-Khmjdu08WYG7DxAg_8xq0R2u5a6w&txp=4532434&vprv=1&xtags=lang%3Den"))]
#[case::lang_fallback(StreamFilter::default().audio_language("xx").to_owned(), Some("https://rr4---sn-h0jeener.googlevideo.com/videoplayback?c=WEB&clen=16104134&dur=1012.661&ei=498HY6KvArqM6dsPiN6QgAE&expire=1661482051&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AK8GbbQovVxldcBz4pkXu4EA9N2sU-4yPPa5hFT3bXta&initcwndbps=1392500&ip=2003%3Ade%3Aaf1e%3A8a00%3A84c6%3A28f3%3A9de2%3A464&itag=251&keepalive=yes&lmt=1659767097097120&lsig=AG3C_xAwRAIgLFPuLqOoHoNQax15AE9Q2YIZ7pM7-olbGWgYGv1MDccCIADKSc_HeOdmD7CDs4AkY5ZtWF4gdZd4rw99Cqlzakbk&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=wB&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jeener%2Csn-h0jeln7l&ms=au%2Crdu&mt=1661460078&mv=m&mvi=4&n=LwUYrFgbIVPzmA&ns=bBqoZtLH6lsaX8ke0xgRMM8H&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgW4IxGJJFRAwZefvDdDkJfjhN7y3bPmh96BCFuyFn6pwCIQDW6pVnk_DwMC3FcZy5rXNUULMNWLdadScxwuhFTFR84g%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-Khmjdu08WYG7DxAg_8xq0R2u5a6w&txp=4532434&vprv=1&xtags=lang%3Den"))]
#[case::noformat(StreamFilter::default().audio_formats(hash_set!()).to_owned(), None)]
#[case::nocodec(StreamFilter::default().audio_codecs(hash_set!()).to_owned(), None)]
fn t_select_audio_stream(#[case] filter: StreamFilter, #[case] expect_url: Option<&str>) {
let selection = PLAYER_ML.select_audio_stream(&filter);
match expect_url {
Some(expect_url) => assert_eq!(selection.unwrap().url, expect_url),
None => assert_eq!(selection, None),
}
}
#[rstest]
#[case::default(StreamFilter::default(), Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C308%2C315%2C330%2C331%2C332%2C333%2C334%2C335%2C336%2C337%2C394%2C395%2C396%2C397%2C398%2C399%2C400%2C401%2C694%2C695%2C696%2C697%2C698%2C699%2C700%2C701&c=WEB&clen=998696577&dur=313.780&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=315&keepalive=yes&lmt=1647476955807851&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIfP4IVSo-00_kq_JIkuh032hcLoJzNEhYjvwgLiDpEzQIhALPVrvDBjRwiFddXiAyADmRtYygte4HvlJ3XOrkOf_TR&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1"))]
#[case::hdr(StreamFilter::default().video_hdr().to_owned(), Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C308%2C315%2C330%2C331%2C332%2C333%2C334%2C335%2C336%2C337%2C394%2C395%2C396%2C397%2C398%2C399%2C400%2C401%2C694%2C695%2C696%2C697%2C698%2C699%2C700%2C701&c=WEB&clen=976824147&dur=313.780&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=701&keepalive=yes&lmt=1647469891607029&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAOax_lAWCW5ENOYxe3gZfBHgHA5oZJPyMlYQFy73t7-pAiEA46J7dsT-1pv9smuoP3Kx5T7c_IJ6cEZN4U9UkSNuT7o%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1"))]
#[case::resolution(StreamFilter::default().video_max_res(720).to_owned(), Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C308%2C315%2C330%2C331%2C332%2C333%2C334%2C335%2C336%2C337%2C394%2C395%2C396%2C397%2C398%2C399%2C400%2C401%2C694%2C695%2C696%2C697%2C698%2C699%2C700%2C701&c=WEB&clen=76313586&dur=313.780&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=302&keepalive=yes&lmt=1647455155369524&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIgW0H1434eh9Axw6zw95qezJB0D2aVd2bxEIs4T5bcfFACIDOjha9WLycp0L188FZyFGa1RBkLPoGrrJOppsaXqwDR&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1"))]
#[case::resolution_fps(StreamFilter::default().video_max_res(720).video_max_fps(30).to_owned(), Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C308%2C315%2C330%2C331%2C332%2C333%2C334%2C335%2C336%2C337%2C394%2C395%2C396%2C397%2C398%2C399%2C400%2C401%2C694%2C695%2C696%2C697%2C698%2C699%2C700%2C701&c=WEB&clen=47531179&dur=313.780&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=247&keepalive=yes&lmt=1647458657499381&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRgIhAMUsmcl1zgbr3YQranPWNV1kcxT5IdEoLL7FTFEDdHHPAiEAhQnrfYMU0A9xZ69MfBujWA4pXtCOQCg2Jn6ve9J_vBQ%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1"))]
#[case::res_fallback(StreamFilter::default().video_max_res(100).to_owned(), Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C308%2C315%2C330%2C331%2C332%2C333%2C334%2C335%2C336%2C337%2C394%2C395%2C396%2C397%2C398%2C399%2C400%2C401%2C694%2C695%2C696%2C697%2C698%2C699%2C700%2C701&c=WEB&clen=2763284&dur=313.780&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=160&keepalive=yes&lmt=1647456833049253&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgLPNxzLxppSSpnDEHxVblrQ38890NMbGnLXlmxljprfQCIQDn4Ir_sjYh7S3ms-Rynm-K0nJpHpQGYsz1nv4TiqeELQ%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1"))]
#[case::webm_format(StreamFilter::default().video_formats(hash_set!(VideoFormat::Webm)).to_owned(), Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C308%2C315%2C330%2C331%2C332%2C333%2C334%2C335%2C336%2C337%2C394%2C395%2C396%2C397%2C398%2C399%2C400%2C401%2C694%2C695%2C696%2C697%2C698%2C699%2C700%2C701&c=WEB&clen=998696577&dur=313.780&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=315&keepalive=yes&lmt=1647476955807851&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIfP4IVSo-00_kq_JIkuh032hcLoJzNEhYjvwgLiDpEzQIhALPVrvDBjRwiFddXiAyADmRtYygte4HvlJ3XOrkOf_TR&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1"))]
#[case::vp9_codec(StreamFilter::default().video_codecs(hash_set!(VideoCodec::Vp9)).to_owned(), Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C308%2C315%2C330%2C331%2C332%2C333%2C334%2C335%2C336%2C337%2C394%2C395%2C396%2C397%2C398%2C399%2C400%2C401%2C694%2C695%2C696%2C697%2C698%2C699%2C700%2C701&c=WEB&clen=998696577&dur=313.780&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=315&keepalive=yes&lmt=1647476955807851&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIfP4IVSo-00_kq_JIkuh032hcLoJzNEhYjvwgLiDpEzQIhALPVrvDBjRwiFddXiAyADmRtYygte4HvlJ3XOrkOf_TR&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1"))]
#[case::noformat(StreamFilter::default().video_formats(hash_set!()).to_owned(), None)]
#[case::nocodec(StreamFilter::default().video_codecs(hash_set!()).to_owned(), None)]
fn t_select_video_only_stream(#[case] filter: StreamFilter, #[case] expect_url: Option<&str>) {
let selection = PLAYER_HDR.select_video_only_stream(&filter);
match expect_url {
Some(expect_url) => assert_eq!(selection.unwrap().url, expect_url),
None => assert_eq!(selection, None),
}
}
#[rstest]
#[case::default(
StreamFilter::default(),
Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C308%2C315%2C330%2C331%2C332%2C333%2C334%2C335%2C336%2C337%2C394%2C395%2C396%2C397%2C398%2C399%2C400%2C401%2C694%2C695%2C696%2C697%2C698%2C699%2C700%2C701&c=WEB&clen=998696577&dur=313.780&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=315&keepalive=yes&lmt=1647476955807851&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIfP4IVSo-00_kq_JIkuh032hcLoJzNEhYjvwgLiDpEzQIhALPVrvDBjRwiFddXiAyADmRtYygte4HvlJ3XOrkOf_TR&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1"),
Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?c=WEB&clen=5199784&dur=313.801&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=251&keepalive=yes&lmt=1647453650291076&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhALtI3j8ZChpNb0LcyDZ3yosbWnSpqaO0-jKAe_UM_RQyAiAMwrpdeNbJEnQn3q1eveaAcRcNIwy5iJ4fIjeBW_MUfg%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1")
)]
#[case::webm(
StreamFilter::default().video_formats(hash_set!(VideoFormat::Webm)).to_owned(),
Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?aitags=133%2C134%2C135%2C136%2C160%2C242%2C243%2C244%2C247%2C278%2C298%2C299%2C302%2C303%2C308%2C315%2C330%2C331%2C332%2C333%2C334%2C335%2C336%2C337%2C394%2C395%2C396%2C397%2C398%2C399%2C400%2C401%2C694%2C695%2C696%2C697%2C698%2C699%2C700%2C701&c=WEB&clen=998696577&dur=313.780&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=315&keepalive=yes&lmt=1647476955807851&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=video%2Fwebm&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRAIfP4IVSo-00_kq_JIkuh032hcLoJzNEhYjvwgLiDpEzQIhALPVrvDBjRwiFddXiAyADmRtYygte4HvlJ3XOrkOf_TR&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1"),
Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?c=WEB&clen=5199784&dur=313.801&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=251&keepalive=yes&lmt=1647453650291076&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhALtI3j8ZChpNb0LcyDZ3yosbWnSpqaO0-jKAe_UM_RQyAiAMwrpdeNbJEnQn3q1eveaAcRcNIwy5iJ4fIjeBW_MUfg%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1")
)]
#[case::noaudio(
StreamFilter::default().audio_formats(hash_set!()).to_owned(),
Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?c=WEB&clen=23544588&dur=313.834&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=18&lmt=1647456546485912&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=video%2Fmp4&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=HWZNhARNT_nJgg&ns=pLFQxzhiCbZ9F2HJmDLveKoH&pl=37&ratebypass=yes&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIgeCEjusAq6p33rH0NHyTAbPIRaaEkjDE32AXBFzDvR-ICIQD0LI8hQVH8oCMWu6OuADzc1FSQhIqYs5RLkxBmObIdsw%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cratebypass%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4530434&vprv=1"),
None
)]
#[case::novideo(
StreamFilter::default().no_video().to_owned(),
None,
Some("https://rr5---sn-h0jelne7.googlevideo.com/videoplayback?c=WEB&clen=5199784&dur=313.801&ei=eckIY72IKcGZ8gOMt6CwDg&expire=1661541849&fexp=24001373%2C24007246&fvip=2&gir=yes&id=o-AOqXE9lVS424yszv6LN5V_gaevdHxenJl-tYNy3Drs6g&initcwndbps=1428750&ip=2003%3Ade%3Aaf05%3A2500%3A5dad%3A319b%3Aca30%3Ae212&itag=251&keepalive=yes&lmt=1647453650291076&lsig=AG3C_xAwRQIhAMioKyc-dqs-6uvAwLViCcCTXKHn9sIbo0cbSSBXGG4kAiBQNsRBAvQrbWdOjZIsQXYrfPEb1KDpE_AlSEGQZXB9uA%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=NH&mime=audio%2Fwebm&mm=31%2C29&mn=sn-h0jelne7%2Csn-h0jeenl6&ms=au%2Crdu&mt=1661519833&mv=m&mvi=5&n=Zd7nrOM1B2C6PA&ns=426LxLap5MonJD_YWdS4lSYH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhALtI3j8ZChpNb0LcyDZ3yosbWnSpqaO0-jKAe_UM_RQyAiAMwrpdeNbJEnQn3q1eveaAcRcNIwy5iJ4fIjeBW_MUfg%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-KhuPtxVzL5-QbZ7S9zNeOHsWTdms&txp=4532434&vprv=1")
)]
#[case::noformat(StreamFilter::default().audio_formats(hash_set!()).video_formats(hash_set!()).to_owned(), None, None)]
fn t_select_video_audio_stream(
#[case] filter: StreamFilter,
#[case] expect_video_url: Option<&str>,
#[case] expect_audio_url: Option<&str>,
) {
let (video, audio) = PLAYER_HDR.select_video_audio_stream(&filter);
match expect_video_url {
Some(expect_url) => assert_eq!(video.unwrap().url, expect_url),
None => assert_eq!(video, None),
}
match expect_audio_url {
Some(expect_url) => assert_eq!(audio.unwrap().url, expect_url),
None => assert_eq!(audio, None),
}
}
}