use std::sync::Arc;
use reqwest::Client;
use url::Url;
use cipher::Cipher;
use crate::{IdBuf, Stream, Video, VideoDetails, VideoInfo};
use crate::error::Error;
use crate::video_info::player_response::streaming_data::RawFormat;
use crate::video_info::player_response::streaming_data::StreamingData;
mod cipher;
#[derive(Clone, derive_more::Display, derivative::Derivative)]
#[display(fmt = "VideoDescrambler({})", "video_info.player_response.video_details.video_id")]
#[derivative(Debug, PartialEq, Eq)]
pub struct VideoDescrambler {
pub(crate) video_info: VideoInfo,
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub(crate) client: Client,
pub(crate) js: String,
}
impl VideoDescrambler {
#[log_derive::logfn(ok = "Trace", err = "Error")]
#[log_derive::logfn_inputs(Trace)]
pub fn descramble(mut self) -> crate::Result<Video> {
let streaming_data = self.video_info.player_response.streaming_data
.as_mut()
.ok_or_else(|| Error::Custom(
"VideoInfo contained no StreamingData, which is essential for downloading.".into()
))?;
if let Some(ref adaptive_fmts_raw) = self.video_info.adaptive_fmts_raw {
apply_descrambler_adaptive_fmts(streaming_data, adaptive_fmts_raw)?;
}
apply_signature(streaming_data, &self.js)?;
let mut streams = Vec::new();
Self::initialize_streams(
streaming_data,
&mut streams,
&self.client,
&self.video_info.player_response.video_details,
);
Ok(Video {
video_info: self.video_info,
streams,
})
}
#[inline]
pub fn video_info(&self) -> &VideoInfo {
&self.video_info
}
#[inline]
pub fn video_details(&self) -> &VideoDetails {
&self.video_info.player_response.video_details
}
#[inline]
pub fn video_id(&self) -> &IdBuf {
&self.video_details().video_id
}
#[inline]
pub fn video_title(&self) -> &String {
&self.video_details().title
}
#[inline]
fn initialize_streams(
streaming_data: &mut StreamingData,
streams: &mut Vec<Stream>,
client: &Client,
video_details: &Arc<VideoDetails>,
) {
for raw_format in streaming_data.formats.drain(..).chain(streaming_data.adaptive_formats.drain(..)) {
let stream = Stream::from_raw_format(
raw_format,
client.clone(),
Arc::clone(video_details),
);
streams.push(stream);
}
}
}
#[inline]
fn apply_descrambler_adaptive_fmts(streaming_data: &mut StreamingData, adaptive_fmts_raw: &str) -> crate::Result<()> {
for raw_fmt in adaptive_fmts_raw.split(',') {
log::warn!(
"`apply_descrambler_adaptive_fmts` is probaply broken!\
Please open an issue on GitHub and paste in the whole warning message (it may be quite long).\
adaptive_fmts_raw: `{}`", raw_fmt
);
let raw_format = serde_qs::from_str::<RawFormat>(raw_fmt)?;
streaming_data.formats.push(raw_format);
}
Ok(())
}
#[inline]
fn apply_signature(streaming_data: &mut StreamingData, js: &str) -> crate::Result<()> {
let cipher = Cipher::from_js(js)?;
for raw_format in streaming_data.formats.iter_mut().chain(streaming_data.adaptive_formats.iter_mut()) {
let url = &mut raw_format.signature_cipher.url;
let s = match raw_format.signature_cipher.s {
Some(ref mut s) => s,
None if url_already_contains_signature(url) => continue,
None => return Err(Error::UnexpectedResponse(
"RawFormat did not contain a signature (s), nor did the url".into()
))
};
cipher.decrypt_signature(s)?;
url
.query_pairs_mut()
.append_pair("sig", s);
}
Ok(())
}
#[inline]
fn url_already_contains_signature(url: &Url) -> bool {
let url = url.as_str();
url.contains("signature") || (url.contains("&sig=") || url.contains("&lsig="))
}