refactor: convert _or_bail macros to ok_or functions
This commit is contained in:
parent
9aafb84e0f
commit
b5f6b7a174
13 changed files with 274 additions and 282 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
|
@ -15,7 +17,7 @@ use super::{response, ClientType, MapResponse, RustyPipeQuery, YTContext};
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QChannel<'a> {
|
struct QChannel<'a> {
|
||||||
context: YTContext,
|
context: YTContext<'a>,
|
||||||
browse_id: &'a str,
|
browse_id: &'a str,
|
||||||
params: Params,
|
params: Params,
|
||||||
}
|
}
|
||||||
|
|
@ -257,11 +259,11 @@ fn map_vanity_url(url: &str, id: &str) -> Option<String> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut parsed_url = ok_or_bail!(Url::parse(url), None);
|
Url::parse(url).ok().map(|mut parsed_url| {
|
||||||
|
// The vanity URL from YouTube is http for some reason
|
||||||
// The vanity URL from YouTube is http for some reason
|
let _ = parsed_url.set_scheme("https");
|
||||||
let _ = parsed_url.set_scheme("https");
|
parsed_url.to_string()
|
||||||
Some(parsed_url.to_string())
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_channel<T>(
|
fn map_channel<T>(
|
||||||
|
|
@ -272,13 +274,17 @@ fn map_channel<T>(
|
||||||
id: &str,
|
id: &str,
|
||||||
lang: Language,
|
lang: Language,
|
||||||
) -> Result<Channel<T>, ExtractionError> {
|
) -> Result<Channel<T>, ExtractionError> {
|
||||||
let header =
|
let header = header.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed(
|
||||||
header.ok_or_else(|| ExtractionError::ContentUnavailable("channel not found".into()))?;
|
"channel not found",
|
||||||
|
)))?;
|
||||||
let metadata = metadata
|
let metadata = metadata
|
||||||
.ok_or_else(|| ExtractionError::ContentUnavailable("channel not found".into()))?
|
.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed(
|
||||||
|
"channel not found",
|
||||||
|
)))?
|
||||||
.channel_metadata_renderer;
|
.channel_metadata_renderer;
|
||||||
let microformat = microformat
|
let microformat = microformat.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed(
|
||||||
.ok_or_else(|| ExtractionError::ContentUnavailable("channel not found".into()))?;
|
"channel not found",
|
||||||
|
)))?;
|
||||||
|
|
||||||
if metadata.external_id != id {
|
if metadata.external_id != id {
|
||||||
return Err(ExtractionError::WrongResult(format!(
|
return Err(ExtractionError::WrongResult(format!(
|
||||||
|
|
@ -384,7 +390,9 @@ fn map_channel_content(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| ExtractionError::InvalidData("could not extract content".into()))?;
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
|
"could not extract content",
|
||||||
|
)))?;
|
||||||
|
|
||||||
if let Some(target_id) = target_id {
|
if let Some(target_id) = target_id {
|
||||||
// YouTube falls back to the featured page if the channel does not have a "videos" tab.
|
// YouTube falls back to the featured page if the channel does not have a "videos" tab.
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ mod video_details;
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "rss")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "rss")))]
|
||||||
mod channel_rss;
|
mod channel_rss;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::{borrow::Cow, fmt::Debug};
|
||||||
|
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
|
|
@ -69,29 +69,29 @@ impl ClientType {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct YTContext {
|
struct YTContext<'a> {
|
||||||
client: ClientInfo,
|
client: ClientInfo<'a>,
|
||||||
/// only used on desktop
|
/// only used on desktop
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
request: Option<RequestYT>,
|
request: Option<RequestYT>,
|
||||||
user: User,
|
user: User,
|
||||||
/// only used for the embedded player
|
/// only used for the embedded player
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
third_party: Option<ThirdParty>,
|
third_party: Option<ThirdParty<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ClientInfo {
|
struct ClientInfo<'a> {
|
||||||
client_name: String,
|
client_name: &'a str,
|
||||||
client_version: String,
|
client_version: Cow<'a, str>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
client_screen: Option<String>,
|
client_screen: Option<&'a str>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
device_model: Option<String>,
|
device_model: Option<&'a str>,
|
||||||
platform: String,
|
platform: &'a str,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
original_url: Option<String>,
|
original_url: Option<&'a str>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
visitor_data: Option<String>,
|
visitor_data: Option<String>,
|
||||||
hl: Language,
|
hl: Language,
|
||||||
|
|
@ -124,21 +124,21 @@ struct User {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ThirdParty {
|
struct ThirdParty<'a> {
|
||||||
embed_url: String,
|
embed_url: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QBrowse {
|
struct QBrowse<'a> {
|
||||||
context: YTContext,
|
context: YTContext<'a>,
|
||||||
browse_id: String,
|
browse_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QContinuation<'a> {
|
struct QContinuation<'a> {
|
||||||
context: YTContext,
|
context: YTContext<'a>,
|
||||||
continuation: &'a str,
|
continuation: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -531,11 +531,11 @@ impl RustyPipe {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| {
|
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or(
|
||||||
Error::from(ExtractionError::InvalidData(
|
Error::Extraction(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
"Could not find desktop client version in sw.js".into(),
|
"Could not find desktop client version in sw.js",
|
||||||
))
|
))),
|
||||||
})
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let from_html = async {
|
let from_html = async {
|
||||||
|
|
@ -549,11 +549,11 @@ impl RustyPipe {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| {
|
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or(
|
||||||
Error::from(ExtractionError::InvalidData(
|
Error::Extraction(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
"Could not find desktop client version in sw.js".into(),
|
"Could not find desktop client version on html page",
|
||||||
))
|
))),
|
||||||
})
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
match from_swjs.await {
|
match from_swjs.await {
|
||||||
|
|
@ -578,11 +578,11 @@ impl RustyPipe {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| {
|
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or(
|
||||||
Error::from(ExtractionError::InvalidData(
|
Error::Extraction(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
"Could not find music client version in sw.js".into(),
|
"Could not find music client version in sw.js",
|
||||||
))
|
))),
|
||||||
})
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let from_html = async {
|
let from_html = async {
|
||||||
|
|
@ -596,11 +596,11 @@ impl RustyPipe {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| {
|
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or(
|
||||||
Error::from(ExtractionError::InvalidData(
|
Error::Extraction(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
"Could not find music client version on html page".into(),
|
"Could not find music client version on html page",
|
||||||
))
|
))),
|
||||||
})
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
match from_swjs.await {
|
match from_swjs.await {
|
||||||
|
|
@ -757,12 +757,12 @@ impl RustyPipeQuery {
|
||||||
match ctype {
|
match ctype {
|
||||||
ClientType::Desktop => YTContext {
|
ClientType::Desktop => YTContext {
|
||||||
client: ClientInfo {
|
client: ClientInfo {
|
||||||
client_name: "WEB".to_owned(),
|
client_name: "WEB",
|
||||||
client_version: self.client.get_desktop_client_version().await,
|
client_version: Cow::Owned(self.client.get_desktop_client_version().await),
|
||||||
client_screen: None,
|
client_screen: None,
|
||||||
device_model: None,
|
device_model: None,
|
||||||
platform: "DESKTOP".to_owned(),
|
platform: "DESKTOP",
|
||||||
original_url: Some("https://www.youtube.com/".to_owned()),
|
original_url: Some("https://www.youtube.com/"),
|
||||||
visitor_data: None,
|
visitor_data: None,
|
||||||
hl,
|
hl,
|
||||||
gl,
|
gl,
|
||||||
|
|
@ -773,12 +773,12 @@ impl RustyPipeQuery {
|
||||||
},
|
},
|
||||||
ClientType::DesktopMusic => YTContext {
|
ClientType::DesktopMusic => YTContext {
|
||||||
client: ClientInfo {
|
client: ClientInfo {
|
||||||
client_name: "WEB_REMIX".to_owned(),
|
client_name: "WEB_REMIX",
|
||||||
client_version: self.client.get_music_client_version().await,
|
client_version: Cow::Owned(self.client.get_music_client_version().await),
|
||||||
client_screen: None,
|
client_screen: None,
|
||||||
device_model: None,
|
device_model: None,
|
||||||
platform: "DESKTOP".to_owned(),
|
platform: "DESKTOP",
|
||||||
original_url: Some("https://music.youtube.com/".to_owned()),
|
original_url: Some("https://music.youtube.com/"),
|
||||||
visitor_data: None,
|
visitor_data: None,
|
||||||
hl,
|
hl,
|
||||||
gl,
|
gl,
|
||||||
|
|
@ -789,11 +789,11 @@ impl RustyPipeQuery {
|
||||||
},
|
},
|
||||||
ClientType::TvHtml5Embed => YTContext {
|
ClientType::TvHtml5Embed => YTContext {
|
||||||
client: ClientInfo {
|
client: ClientInfo {
|
||||||
client_name: "TVHTML5_SIMPLY_EMBEDDED_PLAYER".to_owned(),
|
client_name: "TVHTML5_SIMPLY_EMBEDDED_PLAYER",
|
||||||
client_version: TVHTML5_CLIENT_VERSION.to_owned(),
|
client_version: Cow::Borrowed(TVHTML5_CLIENT_VERSION),
|
||||||
client_screen: Some("EMBED".to_owned()),
|
client_screen: Some("EMBED"),
|
||||||
device_model: None,
|
device_model: None,
|
||||||
platform: "TV".to_owned(),
|
platform: "TV",
|
||||||
original_url: None,
|
original_url: None,
|
||||||
visitor_data: None,
|
visitor_data: None,
|
||||||
hl,
|
hl,
|
||||||
|
|
@ -802,16 +802,16 @@ 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: "https://www.youtube.com/".to_owned(),
|
embed_url: "https://www.youtube.com/",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
ClientType::Android => YTContext {
|
ClientType::Android => YTContext {
|
||||||
client: ClientInfo {
|
client: ClientInfo {
|
||||||
client_name: "ANDROID".to_owned(),
|
client_name: "ANDROID",
|
||||||
client_version: MOBILE_CLIENT_VERSION.to_owned(),
|
client_version: Cow::Borrowed(MOBILE_CLIENT_VERSION),
|
||||||
client_screen: None,
|
client_screen: None,
|
||||||
device_model: None,
|
device_model: None,
|
||||||
platform: "MOBILE".to_owned(),
|
platform: "MOBILE",
|
||||||
original_url: None,
|
original_url: None,
|
||||||
visitor_data: None,
|
visitor_data: None,
|
||||||
hl,
|
hl,
|
||||||
|
|
@ -823,11 +823,11 @@ impl RustyPipeQuery {
|
||||||
},
|
},
|
||||||
ClientType::Ios => YTContext {
|
ClientType::Ios => YTContext {
|
||||||
client: ClientInfo {
|
client: ClientInfo {
|
||||||
client_name: "IOS".to_owned(),
|
client_name: "IOS",
|
||||||
client_version: MOBILE_CLIENT_VERSION.to_owned(),
|
client_version: Cow::Borrowed(MOBILE_CLIENT_VERSION),
|
||||||
client_screen: None,
|
client_screen: None,
|
||||||
device_model: Some(IOS_DEVICE_MODEL.to_owned()),
|
device_model: Some(IOS_DEVICE_MODEL),
|
||||||
platform: "MOBILE".to_owned(),
|
platform: "MOBILE",
|
||||||
original_url: None,
|
original_url: None,
|
||||||
visitor_data: None,
|
visitor_data: None,
|
||||||
hl,
|
hl,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::error::{Error, ExtractionError};
|
use crate::error::{Error, ExtractionError};
|
||||||
use crate::model::{Comment, Paginator, PlaylistVideo, YouTubeItem};
|
use crate::model::{Comment, Paginator, PlaylistVideo, YouTubeItem};
|
||||||
use crate::param::ContinuationEndpoint;
|
use crate::param::ContinuationEndpoint;
|
||||||
|
|
@ -52,14 +54,13 @@ impl<T: TryFrom<YouTubeItem>> MapResponse<Paginator<T>> for response::Continuati
|
||||||
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
|
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
|
||||||
) -> Result<MapResult<Paginator<T>>, ExtractionError> {
|
) -> Result<MapResult<Paginator<T>>, ExtractionError> {
|
||||||
let mut actions = self.on_response_received_actions;
|
let mut actions = self.on_response_received_actions;
|
||||||
let items = some_or_bail!(
|
let items = actions
|
||||||
actions.try_swap_remove(0),
|
.try_swap_remove(0)
|
||||||
Err(ExtractionError::InvalidData(
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
"no item section renderer".into()
|
"no item section renderer",
|
||||||
))
|
)))?
|
||||||
)
|
.append_continuation_items_action
|
||||||
.append_continuation_items_action
|
.continuation_items;
|
||||||
.continuation_items;
|
|
||||||
|
|
||||||
let mut mapper = response::YouTubeListMapper::<YouTubeItem>::new(lang);
|
let mut mapper = response::YouTubeListMapper::<YouTubeItem>::new(lang);
|
||||||
mapper.map_response(items);
|
mapper.map_response(items);
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ use super::{
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QPlayer<'a> {
|
struct QPlayer<'a> {
|
||||||
context: YTContext,
|
context: YTContext<'a>,
|
||||||
/// Website playback context
|
/// Website playback context
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
playback_context: Option<QPlaybackContext>,
|
playback_context: Option<QPlaybackContext>,
|
||||||
|
|
@ -79,14 +79,11 @@ impl RustyPipeQuery {
|
||||||
video_id: &str,
|
video_id: &str,
|
||||||
client_type: ClientType,
|
client_type: ClientType,
|
||||||
) -> Result<VideoPlayer, Error> {
|
) -> Result<VideoPlayer, Error> {
|
||||||
let q1 = self.clone();
|
let (context, deobf) = tokio::join!(
|
||||||
let t_context = tokio::spawn(async move { q1.get_context(client_type, false).await });
|
self.get_context(client_type, false),
|
||||||
let q2 = self.client.clone();
|
self.client.get_deobf()
|
||||||
let t_deobf = tokio::spawn(async move { q2.get_deobf().await });
|
);
|
||||||
|
let deobf = deobf?;
|
||||||
let (context, deobf) = tokio::join!(t_context, t_deobf);
|
|
||||||
let context = context.unwrap();
|
|
||||||
let deobf = deobf.unwrap()?;
|
|
||||||
|
|
||||||
let request_body = if client_type.is_web() {
|
let request_body = if client_type.is_web() {
|
||||||
QPlayer {
|
QPlayer {
|
||||||
|
|
@ -168,14 +165,16 @@ impl MapResponse<VideoPlayer> for response::Player {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut streaming_data = some_or_bail!(
|
let mut streaming_data =
|
||||||
self.streaming_data,
|
self.streaming_data
|
||||||
Err(ExtractionError::InvalidData("no streaming data".into()))
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
);
|
"no streaming data",
|
||||||
let video_details = some_or_bail!(
|
)))?;
|
||||||
self.video_details,
|
let video_details =
|
||||||
Err(ExtractionError::InvalidData("no video details".into()))
|
self.video_details
|
||||||
);
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
|
"no video details",
|
||||||
|
)))?;
|
||||||
|
|
||||||
if video_details.video_id != id {
|
if video_details.video_id != id {
|
||||||
return Err(ExtractionError::WrongResult(format!(
|
return Err(ExtractionError::WrongResult(format!(
|
||||||
|
|
@ -294,12 +293,11 @@ fn cipher_to_url_params(
|
||||||
// `sp`: Signature parameter
|
// `sp`: Signature parameter
|
||||||
// `url`: URL that is missing the signature parameter
|
// `url`: URL that is missing the signature parameter
|
||||||
|
|
||||||
let sig = some_or_bail!(params.get("s"), Err(DeobfError::Extraction("s param")));
|
let sig = params.get("s").ok_or(DeobfError::Extraction("s param"))?;
|
||||||
let sp = some_or_bail!(params.get("sp"), Err(DeobfError::Extraction("sp param")));
|
let sp = params.get("sp").ok_or(DeobfError::Extraction("sp param"))?;
|
||||||
let raw_url = some_or_bail!(
|
let raw_url = params
|
||||||
params.get("url"),
|
.get("url")
|
||||||
Err(DeobfError::Extraction("no url param"))
|
.ok_or(DeobfError::Extraction("no url param"))?;
|
||||||
);
|
|
||||||
let (url_base, mut url_params) =
|
let (url_base, mut url_params) =
|
||||||
util::url_to_params(raw_url).or(Err(DeobfError::Extraction("url params")))?;
|
util::url_to_params(raw_url).or(Err(DeobfError::Extraction("url params")))?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::convert::TryFrom;
|
use std::{borrow::Cow, convert::TryFrom};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
deobfuscate::Deobfuscator,
|
deobfuscate::Deobfuscator,
|
||||||
|
|
@ -63,42 +63,40 @@ impl MapResponse<Playlist> for response::Playlist {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut tcbr_contents = contents.two_column_browse_results_renderer.contents;
|
let mut tcbr_contents = contents.two_column_browse_results_renderer.contents;
|
||||||
let video_items = some_or_bail!(
|
|
||||||
some_or_bail!(
|
let video_items = tcbr_contents
|
||||||
some_or_bail!(
|
.try_swap_remove(0)
|
||||||
tcbr_contents.try_swap_remove(0),
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
Err(ExtractionError::InvalidData(
|
"twoColumnBrowseResultsRenderer empty",
|
||||||
"twoColumnBrowseResultsRenderer empty".into()
|
)))?
|
||||||
))
|
.tab_renderer
|
||||||
)
|
.content
|
||||||
.tab_renderer
|
.section_list_renderer
|
||||||
.content
|
.contents
|
||||||
.section_list_renderer
|
.try_swap_remove(0)
|
||||||
.contents
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
.try_swap_remove(0),
|
"sectionListRenderer empty",
|
||||||
Err(ExtractionError::InvalidData(
|
)))?
|
||||||
"sectionListRenderer empty".into()
|
|
||||||
))
|
|
||||||
)
|
|
||||||
.item_section_renderer
|
.item_section_renderer
|
||||||
.contents
|
.contents
|
||||||
.try_swap_remove(0),
|
.try_swap_remove(0)
|
||||||
Err(ExtractionError::InvalidData(
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
"itemSectionRenderer empty".into()
|
"itemSectionRenderer empty",
|
||||||
))
|
)))?
|
||||||
)
|
.playlist_video_list_renderer
|
||||||
.playlist_video_list_renderer
|
.contents;
|
||||||
.contents;
|
|
||||||
|
|
||||||
let (videos, ctoken) = map_playlist_items(video_items.c);
|
let (videos, ctoken) = map_playlist_items(video_items.c);
|
||||||
|
|
||||||
let (thumbnails, last_update_txt) = match self.sidebar {
|
let (thumbnails, last_update_txt) = match self.sidebar {
|
||||||
Some(sidebar) => {
|
Some(sidebar) => {
|
||||||
let mut sidebar_items = sidebar.playlist_sidebar_renderer.items;
|
let mut sidebar_items = sidebar.playlist_sidebar_renderer.items;
|
||||||
let mut primary = some_or_bail!(
|
let mut primary =
|
||||||
sidebar_items.try_swap_remove(0),
|
sidebar_items
|
||||||
Err(ExtractionError::InvalidData("no primary sidebar".into()))
|
.try_swap_remove(0)
|
||||||
);
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
|
"no primary sidebar",
|
||||||
|
)))?;
|
||||||
|
|
||||||
(
|
(
|
||||||
primary
|
primary
|
||||||
|
|
@ -113,10 +111,12 @@ impl MapResponse<Playlist> for response::Playlist {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let header_banner = some_or_bail!(
|
let header_banner = header
|
||||||
header.playlist_header_renderer.playlist_header_banner,
|
.playlist_header_renderer
|
||||||
Err(ExtractionError::InvalidData("no thumbnail found".into()))
|
.playlist_header_banner
|
||||||
);
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
|
"no thumbnail found",
|
||||||
|
)))?;
|
||||||
|
|
||||||
let mut byline = header.playlist_header_renderer.byline;
|
let mut byline = header.playlist_header_renderer.byline;
|
||||||
let last_update_txt = byline
|
let last_update_txt = byline
|
||||||
|
|
@ -131,12 +131,8 @@ impl MapResponse<Playlist> for response::Playlist {
|
||||||
};
|
};
|
||||||
|
|
||||||
let n_videos = match ctoken {
|
let n_videos = match ctoken {
|
||||||
Some(_) => {
|
Some(_) => util::parse_numeric(&header.playlist_header_renderer.num_videos_text)
|
||||||
ok_or_bail!(
|
.map_err(|_| ExtractionError::InvalidData(Cow::Borrowed("no video count")))?,
|
||||||
util::parse_numeric(&header.playlist_header_renderer.num_videos_text),
|
|
||||||
Err(ExtractionError::InvalidData("no video count".into()))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => videos.len() as u64,
|
None => videos.len() as u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -187,7 +183,9 @@ impl MapResponse<Paginator<PlaylistVideo>> for response::PlaylistCont {
|
||||||
let mut actions = self.on_response_received_actions;
|
let mut actions = self.on_response_received_actions;
|
||||||
let action = actions
|
let action = actions
|
||||||
.try_swap_remove(0)
|
.try_swap_remove(0)
|
||||||
.ok_or_else(|| ExtractionError::InvalidData("no onResponseReceivedAction".into()))?;
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
|
"no onResponseReceivedAction",
|
||||||
|
)))?;
|
||||||
|
|
||||||
let (items, ctoken) =
|
let (items, ctoken) =
|
||||||
map_playlist_items(action.append_continuation_items_action.continuation_items.c);
|
map_playlist_items(action.append_continuation_items_action.continuation_items.c);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use serde::{de::IgnoredAny, Serialize};
|
use serde::{de::IgnoredAny, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -12,7 +14,7 @@ use super::{response, ClientType, MapResponse, MapResult, RustyPipeQuery, YTCont
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QSearch<'a> {
|
struct QSearch<'a> {
|
||||||
context: YTContext,
|
context: YTContext<'a>,
|
||||||
query: &'a str,
|
query: &'a str,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
params: Option<String>,
|
params: Option<String>,
|
||||||
|
|
@ -69,11 +71,11 @@ impl RustyPipeQuery {
|
||||||
.http_request_txt(self.client.inner.http.get(url).build()?)
|
.http_request_txt(self.client.inner.http.get(url).build()?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let trimmed = response.get(5..).ok_or_else(|| {
|
let trimmed = response
|
||||||
Error::Extraction(ExtractionError::InvalidData(
|
.get(5..)
|
||||||
"could not get string slice".into(),
|
.ok_or(Error::Extraction(ExtractionError::InvalidData(
|
||||||
))
|
Cow::Borrowed("could not get string slice"),
|
||||||
})?;
|
)))?;
|
||||||
|
|
||||||
let parsed = serde_json::from_str::<(
|
let parsed = serde_json::from_str::<(
|
||||||
IgnoredAny,
|
IgnoredAny,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{Error, ExtractionError},
|
error::{Error, ExtractionError},
|
||||||
model::{Paginator, VideoItem},
|
model::{Paginator, VideoItem},
|
||||||
|
|
@ -54,7 +56,7 @@ impl MapResponse<Paginator<VideoItem>> for response::Startpage {
|
||||||
let mut contents = self.contents.two_column_browse_results_renderer.tabs;
|
let mut contents = self.contents.two_column_browse_results_renderer.tabs;
|
||||||
let grid = contents
|
let grid = contents
|
||||||
.try_swap_remove(0)
|
.try_swap_remove(0)
|
||||||
.ok_or_else(|| ExtractionError::InvalidData("no contents".into()))?
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed("no contents")))?
|
||||||
.tab_renderer
|
.tab_renderer
|
||||||
.content
|
.content
|
||||||
.section_list_renderer
|
.section_list_renderer
|
||||||
|
|
@ -78,7 +80,7 @@ impl MapResponse<Vec<VideoItem>> for response::Trending {
|
||||||
let mut contents = self.contents.two_column_browse_results_renderer.tabs;
|
let mut contents = self.contents.two_column_browse_results_renderer.tabs;
|
||||||
let items = contents
|
let items = contents
|
||||||
.try_swap_remove(0)
|
.try_swap_remove(0)
|
||||||
.ok_or_else(|| ExtractionError::InvalidData("no contents".into()))?
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed("no contents")))?
|
||||||
.tab_renderer
|
.tab_renderer
|
||||||
.content
|
.content
|
||||||
.section_list_renderer
|
.section_list_renderer
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -12,8 +14,8 @@ use super::{response, ClientType, MapResponse, RustyPipeQuery, YTContext};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QResolveUrl {
|
struct QResolveUrl<'a> {
|
||||||
context: YTContext,
|
context: YTContext<'a>,
|
||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,7 +30,7 @@ impl RustyPipeQuery {
|
||||||
});
|
});
|
||||||
let mut path_split = url
|
let mut path_split = url
|
||||||
.path_segments()
|
.path_segments()
|
||||||
.ok_or_else(|| Error::Other("invalid url: empty path".into()))?;
|
.ok_or(Error::Other(Cow::Borrowed("invalid url: empty path")))?;
|
||||||
|
|
||||||
let get_start_time = || {
|
let get_start_time = || {
|
||||||
params
|
params
|
||||||
|
|
@ -41,7 +43,7 @@ impl RustyPipeQuery {
|
||||||
Some("watch") => {
|
Some("watch") => {
|
||||||
let id = params
|
let id = params
|
||||||
.get("v")
|
.get("v")
|
||||||
.ok_or_else(|| Error::Other("invalid url: no video id".into()))?
|
.ok_or(Error::Other(Cow::Borrowed("invalid url: no video id")))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
Ok(UrlTarget::Video {
|
Ok(UrlTarget::Video {
|
||||||
|
|
@ -56,7 +58,7 @@ impl RustyPipeQuery {
|
||||||
Some("playlist") => {
|
Some("playlist") => {
|
||||||
let id = params
|
let id = params
|
||||||
.get("list")
|
.get("list")
|
||||||
.ok_or_else(|| Error::Other("invalid url: no playlist id".into()))?
|
.ok_or(Error::Other(Cow::Borrowed("invalid url: no playlist id")))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
Ok(UrlTarget::Playlist { id })
|
Ok(UrlTarget::Playlist { id })
|
||||||
|
|
@ -190,14 +192,16 @@ impl MapResponse<UrlTarget> for response::ResolvedUrl {
|
||||||
let page_type = self
|
let page_type = self
|
||||||
.endpoint
|
.endpoint
|
||||||
.command_metadata
|
.command_metadata
|
||||||
.ok_or_else(|| ExtractionError::InvalidData("No command metadata".into()))?
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed(
|
||||||
|
"No command metadata",
|
||||||
|
)))?
|
||||||
.web_command_metadata
|
.web_command_metadata
|
||||||
.web_page_type;
|
.web_page_type;
|
||||||
|
|
||||||
let id = self
|
let id = self
|
||||||
.endpoint
|
.endpoint
|
||||||
.browse_endpoint
|
.browse_endpoint
|
||||||
.ok_or_else(|| ExtractionError::InvalidData("No browse ID".into()))?
|
.ok_or(ExtractionError::InvalidData(Cow::Borrowed("No browse ID")))?
|
||||||
.browse_id;
|
.browse_id;
|
||||||
|
|
||||||
Ok(MapResult {
|
Ok(MapResult {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -16,7 +18,7 @@ use super::{
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct QVideo<'a> {
|
struct QVideo<'a> {
|
||||||
context: YTContext,
|
context: YTContext<'a>,
|
||||||
/// YouTube video ID
|
/// YouTube video ID
|
||||||
video_id: &'a str,
|
video_id: &'a str,
|
||||||
/// Set to true to allow extraction of streams with sensitive content
|
/// Set to true to allow extraction of streams with sensitive content
|
||||||
|
|
@ -79,10 +81,14 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
|
|
||||||
let contents = self
|
let contents = self
|
||||||
.contents
|
.contents
|
||||||
.ok_or_else(|| ExtractionError::ContentUnavailable("Video not found".into()))?;
|
.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed(
|
||||||
let current_video_endpoint = self
|
"Video not found",
|
||||||
.current_video_endpoint
|
)))?;
|
||||||
.ok_or_else(|| ExtractionError::ContentUnavailable("Video not found".into()))?;
|
let current_video_endpoint =
|
||||||
|
self.current_video_endpoint
|
||||||
|
.ok_or(ExtractionError::ContentUnavailable(Cow::Borrowed(
|
||||||
|
"Video not found",
|
||||||
|
)))?;
|
||||||
|
|
||||||
let video_id = current_video_endpoint.watch_endpoint.video_id;
|
let video_id = current_video_endpoint.watch_endpoint.video_id;
|
||||||
if id != video_id {
|
if id != video_id {
|
||||||
|
|
|
||||||
|
|
@ -92,48 +92,44 @@ fn get_sig_fn(player_js: &str) -> Result<String> {
|
||||||
|
|
||||||
let function_pattern_str =
|
let function_pattern_str =
|
||||||
"(".to_owned() + &dfunc_name.replace('$', "\\$") + "=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})";
|
"(".to_owned() + &dfunc_name.replace('$', "\\$") + "=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})";
|
||||||
let function_pattern = ok_or_bail!(
|
let function_pattern = Regex::new(&function_pattern_str)
|
||||||
Regex::new(&function_pattern_str),
|
.map_err(|_| DeobfError::Other("could not parse function pattern regex"))?;
|
||||||
Err(DeobfError::Other("could not parse function pattern regex"))
|
|
||||||
);
|
|
||||||
|
|
||||||
let deobfuscate_function = "var ".to_owned()
|
let deobfuscate_function = "var ".to_owned()
|
||||||
+ some_or_bail!(
|
+ function_pattern
|
||||||
function_pattern.captures(player_js).ok().flatten(),
|
.captures(player_js)
|
||||||
Err(DeobfError::Extraction("deobf function"))
|
.ok()
|
||||||
)
|
.flatten()
|
||||||
.get(1)
|
.ok_or(DeobfError::Extraction("deobf function"))?
|
||||||
.unwrap()
|
.get(1)
|
||||||
.as_str()
|
.unwrap()
|
||||||
|
.as_str()
|
||||||
+ ";";
|
+ ";";
|
||||||
|
|
||||||
static HELPER_OBJECT_NAME_REGEX: Lazy<Regex> =
|
static HELPER_OBJECT_NAME_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(";([A-Za-z0-9_\\$]{2})\\...\\(").unwrap());
|
Lazy::new(|| Regex::new(";([A-Za-z0-9_\\$]{2})\\...\\(").unwrap());
|
||||||
let helper_object_name = some_or_bail!(
|
let helper_object_name = HELPER_OBJECT_NAME_REGEX
|
||||||
HELPER_OBJECT_NAME_REGEX
|
.captures(&deobfuscate_function)
|
||||||
.captures(&deobfuscate_function)
|
.ok()
|
||||||
.ok()
|
.flatten()
|
||||||
.flatten(),
|
.ok_or(DeobfError::Extraction("helper object name"))?
|
||||||
Err(DeobfError::Extraction("helper object name"))
|
.get(1)
|
||||||
)
|
.unwrap()
|
||||||
.get(1)
|
.as_str();
|
||||||
.unwrap()
|
|
||||||
.as_str();
|
|
||||||
|
|
||||||
let helper_pattern_str =
|
let helper_pattern_str =
|
||||||
"(var ".to_owned() + &helper_object_name.replace('$', "\\$") + "=\\{.+?\\}\\};)";
|
"(var ".to_owned() + &helper_object_name.replace('$', "\\$") + "=\\{.+?\\}\\};)";
|
||||||
let helper_pattern = ok_or_bail!(
|
let helper_pattern = Regex::new(&helper_pattern_str)
|
||||||
Regex::new(&helper_pattern_str),
|
.map_err(|_| DeobfError::Other("could not parse helper pattern regex"))?;
|
||||||
Err(DeobfError::Other("could not parse helper pattern regex"))
|
|
||||||
);
|
|
||||||
let player_js_nonl = player_js.replace('\n', "");
|
let player_js_nonl = player_js.replace('\n', "");
|
||||||
let helper_object = some_or_bail!(
|
let helper_object = helper_pattern
|
||||||
helper_pattern.captures(&player_js_nonl).ok().flatten(),
|
.captures(&player_js_nonl)
|
||||||
Err(DeobfError::Extraction("helper object"))
|
.ok()
|
||||||
)
|
.flatten()
|
||||||
.get(1)
|
.ok_or(DeobfError::Extraction("helper object"))?
|
||||||
.unwrap()
|
.get(1)
|
||||||
.as_str();
|
.unwrap()
|
||||||
|
.as_str();
|
||||||
|
|
||||||
Ok(helper_object.to_owned() + &deobfuscate_function + &caller_function(&dfunc_name))
|
Ok(helper_object.to_owned() + &deobfuscate_function + &caller_function(&dfunc_name))
|
||||||
}
|
}
|
||||||
|
|
@ -156,10 +152,11 @@ fn get_nsig_fn_name(player_js: &str) -> Result<String> {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
let fname_match = some_or_bail!(
|
let fname_match = FUNCTION_NAME_REGEX
|
||||||
FUNCTION_NAME_REGEX.captures(player_js).ok().flatten(),
|
.captures(player_js)
|
||||||
Err(DeobfError::Extraction("n_deobf function"))
|
.ok()
|
||||||
);
|
.flatten()
|
||||||
|
.ok_or(DeobfError::Extraction("n_deobf function"))?;
|
||||||
|
|
||||||
let function_name = fname_match.get(1).unwrap().as_str();
|
let function_name = fname_match.get(1).unwrap().as_str();
|
||||||
|
|
||||||
|
|
@ -179,18 +176,19 @@ fn get_nsig_fn_name(player_js: &str) -> Result<String> {
|
||||||
"could not parse helper pattern regex",
|
"could not parse helper pattern regex",
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
let array_str = some_or_bail!(
|
let array_str = array_pattern
|
||||||
array_pattern.captures(player_js).ok().flatten(),
|
.captures(player_js)
|
||||||
Err(DeobfError::Extraction("n_deobf array_str"))
|
.ok()
|
||||||
)
|
.flatten()
|
||||||
.get(1)
|
.ok_or(DeobfError::Extraction("n_deobf array_str"))?
|
||||||
.unwrap()
|
.get(1)
|
||||||
.as_str();
|
.unwrap()
|
||||||
|
.as_str();
|
||||||
|
|
||||||
let mut names = array_str.split(',');
|
let mut names = array_str.split(',');
|
||||||
let name = some_or_bail!(
|
let name = names
|
||||||
names.nth(array_num),
|
.nth(array_num)
|
||||||
Err(DeobfError::Extraction("n_deobf function name"))
|
.ok_or(DeobfError::Extraction("n_deobf function name"))?;
|
||||||
);
|
|
||||||
Ok(name.to_owned())
|
Ok(name.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,13 +277,14 @@ async fn get_player_js_url(http: &Client) -> Result<String> {
|
||||||
Regex::new(r#"https:\\\/\\\/www\.youtube\.com\\\/s\\\/player\\\/([a-z0-9]{8})\\\/"#)
|
Regex::new(r#"https:\\\/\\\/www\.youtube\.com\\\/s\\\/player\\\/([a-z0-9]{8})\\\/"#)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
let player_hash = some_or_bail!(
|
let player_hash = PLAYER_HASH_PATTERN
|
||||||
PLAYER_HASH_PATTERN.captures(&text).ok().flatten(),
|
.captures(&text)
|
||||||
Err(DeobfError::Extraction("player hash"))
|
.ok()
|
||||||
)
|
.flatten()
|
||||||
.get(1)
|
.ok_or(DeobfError::Extraction("player hash"))?
|
||||||
.unwrap()
|
.get(1)
|
||||||
.as_str();
|
.unwrap()
|
||||||
|
.as_str();
|
||||||
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"https://www.youtube.com/s/player/{}/player_ias.vflset/en_US/base.js",
|
"https://www.youtube.com/s/player/{}/player_ias.vflset/en_US/base.js",
|
||||||
|
|
@ -302,14 +301,15 @@ fn get_sts(player_js: &str) -> Result<String> {
|
||||||
static STS_PATTERN: Lazy<Regex> =
|
static STS_PATTERN: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new("signatureTimestamp[=:](\\d+)").unwrap());
|
Lazy::new(|| Regex::new("signatureTimestamp[=:](\\d+)").unwrap());
|
||||||
|
|
||||||
Ok(some_or_bail!(
|
Ok(STS_PATTERN
|
||||||
STS_PATTERN.captures(player_js).ok().flatten(),
|
.captures(player_js)
|
||||||
Err(DeobfError::Extraction("sts"))
|
.ok()
|
||||||
)
|
.flatten()
|
||||||
.get(1)
|
.ok_or(DeobfError::Extraction("sts"))?
|
||||||
.unwrap()
|
.get(1)
|
||||||
.as_str()
|
.unwrap()
|
||||||
.to_owned())
|
.as_str()
|
||||||
|
.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! YouTube audio/video downloader
|
//! YouTube audio/video downloader
|
||||||
|
|
||||||
use std::{cmp::Ordering, ffi::OsString, ops::Range, path::PathBuf};
|
use std::{borrow::Cow, cmp::Ordering, ffi::OsString, ops::Range, path::PathBuf};
|
||||||
|
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
use futures::stream::{self, StreamExt};
|
use futures::stream::{self, StreamExt};
|
||||||
|
|
@ -45,16 +45,15 @@ fn get_download_range(offset: u64, size: Option<u64>) -> Range<u64> {
|
||||||
fn parse_cr_header(cr_header: &str) -> Result<(u64, u64)> {
|
fn parse_cr_header(cr_header: &str) -> Result<(u64, u64)> {
|
||||||
static PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r#"bytes (\d+)-(\d+)/(\d+)"#).unwrap());
|
static PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r#"bytes (\d+)-(\d+)/(\d+)"#).unwrap());
|
||||||
|
|
||||||
let captures = some_or_bail!(
|
let captures = PATTERN.captures(cr_header).ok().flatten().ok_or_else(|| {
|
||||||
PATTERN.captures(cr_header).ok().flatten(),
|
DownloadError::Progressive(
|
||||||
Err(DownloadError::Progressive(
|
|
||||||
format!(
|
format!(
|
||||||
"Content-Range header '{}' does not match pattern",
|
"Content-Range header '{}' does not match pattern",
|
||||||
cr_header
|
cr_header
|
||||||
)
|
)
|
||||||
.into()
|
.into(),
|
||||||
))
|
)
|
||||||
);
|
})?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
captures.get(2).unwrap().as_str().parse().map_err(|_| {
|
captures.get(2).unwrap().as_str().parse().map_err(|_| {
|
||||||
|
|
@ -106,16 +105,18 @@ async fn download_single_file<P: Into<PathBuf>>(
|
||||||
.await?
|
.await?
|
||||||
.error_for_status()?;
|
.error_for_status()?;
|
||||||
|
|
||||||
let cr_header = some_or_bail!(
|
let cr_header = res
|
||||||
res.headers().get(header::CONTENT_RANGE),
|
.headers()
|
||||||
Err(DownloadError::Progressive(
|
.get(header::CONTENT_RANGE)
|
||||||
"Did not get Content-Range header".into()
|
.ok_or(DownloadError::Progressive(Cow::Borrowed(
|
||||||
))
|
"Did not get Content-Range header",
|
||||||
)
|
)))?
|
||||||
.to_str()
|
.to_str()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
DownloadError::Progressive("could not convert Content-Range header to string".into())
|
DownloadError::Progressive(
|
||||||
})?;
|
"could not convert Content-Range header to string".into(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let (_, original_size) = parse_cr_header(cr_header)?;
|
let (_, original_size) = parse_cr_header(cr_header)?;
|
||||||
|
|
||||||
|
|
@ -193,16 +194,18 @@ async fn download_chunks_by_header(
|
||||||
.error_for_status()?;
|
.error_for_status()?;
|
||||||
|
|
||||||
// Content-Range: bytes 0-100/451368980
|
// Content-Range: bytes 0-100/451368980
|
||||||
let cr_header = some_or_bail!(
|
let cr_header = res
|
||||||
res.headers().get(header::CONTENT_RANGE),
|
.headers()
|
||||||
Err(DownloadError::Progressive(
|
.get(header::CONTENT_RANGE)
|
||||||
"Did not get Content-Range header".into()
|
.ok_or(DownloadError::Progressive(Cow::Borrowed(
|
||||||
))
|
"Did not get Content-Range header",
|
||||||
)
|
)))?
|
||||||
.to_str()
|
.to_str()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
DownloadError::Progressive("could not convert Content-Range header to string".into())
|
DownloadError::Progressive(
|
||||||
})?;
|
"could not convert Content-Range header to string".into(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let (parsed_offset, parsed_size) = parse_cr_header(cr_header)?;
|
let (parsed_offset, parsed_size) = parse_cr_header(cr_header)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
/// Returns an unwrapped Option if Some() otherwise returns the passed expression
|
/// Returns an unwrapped Option if Some() otherwise returns the passed expression
|
||||||
#[allow(unused_macros)]
|
|
||||||
macro_rules! some_or_bail {
|
macro_rules! some_or_bail {
|
||||||
($opt:expr, $ret:expr $(,)?) => {{
|
($opt:expr, $ret:expr $(,)?) => {{
|
||||||
match $opt {
|
match $opt {
|
||||||
|
|
@ -11,21 +10,7 @@ macro_rules! some_or_bail {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an unwrapped Option if Some() otherwise continues a loop.
|
|
||||||
#[allow(unused_macros)]
|
|
||||||
macro_rules! some_or_continue {
|
|
||||||
($opt:expr $(,)?) => {{
|
|
||||||
match $opt {
|
|
||||||
Some(stuff) => stuff,
|
|
||||||
None => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an unwrapped Result if Ok() otherwise returns the passed expression
|
/// Returns an unwrapped Result if Ok() otherwise returns the passed expression
|
||||||
#[allow(unused_macros)]
|
|
||||||
macro_rules! ok_or_bail {
|
macro_rules! ok_or_bail {
|
||||||
($result:expr, $ret:expr $(,)?) => {{
|
($result:expr, $ret:expr $(,)?) => {{
|
||||||
match $result {
|
match $result {
|
||||||
|
|
@ -36,15 +21,3 @@ macro_rules! ok_or_bail {
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_macros)]
|
|
||||||
macro_rules! ok_or_continue {
|
|
||||||
($opt:expr $(,)?) => {{
|
|
||||||
match $opt {
|
|
||||||
Ok(stuff) => stuff,
|
|
||||||
Err(_) => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -304,13 +304,10 @@ impl VideoPlayer {
|
||||||
(Some(video_stream), None) => (Some(video_stream), None),
|
(Some(video_stream), None) => (Some(video_stream), None),
|
||||||
(Some(video_stream), Some(video_only_stream)) => match video_only_stream > video_stream
|
(Some(video_stream), Some(video_only_stream)) => match video_only_stream > video_stream
|
||||||
{
|
{
|
||||||
true => (
|
true => match self.select_audio_stream(filter) {
|
||||||
Some(video_only_stream),
|
Some(audio_stream) => (Some(video_only_stream), Some(audio_stream)),
|
||||||
Some(some_or_bail!(
|
None => (Some(video_stream), None),
|
||||||
self.select_audio_stream(filter),
|
},
|
||||||
(Some(video_stream), None)
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
false => (Some(video_stream), None),
|
false => (Some(video_stream), None),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue