Compare commits
No commits in common. "b862d2d1f9f70fb751d90635b456dfb1312bd450" and "6ad77d8daaa52a73ea8f40cf04bee20d1094a897" have entirely different histories.
b862d2d1f9
...
6ad77d8daa
36 changed files with 683 additions and 2521 deletions
|
@ -47,7 +47,6 @@ serde_with = { version = "3.0.0", default-features = false, features = [
|
||||||
"macros",
|
"macros",
|
||||||
"json",
|
"json",
|
||||||
] }
|
] }
|
||||||
serde_plain = "1.0.1"
|
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
time = { version = "0.3.15", features = [
|
time = { version = "0.3.15", features = [
|
||||||
"macros",
|
"macros",
|
||||||
|
|
|
@ -8,7 +8,7 @@ use reqwest::{Client, ClientBuilder};
|
||||||
use rustypipe::{
|
use rustypipe::{
|
||||||
client::RustyPipe,
|
client::RustyPipe,
|
||||||
model::{UrlTarget, VideoId},
|
model::{UrlTarget, VideoId},
|
||||||
param::{search_filter, ChannelVideoTab, StreamFilter},
|
param::{search_filter, StreamFilter},
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
@ -113,7 +113,6 @@ enum ChannelTab {
|
||||||
Videos,
|
Videos,
|
||||||
Shorts,
|
Shorts,
|
||||||
Live,
|
Live,
|
||||||
Playlists,
|
|
||||||
Info,
|
Info,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -565,16 +564,8 @@ async fn main() {
|
||||||
print_data(&artist, format, pretty);
|
print_data(&artist, format, pretty);
|
||||||
} else {
|
} else {
|
||||||
match tab {
|
match tab {
|
||||||
ChannelTab::Videos | ChannelTab::Shorts | ChannelTab::Live => {
|
ChannelTab::Videos => {
|
||||||
let video_tab = match tab {
|
let mut channel = rp.query().channel_videos(&id).await.unwrap();
|
||||||
ChannelTab::Videos => ChannelVideoTab::Videos,
|
|
||||||
ChannelTab::Shorts => ChannelVideoTab::Shorts,
|
|
||||||
ChannelTab::Live => ChannelVideoTab::Live,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let mut channel =
|
|
||||||
rp.query().channel_videos_tab(&id, video_tab).await.unwrap();
|
|
||||||
|
|
||||||
channel
|
channel
|
||||||
.content
|
.content
|
||||||
.extend_limit(rp.query(), limit)
|
.extend_limit(rp.query(), limit)
|
||||||
|
@ -582,8 +573,23 @@ async fn main() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
print_data(&channel, format, pretty);
|
print_data(&channel, format, pretty);
|
||||||
}
|
}
|
||||||
ChannelTab::Playlists => {
|
ChannelTab::Shorts => {
|
||||||
let channel = rp.query().channel_playlists(&id).await.unwrap();
|
let mut channel = rp.query().channel_shorts(&id).await.unwrap();
|
||||||
|
channel
|
||||||
|
.content
|
||||||
|
.extend_limit(rp.query(), limit)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
print_data(&channel, format, pretty);
|
||||||
|
}
|
||||||
|
ChannelTab::Live => {
|
||||||
|
let mut channel =
|
||||||
|
rp.query().channel_livestreams(&id).await.unwrap();
|
||||||
|
channel
|
||||||
|
.content
|
||||||
|
.extend_limit(rp.query(), limit)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
print_data(&channel, format, pretty);
|
print_data(&channel, format, pretty);
|
||||||
}
|
}
|
||||||
ChannelTab::Info => {
|
ChannelTab::Info => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ use rustypipe::{
|
||||||
client::{ClientType, RustyPipe},
|
client::{ClientType, RustyPipe},
|
||||||
param::{
|
param::{
|
||||||
search_filter::{self, ItemType, SearchFilter},
|
search_filter::{self, ItemType, SearchFilter},
|
||||||
ChannelVideoTab, Country,
|
Country,
|
||||||
},
|
},
|
||||||
report::{Report, Reporter},
|
report::{Report, Reporter},
|
||||||
};
|
};
|
||||||
|
@ -305,7 +305,7 @@ async fn channel_shorts() {
|
||||||
|
|
||||||
let rp = rp_testfile(&json_path);
|
let rp = rp_testfile(&json_path);
|
||||||
rp.query()
|
rp.query()
|
||||||
.channel_videos_tab("UCh8gHdtzO2tXd593_bjErWg", ChannelVideoTab::Shorts)
|
.channel_shorts("UCh8gHdtzO2tXd593_bjErWg")
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -318,7 +318,7 @@ async fn channel_livestreams() {
|
||||||
|
|
||||||
let rp = rp_testfile(&json_path);
|
let rp = rp_testfile(&json_path);
|
||||||
rp.query()
|
rp.query()
|
||||||
.channel_videos_tab("UC2DjFE7Xf11URZqWBigcVOQ", ChannelVideoTab::Live)
|
.channel_livestreams("UC2DjFE7Xf11URZqWBigcVOQ")
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::BufReader;
|
|
||||||
|
|
||||||
use path_macro::path;
|
use path_macro::path;
|
||||||
use reqwest::header;
|
use reqwest::header;
|
||||||
|
@ -11,7 +9,6 @@ use serde_with::serde_as;
|
||||||
use serde_with::VecSkipError;
|
use serde_with::VecSkipError;
|
||||||
|
|
||||||
use crate::model::Text;
|
use crate::model::Text;
|
||||||
use crate::util::DICT_DIR;
|
|
||||||
use crate::util::SRC_DIR;
|
use crate::util::SRC_DIR;
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
|
@ -144,45 +141,44 @@ struct LanguageCountryCommand {
|
||||||
pub async fn generate_locales() {
|
pub async fn generate_locales() {
|
||||||
let (languages, countries) = get_locales().await;
|
let (languages, countries) = get_locales().await;
|
||||||
|
|
||||||
let json_path = path!(*DICT_DIR / "lang_names.json");
|
|
||||||
let json_file = File::open(json_path).unwrap();
|
|
||||||
let lang_names: BTreeMap<String, String> =
|
|
||||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
|
||||||
|
|
||||||
let code_head = r#"// This file is automatically generated. DO NOT EDIT.
|
let code_head = r#"// This file is automatically generated. DO NOT EDIT.
|
||||||
|
|
||||||
//! Languages and countries
|
//! Languages and countries
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::error::Error;
|
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let code_foot = r#"impl FromStr for Language {
|
let code_foot = r#"impl Display for Language {
|
||||||
type Err = Error;
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
&serde_json::to_string(self).map_or("".to_owned(), |s| s[1..s.len() - 1].to_owned()),
|
||||||
let mut sub = s;
|
)
|
||||||
loop {
|
|
||||||
if let Ok(v) = serde_plain::from_str(sub) {
|
|
||||||
return Ok(v);
|
|
||||||
}
|
|
||||||
match sub.rfind('-') {
|
|
||||||
Some(pos) => {
|
|
||||||
sub = &sub[..pos];
|
|
||||||
}
|
|
||||||
None => return Err(Error::Other("could not parse language `{s}`".into())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_plain::derive_display_from_serialize!(Language);
|
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()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serde_plain::derive_fromstr_from_deserialize!(Country, Error);
|
impl FromStr for Language {
|
||||||
serde_plain::derive_display_from_serialize!(Country);
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let mut code_langs = r#"/// Available languages
|
let mut code_langs = r#"/// Available languages
|
||||||
|
@ -227,8 +223,8 @@ pub enum Country {
|
||||||
"#
|
"#
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
languages.iter().for_each(|(code, native_name)| {
|
languages.iter().for_each(|(c, n)| {
|
||||||
let enum_name = code
|
let enum_name = c
|
||||||
.split('-')
|
.split('-')
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
format!(
|
format!(
|
||||||
|
@ -239,16 +235,10 @@ pub enum Country {
|
||||||
})
|
})
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
let en_name = lang_names.get(code).expect(code);
|
|
||||||
|
|
||||||
// Language enum
|
// Language enum
|
||||||
if en_name == native_name || code.starts_with("en") {
|
write!(code_langs, " /// {n}\n ").unwrap();
|
||||||
write!(code_langs, " /// {native_name}\n ").unwrap();
|
if c.contains('-') {
|
||||||
} else {
|
write!(code_langs, "#[serde(rename = \"{c}\")]\n ").unwrap();
|
||||||
write!(code_langs, " /// {en_name} / {native_name}\n ").unwrap();
|
|
||||||
}
|
|
||||||
if code.contains('-') {
|
|
||||||
write!(code_langs, "#[serde(rename = \"{code}\")]\n ").unwrap();
|
|
||||||
}
|
}
|
||||||
code_langs += &enum_name;
|
code_langs += &enum_name;
|
||||||
code_langs += ",\n";
|
code_langs += ",\n";
|
||||||
|
@ -259,7 +249,7 @@ pub enum Country {
|
||||||
// Language names
|
// Language names
|
||||||
writeln!(
|
writeln!(
|
||||||
code_lang_names,
|
code_lang_names,
|
||||||
" Language::{enum_name} => \"{native_name}\","
|
" Language::{enum_name} => \"{n}\","
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
# Channel order
|
|
||||||
|
|
||||||
Fields:
|
|
||||||
|
|
||||||
- `2:0:string` Channel ID
|
|
||||||
- `15:0:embedded` Videos tab
|
|
||||||
- `10:0:embedded` Shorts tab
|
|
||||||
- `14:0:embedded` Livestreams tab
|
|
||||||
- `2:0:string`: targetId for YouTube's web framework (`"\n$"` + any UUID)
|
|
||||||
- `3:1:varint` Sort order (1: Latest, 2: Popular)
|
|
||||||
|
|
||||||
Popular videos
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"80226972:0:embedded": {
|
|
||||||
"2:0:string": "UCXuqSBlHAE6Xw-yeJA0Tunw",
|
|
||||||
"3:1:base64": {
|
|
||||||
"110:0:embedded": {
|
|
||||||
"3:0:embedded": {
|
|
||||||
"15:0:embedded": {
|
|
||||||
"2:0:string": "\n$6461d7c8-0000-2040-87aa-089e0827e420",
|
|
||||||
"3:1:varint": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Popular shorts
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"80226972:0:embedded": {
|
|
||||||
"2:0:string": "UCXuqSBlHAE6Xw-yeJA0Tunw",
|
|
||||||
"3:1:base64": {
|
|
||||||
"110:0:embedded": {
|
|
||||||
"3:0:embedded": {
|
|
||||||
"10:0:embedded": {
|
|
||||||
"2:0:string": "\n$64679ffb-0000-26b3-a1bd-582429d2c794",
|
|
||||||
"3:1:varint": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Popular streams
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"80226972:0:embedded": {
|
|
||||||
"2:0:string": "UCXuqSBlHAE6Xw-yeJA0Tunw",
|
|
||||||
"3:1:base64": {
|
|
||||||
"110:0:embedded": {
|
|
||||||
"3:0:embedded": {
|
|
||||||
"14:0:embedded": {
|
|
||||||
"2:0:string": "\n$64693069-0000-2a1e-8c7d-582429bd5ba8",
|
|
||||||
"3:1:varint": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,15 +1,14 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, ExtractionError},
|
error::{Error, ExtractionError},
|
||||||
model::{
|
model::{paginator::Paginator, Channel, ChannelInfo, PlaylistItem, VideoItem, YouTubeItem},
|
||||||
paginator::{ContinuationEndpoint, Paginator},
|
param::Language,
|
||||||
Channel, ChannelInfo, PlaylistItem, VideoItem, YouTubeItem,
|
|
||||||
},
|
|
||||||
param::{ChannelOrder, ChannelVideoTab, Language},
|
|
||||||
serializer::MapResult,
|
serializer::MapResult,
|
||||||
util::{self, ProtoBuilder},
|
util,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{response, ClientType, MapResponse, RustyPipeQuery, YTContext};
|
use super::{response, ClientType, MapResponse, RustyPipeQuery, YTContext};
|
||||||
|
@ -19,13 +18,13 @@ use super::{response, ClientType, MapResponse, RustyPipeQuery, YTContext};
|
||||||
struct QChannel<'a> {
|
struct QChannel<'a> {
|
||||||
context: YTContext<'a>,
|
context: YTContext<'a>,
|
||||||
browse_id: &'a str,
|
browse_id: &'a str,
|
||||||
params: ChannelTab,
|
params: Params,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
query: Option<&'a str>,
|
query: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
enum ChannelTab {
|
enum Params {
|
||||||
#[serde(rename = "EgZ2aWRlb3PyBgQKAjoA")]
|
#[serde(rename = "EgZ2aWRlb3PyBgQKAjoA")]
|
||||||
Videos,
|
Videos,
|
||||||
#[serde(rename = "EgZzaG9ydHPyBgUKA5oBAA%3D%3D")]
|
#[serde(rename = "EgZzaG9ydHPyBgUKA5oBAA%3D%3D")]
|
||||||
|
@ -40,21 +39,11 @@ enum ChannelTab {
|
||||||
Search,
|
Search,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ChannelVideoTab> for ChannelTab {
|
|
||||||
fn from(value: ChannelVideoTab) -> Self {
|
|
||||||
match value {
|
|
||||||
ChannelVideoTab::Videos => Self::Videos,
|
|
||||||
ChannelVideoTab::Shorts => Self::Shorts,
|
|
||||||
ChannelVideoTab::Live => Self::Live,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RustyPipeQuery {
|
impl RustyPipeQuery {
|
||||||
async fn _channel_videos<S: AsRef<str>>(
|
async fn _channel_videos<S: AsRef<str>>(
|
||||||
&self,
|
&self,
|
||||||
channel_id: S,
|
channel_id: S,
|
||||||
params: ChannelTab,
|
params: Params,
|
||||||
query: Option<&str>,
|
query: Option<&str>,
|
||||||
operation: &str,
|
operation: &str,
|
||||||
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
||||||
|
@ -82,52 +71,26 @@ impl RustyPipeQuery {
|
||||||
&self,
|
&self,
|
||||||
channel_id: S,
|
channel_id: S,
|
||||||
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
||||||
self._channel_videos(channel_id, ChannelTab::Videos, None, "channel_videos")
|
self._channel_videos(channel_id, Params::Videos, None, "channel_videos")
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a ordered list of videos from a YouTube channel
|
/// Get the short videos from a YouTube channel
|
||||||
///
|
pub async fn channel_shorts<S: AsRef<str>>(
|
||||||
/// This function does not return channel metadata.
|
|
||||||
pub async fn channel_videos_order<S: AsRef<str>>(
|
|
||||||
&self,
|
&self,
|
||||||
channel_id: S,
|
channel_id: S,
|
||||||
order: ChannelOrder,
|
|
||||||
) -> Result<Paginator<VideoItem>, Error> {
|
|
||||||
self.channel_videos_tab_order(channel_id, ChannelVideoTab::Videos, order)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the specified video tab from a YouTube channel
|
|
||||||
pub async fn channel_videos_tab<S: AsRef<str>>(
|
|
||||||
&self,
|
|
||||||
channel_id: S,
|
|
||||||
tab: ChannelVideoTab,
|
|
||||||
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
||||||
self._channel_videos(channel_id, tab.into(), None, "channel_videos")
|
self._channel_videos(channel_id, Params::Shorts, None, "channel_shorts")
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a ordered list of videos from the specified tab of a YouTube channel
|
/// Get the livestreams from a YouTube channel
|
||||||
///
|
pub async fn channel_livestreams<S: AsRef<str>>(
|
||||||
/// This function does not return channel metadata.
|
|
||||||
pub async fn channel_videos_tab_order<S: AsRef<str>>(
|
|
||||||
&self,
|
&self,
|
||||||
channel_id: S,
|
channel_id: S,
|
||||||
tab: ChannelVideoTab,
|
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
||||||
order: ChannelOrder,
|
self._channel_videos(channel_id, Params::Live, None, "channel_livestreams")
|
||||||
) -> Result<Paginator<VideoItem>, Error> {
|
.await
|
||||||
let visitor_data = match tab {
|
|
||||||
ChannelVideoTab::Shorts => Some(self.get_visitor_data().await?),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.continuation(
|
|
||||||
order_ctoken(channel_id.as_ref(), tab, order),
|
|
||||||
ContinuationEndpoint::Browse,
|
|
||||||
visitor_data.as_deref(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search the videos of a channel
|
/// Search the videos of a channel
|
||||||
|
@ -138,7 +101,7 @@ impl RustyPipeQuery {
|
||||||
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
||||||
self._channel_videos(
|
self._channel_videos(
|
||||||
channel_id,
|
channel_id,
|
||||||
ChannelTab::Search,
|
Params::Search,
|
||||||
Some(query.as_ref()),
|
Some(query.as_ref()),
|
||||||
"channel_search",
|
"channel_search",
|
||||||
)
|
)
|
||||||
|
@ -155,7 +118,7 @@ impl RustyPipeQuery {
|
||||||
let request_body = QChannel {
|
let request_body = QChannel {
|
||||||
context,
|
context,
|
||||||
browse_id: channel_id,
|
browse_id: channel_id,
|
||||||
params: ChannelTab::Playlists,
|
params: Params::Playlists,
|
||||||
query: None,
|
query: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -179,7 +142,7 @@ impl RustyPipeQuery {
|
||||||
let request_body = QChannel {
|
let request_body = QChannel {
|
||||||
context,
|
context,
|
||||||
browse_id: channel_id,
|
browse_id: channel_id,
|
||||||
params: ChannelTab::Info,
|
params: Params::Info,
|
||||||
query: None,
|
query: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -488,16 +451,16 @@ fn map_channel_content(
|
||||||
.or(tab.tab_renderer.content.section_list_renderer)
|
.or(tab.tab_renderer.content.section_list_renderer)
|
||||||
});
|
});
|
||||||
|
|
||||||
// YouTube may show the "Featured" tab if the requested tab is empty/does not exist
|
let content = match channel_content {
|
||||||
let content = if featured_tab {
|
Some(list) => list.contents,
|
||||||
MapResult::default()
|
None => {
|
||||||
} else {
|
// YouTube may show the "Featured" tab if the requested tab is empty/does not exist
|
||||||
match channel_content {
|
if featured_tab {
|
||||||
Some(list) => list.contents,
|
MapResult::default()
|
||||||
None => {
|
} else {
|
||||||
return Err(ExtractionError::InvalidData(
|
return Err(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
"could not extract content".into(),
|
"could not extract content",
|
||||||
))
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -532,47 +495,6 @@ fn combine_channel_data<T>(channel_data: Channel<()>, content: T) -> Channel<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the continuation token to fetch channel videos in the given order
|
|
||||||
fn order_ctoken(channel_id: &str, tab: ChannelVideoTab, order: ChannelOrder) -> String {
|
|
||||||
_order_ctoken(
|
|
||||||
channel_id,
|
|
||||||
tab,
|
|
||||||
order,
|
|
||||||
&format!("\n${}", util::random_uuid()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the continuation token to fetch channel videos in the given order
|
|
||||||
/// (fixed targetId for testing)
|
|
||||||
fn _order_ctoken(
|
|
||||||
channel_id: &str,
|
|
||||||
tab: ChannelVideoTab,
|
|
||||||
order: ChannelOrder,
|
|
||||||
target_id: &str,
|
|
||||||
) -> String {
|
|
||||||
let mut pb_tab = ProtoBuilder::new();
|
|
||||||
pb_tab.string(2, target_id);
|
|
||||||
pb_tab.varint(3, order as u64);
|
|
||||||
|
|
||||||
let mut pb_3 = ProtoBuilder::new();
|
|
||||||
pb_3.embedded(tab.order_ctoken_id(), pb_tab);
|
|
||||||
|
|
||||||
let mut pb_110 = ProtoBuilder::new();
|
|
||||||
pb_110.embedded(3, pb_3);
|
|
||||||
|
|
||||||
let mut pbi = ProtoBuilder::new();
|
|
||||||
pbi.embedded(110, pb_110);
|
|
||||||
|
|
||||||
let mut pb_80226972 = ProtoBuilder::new();
|
|
||||||
pb_80226972.string(2, channel_id);
|
|
||||||
pb_80226972.string(3, &pbi.to_base64());
|
|
||||||
|
|
||||||
let mut pb = ProtoBuilder::new();
|
|
||||||
pb.embedded(80226972, pb_80226972);
|
|
||||||
|
|
||||||
pb.to_base64()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{fs::File, io::BufReader};
|
use std::{fs::File, io::BufReader};
|
||||||
|
@ -583,13 +505,11 @@ mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
client::{response, MapResponse},
|
client::{response, MapResponse},
|
||||||
model::{paginator::Paginator, Channel, ChannelInfo, PlaylistItem, VideoItem},
|
model::{paginator::Paginator, Channel, ChannelInfo, PlaylistItem, VideoItem},
|
||||||
param::{ChannelOrder, ChannelVideoTab, Language},
|
param::Language,
|
||||||
serializer::MapResult,
|
serializer::MapResult,
|
||||||
util::tests::TESTFILES,
|
util::tests::TESTFILES,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::_order_ctoken;
|
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::base("videos_base", "UC2DjFE7Xf11URZqWBigcVOQ")]
|
#[case::base("videos_base", "UC2DjFE7Xf11URZqWBigcVOQ")]
|
||||||
#[case::music("videos_music", "UC_vmjW5e1xEHhYjY2a0kK1A")]
|
#[case::music("videos_music", "UC_vmjW5e1xEHhYjY2a0kK1A")]
|
||||||
|
@ -665,33 +585,4 @@ mod tests {
|
||||||
);
|
);
|
||||||
insta::assert_ron_snapshot!("map_channel_info", map_res.c);
|
insta::assert_ron_snapshot!("map_channel_info", map_res.c);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn order_ctoken() {
|
|
||||||
let channel_id = "UCXuqSBlHAE6Xw-yeJA0Tunw";
|
|
||||||
|
|
||||||
let videos_popular_token = _order_ctoken(
|
|
||||||
channel_id,
|
|
||||||
ChannelVideoTab::Videos,
|
|
||||||
ChannelOrder::Popular,
|
|
||||||
"\n$6461d7c8-0000-2040-87aa-089e0827e420",
|
|
||||||
);
|
|
||||||
assert_eq!(videos_popular_token, "4qmFsgJkEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaSDhnWXVHaXg2S2hJbUNpUTJORFl4WkRkak9DMHdNREF3TFRJd05EQXRPRGRoWVMwd09EbGxNRGd5TjJVME1qQVlBZyUzRCUzRA%3D%3D");
|
|
||||||
|
|
||||||
let shorts_popular_token = _order_ctoken(
|
|
||||||
channel_id,
|
|
||||||
ChannelVideoTab::Shorts,
|
|
||||||
ChannelOrder::Popular,
|
|
||||||
"\n$64679ffb-0000-26b3-a1bd-582429d2c794",
|
|
||||||
);
|
|
||||||
assert_eq!(shorts_popular_token, "4qmFsgJkEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaSDhnWXVHaXhTS2hJbUNpUTJORFkzT1dabVlpMHdNREF3TFRJMllqTXRZVEZpWkMwMU9ESTBNamxrTW1NM09UUVlBZyUzRCUzRA%3D%3D");
|
|
||||||
|
|
||||||
let live_popular_token = _order_ctoken(
|
|
||||||
channel_id,
|
|
||||||
ChannelVideoTab::Live,
|
|
||||||
ChannelOrder::Popular,
|
|
||||||
"\n$64693069-0000-2a1e-8c7d-582429bd5ba8",
|
|
||||||
);
|
|
||||||
assert_eq!(live_popular_token, "4qmFsgJkEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaSDhnWXVHaXh5S2hJbUNpUTJORFk1TXpBMk9TMHdNREF3TFRKaE1XVXRPR00zWkMwMU9ESTBNamxpWkRWaVlUZ1lBZyUzRCUzRA%3D%3D");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,7 +191,6 @@ const CONSENT_COOKIE_YES: &str = "YES+yt.462272069.de+FX+";
|
||||||
const YOUTUBEI_V1_URL: &str = "https://www.youtube.com/youtubei/v1/";
|
const YOUTUBEI_V1_URL: &str = "https://www.youtube.com/youtubei/v1/";
|
||||||
const YOUTUBEI_V1_GAPIS_URL: &str = "https://youtubei.googleapis.com/youtubei/v1/";
|
const YOUTUBEI_V1_GAPIS_URL: &str = "https://youtubei.googleapis.com/youtubei/v1/";
|
||||||
const YOUTUBE_MUSIC_V1_URL: &str = "https://music.youtube.com/youtubei/v1/";
|
const YOUTUBE_MUSIC_V1_URL: &str = "https://music.youtube.com/youtubei/v1/";
|
||||||
const YOUTUBE_HOME_URL: &str = "https://www.youtube.com/";
|
|
||||||
const YOUTUBE_MUSIC_HOME_URL: &str = "https://music.youtube.com/";
|
const YOUTUBE_MUSIC_HOME_URL: &str = "https://music.youtube.com/";
|
||||||
|
|
||||||
const DISABLE_PRETTY_PRINT_PARAMETER: &str = "&prettyPrint=false";
|
const DISABLE_PRETTY_PRINT_PARAMETER: &str = "&prettyPrint=false";
|
||||||
|
@ -390,17 +389,21 @@ impl RustyPipeBuilder {
|
||||||
Box::new(FileStorage::new(cache_file))
|
Box::new(FileStorage::new(cache_file))
|
||||||
});
|
});
|
||||||
|
|
||||||
let cdata = storage
|
let cdata = if let Some(storage) = &storage {
|
||||||
.as_ref()
|
if let Some(data) = storage.read() {
|
||||||
.and_then(|storage| storage.read())
|
match serde_json::from_str::<CacheData>(&data) {
|
||||||
.and_then(|data| match serde_json::from_str::<CacheData>(&data) {
|
Ok(data) => data,
|
||||||
Ok(data) => Some(data),
|
Err(e) => {
|
||||||
Err(e) => {
|
log::error!("Could not deserialize cache. Error: {}", e);
|
||||||
log::error!("Could not deserialize cache. Error: {}", e);
|
CacheData::default()
|
||||||
None
|
}
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
.unwrap_or_default();
|
CacheData::default()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CacheData::default()
|
||||||
|
};
|
||||||
|
|
||||||
RustyPipe {
|
RustyPipe {
|
||||||
inner: Arc::new(RustyPipeRef {
|
inner: Arc::new(RustyPipeRef {
|
||||||
|
@ -645,7 +648,7 @@ impl RustyPipe {
|
||||||
self.extract_client_version(
|
self.extract_client_version(
|
||||||
Some("https://www.youtube.com/sw.js"),
|
Some("https://www.youtube.com/sw.js"),
|
||||||
"https://www.youtube.com/results?search_query=",
|
"https://www.youtube.com/results?search_query=",
|
||||||
YOUTUBE_HOME_URL,
|
"https://www.youtube.com",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -655,8 +658,8 @@ impl RustyPipe {
|
||||||
async fn extract_music_client_version(&self) -> Result<String, Error> {
|
async fn extract_music_client_version(&self) -> Result<String, Error> {
|
||||||
self.extract_client_version(
|
self.extract_client_version(
|
||||||
Some("https://music.youtube.com/sw.js"),
|
Some("https://music.youtube.com/sw.js"),
|
||||||
YOUTUBE_MUSIC_HOME_URL,
|
"https://music.youtube.com",
|
||||||
YOUTUBE_MUSIC_HOME_URL,
|
"https://music.youtube.com",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -813,7 +816,7 @@ impl RustyPipe {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_visitor_data(&self) -> Result<String, Error> {
|
async fn get_ytm_visitor_data(&self) -> Result<String, Error> {
|
||||||
log::debug!("getting YTM visitor data");
|
log::debug!("getting YTM visitor data");
|
||||||
let resp = self.inner.http.get(YOUTUBE_MUSIC_HOME_URL).send().await?;
|
let resp = self.inner.http.get(YOUTUBE_MUSIC_HOME_URL).send().await?;
|
||||||
|
|
||||||
|
@ -904,7 +907,7 @@ impl RustyPipeQuery {
|
||||||
client_name: "WEB",
|
client_name: "WEB",
|
||||||
client_version: Cow::Owned(self.client.get_desktop_client_version().await),
|
client_version: Cow::Owned(self.client.get_desktop_client_version().await),
|
||||||
platform: "DESKTOP",
|
platform: "DESKTOP",
|
||||||
original_url: Some(YOUTUBE_HOME_URL),
|
original_url: Some("https://www.youtube.com/"),
|
||||||
visitor_data,
|
visitor_data,
|
||||||
hl,
|
hl,
|
||||||
gl,
|
gl,
|
||||||
|
@ -919,7 +922,7 @@ impl RustyPipeQuery {
|
||||||
client_name: "WEB_REMIX",
|
client_name: "WEB_REMIX",
|
||||||
client_version: Cow::Owned(self.client.get_music_client_version().await),
|
client_version: Cow::Owned(self.client.get_music_client_version().await),
|
||||||
platform: "DESKTOP",
|
platform: "DESKTOP",
|
||||||
original_url: Some(YOUTUBE_MUSIC_HOME_URL),
|
original_url: Some("https://music.youtube.com/"),
|
||||||
visitor_data,
|
visitor_data,
|
||||||
hl,
|
hl,
|
||||||
gl,
|
gl,
|
||||||
|
@ -943,7 +946,7 @@ impl RustyPipeQuery {
|
||||||
request: Some(RequestYT::default()),
|
request: Some(RequestYT::default()),
|
||||||
user: User::default(),
|
user: User::default(),
|
||||||
third_party: Some(ThirdParty {
|
third_party: Some(ThirdParty {
|
||||||
embed_url: YOUTUBE_HOME_URL,
|
embed_url: "https://www.youtube.com/",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
ClientType::Android => YTContext {
|
ClientType::Android => YTContext {
|
||||||
|
@ -994,8 +997,8 @@ impl RustyPipeQuery {
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{YOUTUBEI_V1_URL}{endpoint}?key={DESKTOP_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
"{YOUTUBEI_V1_URL}{endpoint}?key={DESKTOP_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
||||||
))
|
))
|
||||||
.header(header::ORIGIN, YOUTUBE_HOME_URL)
|
.header(header::ORIGIN, "https://www.youtube.com")
|
||||||
.header(header::REFERER, YOUTUBE_HOME_URL)
|
.header(header::REFERER, "https://www.youtube.com")
|
||||||
.header(header::COOKIE, self.client.inner.consent_cookie.to_owned())
|
.header(header::COOKIE, self.client.inner.consent_cookie.to_owned())
|
||||||
.header("X-YouTube-Client-Name", "1")
|
.header("X-YouTube-Client-Name", "1")
|
||||||
.header(
|
.header(
|
||||||
|
@ -1009,8 +1012,8 @@ impl RustyPipeQuery {
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{YOUTUBE_MUSIC_V1_URL}{endpoint}?key={DESKTOP_MUSIC_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
"{YOUTUBE_MUSIC_V1_URL}{endpoint}?key={DESKTOP_MUSIC_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
||||||
))
|
))
|
||||||
.header(header::ORIGIN, YOUTUBE_MUSIC_HOME_URL)
|
.header(header::ORIGIN, "https://music.youtube.com")
|
||||||
.header(header::REFERER, YOUTUBE_MUSIC_HOME_URL)
|
.header(header::REFERER, "https://music.youtube.com")
|
||||||
.header(header::COOKIE, self.client.inner.consent_cookie.to_owned())
|
.header(header::COOKIE, self.client.inner.consent_cookie.to_owned())
|
||||||
.header("X-YouTube-Client-Name", "67")
|
.header("X-YouTube-Client-Name", "67")
|
||||||
.header(
|
.header(
|
||||||
|
@ -1024,8 +1027,8 @@ impl RustyPipeQuery {
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{YOUTUBEI_V1_URL}{endpoint}?key={DESKTOP_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
"{YOUTUBEI_V1_URL}{endpoint}?key={DESKTOP_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
||||||
))
|
))
|
||||||
.header(header::ORIGIN, YOUTUBE_HOME_URL)
|
.header(header::ORIGIN, "https://www.youtube.com")
|
||||||
.header(header::REFERER, YOUTUBE_HOME_URL)
|
.header(header::REFERER, "https://www.youtube.com")
|
||||||
.header("X-YouTube-Client-Name", "1")
|
.header("X-YouTube-Client-Name", "1")
|
||||||
.header("X-YouTube-Client-Version", TVHTML5_CLIENT_VERSION),
|
.header("X-YouTube-Client-Version", TVHTML5_CLIENT_VERSION),
|
||||||
ClientType::Android => self
|
ClientType::Android => self
|
||||||
|
@ -1061,11 +1064,11 @@ impl RustyPipeQuery {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a YouTube visitor data cookie, which is necessary for certain requests
|
/// Get a YouTube Music visitor data cookie, which is necessary for certain requests
|
||||||
async fn get_visitor_data(&self) -> Result<String, Error> {
|
async fn get_ytm_visitor_data(&self) -> Result<String, Error> {
|
||||||
match &self.opts.visitor_data {
|
match &self.opts.visitor_data {
|
||||||
Some(vd) => Ok(vd.to_owned()),
|
Some(vd) => Ok(vd.to_owned()),
|
||||||
None => self.client.get_visitor_data().await,
|
None => self.client.get_ytm_visitor_data().await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1305,9 +1308,9 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn t_get_visitor_data() {
|
fn t_get_ytm_visitor_data() {
|
||||||
let rp = RustyPipe::new();
|
let rp = RustyPipe::new();
|
||||||
let visitor_data = tokio_test::block_on(rp.get_visitor_data()).unwrap();
|
let visitor_data = tokio_test::block_on(rp.get_ytm_visitor_data()).unwrap();
|
||||||
assert!(visitor_data.ends_with("%3D"));
|
assert!(visitor_data.ends_with("%3D"));
|
||||||
assert_eq!(visitor_data.len(), 32)
|
assert_eq!(visitor_data.len(), 32)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ impl RustyPipeQuery {
|
||||||
) -> Result<MusicArtist, Error> {
|
) -> Result<MusicArtist, Error> {
|
||||||
let artist_id = artist_id.as_ref();
|
let artist_id = artist_id.as_ref();
|
||||||
let visitor_data = match all_albums {
|
let visitor_data = match all_albums {
|
||||||
true => Some(self.get_visitor_data().await?),
|
true => Some(self.get_ytm_visitor_data().await?),
|
||||||
false => None,
|
false => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ impl RustyPipeQuery {
|
||||||
radio_id: S,
|
radio_id: S,
|
||||||
) -> Result<Paginator<TrackItem>, Error> {
|
) -> Result<Paginator<TrackItem>, Error> {
|
||||||
let radio_id = radio_id.as_ref();
|
let radio_id = radio_id.as_ref();
|
||||||
let visitor_data = self.get_visitor_data().await?;
|
let visitor_data = self.get_ytm_visitor_data().await?;
|
||||||
let context = self
|
let context = self
|
||||||
.get_context(ClientType::DesktopMusic, true, Some(&visitor_data))
|
.get_context(ClientType::DesktopMusic, true, Some(&visitor_data))
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -102,12 +102,8 @@ impl MapResponse<Paginator<YouTubeItem>> for response::Continuation {
|
||||||
.and_then(|actions| {
|
.and_then(|actions| {
|
||||||
actions
|
actions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
.next()
|
||||||
.map(|action| action.append_continuation_items_action.continuation_items)
|
.map(|action| action.append_continuation_items_action.continuation_items)
|
||||||
.reduce(|mut acc, mut items| {
|
|
||||||
acc.c.append(&mut items.c);
|
|
||||||
acc.warnings.append(&mut items.warnings);
|
|
||||||
acc
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
self.continuation_contents
|
self.continuation_contents
|
||||||
|
|
|
@ -12,8 +12,8 @@ use crate::{
|
||||||
deobfuscate::Deobfuscator,
|
deobfuscate::Deobfuscator,
|
||||||
error::{internal::DeobfError, Error, ExtractionError, UnavailabilityReason},
|
error::{internal::DeobfError, Error, ExtractionError, UnavailabilityReason},
|
||||||
model::{
|
model::{
|
||||||
traits::QualityOrd, AudioCodec, AudioFormat, AudioStream, AudioTrack, ChannelId, Frameset,
|
traits::QualityOrd, AudioCodec, AudioFormat, AudioStream, AudioTrack, ChannelId, Subtitle,
|
||||||
Subtitle, VideoCodec, VideoFormat, VideoPlayer, VideoPlayerDetails, VideoStream,
|
VideoCodec, VideoFormat, VideoPlayer, VideoPlayerDetails, VideoStream,
|
||||||
},
|
},
|
||||||
param::Language,
|
param::Language,
|
||||||
util,
|
util,
|
||||||
|
@ -313,54 +313,6 @@ impl MapResponse<VideoPlayer> for response::Player {
|
||||||
.collect()
|
.collect()
|
||||||
});
|
});
|
||||||
|
|
||||||
let preview_frames = self
|
|
||||||
.storyboards
|
|
||||||
.and_then(|sb| {
|
|
||||||
let spec = sb.player_storyboard_spec_renderer.spec;
|
|
||||||
let mut spec_parts = spec.split('|');
|
|
||||||
let url_tmpl = spec_parts.next()?;
|
|
||||||
|
|
||||||
Some(
|
|
||||||
spec_parts
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, fs_spec)| {
|
|
||||||
// Example: 160#90#131#5#5#2000#M$M#rs$AOn4CLCV3TJ2Nty5fbw2r-Lqg4VDOZcVvQ
|
|
||||||
let mut parts = fs_spec.split('#');
|
|
||||||
|
|
||||||
let frame_width = parts.next()?.parse().ok()?;
|
|
||||||
let frame_height = parts.next()?.parse().ok()?;
|
|
||||||
let total_count = parts.next()?.parse().ok()?;
|
|
||||||
let frames_per_page_x = parts.next()?.parse().ok()?;
|
|
||||||
let frames_per_page_y = parts.next()?.parse().ok()?;
|
|
||||||
let duration_per_frame = parts.next()?.parse().ok()?;
|
|
||||||
|
|
||||||
let n = parts.next()?;
|
|
||||||
let sigh = parts.next()?;
|
|
||||||
|
|
||||||
let url = url_tmpl.replace("$L", &i.to_string()).replace("$N", n)
|
|
||||||
+ "&sigh="
|
|
||||||
+ sigh;
|
|
||||||
|
|
||||||
let sprite_count = ((total_count as f64)
|
|
||||||
/ (frames_per_page_x * frames_per_page_y) as f64)
|
|
||||||
.ceil() as u32;
|
|
||||||
|
|
||||||
Some(Frameset {
|
|
||||||
url_template: url,
|
|
||||||
frame_width,
|
|
||||||
frame_height,
|
|
||||||
page_count: sprite_count,
|
|
||||||
total_count,
|
|
||||||
duration_per_frame,
|
|
||||||
frames_per_page_x,
|
|
||||||
frames_per_page_y,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
Ok(MapResult {
|
Ok(MapResult {
|
||||||
c: VideoPlayer {
|
c: VideoPlayer {
|
||||||
details: video_info,
|
details: video_info,
|
||||||
|
@ -371,7 +323,6 @@ impl MapResponse<VideoPlayer> for response::Player {
|
||||||
expires_in_seconds: streaming_data.expires_in_seconds,
|
expires_in_seconds: streaming_data.expires_in_seconds,
|
||||||
hls_manifest_url: streaming_data.hls_manifest_url,
|
hls_manifest_url: streaming_data.hls_manifest_url,
|
||||||
dash_manifest_url: streaming_data.dash_manifest_url,
|
dash_manifest_url: streaming_data.dash_manifest_url,
|
||||||
preview_frames,
|
|
||||||
visitor_data: self.response_context.visitor_data,
|
visitor_data: self.response_context.visitor_data,
|
||||||
},
|
},
|
||||||
warnings,
|
warnings,
|
||||||
|
@ -426,18 +377,12 @@ fn deobf_nsig(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UrlMapRes {
|
|
||||||
url: String,
|
|
||||||
throttled: bool,
|
|
||||||
xtags: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_url(
|
fn map_url(
|
||||||
url: &Option<String>,
|
url: &Option<String>,
|
||||||
signature_cipher: &Option<String>,
|
signature_cipher: &Option<String>,
|
||||||
deobf: &Deobfuscator,
|
deobf: &Deobfuscator,
|
||||||
last_nsig: &mut [String; 2],
|
last_nsig: &mut [String; 2],
|
||||||
) -> MapResult<Option<UrlMapRes>> {
|
) -> MapResult<Option<(String, bool)>> {
|
||||||
let x = match url {
|
let x = match url {
|
||||||
Some(url) => util::url_to_params(url).map_err(|_| format!("Could not parse url `{url}`")),
|
Some(url) => util::url_to_params(url).map_err(|_| format!("Could not parse url `{url}`")),
|
||||||
None => match signature_cipher {
|
None => match signature_cipher {
|
||||||
|
@ -469,11 +414,7 @@ fn map_url(
|
||||||
|
|
||||||
match Url::parse_with_params(url_base.as_str(), url_params.iter()) {
|
match Url::parse_with_params(url_base.as_str(), url_params.iter()) {
|
||||||
Ok(url) => MapResult {
|
Ok(url) => MapResult {
|
||||||
c: Some(UrlMapRes {
|
c: Some((url.to_string(), throttled)),
|
||||||
url: url.to_string(),
|
|
||||||
throttled,
|
|
||||||
xtags: url_params.get("xtags").cloned(),
|
|
||||||
}),
|
|
||||||
warnings,
|
warnings,
|
||||||
},
|
},
|
||||||
Err(_) => MapResult {
|
Err(_) => MapResult {
|
||||||
|
@ -514,9 +455,9 @@ fn map_video_stream(
|
||||||
let map_res = map_url(&f.url, &f.signature_cipher, deobf, last_nsig);
|
let map_res = map_url(&f.url, &f.signature_cipher, deobf, last_nsig);
|
||||||
|
|
||||||
match map_res.c {
|
match map_res.c {
|
||||||
Some(url) => MapResult {
|
Some((url, throttled)) => MapResult {
|
||||||
c: Some(VideoStream {
|
c: Some(VideoStream {
|
||||||
url: url.url,
|
url,
|
||||||
itag: f.itag,
|
itag: f.itag,
|
||||||
bitrate: f.bitrate,
|
bitrate: f.bitrate,
|
||||||
average_bitrate: f.average_bitrate.unwrap_or(f.bitrate),
|
average_bitrate: f.average_bitrate.unwrap_or(f.bitrate),
|
||||||
|
@ -535,7 +476,7 @@ fn map_video_stream(
|
||||||
mime: f.mime_type.to_owned(),
|
mime: f.mime_type.to_owned(),
|
||||||
format,
|
format,
|
||||||
codec: get_video_codec(codecs),
|
codec: get_video_codec(codecs),
|
||||||
throttled: url.throttled,
|
throttled,
|
||||||
}),
|
}),
|
||||||
warnings: map_res.warnings,
|
warnings: map_res.warnings,
|
||||||
},
|
},
|
||||||
|
@ -551,6 +492,8 @@ fn map_audio_stream(
|
||||||
deobf: &Deobfuscator,
|
deobf: &Deobfuscator,
|
||||||
last_nsig: &mut [String; 2],
|
last_nsig: &mut [String; 2],
|
||||||
) -> MapResult<Option<AudioStream>> {
|
) -> MapResult<Option<AudioStream>> {
|
||||||
|
static LANG_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r#"^([a-z]{2,3})\."#).unwrap());
|
||||||
|
|
||||||
let (mtype, codecs) = match parse_mime(&f.mime_type) {
|
let (mtype, codecs) = match parse_mime(&f.mime_type) {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => {
|
None => {
|
||||||
|
@ -573,12 +516,11 @@ fn map_audio_stream(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let map_res = map_url(&f.url, &f.signature_cipher, deobf, last_nsig);
|
let map_res = map_url(&f.url, &f.signature_cipher, deobf, last_nsig);
|
||||||
let mut warnings = map_res.warnings;
|
|
||||||
|
|
||||||
match map_res.c {
|
match map_res.c {
|
||||||
Some(url) => MapResult {
|
Some((url, throttled)) => MapResult {
|
||||||
c: Some(AudioStream {
|
c: Some(AudioStream {
|
||||||
url: url.url,
|
url,
|
||||||
itag: f.itag,
|
itag: f.itag,
|
||||||
bitrate: f.bitrate,
|
bitrate: f.bitrate,
|
||||||
average_bitrate: f.average_bitrate.unwrap_or(f.bitrate),
|
average_bitrate: f.average_bitrate.unwrap_or(f.bitrate),
|
||||||
|
@ -591,14 +533,29 @@ fn map_audio_stream(
|
||||||
codec: get_audio_codec(codecs),
|
codec: get_audio_codec(codecs),
|
||||||
channels: f.audio_channels,
|
channels: f.audio_channels,
|
||||||
loudness_db: f.loudness_db,
|
loudness_db: f.loudness_db,
|
||||||
throttled: url.throttled,
|
throttled,
|
||||||
track: f
|
track: match f.audio_track {
|
||||||
.audio_track
|
Some(t) => {
|
||||||
.map(|t| map_audio_track(t, url.xtags, &mut warnings)),
|
let lang = LANG_PATTERN
|
||||||
|
.captures(&t.id)
|
||||||
|
.map(|m| m.get(1).unwrap().as_str().to_owned());
|
||||||
|
|
||||||
|
Some(AudioTrack {
|
||||||
|
id: t.id,
|
||||||
|
lang,
|
||||||
|
lang_name: t.display_name,
|
||||||
|
is_default: t.audio_is_default,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
warnings,
|
warnings: map_res.warnings,
|
||||||
|
},
|
||||||
|
None => MapResult {
|
||||||
|
c: None,
|
||||||
|
warnings: map_res.warnings,
|
||||||
},
|
},
|
||||||
None => MapResult { c: None, warnings },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -661,43 +618,6 @@ fn get_audio_codec(codecs: Vec<&str>) -> AudioCodec {
|
||||||
AudioCodec::Unknown
|
AudioCodec::Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_audio_track(
|
|
||||||
track: response::player::AudioTrack,
|
|
||||||
xtags: Option<String>,
|
|
||||||
warnings: &mut Vec<String>,
|
|
||||||
) -> AudioTrack {
|
|
||||||
let mut lang = None;
|
|
||||||
let mut track_type = None;
|
|
||||||
|
|
||||||
if let Some(xtags) = xtags {
|
|
||||||
xtags
|
|
||||||
.split(':')
|
|
||||||
.filter_map(|param| param.split_once('='))
|
|
||||||
.for_each(|(k, v)| match k {
|
|
||||||
"lang" => {
|
|
||||||
lang = Some(v.to_owned());
|
|
||||||
}
|
|
||||||
"acont" => match serde_plain::from_str(v) {
|
|
||||||
Ok(v) => {
|
|
||||||
track_type = Some(v);
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
warnings.push(format!("could not parse audio track type `{v}`"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioTrack {
|
|
||||||
id: track.id,
|
|
||||||
lang,
|
|
||||||
lang_name: track.display_name,
|
|
||||||
is_default: track.audio_is_default,
|
|
||||||
track_type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{fs::File, io::BufReader};
|
use std::{fs::File, io::BufReader};
|
||||||
|
@ -762,10 +682,10 @@ mod tests {
|
||||||
&deobf,
|
&deobf,
|
||||||
&mut last_nsig,
|
&mut last_nsig,
|
||||||
);
|
);
|
||||||
let url = map_res.c.unwrap();
|
let (url, throttled) = map_res.c.unwrap();
|
||||||
|
|
||||||
assert_eq!(url.url, "https://rr5---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=3781277&dur=229.301&ei=vb7nYvH5BMK8gAfBj7ToBQ&expire=1659376413&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-AB_BABwrXZJN428ZwDxq5ScPn2AbcGODnRlTVhCQ3mj2&initcwndbps=1588750&ip=2003%3Ade%3Aaf06%3A6300%3Ac750%3A1b77%3Ac74a%3A80e3&itag=251&keepalive=yes&lmt=1655510291473933&lsig=AG3C_xAwRQIgCKCGJ1iu4wlaGXy3jcJyU3inh9dr1FIfqYOZEG_MdmACIQCbungkQYFk7EhD6K2YvLaHFMjKOFWjw001_tLb0lPDtg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=hH&mime=audio%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5ednsl&ms=au%2Conr&mt=1659354538&mv=m&mvi=5&n=XzXGSfGusw6OCQ&ns=b_Mq_qlTFcSGlG9RpwpM9xQH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAPIsKd7-xi4xVHEC9gb__dU4hzfzsHEj9ytd3nt0gEceAiACJWBcw-wFEq9qir35bwKHJZxtQ9mOL7SKiVkLQNDa6A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-Khi831z8dTejFIRCvCEwx_6romtM&txp=4532434&vprv=1");
|
assert_eq!(url, "https://rr5---sn-h0jelnez.googlevideo.com/videoplayback?c=WEB&clen=3781277&dur=229.301&ei=vb7nYvH5BMK8gAfBj7ToBQ&expire=1659376413&fexp=24001373%2C24007246&fvip=5&gir=yes&id=o-AB_BABwrXZJN428ZwDxq5ScPn2AbcGODnRlTVhCQ3mj2&initcwndbps=1588750&ip=2003%3Ade%3Aaf06%3A6300%3Ac750%3A1b77%3Ac74a%3A80e3&itag=251&keepalive=yes&lmt=1655510291473933&lsig=AG3C_xAwRQIgCKCGJ1iu4wlaGXy3jcJyU3inh9dr1FIfqYOZEG_MdmACIQCbungkQYFk7EhD6K2YvLaHFMjKOFWjw001_tLb0lPDtg%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&mh=hH&mime=audio%2Fwebm&mm=31%2C26&mn=sn-h0jelnez%2Csn-4g5ednsl&ms=au%2Conr&mt=1659354538&mv=m&mvi=5&n=XzXGSfGusw6OCQ&ns=b_Mq_qlTFcSGlG9RpwpM9xQH&pl=37&rbqsm=fr&requiressl=yes&sig=AOq0QJ8wRQIhAPIsKd7-xi4xVHEC9gb__dU4hzfzsHEj9ytd3nt0gEceAiACJWBcw-wFEq9qir35bwKHJZxtQ9mOL7SKiVkLQNDa6A%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=lT-Khi831z8dTejFIRCvCEwx_6romtM&txp=4532434&vprv=1");
|
||||||
assert!(!url.throttled);
|
assert!(!throttled);
|
||||||
assert!(
|
assert!(
|
||||||
map_res.warnings.is_empty(),
|
map_res.warnings.is_empty(),
|
||||||
"deserialization/mapping warnings: {:?}",
|
"deserialization/mapping warnings: {:?}",
|
||||||
|
|
|
@ -219,7 +219,6 @@ pub(crate) struct Continuation {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct ContinuationActionWrap {
|
pub(crate) struct ContinuationActionWrap {
|
||||||
#[serde(alias = "reloadContinuationItemsCommand")]
|
|
||||||
pub append_continuation_items_action: ContinuationAction,
|
pub append_continuation_items_action: ContinuationAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ use serde_with::{json::JsonString, DefaultOnError};
|
||||||
use super::{ResponseContext, Thumbnails};
|
use super::{ResponseContext, Thumbnails};
|
||||||
use crate::serializer::{text::Text, MapResult};
|
use crate::serializer::{text::Text, MapResult};
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct Player {
|
pub(crate) struct Player {
|
||||||
|
@ -15,9 +14,6 @@ pub(crate) struct Player {
|
||||||
pub streaming_data: Option<StreamingData>,
|
pub streaming_data: Option<StreamingData>,
|
||||||
pub captions: Option<Captions>,
|
pub captions: Option<Captions>,
|
||||||
pub video_details: Option<VideoDetails>,
|
pub video_details: Option<VideoDetails>,
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
|
||||||
pub storyboards: Option<Storyboards>,
|
|
||||||
pub response_context: ResponseContext,
|
pub response_context: ResponseContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,15 +246,3 @@ pub(crate) struct VideoDetails {
|
||||||
pub author: String,
|
pub author: String,
|
||||||
pub is_live_content: bool,
|
pub is_live_content: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub(crate) struct Storyboards {
|
|
||||||
pub player_storyboard_spec_renderer: StoryboardRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub(crate) struct StoryboardRenderer {
|
|
||||||
pub spec: String,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_with::{
|
use serde_with::{
|
||||||
json::JsonString, rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError,
|
json::JsonString, rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError,
|
||||||
|
@ -428,17 +430,11 @@ impl<T> YouTubeListMapper<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_video(&mut self, video: VideoRenderer) -> VideoItem {
|
fn map_video(&mut self, video: VideoRenderer) -> VideoItem {
|
||||||
let is_live = video.thumbnail_overlays.is_live() || video.badges.is_live();
|
let mut tn_overlays = video.thumbnail_overlays;
|
||||||
let is_short = video.thumbnail_overlays.is_short();
|
|
||||||
|
|
||||||
let length_text = video.length_text.or_else(|| {
|
let length_text = video.length_text.or_else(|| {
|
||||||
video
|
tn_overlays
|
||||||
.thumbnail_overlays
|
.try_swap_remove(0)
|
||||||
.into_iter()
|
.map(|overlay| overlay.thumbnail_overlay_time_status_renderer.text)
|
||||||
.find(|ol| {
|
|
||||||
ol.thumbnail_overlay_time_status_renderer.style == TimeOverlayStyle::Default
|
|
||||||
})
|
|
||||||
.map(|ol| ol.thumbnail_overlay_time_status_renderer.text)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
VideoItem {
|
VideoItem {
|
||||||
|
@ -476,8 +472,8 @@ impl<T> YouTubeListMapper<T> {
|
||||||
view_count: video
|
view_count: video
|
||||||
.view_count_text
|
.view_count_text
|
||||||
.map(|txt| util::parse_numeric(&txt).unwrap_or_default()),
|
.map(|txt| util::parse_numeric(&txt).unwrap_or_default()),
|
||||||
is_live,
|
is_live: tn_overlays.is_live() || video.badges.is_live(),
|
||||||
is_short,
|
is_short: tn_overlays.is_short(),
|
||||||
is_upcoming: video.upcoming_event_data.is_some(),
|
is_upcoming: video.upcoming_event_data.is_some(),
|
||||||
short_description: video
|
short_description: video
|
||||||
.detailed_metadata_snippets
|
.detailed_metadata_snippets
|
||||||
|
@ -487,6 +483,9 @@ impl<T> YouTubeListMapper<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_short_video(&mut self, video: ReelItemRenderer, lang: Language) -> VideoItem {
|
fn map_short_video(&mut self, video: ReelItemRenderer, lang: Language) -> VideoItem {
|
||||||
|
static ACCESSIBILITY_SEP_REGEX: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(" [-\u{2013}] ").unwrap());
|
||||||
|
|
||||||
let pub_date_txt = video.navigation_endpoint.map(|n| {
|
let pub_date_txt = video.navigation_endpoint.map(|n| {
|
||||||
n.reel_watch_endpoint
|
n.reel_watch_endpoint
|
||||||
.overlay
|
.overlay
|
||||||
|
@ -500,7 +499,7 @@ impl<T> YouTubeListMapper<T> {
|
||||||
id: video.video_id,
|
id: video.video_id,
|
||||||
name: video.headline,
|
name: video.headline,
|
||||||
length: video.accessibility.and_then(|acc| {
|
length: video.accessibility.and_then(|acc| {
|
||||||
acc.rsplit(" - ").nth(1).and_then(|s| {
|
ACCESSIBILITY_SEP_REGEX.split(&acc).nth(1).and_then(|s| {
|
||||||
timeago::parse_video_duration_or_warn(self.lang, s, &mut self.warnings)
|
timeago::parse_video_duration_or_warn(self.lang, s, &mut self.warnings)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -168,7 +168,7 @@ Channel(
|
||||||
publish_date: "[date]",
|
publish_date: "[date]",
|
||||||
publish_date_txt: None,
|
publish_date_txt: None,
|
||||||
view_count: Some(94),
|
view_count: Some(94),
|
||||||
is_live: true,
|
is_live: false,
|
||||||
is_short: false,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
|
@ -209,7 +209,7 @@ Channel(
|
||||||
publish_date: "[date]",
|
publish_date: "[date]",
|
||||||
publish_date_txt: None,
|
publish_date_txt: None,
|
||||||
view_count: Some(381),
|
view_count: Some(381),
|
||||||
is_live: true,
|
is_live: false,
|
||||||
is_short: false,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
|
@ -414,7 +414,7 @@ Channel(
|
||||||
publish_date: "[date]",
|
publish_date: "[date]",
|
||||||
publish_date_txt: None,
|
publish_date_txt: None,
|
||||||
view_count: Some(2043),
|
view_count: Some(2043),
|
||||||
is_live: true,
|
is_live: false,
|
||||||
is_short: false,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
|
@ -783,7 +783,7 @@ Channel(
|
||||||
publish_date: "[date]",
|
publish_date: "[date]",
|
||||||
publish_date_txt: None,
|
publish_date_txt: None,
|
||||||
view_count: Some(4030),
|
view_count: Some(4030),
|
||||||
is_live: true,
|
is_live: false,
|
||||||
is_short: false,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
|
|
|
@ -141,7 +141,7 @@ Channel(
|
||||||
publish_date_txt: Some("1 day ago"),
|
publish_date_txt: Some("1 day ago"),
|
||||||
view_count: Some(443549),
|
view_count: Some(443549),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -167,7 +167,7 @@ Channel(
|
||||||
publish_date_txt: Some("2 days ago"),
|
publish_date_txt: Some("2 days ago"),
|
||||||
view_count: Some(1154962),
|
view_count: Some(1154962),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -234,7 +234,7 @@ Channel(
|
||||||
publish_date_txt: Some("6 days ago"),
|
publish_date_txt: Some("6 days ago"),
|
||||||
view_count: Some(1388173),
|
view_count: Some(1388173),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -260,7 +260,7 @@ Channel(
|
||||||
publish_date_txt: Some("7 days ago"),
|
publish_date_txt: Some("7 days ago"),
|
||||||
view_count: Some(1738301),
|
view_count: Some(1738301),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -286,7 +286,7 @@ Channel(
|
||||||
publish_date_txt: Some("9 days ago"),
|
publish_date_txt: Some("9 days ago"),
|
||||||
view_count: Some(1316594),
|
view_count: Some(1316594),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -353,7 +353,7 @@ Channel(
|
||||||
publish_date_txt: Some("11 days ago"),
|
publish_date_txt: Some("11 days ago"),
|
||||||
view_count: Some(1412213),
|
view_count: Some(1412213),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -379,7 +379,7 @@ Channel(
|
||||||
publish_date_txt: Some("13 days ago"),
|
publish_date_txt: Some("13 days ago"),
|
||||||
view_count: Some(1513305),
|
view_count: Some(1513305),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -405,7 +405,7 @@ Channel(
|
||||||
publish_date_txt: Some("2 weeks ago"),
|
publish_date_txt: Some("2 weeks ago"),
|
||||||
view_count: Some(8936223),
|
view_count: Some(8936223),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -472,7 +472,7 @@ Channel(
|
||||||
publish_date_txt: Some("2 weeks ago"),
|
publish_date_txt: Some("2 weeks ago"),
|
||||||
view_count: Some(2769717),
|
view_count: Some(2769717),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -539,7 +539,7 @@ Channel(
|
||||||
publish_date_txt: Some("3 weeks ago"),
|
publish_date_txt: Some("3 weeks ago"),
|
||||||
view_count: Some(572107),
|
view_count: Some(572107),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -565,7 +565,7 @@ Channel(
|
||||||
publish_date_txt: Some("3 weeks ago"),
|
publish_date_txt: Some("3 weeks ago"),
|
||||||
view_count: Some(1707132),
|
view_count: Some(1707132),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -591,7 +591,7 @@ Channel(
|
||||||
publish_date_txt: Some("3 weeks ago"),
|
publish_date_txt: Some("3 weeks ago"),
|
||||||
view_count: Some(933094),
|
view_count: Some(933094),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -617,7 +617,7 @@ Channel(
|
||||||
publish_date_txt: Some("1 month ago"),
|
publish_date_txt: Some("1 month ago"),
|
||||||
view_count: Some(5985184),
|
view_count: Some(5985184),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -643,7 +643,7 @@ Channel(
|
||||||
publish_date_txt: Some("1 month ago"),
|
publish_date_txt: Some("1 month ago"),
|
||||||
view_count: Some(14741387),
|
view_count: Some(14741387),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -669,7 +669,7 @@ Channel(
|
||||||
publish_date_txt: Some("1 month ago"),
|
publish_date_txt: Some("1 month ago"),
|
||||||
view_count: Some(2511322),
|
view_count: Some(2511322),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -695,7 +695,7 @@ Channel(
|
||||||
publish_date_txt: Some("1 month ago"),
|
publish_date_txt: Some("1 month ago"),
|
||||||
view_count: Some(2364408),
|
view_count: Some(2364408),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -762,7 +762,7 @@ Channel(
|
||||||
publish_date_txt: Some("1 month ago"),
|
publish_date_txt: Some("1 month ago"),
|
||||||
view_count: Some(1947627),
|
view_count: Some(1947627),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -788,7 +788,7 @@ Channel(
|
||||||
publish_date_txt: Some("1 month ago"),
|
publish_date_txt: Some("1 month ago"),
|
||||||
view_count: Some(4763839),
|
view_count: Some(4763839),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -814,7 +814,7 @@ Channel(
|
||||||
publish_date_txt: Some("1 month ago"),
|
publish_date_txt: Some("1 month ago"),
|
||||||
view_count: Some(1915695),
|
view_count: Some(1915695),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -840,7 +840,7 @@ Channel(
|
||||||
publish_date_txt: Some("1 month ago"),
|
publish_date_txt: Some("1 month ago"),
|
||||||
view_count: Some(7268944),
|
view_count: Some(7268944),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -866,7 +866,7 @@ Channel(
|
||||||
publish_date_txt: Some("1 month ago"),
|
publish_date_txt: Some("1 month ago"),
|
||||||
view_count: Some(2539103),
|
view_count: Some(2539103),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -892,7 +892,7 @@ Channel(
|
||||||
publish_date_txt: Some("2 months ago"),
|
publish_date_txt: Some("2 months ago"),
|
||||||
view_count: Some(5545680),
|
view_count: Some(5545680),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -918,7 +918,7 @@ Channel(
|
||||||
publish_date_txt: Some("2 months ago"),
|
publish_date_txt: Some("2 months ago"),
|
||||||
view_count: Some(2202314),
|
view_count: Some(2202314),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
@ -985,7 +985,7 @@ Channel(
|
||||||
publish_date_txt: Some("2 months ago"),
|
publish_date_txt: Some("2 months ago"),
|
||||||
view_count: Some(6443699),
|
view_count: Some(6443699),
|
||||||
is_live: false,
|
is_live: false,
|
||||||
is_short: true,
|
is_short: false,
|
||||||
is_upcoming: false,
|
is_upcoming: false,
|
||||||
short_description: None,
|
short_description: None,
|
||||||
),
|
),
|
||||||
|
|
|
@ -450,37 +450,5 @@ VideoPlayer(
|
||||||
expires_in_seconds: 21540,
|
expires_in_seconds: 21540,
|
||||||
hls_manifest_url: None,
|
hls_manifest_url: None,
|
||||||
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYtOPEYSBgQeHmqbwAQ/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr5---sn-h0jeenek.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jeenek%2Csn-h0jelnez/ms/au%2Crdu/mv/m/mvi/5/pl/37/hfr/1/as/fmp4_audio_clear%2Cfmp4_sd_hd_clear/initcwndbps/1527500/vprv/1/mt/1659459429/fvip/4/itag_bl/376%2C377%2C384%2C385%2C612%2C613%2C617%2C619%2C623%2C628%2C655%2C656%2C660%2C662%2C666%2C671/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cvprv%2Citag/sig/AOq0QJ8wRAIgMm4a_MIHA3YUszKeruSy3exs5JwNjJAyLAwxL0yPdNMCIANb9GDMSTp_NT-PPhbvYMwRULJ5a9BO6MYD9FuWprC1/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgETSOwhwWVMy7gmrFXZlJu655ToLzSwOEsT16oRyrWhACIQDkvOEw1fImz5omu4iVIRNFe-z-JC9v8WUyx281dW2NOw%3D%3D"),
|
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYtOPEYSBgQeHmqbwAQ/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr5---sn-h0jeenek.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jeenek%2Csn-h0jelnez/ms/au%2Crdu/mv/m/mvi/5/pl/37/hfr/1/as/fmp4_audio_clear%2Cfmp4_sd_hd_clear/initcwndbps/1527500/vprv/1/mt/1659459429/fvip/4/itag_bl/376%2C377%2C384%2C385%2C612%2C613%2C617%2C619%2C623%2C628%2C655%2C656%2C660%2C662%2C666%2C671/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cvprv%2Citag/sig/AOq0QJ8wRAIgMm4a_MIHA3YUszKeruSy3exs5JwNjJAyLAwxL0yPdNMCIANb9GDMSTp_NT-PPhbvYMwRULJ5a9BO6MYD9FuWprC1/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgETSOwhwWVMy7gmrFXZlJu655ToLzSwOEsT16oRyrWhACIQDkvOEw1fImz5omu4iVIRNFe-z-JC9v8WUyx281dW2NOw%3D%3D"),
|
||||||
preview_frames: [
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L0/default.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLAXobPyrylgm8IEvjlZzqYTiPe1Ow",
|
|
||||||
frame_width: 48,
|
|
||||||
frame_height: 27,
|
|
||||||
page_count: 1,
|
|
||||||
total_count: 100,
|
|
||||||
duration_per_frame: 0,
|
|
||||||
frames_per_page_x: 10,
|
|
||||||
frames_per_page_y: 10,
|
|
||||||
),
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L1/M$M.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLCWd3ylPF7ViQFBu5RUODMcusr_5g",
|
|
||||||
frame_width: 80,
|
|
||||||
frame_height: 45,
|
|
||||||
page_count: 1,
|
|
||||||
total_count: 83,
|
|
||||||
duration_per_frame: 2000,
|
|
||||||
frames_per_page_x: 10,
|
|
||||||
frames_per_page_y: 10,
|
|
||||||
),
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L2/M$M.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLA6xat5cfw0e3EX_5SW-TPwkmExxA",
|
|
||||||
frame_width: 160,
|
|
||||||
frame_height: 90,
|
|
||||||
page_count: 4,
|
|
||||||
total_count: 83,
|
|
||||||
duration_per_frame: 2000,
|
|
||||||
frames_per_page_x: 5,
|
|
||||||
frames_per_page_y: 5,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
visitor_data: Some("Cgt2aHFtQU5YZFBvYyirsaWXBg%3D%3D"),
|
visitor_data: Some("Cgt2aHFtQU5YZFBvYyirsaWXBg%3D%3D"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -569,37 +569,5 @@ VideoPlayer(
|
||||||
expires_in_seconds: 21540,
|
expires_in_seconds: 21540,
|
||||||
hls_manifest_url: None,
|
hls_manifest_url: None,
|
||||||
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYtq3BJCX1gKVyJGQDg/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C26/mn/sn-h0jelnez%2Csn-4g5edn6k/ms/au%2Conr/mv/m/mvi/4/pl/37/hfr/all/as/fmp4_audio_clear%2Cwebm_audio_clear%2Cwebm2_audio_clear%2Cfmp4_sd_hd_clear%2Cwebm2_sd_hd_clear/initcwndbps/1513750/spc/lT-KhrZGE2opztWyVdAtyUNlb8dXPDs/vprv/1/mt/1659459429/fvip/4/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cspc%2Cvprv%2Citag/sig/AOq0QJ8wRgIhAPEjHK19PKVHqQeia6WF4qubuMYk74LGi8F8lk5ZMPkFAiEAsaB2pKQWBvuPnNUnbdQXHc-izgsHJUP793woC2xNJlg%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgOY4xu4H9wqPVZ7vF2i0hFcOnqrur1XGoA43a7ZEuuSUCIQCyPxBKXUQrKFmknNEGpX5GSWySKgMw_xHBikWpKpKwvg%3D%3D"),
|
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYtq3BJCX1gKVyJGQDg/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C26/mn/sn-h0jelnez%2Csn-4g5edn6k/ms/au%2Conr/mv/m/mvi/4/pl/37/hfr/all/as/fmp4_audio_clear%2Cwebm_audio_clear%2Cwebm2_audio_clear%2Cfmp4_sd_hd_clear%2Cwebm2_sd_hd_clear/initcwndbps/1513750/spc/lT-KhrZGE2opztWyVdAtyUNlb8dXPDs/vprv/1/mt/1659459429/fvip/4/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cspc%2Cvprv%2Citag/sig/AOq0QJ8wRgIhAPEjHK19PKVHqQeia6WF4qubuMYk74LGi8F8lk5ZMPkFAiEAsaB2pKQWBvuPnNUnbdQXHc-izgsHJUP793woC2xNJlg%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgOY4xu4H9wqPVZ7vF2i0hFcOnqrur1XGoA43a7ZEuuSUCIQCyPxBKXUQrKFmknNEGpX5GSWySKgMw_xHBikWpKpKwvg%3D%3D"),
|
||||||
preview_frames: [
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L0/default.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLAXobPyrylgm8IEvjlZzqYTiPe1Ow",
|
|
||||||
frame_width: 48,
|
|
||||||
frame_height: 27,
|
|
||||||
page_count: 1,
|
|
||||||
total_count: 100,
|
|
||||||
duration_per_frame: 0,
|
|
||||||
frames_per_page_x: 10,
|
|
||||||
frames_per_page_y: 10,
|
|
||||||
),
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L1/M$M.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLCWd3ylPF7ViQFBu5RUODMcusr_5g",
|
|
||||||
frame_width: 80,
|
|
||||||
frame_height: 45,
|
|
||||||
page_count: 1,
|
|
||||||
total_count: 83,
|
|
||||||
duration_per_frame: 2000,
|
|
||||||
frames_per_page_x: 10,
|
|
||||||
frames_per_page_y: 10,
|
|
||||||
),
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L2/M$M.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLA6xat5cfw0e3EX_5SW-TPwkmExxA",
|
|
||||||
frame_width: 160,
|
|
||||||
frame_height: 90,
|
|
||||||
page_count: 4,
|
|
||||||
total_count: 83,
|
|
||||||
duration_per_frame: 2000,
|
|
||||||
frames_per_page_x: 5,
|
|
||||||
frames_per_page_y: 5,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
visitor_data: Some("CgtoS1pCMVJTNUJISSirsaWXBg%3D%3D"),
|
visitor_data: Some("CgtoS1pCMVJTNUJISSirsaWXBg%3D%3D"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -387,37 +387,5 @@ VideoPlayer(
|
||||||
expires_in_seconds: 21540,
|
expires_in_seconds: 21540,
|
||||||
hls_manifest_url: None,
|
hls_manifest_url: None,
|
||||||
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659487474/ei/knDpYub6BojEgAf6jbLgDw/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr5---sn-h0jeenek.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jeenek%2Csn-h0jelnez/ms/au%2Crdu/mv/m/mvi/5/pl/37/hfr/all/as/fmp4_audio_clear%2Cwebm_audio_clear%2Cwebm2_audio_clear%2Cfmp4_sd_hd_clear%2Cwebm2_sd_hd_clear/initcwndbps/1418750/spc/lT-Khox4YuJQ2wmH79zYALRvsWTPCUc/vprv/1/mt/1659465669/fvip/4/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cspc%2Cvprv%2Citag/sig/AOq0QJ8wRAIgErABhAEaoKHUDu9dDbpxE_8gR4b8WWAi61fnu8UKnuICIEYrEKcHvqHdO4V3R7cvSGwi_HGH34IlQsKbziOfMBov/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgJxHmH0Sxo3cY_pW_ZzQ3hW9-7oz6K_pZWcUdrDDQ2sQCIQDJYNINQwLgKelgbO3CZYx7sMxdUAFpWdokmRBQ77vwvw%3D%3D"),
|
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659487474/ei/knDpYub6BojEgAf6jbLgDw/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr5---sn-h0jeenek.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jeenek%2Csn-h0jelnez/ms/au%2Crdu/mv/m/mvi/5/pl/37/hfr/all/as/fmp4_audio_clear%2Cwebm_audio_clear%2Cwebm2_audio_clear%2Cfmp4_sd_hd_clear%2Cwebm2_sd_hd_clear/initcwndbps/1418750/spc/lT-Khox4YuJQ2wmH79zYALRvsWTPCUc/vprv/1/mt/1659465669/fvip/4/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cspc%2Cvprv%2Citag/sig/AOq0QJ8wRAIgErABhAEaoKHUDu9dDbpxE_8gR4b8WWAi61fnu8UKnuICIEYrEKcHvqHdO4V3R7cvSGwi_HGH34IlQsKbziOfMBov/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgJxHmH0Sxo3cY_pW_ZzQ3hW9-7oz6K_pZWcUdrDDQ2sQCIQDJYNINQwLgKelgbO3CZYx7sMxdUAFpWdokmRBQ77vwvw%3D%3D"),
|
||||||
preview_frames: [
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L0/default.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLAXobPyrylgm8IEvjlZzqYTiPe1Ow",
|
|
||||||
frame_width: 48,
|
|
||||||
frame_height: 27,
|
|
||||||
page_count: 1,
|
|
||||||
total_count: 100,
|
|
||||||
duration_per_frame: 0,
|
|
||||||
frames_per_page_x: 10,
|
|
||||||
frames_per_page_y: 10,
|
|
||||||
),
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L1/M$M.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLCWd3ylPF7ViQFBu5RUODMcusr_5g",
|
|
||||||
frame_width: 80,
|
|
||||||
frame_height: 45,
|
|
||||||
page_count: 1,
|
|
||||||
total_count: 83,
|
|
||||||
duration_per_frame: 2000,
|
|
||||||
frames_per_page_x: 10,
|
|
||||||
frames_per_page_y: 10,
|
|
||||||
),
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L2/M$M.jpg?sqp=-oaymwENSDfyq4qpAwVwAcABBqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLA6xat5cfw0e3EX_5SW-TPwkmExxA",
|
|
||||||
frame_width: 160,
|
|
||||||
frame_height: 90,
|
|
||||||
page_count: 4,
|
|
||||||
total_count: 83,
|
|
||||||
duration_per_frame: 2000,
|
|
||||||
frames_per_page_x: 5,
|
|
||||||
frames_per_page_y: 5,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
visitor_data: Some("CgszSHZWNWs0SDhpTSiS4aWXBg%3D%3D"),
|
visitor_data: Some("CgszSHZWNWs0SDhpTSiS4aWXBg%3D%3D"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -168,37 +168,5 @@ VideoPlayer(
|
||||||
expires_in_seconds: 21540,
|
expires_in_seconds: 21540,
|
||||||
hls_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/hls_variant/expire/1659481355/ei/q1jpYq-xHs7NgQev0bfwAQ/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jelnez%2Csn-h0jeenek/ms/au%2Crdu/mv/m/mvi/4/pl/37/hfr/1/demuxed/1/tts_caps/1/maudio/1/initcwndbps/1513750/vprv/1/go/1/mt/1659459429/fvip/5/nvgoi/1/short_key/1/ncsapi/1/keepalive/yes/fexp/24001373%2C24007246/dover/13/itag/0/playlist_type/DVR/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cdemuxed%2Ctts_caps%2Cmaudio%2Cvprv%2Cgo%2Citag%2Cplaylist_type/sig/AOq0QJ8wRQIhAIYnEHvIgJtJ8hehAXNtVY3qsgsq_GdOhWf2hkJZe6lCAiBxaRY_nubYp6hBizcAg_KFkKnkG-t2XYLRQ5wGdM3AjA%3D%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRgIhAM_91Kk_0VLuSsR6nLCY7LdtWojyRAzXSScd_X9ShRROAiEA1AF4VY04F71NsAI8_j3iqjuXnWL9s6NoXHq7P8-bHx8%3D/file/index.m3u8"),
|
hls_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/hls_variant/expire/1659481355/ei/q1jpYq-xHs7NgQev0bfwAQ/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jelnez%2Csn-h0jeenek/ms/au%2Crdu/mv/m/mvi/4/pl/37/hfr/1/demuxed/1/tts_caps/1/maudio/1/initcwndbps/1513750/vprv/1/go/1/mt/1659459429/fvip/5/nvgoi/1/short_key/1/ncsapi/1/keepalive/yes/fexp/24001373%2C24007246/dover/13/itag/0/playlist_type/DVR/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cdemuxed%2Ctts_caps%2Cmaudio%2Cvprv%2Cgo%2Citag%2Cplaylist_type/sig/AOq0QJ8wRQIhAIYnEHvIgJtJ8hehAXNtVY3qsgsq_GdOhWf2hkJZe6lCAiBxaRY_nubYp6hBizcAg_KFkKnkG-t2XYLRQ5wGdM3AjA%3D%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRgIhAM_91Kk_0VLuSsR6nLCY7LdtWojyRAzXSScd_X9ShRROAiEA1AF4VY04F71NsAI8_j3iqjuXnWL9s6NoXHq7P8-bHx8%3D/file/index.m3u8"),
|
||||||
dash_manifest_url: None,
|
dash_manifest_url: None,
|
||||||
preview_frames: [
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L0/default.jpg?sqp=-oaymwGbA0g48quKqQOSA4gBAZUBAAAEQpgBMqABPKgBBLABELABDbABDLABELABFbABH7ABJrABLbABDrABDrABD7ABErABF7ABK7ABLLABKbABD7ABDrABELABFbABH7ABKrABMrABKbABD7ABEbABFLABGLABJrABPbABOLABLbABEbABFLABHrABKrABMbABS7ABR7ABNrABFbABHLABKbABLrABObABR7ABTbABP7ABJbABLrABN7ABPbABR7ABUrABUbABRbABM7ABQLABQrABQ7ABTLABRLABRrABQ7gBEbgBEbgBFbgBI7gBRLgBQ7gBQ7gBQ7gBEbgBE7gBFrgBL7gBQ7gBQ7gBQ7gBQ7gBFbgBFrgBKbgBQ7gBQ7gBQ7gBQ7gBQ7gBI7gBL7gBQ7gBQ7gBQ7gBQ7gBQ7gBQ7gBRLgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLCsCT8Lprh2S0ptmCRsWH7VtDl3YQ",
|
|
||||||
frame_width: 48,
|
|
||||||
frame_height: 27,
|
|
||||||
page_count: 1,
|
|
||||||
total_count: 100,
|
|
||||||
duration_per_frame: 0,
|
|
||||||
frames_per_page_x: 10,
|
|
||||||
frames_per_page_y: 10,
|
|
||||||
),
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L1/M$M.jpg?sqp=-oaymwGbA0g48quKqQOSA4gBAZUBAAAEQpgBMqABPKgBBLABELABDbABDLABELABFbABH7ABJrABLbABDrABDrABD7ABErABF7ABK7ABLLABKbABD7ABDrABELABFbABH7ABKrABMrABKbABD7ABEbABFLABGLABJrABPbABOLABLbABEbABFLABHrABKrABMbABS7ABR7ABNrABFbABHLABKbABLrABObABR7ABTbABP7ABJbABLrABN7ABPbABR7ABUrABUbABRbABM7ABQLABQrABQ7ABTLABRLABRrABQ7gBEbgBEbgBFbgBI7gBRLgBQ7gBQ7gBQ7gBEbgBE7gBFrgBL7gBQ7gBQ7gBQ7gBQ7gBFbgBFrgBKbgBQ7gBQ7gBQ7gBQ7gBQ7gBI7gBL7gBQ7gBQ7gBQ7gBQ7gBQ7gBQ7gBRLgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLBXrdgfuYV1WLnTGXqZtSAUm8oZCA",
|
|
||||||
frame_width: 80,
|
|
||||||
frame_height: 45,
|
|
||||||
page_count: 1,
|
|
||||||
total_count: 83,
|
|
||||||
duration_per_frame: 2000,
|
|
||||||
frames_per_page_x: 10,
|
|
||||||
frames_per_page_y: 10,
|
|
||||||
),
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L2/M$M.jpg?sqp=-oaymwGbA0g48quKqQOSA4gBAZUBAAAEQpgBMqABPKgBBLABELABDbABDLABELABFbABH7ABJrABLbABDrABDrABD7ABErABF7ABK7ABLLABKbABD7ABDrABELABFbABH7ABKrABMrABKbABD7ABEbABFLABGLABJrABPbABOLABLbABEbABFLABHrABKrABMbABS7ABR7ABNrABFbABHLABKbABLrABObABR7ABTbABP7ABJbABLrABN7ABPbABR7ABUrABUbABRbABM7ABQLABQrABQ7ABTLABRLABRrABQ7gBEbgBEbgBFbgBI7gBRLgBQ7gBQ7gBQ7gBEbgBE7gBFrgBL7gBQ7gBQ7gBQ7gBQ7gBFbgBFrgBKbgBQ7gBQ7gBQ7gBQ7gBQ7gBI7gBL7gBQ7gBQ7gBQ7gBQ7gBQ7gBQ7gBRLgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLCRazj84zMuwJLaCCc_PiUakX_YdQ",
|
|
||||||
frame_width: 160,
|
|
||||||
frame_height: 90,
|
|
||||||
page_count: 4,
|
|
||||||
total_count: 83,
|
|
||||||
duration_per_frame: 2000,
|
|
||||||
frames_per_page_x: 5,
|
|
||||||
frames_per_page_y: 5,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
visitor_data: Some("Cgs4TXV4dk13WVEyWSirsaWXBg%3D%3D"),
|
visitor_data: Some("Cgs4TXV4dk13WVEyWSirsaWXBg%3D%3D"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -569,37 +569,5 @@ VideoPlayer(
|
||||||
expires_in_seconds: 21540,
|
expires_in_seconds: 21540,
|
||||||
hls_manifest_url: None,
|
hls_manifest_url: None,
|
||||||
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYv-eJ9uF6dsPhvyH8As/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jelnez%2Csn-h0jeenek/ms/au%2Crdu/mv/m/mvi/4/pl/37/hfr/all/as/fmp4_audio_clear%2Cfmp4_sd_hd_clear/initcwndbps/1527500/vprv/1/mt/1659459429/fvip/5/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cvprv%2Citag/sig/AOq0QJ8wRQIhANKWS7GCN4pSoHIQ6BMZdOaHAD0I25nHwRj7ds4qrxdEAiBsd9l8WIceqF7-2xyR82DGecCiS9hgUIPJhdNhkwVpHg%3D%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgMbu-wTOcXGCwGh27y0YZHktumKM1sopgxfQf8LCcCnECIQDnhFbgddOxwiQbnMOIcCn6ncpN54UyALRNigUSCp9Deg%3D%3D"),
|
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYv-eJ9uF6dsPhvyH8As/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jelnez%2Csn-h0jeenek/ms/au%2Crdu/mv/m/mvi/4/pl/37/hfr/all/as/fmp4_audio_clear%2Cfmp4_sd_hd_clear/initcwndbps/1527500/vprv/1/mt/1659459429/fvip/5/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cvprv%2Citag/sig/AOq0QJ8wRQIhANKWS7GCN4pSoHIQ6BMZdOaHAD0I25nHwRj7ds4qrxdEAiBsd9l8WIceqF7-2xyR82DGecCiS9hgUIPJhdNhkwVpHg%3D%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgMbu-wTOcXGCwGh27y0YZHktumKM1sopgxfQf8LCcCnECIQDnhFbgddOxwiQbnMOIcCn6ncpN54UyALRNigUSCp9Deg%3D%3D"),
|
||||||
preview_frames: [
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L0/default.jpg?sqp=-oaymwGbA0g48quKqQOSA4gBAZUBAAAEQpgBMqABPKgBBLABELABDbABDLABELABFbABH7ABJrABLbABDrABDrABD7ABErABF7ABK7ABLLABKbABD7ABDrABELABFbABH7ABKrABMrABKbABD7ABEbABFLABGLABJrABPbABOLABLbABEbABFLABHrABKrABMbABS7ABR7ABNrABFbABHLABKbABLrABObABR7ABTbABP7ABJbABLrABN7ABPbABR7ABUrABUbABRbABM7ABQLABQrABQ7ABTLABRLABRrABQ7gBEbgBEbgBFbgBI7gBRLgBQ7gBQ7gBQ7gBEbgBE7gBFrgBL7gBQ7gBQ7gBQ7gBQ7gBFbgBFrgBKbgBQ7gBQ7gBQ7gBQ7gBQ7gBI7gBL7gBQ7gBQ7gBQ7gBQ7gBQ7gBQ7gBRLgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLCsCT8Lprh2S0ptmCRsWH7VtDl3YQ",
|
|
||||||
frame_width: 48,
|
|
||||||
frame_height: 27,
|
|
||||||
page_count: 1,
|
|
||||||
total_count: 100,
|
|
||||||
duration_per_frame: 0,
|
|
||||||
frames_per_page_x: 10,
|
|
||||||
frames_per_page_y: 10,
|
|
||||||
),
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L1/M$M.jpg?sqp=-oaymwGbA0g48quKqQOSA4gBAZUBAAAEQpgBMqABPKgBBLABELABDbABDLABELABFbABH7ABJrABLbABDrABDrABD7ABErABF7ABK7ABLLABKbABD7ABDrABELABFbABH7ABKrABMrABKbABD7ABEbABFLABGLABJrABPbABOLABLbABEbABFLABHrABKrABMbABS7ABR7ABNrABFbABHLABKbABLrABObABR7ABTbABP7ABJbABLrABN7ABPbABR7ABUrABUbABRbABM7ABQLABQrABQ7ABTLABRLABRrABQ7gBEbgBEbgBFbgBI7gBRLgBQ7gBQ7gBQ7gBEbgBE7gBFrgBL7gBQ7gBQ7gBQ7gBQ7gBFbgBFrgBKbgBQ7gBQ7gBQ7gBQ7gBQ7gBI7gBL7gBQ7gBQ7gBQ7gBQ7gBQ7gBQ7gBRLgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLBXrdgfuYV1WLnTGXqZtSAUm8oZCA",
|
|
||||||
frame_width: 80,
|
|
||||||
frame_height: 45,
|
|
||||||
page_count: 1,
|
|
||||||
total_count: 83,
|
|
||||||
duration_per_frame: 2000,
|
|
||||||
frames_per_page_x: 10,
|
|
||||||
frames_per_page_y: 10,
|
|
||||||
),
|
|
||||||
Frameset(
|
|
||||||
url_template: "https://i.ytimg.com/sb/pPvd8UxmSbQ/storyboard3_L2/M$M.jpg?sqp=-oaymwGbA0g48quKqQOSA4gBAZUBAAAEQpgBMqABPKgBBLABELABDbABDLABELABFbABH7ABJrABLbABDrABDrABD7ABErABF7ABK7ABLLABKbABD7ABDrABELABFbABH7ABKrABMrABKbABD7ABEbABFLABGLABJrABPbABOLABLbABEbABFLABHrABKrABMbABS7ABR7ABNrABFbABHLABKbABLrABObABR7ABTbABP7ABJbABLrABN7ABPbABR7ABUrABUbABRbABM7ABQLABQrABQ7ABTLABRLABRrABQ7gBEbgBEbgBFbgBI7gBRLgBQ7gBQ7gBQ7gBEbgBE7gBFrgBL7gBQ7gBQ7gBQ7gBQ7gBFbgBFrgBKbgBQ7gBQ7gBQ7gBQ7gBQ7gBI7gBL7gBQ7gBQ7gBQ7gBQ7gBQ7gBQ7gBRLgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQ7gBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQ7gBQ7gBQ7gBQrgBQrgBQrgBQrgBQqLzl_8DBgjf8LPxBQ==&sigh=rs$AOn4CLCRazj84zMuwJLaCCc_PiUakX_YdQ",
|
|
||||||
frame_width: 160,
|
|
||||||
frame_height: 90,
|
|
||||||
page_count: 4,
|
|
||||||
total_count: 83,
|
|
||||||
duration_per_frame: 2000,
|
|
||||||
frames_per_page_x: 5,
|
|
||||||
frames_per_page_y: 5,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
visitor_data: Some("CgtacUJOMG81dTI3cyirsaWXBg%3D%3D"),
|
visitor_data: Some("CgtacUJOMG81dTI3cyirsaWXBg%3D%3D"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -171,12 +171,6 @@ impl From<reqwest::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<serde_plain::Error> for Error {
|
|
||||||
fn from(value: serde_plain::Error) -> Self {
|
|
||||||
Self::Other(value.to_string().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtractionError {
|
impl ExtractionError {
|
||||||
pub(crate) fn should_report(&self) -> bool {
|
pub(crate) fn should_report(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Set of video frames for seek preview
|
|
||||||
///
|
|
||||||
/// YouTube generates a set of images containing a grid of frames for each video.
|
|
||||||
/// These images are used by the player for the seekbar preview.
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct Frameset {
|
|
||||||
/// Url template of the frameset
|
|
||||||
///
|
|
||||||
/// The `$M` placeholder has to be replaced with the page index (starting from 0).
|
|
||||||
pub url_template: String,
|
|
||||||
/// Width of a single frame in pixels
|
|
||||||
pub frame_width: u32,
|
|
||||||
/// Height of a single frame in pixels
|
|
||||||
pub frame_height: u32,
|
|
||||||
/// Number of pages (individual images)
|
|
||||||
pub page_count: u32,
|
|
||||||
/// Total number of frames in the set
|
|
||||||
pub total_count: u32,
|
|
||||||
/// Duration per frame in milliseconds
|
|
||||||
pub duration_per_frame: u32,
|
|
||||||
/// Number of frames in the x direction
|
|
||||||
pub frames_per_page_x: u32,
|
|
||||||
/// Number of frames in the y direction.
|
|
||||||
pub frames_per_page_y: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterator producing frameset page urls
|
|
||||||
pub struct FramesetUrls<'a> {
|
|
||||||
frameset: &'a Frameset,
|
|
||||||
i: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Frameset {
|
|
||||||
/// Gets an iterator over the page URLs of the frameset
|
|
||||||
pub fn urls(&self) -> FramesetUrls {
|
|
||||||
FramesetUrls {
|
|
||||||
frameset: self,
|
|
||||||
i: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for FramesetUrls<'_> {
|
|
||||||
type Item = String;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.i < self.frameset.page_count {
|
|
||||||
let url = self
|
|
||||||
.frameset
|
|
||||||
.url_template
|
|
||||||
.replace("$M", &self.i.to_string());
|
|
||||||
self.i += 1;
|
|
||||||
Some(url)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +1,24 @@
|
||||||
//! YouTube API response models
|
//! YouTube API response models
|
||||||
|
|
||||||
mod convert;
|
mod convert;
|
||||||
mod frameset;
|
|
||||||
mod ordering;
|
mod ordering;
|
||||||
|
|
||||||
pub mod paginator;
|
pub mod paginator;
|
||||||
pub mod richtext;
|
pub mod richtext;
|
||||||
|
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
pub use frameset::{Frameset, FramesetUrls};
|
|
||||||
|
use serde_with::serde_as;
|
||||||
|
|
||||||
use std::{collections::BTreeSet, ops::Range};
|
use std::{collections::BTreeSet, ops::Range};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::serde_as;
|
|
||||||
use time::{Date, OffsetDateTime};
|
use time::{Date, OffsetDateTime};
|
||||||
|
|
||||||
use self::{paginator::Paginator, richtext::RichText};
|
|
||||||
use crate::{error::Error, param::Country, serializer::DateYmd, util};
|
use crate::{error::Error, param::Country, serializer::DateYmd, util};
|
||||||
|
|
||||||
|
use self::{paginator::Paginator, richtext::RichText};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#COMMON
|
#COMMON
|
||||||
*/
|
*/
|
||||||
|
@ -154,8 +155,6 @@ pub struct VideoPlayer {
|
||||||
pub hls_manifest_url: Option<String>,
|
pub hls_manifest_url: Option<String>,
|
||||||
/// Dash manifest URL (for livestreams)
|
/// Dash manifest URL (for livestreams)
|
||||||
pub dash_manifest_url: Option<String>,
|
pub dash_manifest_url: Option<String>,
|
||||||
/// Video frames for seek preview
|
|
||||||
pub preview_frames: Vec<Frameset>,
|
|
||||||
/// YouTube visitor data cookie
|
/// YouTube visitor data cookie
|
||||||
pub visitor_data: Option<String>,
|
pub visitor_data: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -343,14 +342,14 @@ pub enum VideoFormat {
|
||||||
pub struct AudioTrack {
|
pub struct AudioTrack {
|
||||||
/// Track ID (e.g. `en.0`)
|
/// Track ID (e.g. `en.0`)
|
||||||
pub id: String,
|
pub id: String,
|
||||||
/// Language code (e.g. `en-US`, `de`)
|
/// 2/3 letter language code (e.g. `en`)
|
||||||
|
///
|
||||||
|
/// Extracted from the track ID
|
||||||
pub lang: Option<String>,
|
pub lang: Option<String>,
|
||||||
/// Language name (e.g. "English")
|
/// Language name (e.g. "English")
|
||||||
pub lang_name: String,
|
pub lang_name: String,
|
||||||
/// True if this is the default audio track
|
/// True if this is the default audio track
|
||||||
pub is_default: bool,
|
pub is_default: bool,
|
||||||
/// Audio track type (e.g. *Original*, *Dubbed*)
|
|
||||||
pub track_type: Option<AudioTrackType>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Audio file type
|
/// Audio file type
|
||||||
|
@ -364,25 +363,6 @@ pub enum AudioFormat {
|
||||||
Webm,
|
Webm,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Audio track type
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum AudioTrackType {
|
|
||||||
/// An original audio track of the video
|
|
||||||
Original,
|
|
||||||
/// An audio track with the original voices replaced, typically in a different language
|
|
||||||
Dubbed,
|
|
||||||
/// A descriptive audio track
|
|
||||||
///
|
|
||||||
/// A descriptive audio track is an audio track in which descriptions of visual elements of
|
|
||||||
/// a video are added to the original audio, with the goal to make a video more accessible to
|
|
||||||
/// blind and visually impaired people.
|
|
||||||
///
|
|
||||||
/// See <https://en.wikipedia.org/wiki/Audio_description>
|
|
||||||
Descriptive,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// YouTube provides subtitles in different formats.
|
/// YouTube provides subtitles in different formats.
|
||||||
///
|
///
|
||||||
/// srv1 (XML) is the default format, to request a different format you have
|
/// srv1 (XML) is the default format, to request a different format you have
|
||||||
|
@ -870,7 +850,7 @@ pub struct VideoItem {
|
||||||
pub publish_date: Option<OffsetDateTime>,
|
pub publish_date: Option<OffsetDateTime>,
|
||||||
/// Textual video publish date (e.g. `11 months ago`, depends on language)
|
/// Textual video publish date (e.g. `11 months ago`, depends on language)
|
||||||
///
|
///
|
||||||
/// Is [`None`] for livestreams and upcoming videos.
|
/// Is [`None`] for livestreams.
|
||||||
pub publish_date_txt: Option<String>,
|
pub publish_date_txt: Option<String>,
|
||||||
/// View count
|
/// View count
|
||||||
///
|
///
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub struct Paginator<T> {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub visitor_data: Option<String>,
|
pub visitor_data: Option<String>,
|
||||||
/// YouTube API endpoint to fetch continuations from
|
/// YouTube API endpoint to fetch continuations from
|
||||||
pub endpoint: ContinuationEndpoint,
|
pub(crate) endpoint: ContinuationEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for Paginator<T> {
|
impl<T> Default for Paginator<T> {
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
//! Languages and countries
|
//! Languages and countries
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::error::Error;
|
|
||||||
|
|
||||||
/// Available languages
|
/// Available languages
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
|
@ -15,31 +13,31 @@ use crate::error::Error;
|
||||||
pub enum Language {
|
pub enum Language {
|
||||||
/// Afrikaans
|
/// Afrikaans
|
||||||
Af,
|
Af,
|
||||||
/// Amharic / አማርኛ
|
/// አማርኛ
|
||||||
Am,
|
Am,
|
||||||
/// Arabic / العربية
|
/// العربية
|
||||||
Ar,
|
Ar,
|
||||||
/// Assamese / অসমীয়া
|
/// অসমীয়া
|
||||||
As,
|
As,
|
||||||
/// Azerbaijani / Azərbaycan
|
/// Azərbaycan
|
||||||
Az,
|
Az,
|
||||||
/// Belarusian / Беларуская
|
/// Беларуская
|
||||||
Be,
|
Be,
|
||||||
/// Bulgarian / Български
|
/// Български
|
||||||
Bg,
|
Bg,
|
||||||
/// Bangla / বাংলা
|
/// বাংলা
|
||||||
Bn,
|
Bn,
|
||||||
/// Bosnian / Bosanski
|
/// Bosanski
|
||||||
Bs,
|
Bs,
|
||||||
/// Catalan / Català
|
/// Català
|
||||||
Ca,
|
Ca,
|
||||||
/// Czech / Čeština
|
/// Čeština
|
||||||
Cs,
|
Cs,
|
||||||
/// Danish / Dansk
|
/// Dansk
|
||||||
Da,
|
Da,
|
||||||
/// German / Deutsch
|
/// Deutsch
|
||||||
De,
|
De,
|
||||||
/// Greek / Ελληνικά
|
/// Ελληνικά
|
||||||
El,
|
El,
|
||||||
/// English (US)
|
/// English (US)
|
||||||
En,
|
En,
|
||||||
|
@ -49,145 +47,145 @@ pub enum Language {
|
||||||
/// English (India)
|
/// English (India)
|
||||||
#[serde(rename = "en-IN")]
|
#[serde(rename = "en-IN")]
|
||||||
EnIn,
|
EnIn,
|
||||||
/// Spanish / Español (España)
|
/// Español (España)
|
||||||
Es,
|
Es,
|
||||||
/// Latin American Spanish / Español (Latinoamérica)
|
/// Español (Latinoamérica)
|
||||||
#[serde(rename = "es-419")]
|
#[serde(rename = "es-419")]
|
||||||
Es419,
|
Es419,
|
||||||
/// Spanish (United States) / Español (US)
|
/// Español (US)
|
||||||
#[serde(rename = "es-US")]
|
#[serde(rename = "es-US")]
|
||||||
EsUs,
|
EsUs,
|
||||||
/// Estonian / Eesti
|
/// Eesti
|
||||||
Et,
|
Et,
|
||||||
/// Basque / Euskara
|
/// Euskara
|
||||||
Eu,
|
Eu,
|
||||||
/// Persian / فارسی
|
/// فارسی
|
||||||
Fa,
|
Fa,
|
||||||
/// Finnish / Suomi
|
/// Suomi
|
||||||
Fi,
|
Fi,
|
||||||
/// Filipino
|
/// Filipino
|
||||||
Fil,
|
Fil,
|
||||||
/// French / Français
|
/// Français
|
||||||
Fr,
|
Fr,
|
||||||
/// Canadian French / Français (Canada)
|
/// Français (Canada)
|
||||||
#[serde(rename = "fr-CA")]
|
#[serde(rename = "fr-CA")]
|
||||||
FrCa,
|
FrCa,
|
||||||
/// Galician / Galego
|
/// Galego
|
||||||
Gl,
|
Gl,
|
||||||
/// Gujarati / ગુજરાતી
|
/// ગુજરાતી
|
||||||
Gu,
|
Gu,
|
||||||
/// Hindi / हिन्दी
|
/// हिन्दी
|
||||||
Hi,
|
Hi,
|
||||||
/// Croatian / Hrvatski
|
/// Hrvatski
|
||||||
Hr,
|
Hr,
|
||||||
/// Hungarian / Magyar
|
/// Magyar
|
||||||
Hu,
|
Hu,
|
||||||
/// Armenian / Հայերեն
|
/// Հայերեն
|
||||||
Hy,
|
Hy,
|
||||||
/// Indonesian / Bahasa Indonesia
|
/// Bahasa Indonesia
|
||||||
Id,
|
Id,
|
||||||
/// Icelandic / Íslenska
|
/// Íslenska
|
||||||
Is,
|
Is,
|
||||||
/// Italian / Italiano
|
/// Italiano
|
||||||
It,
|
It,
|
||||||
/// Hebrew / עברית
|
/// עברית
|
||||||
Iw,
|
Iw,
|
||||||
/// Japanese / 日本語
|
/// 日本語
|
||||||
Ja,
|
Ja,
|
||||||
/// Georgian / ქართული
|
/// ქართული
|
||||||
Ka,
|
Ka,
|
||||||
/// Kazakh / Қазақ Тілі
|
/// Қазақ Тілі
|
||||||
Kk,
|
Kk,
|
||||||
/// Khmer / ខ្មែរ
|
/// ខ្មែរ
|
||||||
Km,
|
Km,
|
||||||
/// Kannada / ಕನ್ನಡ
|
/// ಕನ್ನಡ
|
||||||
Kn,
|
Kn,
|
||||||
/// Korean / 한국어
|
/// 한국어
|
||||||
Ko,
|
Ko,
|
||||||
/// Kyrgyz / Кыргызча
|
/// Кыргызча
|
||||||
Ky,
|
Ky,
|
||||||
/// Lao / ລາວ
|
/// ລາວ
|
||||||
Lo,
|
Lo,
|
||||||
/// Lithuanian / Lietuvių
|
/// Lietuvių
|
||||||
Lt,
|
Lt,
|
||||||
/// Latvian / Latviešu valoda
|
/// Latviešu valoda
|
||||||
Lv,
|
Lv,
|
||||||
/// Macedonian / Македонски
|
/// Македонски
|
||||||
Mk,
|
Mk,
|
||||||
/// Malayalam / മലയാളം
|
/// മലയാളം
|
||||||
Ml,
|
Ml,
|
||||||
/// Mongolian / Монгол
|
/// Монгол
|
||||||
Mn,
|
Mn,
|
||||||
/// Marathi / मराठी
|
/// मराठी
|
||||||
Mr,
|
Mr,
|
||||||
/// Malay / Bahasa Malaysia
|
/// Bahasa Malaysia
|
||||||
Ms,
|
Ms,
|
||||||
/// Burmese / ဗမာ
|
/// ဗမာ
|
||||||
My,
|
My,
|
||||||
/// Nepali / नेपाली
|
/// नेपाली
|
||||||
Ne,
|
Ne,
|
||||||
/// Dutch / Nederlands
|
/// Nederlands
|
||||||
Nl,
|
Nl,
|
||||||
/// Norwegian / Norsk
|
/// Norsk
|
||||||
No,
|
No,
|
||||||
/// Odia / ଓଡ଼ିଆ
|
/// ଓଡ଼ିଆ
|
||||||
Or,
|
Or,
|
||||||
/// Punjabi / ਪੰਜਾਬੀ
|
/// ਪੰਜਾਬੀ
|
||||||
Pa,
|
Pa,
|
||||||
/// Polish / Polski
|
/// Polski
|
||||||
Pl,
|
Pl,
|
||||||
/// Portuguese / Português (Brasil)
|
/// Português (Brasil)
|
||||||
Pt,
|
Pt,
|
||||||
/// European Portuguese / Português
|
/// Português
|
||||||
#[serde(rename = "pt-PT")]
|
#[serde(rename = "pt-PT")]
|
||||||
PtPt,
|
PtPt,
|
||||||
/// Romanian / Română
|
/// Română
|
||||||
Ro,
|
Ro,
|
||||||
/// Russian / Русский
|
/// Русский
|
||||||
Ru,
|
Ru,
|
||||||
/// Sinhala / සිංහල
|
/// සිංහල
|
||||||
Si,
|
Si,
|
||||||
/// Slovak / Slovenčina
|
/// Slovenčina
|
||||||
Sk,
|
Sk,
|
||||||
/// Slovenian / Slovenščina
|
/// Slovenščina
|
||||||
Sl,
|
Sl,
|
||||||
/// Albanian / Shqip
|
/// Shqip
|
||||||
Sq,
|
Sq,
|
||||||
/// Serbian / Српски
|
/// Српски
|
||||||
Sr,
|
Sr,
|
||||||
/// Serbian (Latin) / Srpski
|
/// Srpski
|
||||||
#[serde(rename = "sr-Latn")]
|
#[serde(rename = "sr-Latn")]
|
||||||
SrLatn,
|
SrLatn,
|
||||||
/// Swedish / Svenska
|
/// Svenska
|
||||||
Sv,
|
Sv,
|
||||||
/// Swahili / Kiswahili
|
/// Kiswahili
|
||||||
Sw,
|
Sw,
|
||||||
/// Tamil / தமிழ்
|
/// தமிழ்
|
||||||
Ta,
|
Ta,
|
||||||
/// Telugu / తెలుగు
|
/// తెలుగు
|
||||||
Te,
|
Te,
|
||||||
/// Thai / ภาษาไทย
|
/// ภาษาไทย
|
||||||
Th,
|
Th,
|
||||||
/// Turkish / Türkçe
|
/// Türkçe
|
||||||
Tr,
|
Tr,
|
||||||
/// Ukrainian / Українська
|
/// Українська
|
||||||
Uk,
|
Uk,
|
||||||
/// Urdu / اردو
|
/// اردو
|
||||||
Ur,
|
Ur,
|
||||||
/// Uzbek / O‘zbek
|
/// O‘zbek
|
||||||
Uz,
|
Uz,
|
||||||
/// Vietnamese / Tiếng Việt
|
/// Tiếng Việt
|
||||||
Vi,
|
Vi,
|
||||||
/// Chinese (China) / 中文 (简体)
|
/// 中文 (简体)
|
||||||
#[serde(rename = "zh-CN")]
|
#[serde(rename = "zh-CN")]
|
||||||
ZhCn,
|
ZhCn,
|
||||||
/// Chinese (Hong Kong) / 中文 (香港)
|
/// 中文 (香港)
|
||||||
#[serde(rename = "zh-HK")]
|
#[serde(rename = "zh-HK")]
|
||||||
ZhHk,
|
ZhHk,
|
||||||
/// Chinese (Taiwan) / 中文 (繁體)
|
/// 中文 (繁體)
|
||||||
#[serde(rename = "zh-TW")]
|
#[serde(rename = "zh-TW")]
|
||||||
ZhTw,
|
ZhTw,
|
||||||
/// Zulu / IsiZulu
|
/// IsiZulu
|
||||||
Zu,
|
Zu,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -831,26 +829,32 @@ impl Country {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Language {
|
impl Display for Language {
|
||||||
type Err = Error;
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
&serde_json::to_string(self).map_or("".to_owned(), |s| s[1..s.len() - 1].to_owned()),
|
||||||
let mut sub = s;
|
)
|
||||||
loop {
|
|
||||||
if let Ok(v) = serde_plain::from_str(sub) {
|
|
||||||
return Ok(v);
|
|
||||||
}
|
|
||||||
match sub.rfind('-') {
|
|
||||||
Some(pos) => {
|
|
||||||
sub = &sub[..pos];
|
|
||||||
}
|
|
||||||
None => return Err(Error::Other("could not parse language `{s}`".into())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serde_plain::derive_display_from_serialize!(Language);
|
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()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
serde_plain::derive_fromstr_from_deserialize!(Country, Error);
|
impl FromStr for Language {
|
||||||
serde_plain::derive_display_from_serialize!(Country);
|
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}\""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,34 +7,3 @@ pub mod search_filter;
|
||||||
|
|
||||||
pub use locale::{Country, Language};
|
pub use locale::{Country, Language};
|
||||||
pub use stream_filter::StreamFilter;
|
pub use stream_filter::StreamFilter;
|
||||||
|
|
||||||
/// Channel video tab
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ChannelVideoTab {
|
|
||||||
/// Regular videos
|
|
||||||
Videos,
|
|
||||||
/// Short videos
|
|
||||||
Shorts,
|
|
||||||
/// Livestreams
|
|
||||||
Live,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sort order for channel videos
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ChannelOrder {
|
|
||||||
/// Order videos with the latest upload date first (default)
|
|
||||||
Latest = 1,
|
|
||||||
/// Order videos with the highest number of views first
|
|
||||||
Popular = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChannelVideoTab {
|
|
||||||
/// Get the tab ID used to create ordered continuation tokens
|
|
||||||
pub(crate) const fn order_ctoken_id(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
ChannelVideoTab::Videos => 15,
|
|
||||||
ChannelVideoTab::Shorts => 10,
|
|
||||||
ChannelVideoTab::Live => 14,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use crate::util::ProtoBuilder;
|
use crate::util::{self, ProtoBuilder};
|
||||||
|
|
||||||
/// YouTube search filter
|
/// YouTube search filter
|
||||||
///
|
///
|
||||||
|
@ -200,7 +200,8 @@ impl SearchFilter {
|
||||||
pb.embedded(8, extras)
|
pb.embedded(8, extras)
|
||||||
}
|
}
|
||||||
|
|
||||||
pb.to_base64()
|
let b64 = util::b64_encode(pb.bytes);
|
||||||
|
urlencoding::encode(&b64).to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -352,13 +352,13 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::default(StreamFilter::default(), Some("https://rr4---sn-h0jeener.googlevideo.com/videoplayback?c=WEB&clen=16104136&dur=1012.661&ei=6OtcZNqtBdOi7gP1upHYCQ&expire=1683832904&fexp=24007246&fvip=2&gir=yes&id=o-ABVtPh3j24hkJeXp8igjvreyODn-oV0CacOqb7pDjJoG&initcwndbps=1720000&ip=2003%3Ade%3Aaf31%3A5200%3A791a%3A897%3Ac15c%3Aae59&itag=251&keepalive=yes&lmt=1683782301237288&lsig=AG3C_xAwRQIgC7HZtYuc6dI92m6wCcoXYpdzSpVtPTIbO7jBKGpUrYMCIQCc0WNtFvN8Awqx9uuRVp5SUSe3rOt2D7M-rCKpgVv_0A%3D%3D&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=1683811031&mv=m&mvi=4&n=U8mCOo4eYD4n0A&ns=LToEdXWVFHcH53e3aTe1N7kN&pl=37&requiressl=yes&sig=AOq0QJ8wRQIhAPcUhhfkNVA_JcdU6KLTOFjRCnNl6n8gamJA-Q0PgCpIAiBTMV2k2JfHzbHBtsHxuNW7zHvSaYaUbz-dEIQC45o1eA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Csvpuc%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=qEK7B81AP536F3aOi5JzMyLCUDiktWigtEpf9nI2xg&svpuc=1&txp=4532434&vprv=1&xtags=acont%3Doriginal%3Alang%3Den-US"))]
|
#[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=8217508&dur=1012.661&ei=6OtcZNqtBdOi7gP1upHYCQ&expire=1683832904&fexp=24007246&fvip=2&gir=yes&id=o-ABVtPh3j24hkJeXp8igjvreyODn-oV0CacOqb7pDjJoG&initcwndbps=1720000&ip=2003%3Ade%3Aaf31%3A5200%3A791a%3A897%3Ac15c%3Aae59&itag=250&keepalive=yes&lmt=1683782195315620&lsig=AG3C_xAwRQIgC7HZtYuc6dI92m6wCcoXYpdzSpVtPTIbO7jBKGpUrYMCIQCc0WNtFvN8Awqx9uuRVp5SUSe3rOt2D7M-rCKpgVv_0A%3D%3D&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=1683811031&mv=m&mvi=4&n=U8mCOo4eYD4n0A&ns=LToEdXWVFHcH53e3aTe1N7kN&pl=37&requiressl=yes&sig=AOq0QJ8wRQIga2iMQsToMxO7hTOx0gNAzhYoV1lL5PpE9lkAuBXt1nkCIQCuFuQXWNixIquEugtkT1C9khuKRP_C-wzSOiUmRp1DRg%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Csvpuc%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=qEK7B81AP536F3aOi5JzMyLCUDiktWigtEpf9nI2xg&svpuc=1&txp=4532434&vprv=1&xtags=acont%3Doriginal%3Alang%3Den-US"))]
|
#[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(&[AudioFormat::M4a]).to_owned(), Some("https://rr4---sn-h0jeener.googlevideo.com/videoplayback?c=WEB&clen=16390508&dur=1012.691&ei=6OtcZNqtBdOi7gP1upHYCQ&expire=1683832904&fexp=24007246&fvip=2&gir=yes&id=o-ABVtPh3j24hkJeXp8igjvreyODn-oV0CacOqb7pDjJoG&initcwndbps=1720000&ip=2003%3Ade%3Aaf31%3A5200%3A791a%3A897%3Ac15c%3Aae59&itag=140&keepalive=yes&lmt=1683782363698612&lsig=AG3C_xAwRQIgC7HZtYuc6dI92m6wCcoXYpdzSpVtPTIbO7jBKGpUrYMCIQCc0WNtFvN8Awqx9uuRVp5SUSe3rOt2D7M-rCKpgVv_0A%3D%3D&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=1683811031&mv=m&mvi=4&n=U8mCOo4eYD4n0A&ns=LToEdXWVFHcH53e3aTe1N7kN&pl=37&requiressl=yes&sig=AOq0QJ8wRgIhAMgM470I-QXq4lTRuPtXf5UInHB_tG0tTGXRhVZ6nwImAiEAn0JYRknq5dtTwcmzZheekxVOZKhZ2Rpxc_UyvX2CMRY%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Csvpuc%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=qEK7B81AP536F3aOi5JzMyLCUDiktWigtEpf9nI2xg&svpuc=1&txp=4532434&vprv=1&xtags=acont%3Doriginal%3Alang%3Den-US"))]
|
#[case::m4a_format(StreamFilter::default().audio_formats(&[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(&[AudioCodec::Mp4a]).to_owned(), Some("https://rr4---sn-h0jeener.googlevideo.com/videoplayback?c=WEB&clen=16390508&dur=1012.691&ei=6OtcZNqtBdOi7gP1upHYCQ&expire=1683832904&fexp=24007246&fvip=2&gir=yes&id=o-ABVtPh3j24hkJeXp8igjvreyODn-oV0CacOqb7pDjJoG&initcwndbps=1720000&ip=2003%3Ade%3Aaf31%3A5200%3A791a%3A897%3Ac15c%3Aae59&itag=140&keepalive=yes&lmt=1683782363698612&lsig=AG3C_xAwRQIgC7HZtYuc6dI92m6wCcoXYpdzSpVtPTIbO7jBKGpUrYMCIQCc0WNtFvN8Awqx9uuRVp5SUSe3rOt2D7M-rCKpgVv_0A%3D%3D&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=1683811031&mv=m&mvi=4&n=U8mCOo4eYD4n0A&ns=LToEdXWVFHcH53e3aTe1N7kN&pl=37&requiressl=yes&sig=AOq0QJ8wRgIhAMgM470I-QXq4lTRuPtXf5UInHB_tG0tTGXRhVZ6nwImAiEAn0JYRknq5dtTwcmzZheekxVOZKhZ2Rpxc_UyvX2CMRY%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Csvpuc%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=qEK7B81AP536F3aOi5JzMyLCUDiktWigtEpf9nI2xg&svpuc=1&txp=4532434&vprv=1&xtags=acont%3Doriginal%3Alang%3Den-US"))]
|
#[case::m4a_codec(StreamFilter::default().audio_codecs(&[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=940286&dur=60.101&ei=6OtcZNqtBdOi7gP1upHYCQ&expire=1683832904&fexp=24007246&fvip=2&gir=yes&id=o-ABVtPh3j24hkJeXp8igjvreyODn-oV0CacOqb7pDjJoG&initcwndbps=1720000&ip=2003%3Ade%3Aaf31%3A5200%3A791a%3A897%3Ac15c%3Aae59&itag=251&keepalive=yes&lmt=1683774002236584&lsig=AG3C_xAwRQIgC7HZtYuc6dI92m6wCcoXYpdzSpVtPTIbO7jBKGpUrYMCIQCc0WNtFvN8Awqx9uuRVp5SUSe3rOt2D7M-rCKpgVv_0A%3D%3D&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=1683811031&mv=m&mvi=4&n=U8mCOo4eYD4n0A&ns=LToEdXWVFHcH53e3aTe1N7kN&pl=37&requiressl=yes&sig=AOq0QJ8wRQIhAIUUin7WZBnoVDb2p0wuTPc7HZwbF8I5sxzLrVN9WeBwAiBQTZwhxCQ1IdrUkkD1-cSGYBtMF1aKkjPZ-LWeie0aZA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Csvpuc%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=qEK7B81AP536F3aOi5JzMyLCUDiktWigtEpf9nI2xg&svpuc=1&txp=4532434&vprv=1&xtags=acont%3Ddubbed%3Alang%3Dfr"))]
|
#[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=6306327&dur=1012.661&ei=6OtcZNqtBdOi7gP1upHYCQ&expire=1683832904&fexp=24007246&fvip=2&gir=yes&id=o-ABVtPh3j24hkJeXp8igjvreyODn-oV0CacOqb7pDjJoG&initcwndbps=1720000&ip=2003%3Ade%3Aaf31%3A5200%3A791a%3A897%3Ac15c%3Aae59&itag=249&keepalive=yes&lmt=1683782187865292&lsig=AG3C_xAwRQIgC7HZtYuc6dI92m6wCcoXYpdzSpVtPTIbO7jBKGpUrYMCIQCc0WNtFvN8Awqx9uuRVp5SUSe3rOt2D7M-rCKpgVv_0A%3D%3D&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=1683811031&mv=m&mvi=4&n=U8mCOo4eYD4n0A&ns=LToEdXWVFHcH53e3aTe1N7kN&pl=37&requiressl=yes&sig=AOq0QJ8wRAIgW1DTCrLV_GyEM1rdjScgyceZE1llb73KJMFXmPm5Y04CIAYOLZuuzFX4ba5720kMOcQ1-Ld1DULs85nLxJglitCl&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Csvpuc%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=qEK7B81AP536F3aOi5JzMyLCUDiktWigtEpf9nI2xg&svpuc=1&txp=4532434&vprv=1&xtags=acont%3Doriginal%3Alang%3Den-US"))]
|
#[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=16104136&dur=1012.661&ei=6OtcZNqtBdOi7gP1upHYCQ&expire=1683832904&fexp=24007246&fvip=2&gir=yes&id=o-ABVtPh3j24hkJeXp8igjvreyODn-oV0CacOqb7pDjJoG&initcwndbps=1720000&ip=2003%3Ade%3Aaf31%3A5200%3A791a%3A897%3Ac15c%3Aae59&itag=251&keepalive=yes&lmt=1683782301237288&lsig=AG3C_xAwRQIgC7HZtYuc6dI92m6wCcoXYpdzSpVtPTIbO7jBKGpUrYMCIQCc0WNtFvN8Awqx9uuRVp5SUSe3rOt2D7M-rCKpgVv_0A%3D%3D&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=1683811031&mv=m&mvi=4&n=U8mCOo4eYD4n0A&ns=LToEdXWVFHcH53e3aTe1N7kN&pl=37&requiressl=yes&sig=AOq0QJ8wRQIhAPcUhhfkNVA_JcdU6KLTOFjRCnNl6n8gamJA-Q0PgCpIAiBTMV2k2JfHzbHBtsHxuNW7zHvSaYaUbz-dEIQC45o1eA%3D%3D&source=youtube&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cspc%2Cvprv%2Csvpuc%2Cxtags%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&spc=qEK7B81AP536F3aOi5JzMyLCUDiktWigtEpf9nI2xg&svpuc=1&txp=4532434&vprv=1&xtags=acont%3Doriginal%3Alang%3Den-US"))]
|
#[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(&[]).to_owned(), None)]
|
#[case::noformat(StreamFilter::default().audio_formats(&[]).to_owned(), None)]
|
||||||
#[case::nocodec(StreamFilter::default().audio_codecs(&[]).to_owned(), None)]
|
#[case::nocodec(StreamFilter::default().audio_codecs(&[]).to_owned(), None)]
|
||||||
fn t_select_audio_stream(#[case] filter: StreamFilter, #[case] expect_url: Option<&str>) {
|
fn t_select_audio_stream(#[case] filter: StreamFilter, #[case] expect_url: Option<&str>) {
|
||||||
|
|
|
@ -83,18 +83,6 @@ pub fn generate_content_playback_nonce() -> String {
|
||||||
random_string(CONTENT_PLAYBACK_NONCE_ALPHABET, 16)
|
random_string(CONTENT_PLAYBACK_NONCE_ALPHABET, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random_uuid() -> String {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
format!(
|
|
||||||
"{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
|
|
||||||
rng.gen::<u32>(),
|
|
||||||
rng.gen::<u16>(),
|
|
||||||
rng.gen::<u16>(),
|
|
||||||
rng.gen::<u16>(),
|
|
||||||
rng.gen::<u64>() & 0xffffffffffff,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Split an URL into its base string and parameter map
|
/// Split an URL into its base string and parameter map
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
|
@ -618,16 +606,4 @@ pub(crate) mod tests {
|
||||||
let res_str = res.join(" ");
|
let res_str = res.join(" ");
|
||||||
assert_eq!(res_str, teststr)
|
assert_eq!(res_str, teststr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case("en", Some(Language::En))]
|
|
||||||
#[case("en-GB", Some(Language::EnGb))]
|
|
||||||
#[case("en-US", Some(Language::En))]
|
|
||||||
#[case("en-ZZ", Some(Language::En))]
|
|
||||||
#[case("xy", None)]
|
|
||||||
#[case("xy-ZZ", None)]
|
|
||||||
fn parse_language(#[case] s: &str, #[case] expect: Option<Language>) {
|
|
||||||
let res = Language::from_str(s).ok();
|
|
||||||
assert_eq!(res, expect);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,13 +45,6 @@ impl ProtoBuilder {
|
||||||
self._varint(val);
|
self._varint(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a string field
|
|
||||||
pub fn string(&mut self, field: u32, string: &str) {
|
|
||||||
self._field(field, 2);
|
|
||||||
self._varint(string.len() as u64);
|
|
||||||
self.bytes.extend_from_slice(string.as_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write an embedded message
|
/// Write an embedded message
|
||||||
///
|
///
|
||||||
/// Requires passing another [`ProtoBuilder`] with the embedded message.
|
/// Requires passing another [`ProtoBuilder`] with the embedded message.
|
||||||
|
@ -60,12 +53,6 @@ impl ProtoBuilder {
|
||||||
self._varint(pb.bytes.len() as u64);
|
self._varint(pb.bytes.len() as u64);
|
||||||
self.bytes.append(&mut pb.bytes);
|
self.bytes.append(&mut pb.bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Base64 + urlencode the protobuf data
|
|
||||||
pub fn to_base64(&self) -> String {
|
|
||||||
let b64 = super::b64_encode(&self.bytes);
|
|
||||||
urlencoding::encode(&b64).to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_varint<P: Iterator<Item = u8>>(pb: &mut P) -> Option<u64> {
|
fn parse_varint<P: Iterator<Item = u8>>(pb: &mut P) -> Option<u64> {
|
||||||
|
@ -137,6 +124,11 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn t_parse_varint() {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn t_parse_proto() {
|
fn t_parse_proto() {
|
||||||
let p = "GhhVQzl2cnZOU0wzeGNXR1NrVjg2UkVCU2c%3D";
|
let p = "GhhVQzl2cnZOU0wzeGNXR1NrVjg2UkVCU2c%3D";
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
const dict = JSON.parse(fs.readFileSync("dictionary.json"));
|
|
||||||
|
|
||||||
const intl = new Intl.DisplayNames(["en"], { type: "language" });
|
|
||||||
|
|
||||||
let langs = Object.keys(dict);
|
|
||||||
Object.values(dict).forEach(entry => {
|
|
||||||
if (entry.equivalent) {
|
|
||||||
langs.push(...entry.equivalent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
langs.sort();
|
|
||||||
|
|
||||||
const res = Object.fromEntries(langs.map((l) => [l, intl.of(l)]));
|
|
||||||
fs.writeFileSync("lang_names.json", JSON.stringify(res, null, 2));
|
|
|
@ -1,85 +0,0 @@
|
||||||
{
|
|
||||||
"af": "Afrikaans",
|
|
||||||
"am": "Amharic",
|
|
||||||
"ar": "Arabic",
|
|
||||||
"as": "Assamese",
|
|
||||||
"az": "Azerbaijani",
|
|
||||||
"be": "Belarusian",
|
|
||||||
"bg": "Bulgarian",
|
|
||||||
"bn": "Bangla",
|
|
||||||
"bs": "Bosnian",
|
|
||||||
"ca": "Catalan",
|
|
||||||
"cs": "Czech",
|
|
||||||
"da": "Danish",
|
|
||||||
"de": "German",
|
|
||||||
"el": "Greek",
|
|
||||||
"en": "English",
|
|
||||||
"en-GB": "British English",
|
|
||||||
"en-IN": "English (India)",
|
|
||||||
"es": "Spanish",
|
|
||||||
"es-419": "Latin American Spanish",
|
|
||||||
"es-US": "Spanish (United States)",
|
|
||||||
"et": "Estonian",
|
|
||||||
"eu": "Basque",
|
|
||||||
"fa": "Persian",
|
|
||||||
"fi": "Finnish",
|
|
||||||
"fil": "Filipino",
|
|
||||||
"fr": "French",
|
|
||||||
"fr-CA": "Canadian French",
|
|
||||||
"gl": "Galician",
|
|
||||||
"gu": "Gujarati",
|
|
||||||
"hi": "Hindi",
|
|
||||||
"hr": "Croatian",
|
|
||||||
"hu": "Hungarian",
|
|
||||||
"hy": "Armenian",
|
|
||||||
"id": "Indonesian",
|
|
||||||
"is": "Icelandic",
|
|
||||||
"it": "Italian",
|
|
||||||
"iw": "Hebrew",
|
|
||||||
"ja": "Japanese",
|
|
||||||
"ka": "Georgian",
|
|
||||||
"kk": "Kazakh",
|
|
||||||
"km": "Khmer",
|
|
||||||
"kn": "Kannada",
|
|
||||||
"ko": "Korean",
|
|
||||||
"ky": "Kyrgyz",
|
|
||||||
"lo": "Lao",
|
|
||||||
"lt": "Lithuanian",
|
|
||||||
"lv": "Latvian",
|
|
||||||
"mk": "Macedonian",
|
|
||||||
"ml": "Malayalam",
|
|
||||||
"mn": "Mongolian",
|
|
||||||
"mr": "Marathi",
|
|
||||||
"ms": "Malay",
|
|
||||||
"my": "Burmese",
|
|
||||||
"ne": "Nepali",
|
|
||||||
"nl": "Dutch",
|
|
||||||
"no": "Norwegian",
|
|
||||||
"or": "Odia",
|
|
||||||
"pa": "Punjabi",
|
|
||||||
"pl": "Polish",
|
|
||||||
"pt": "Portuguese",
|
|
||||||
"pt-PT": "European Portuguese",
|
|
||||||
"ro": "Romanian",
|
|
||||||
"ru": "Russian",
|
|
||||||
"si": "Sinhala",
|
|
||||||
"sk": "Slovak",
|
|
||||||
"sl": "Slovenian",
|
|
||||||
"sq": "Albanian",
|
|
||||||
"sr": "Serbian",
|
|
||||||
"sr-Latn": "Serbian (Latin)",
|
|
||||||
"sv": "Swedish",
|
|
||||||
"sw": "Swahili",
|
|
||||||
"ta": "Tamil",
|
|
||||||
"te": "Telugu",
|
|
||||||
"th": "Thai",
|
|
||||||
"tr": "Turkish",
|
|
||||||
"uk": "Ukrainian",
|
|
||||||
"ur": "Urdu",
|
|
||||||
"uz": "Uzbek",
|
|
||||||
"vi": "Vietnamese",
|
|
||||||
"zh-CN": "Chinese (China)",
|
|
||||||
"zh-HK": "Chinese (Hong Kong)",
|
|
||||||
"zh-TW": "Chinese (Taiwan)",
|
|
||||||
"zu": "Zulu"
|
|
||||||
}
|
|
|
@ -1124,6 +1124,5 @@
|
||||||
"subtitles": [],
|
"subtitles": [],
|
||||||
"expires_in_seconds": 21540,
|
"expires_in_seconds": 21540,
|
||||||
"hls_manifest_url": null,
|
"hls_manifest_url": null,
|
||||||
"dash_manifest_url": null,
|
"dash_manifest_url": null
|
||||||
"preview_frames": []
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
137
tests/youtube.rs
137
tests/youtube.rs
|
@ -1,10 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashSet;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use rstest::{fixture, rstest};
|
use rstest::{fixture, rstest};
|
||||||
use rustypipe::model::paginator::ContinuationEndpoint;
|
use rustypipe::model::paginator::ContinuationEndpoint;
|
||||||
use rustypipe::param::{ChannelOrder, ChannelVideoTab, Language};
|
use rustypipe::param::Language;
|
||||||
use rustypipe::validate;
|
use rustypipe::validate;
|
||||||
use time::macros::date;
|
use time::macros::date;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
@ -15,8 +15,8 @@ use rustypipe::model::{
|
||||||
paginator::Paginator,
|
paginator::Paginator,
|
||||||
richtext::ToPlaintext,
|
richtext::ToPlaintext,
|
||||||
traits::{FromYtItem, YtStream},
|
traits::{FromYtItem, YtStream},
|
||||||
AlbumType, AudioCodec, AudioFormat, AudioTrackType, Channel, Frameset, MusicGenre,
|
AlbumType, AudioCodec, AudioFormat, Channel, MusicGenre, MusicItemType, UrlTarget,
|
||||||
MusicItemType, UrlTarget, Verification, VideoCodec, VideoFormat, YouTubeItem,
|
Verification, VideoCodec, VideoFormat, YouTubeItem,
|
||||||
};
|
};
|
||||||
use rustypipe::param::{
|
use rustypipe::param::{
|
||||||
search_filter::{self, SearchFilter},
|
search_filter::{self, SearchFilter},
|
||||||
|
@ -265,33 +265,18 @@ fn get_player(
|
||||||
stream
|
stream
|
||||||
.track
|
.track
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|t| (t.lang.as_deref().unwrap(), t.track_type.unwrap()))
|
.map(|t| t.lang.as_ref().unwrap().to_owned())
|
||||||
})
|
})
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
assert_eq!(
|
for l in ["en", "es", "fr", "pt", "ru"] {
|
||||||
langs.get("en-US"),
|
assert!(langs.contains(l), "missing lang: {l}");
|
||||||
Some(&AudioTrackType::Original),
|
|
||||||
"missing lang: en-US"
|
|
||||||
);
|
|
||||||
|
|
||||||
for l in ["es", "fr", "pt", "ru"] {
|
|
||||||
assert_eq!(
|
|
||||||
langs.get(l),
|
|
||||||
Some(&AudioTrackType::Dubbed),
|
|
||||||
"missing lang: {l}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_gte(player_data.expires_in_seconds, 10_000, "expiry time");
|
assert_gte(player_data.expires_in_seconds, 10_000, "expiry time");
|
||||||
|
|
||||||
if !is_live {
|
|
||||||
assert_gte(player_data.preview_frames.len(), 3, "preview framesets");
|
|
||||||
player_data.preview_frames.iter().for_each(assert_frameset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
|
@ -794,11 +779,8 @@ fn channel_videos(rp: RustyPipe) {
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn channel_shorts(rp: RustyPipe) {
|
fn channel_shorts(rp: RustyPipe) {
|
||||||
let channel = tokio_test::block_on(
|
let channel =
|
||||||
rp.query()
|
tokio_test::block_on(rp.query().channel_shorts("UCh8gHdtzO2tXd593_bjErWg")).unwrap();
|
||||||
.channel_videos_tab("UCh8gHdtzO2tXd593_bjErWg", ChannelVideoTab::Shorts),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&channel);
|
// dbg!(&channel);
|
||||||
assert_eq!(channel.id, "UCh8gHdtzO2tXd593_bjErWg");
|
assert_eq!(channel.id, "UCh8gHdtzO2tXd593_bjErWg");
|
||||||
|
@ -827,11 +809,8 @@ fn channel_shorts(rp: RustyPipe) {
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn channel_livestreams(rp: RustyPipe) {
|
fn channel_livestreams(rp: RustyPipe) {
|
||||||
let channel = tokio_test::block_on(
|
let channel =
|
||||||
rp.query()
|
tokio_test::block_on(rp.query().channel_livestreams("UC2DjFE7Xf11URZqWBigcVOQ")).unwrap();
|
||||||
.channel_videos_tab("UC2DjFE7Xf11URZqWBigcVOQ", ChannelVideoTab::Live),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// dbg!(&channel);
|
// dbg!(&channel);
|
||||||
assert_channel_eevblog(&channel);
|
assert_channel_eevblog(&channel);
|
||||||
|
@ -976,63 +955,6 @@ fn channel_more(
|
||||||
assert_channel(&channel_info, id, name, unlocalized || name_unlocalized);
|
assert_channel(&channel_info, id, name, unlocalized || name_unlocalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::videos("UCcdwLMPsaU2ezNSJU1nFoBQ", ChannelVideoTab::Videos, "XqZsoesa55w")]
|
|
||||||
#[case::shorts("UCcdwLMPsaU2ezNSJU1nFoBQ", ChannelVideoTab::Shorts, "k91vRvXGwHs")]
|
|
||||||
#[case::live("UCvqRdlKsE5Q8mf8YXbdIJLw", ChannelVideoTab::Live, "ojes5ULOqhc")]
|
|
||||||
fn channel_order(
|
|
||||||
#[case] id: &str,
|
|
||||||
#[case] tab: ChannelVideoTab,
|
|
||||||
#[case] most_popular: &str,
|
|
||||||
rp: RustyPipe,
|
|
||||||
) {
|
|
||||||
let latest = tokio_test::block_on(rp.query().channel_videos_tab_order(
|
|
||||||
id,
|
|
||||||
tab,
|
|
||||||
ChannelOrder::Latest,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
// Upload dates should be in descending order
|
|
||||||
if tab != ChannelVideoTab::Shorts {
|
|
||||||
let mut latest_items = latest.items.iter().peekable();
|
|
||||||
while let (Some(v), Some(next_v)) = (latest_items.next(), latest_items.peek()) {
|
|
||||||
if !v.is_upcoming && !v.is_live && !next_v.is_upcoming && !next_v.is_live {
|
|
||||||
assert_gte(
|
|
||||||
v.publish_date.unwrap(),
|
|
||||||
next_v.publish_date.unwrap(),
|
|
||||||
"latest video date",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert_next(latest, rp.query(), 15, 2);
|
|
||||||
|
|
||||||
let popular = tokio_test::block_on(rp.query().channel_videos_tab_order(
|
|
||||||
id,
|
|
||||||
tab,
|
|
||||||
ChannelOrder::Popular,
|
|
||||||
))
|
|
||||||
.unwrap();
|
|
||||||
// Most popular video should be in top 5
|
|
||||||
assert!(
|
|
||||||
popular.items.iter().take(5).any(|v| v.id == most_popular),
|
|
||||||
"most popular video {most_popular} not found"
|
|
||||||
);
|
|
||||||
|
|
||||||
// View counts should be in descending order
|
|
||||||
if tab != ChannelVideoTab::Shorts {
|
|
||||||
let mut popular_items = popular.items.iter().peekable();
|
|
||||||
while let (Some(v), Some(next_v)) = (popular_items.next(), popular_items.peek()) {
|
|
||||||
assert_gte(
|
|
||||||
v.view_count.unwrap(),
|
|
||||||
next_v.view_count.unwrap(),
|
|
||||||
"most popular view count",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert_next(popular, rp.query(), 15, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::not_exist("UCOpNcN46UbXVtpKMrmU4Abx")]
|
#[case::not_exist("UCOpNcN46UbXVtpKMrmU4Abx")]
|
||||||
#[case::gaming("UCOpNcN46UbXVtpKMrmU4Abg")]
|
#[case::gaming("UCOpNcN46UbXVtpKMrmU4Abg")]
|
||||||
|
@ -1050,19 +972,6 @@ fn channel_not_found(#[case] id: &str, rp: RustyPipe) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::shorts(ChannelVideoTab::Shorts)]
|
|
||||||
#[case::live(ChannelVideoTab::Live)]
|
|
||||||
fn channel_tab_not_found(#[case] tab: ChannelVideoTab, rp: RustyPipe) {
|
|
||||||
let channel = tokio_test::block_on(
|
|
||||||
rp.query()
|
|
||||||
.channel_videos_tab("UCGiJh0NZ52wRhYKYnuZI08Q", tab),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(channel.content.is_empty(), "got: {:?}", channel.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
//#CHANNEL_RSS
|
//#CHANNEL_RSS
|
||||||
|
|
||||||
#[cfg(feature = "rss")]
|
#[cfg(feature = "rss")]
|
||||||
|
@ -1177,8 +1086,12 @@ fn search_suggestion(rp: RustyPipe) {
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn search_suggestion_empty(rp: RustyPipe) {
|
fn search_suggestion_empty(rp: RustyPipe) {
|
||||||
let result =
|
let result = tokio_test::block_on(
|
||||||
tokio_test::block_on(rp.query().search_suggestion("fjew327p4ifjelwfvnewg49")).unwrap();
|
rp.query()
|
||||||
|
.lang(Language::Th)
|
||||||
|
.search_suggestion("fjew327p4ifjelwfvnewg49"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert!(result.is_empty());
|
assert!(result.is_empty());
|
||||||
}
|
}
|
||||||
|
@ -2327,7 +2240,7 @@ fn assert_approx(left: f64, right: f64) {
|
||||||
|
|
||||||
/// Assert that number A is greater than or equal to number B
|
/// Assert that number A is greater than or equal to number B
|
||||||
fn assert_gte<T: PartialOrd + Display>(a: T, b: T, msg: &str) {
|
fn assert_gte<T: PartialOrd + Display>(a: T, b: T, msg: &str) {
|
||||||
assert!(a >= b, "expected >= {b} {msg}, got {a}");
|
assert!(a >= b, "expected {b} {msg}, got {a}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assert that the paginator produces at least n pages
|
/// Assert that the paginator produces at least n pages
|
||||||
|
@ -2380,15 +2293,3 @@ fn assert_album_id(id: &str) {
|
||||||
fn assert_playlist_id(id: &str) {
|
fn assert_playlist_id(id: &str) {
|
||||||
assert!(validate::playlist_id(id), "invalid playlist id: `{id}`");
|
assert!(validate::playlist_id(id), "invalid playlist id: `{id}`");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_frameset(frameset: &Frameset) {
|
|
||||||
assert_gte(frameset.frame_height, 20, "frame height");
|
|
||||||
assert_gte(frameset.frame_height, 20, "frame width");
|
|
||||||
assert_gte(frameset.page_count, 1, "page count");
|
|
||||||
assert_gte(frameset.total_count, 50, "total count");
|
|
||||||
assert_gte(frameset.frames_per_page_x, 5, "frames per page x");
|
|
||||||
assert_gte(frameset.frames_per_page_y, 5, "frames per page y");
|
|
||||||
|
|
||||||
let n = frameset.urls().count() as u32;
|
|
||||||
assert_eq!(n, frameset.page_count);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue