feat: add list of clients to downloader

This commit is contained in:
ThetaDev 2024-08-10 02:29:54 +02:00
parent 00fa95d92c
commit fbecd232e5
2 changed files with 69 additions and 31 deletions

View file

@ -107,7 +107,7 @@ enum Commands {
limit: usize, limit: usize,
/// YT Client used to fetch player data /// YT Client used to fetch player data
#[clap(long)] #[clap(long)]
client_type: Option<PlayerType>, client_type: Option<Vec<ClientTypeArg>>,
/// Pot token to circumvent bot detection /// Pot token to circumvent bot detection
#[clap(long)] #[clap(long)]
pot: Option<String>, pot: Option<String>,
@ -145,7 +145,7 @@ enum Commands {
player: bool, player: bool,
/// YT Client used to fetch player data /// YT Client used to fetch player data
#[clap(long)] #[clap(long)]
client_type: Option<PlayerType>, client_type: Option<ClientTypeArg>,
}, },
/// Search YouTube /// Search YouTube
Search { Search {
@ -260,7 +260,7 @@ enum MusicSearchCategory {
} }
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)] #[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
enum PlayerType { enum ClientTypeArg {
Desktop, Desktop,
Tv, Tv,
TvEmbed, TvEmbed,
@ -310,14 +310,14 @@ impl From<SearchOrder> for search_filter::Order {
} }
} }
impl From<PlayerType> for ClientType { impl From<ClientTypeArg> for ClientType {
fn from(value: PlayerType) -> Self { fn from(value: ClientTypeArg) -> Self {
match value { match value {
PlayerType::Desktop => Self::Desktop, ClientTypeArg::Desktop => Self::Desktop,
PlayerType::TvEmbed => Self::TvHtml5Embed, ClientTypeArg::TvEmbed => Self::TvHtml5Embed,
PlayerType::Tv => Self::Tv, ClientTypeArg::Tv => Self::Tv,
PlayerType::Android => Self::Android, ClientTypeArg::Android => Self::Android,
PlayerType::Ios => Self::Ios, ClientTypeArg::Ios => Self::Ios,
} }
} }
} }
@ -424,11 +424,11 @@ async fn download_video(
dl: &Downloader, dl: &Downloader,
id: &str, id: &str,
target: &DownloadTarget, target: &DownloadTarget,
client_type: Option<PlayerType>, client_types: Option<&[ClientType]>,
) { ) {
let mut q = target.apply(dl.id(id)); let mut q = target.apply(dl.id(id));
if let Some(client_type) = client_type { if let Some(client_types) = client_types {
q = q.client_type(client_type.into()); q = q.client_types(client_types);
} }
let res = q.download().await; let res = q.download().await;
if let Err(e) = res { if let Err(e) = res {
@ -441,7 +441,7 @@ async fn download_videos(
videos: Vec<DownloadVideo>, videos: Vec<DownloadVideo>,
target: &DownloadTarget, target: &DownloadTarget,
parallel: usize, parallel: usize,
client_type: Option<PlayerType>, client_types: Option<&[ClientType]>,
multi: MultiProgress, multi: MultiProgress,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
// Indicatif setup // Indicatif setup
@ -467,8 +467,8 @@ async fn download_videos(
let n_failed = n_failed.clone(); let n_failed = n_failed.clone();
let mut q = target.apply(dl.video(video)); let mut q = target.apply(dl.video(video));
if let Some(client_type) = client_type { if let Some(client_types) = client_types {
q = q.client_type(client_type.into()); q = q.client_types(client_types);
} }
async move { async move {
@ -589,9 +589,11 @@ async fn run() -> anyhow::Result<()> {
} }
let dl = dl.stream_filter(filter).build(); let dl = dl.stream_filter(filter).build();
let cts = client_type.map(|c| c.into_iter().map(ClientType::from).collect::<Vec<_>>());
match url_target { match url_target {
UrlTarget::Video { id, .. } => { UrlTarget::Video { id, .. } => {
download_video(&dl, &id, &target, client_type).await; download_video(&dl, &id, &target, cts.as_deref()).await;
} }
UrlTarget::Channel { id } => { UrlTarget::Channel { id } => {
target.assert_dir(); target.assert_dir();
@ -604,7 +606,7 @@ async fn run() -> anyhow::Result<()> {
.take(limit) .take(limit)
.map(|v| DownloadVideo::from_entity(&v)) .map(|v| DownloadVideo::from_entity(&v))
.collect(); .collect();
download_videos(&dl, videos, &target, parallel, client_type, multi).await?; download_videos(&dl, videos, &target, parallel, cts.as_deref(), multi).await?;
} }
UrlTarget::Playlist { id } => { UrlTarget::Playlist { id } => {
target.assert_dir(); target.assert_dir();
@ -629,7 +631,7 @@ async fn run() -> anyhow::Result<()> {
.map(|v| DownloadVideo::from_entity(&v)) .map(|v| DownloadVideo::from_entity(&v))
.collect() .collect()
}; };
download_videos(&dl, videos, &target, parallel, client_type, multi).await?; download_videos(&dl, videos, &target, parallel, cts.as_deref(), multi).await?;
} }
UrlTarget::Album { id } => { UrlTarget::Album { id } => {
target.assert_dir(); target.assert_dir();
@ -640,7 +642,7 @@ async fn run() -> anyhow::Result<()> {
.take(limit) .take(limit)
.map(|v| DownloadVideo::from_track(&v)) .map(|v| DownloadVideo::from_track(&v))
.collect(); .collect();
download_videos(&dl, videos, &target, parallel, client_type, multi).await?; download_videos(&dl, videos, &target, parallel, cts.as_deref(), multi).await?;
} }
} }
} }

View file

@ -74,6 +74,7 @@ pub struct DownloaderBuilder {
audio_tag: bool, audio_tag: bool,
#[cfg(feature = "audiotag")] #[cfg(feature = "audiotag")]
crop_cover: bool, crop_cover: bool,
client_types: Option<Vec<ClientType>>,
pot: Option<String>, pot: Option<String>,
} }
@ -104,6 +105,8 @@ struct DownloaderInner {
/// Crop YT thumbnails to ensure square album covers /// Crop YT thumbnails to ensure square album covers
#[cfg(feature = "audiotag")] #[cfg(feature = "audiotag")]
crop_cover: bool, crop_cover: bool,
/// Client types for fetching videos
client_types: Option<Vec<ClientType>>,
/// Pot token to circumvent bot detection /// Pot token to circumvent bot detection
pot: Option<String>, pot: Option<String>,
} }
@ -123,8 +126,8 @@ pub struct DownloadQuery {
filter: Option<StreamFilter>, filter: Option<StreamFilter>,
/// Target video format /// Target video format
video_format: Option<DownloadVideoFormat>, video_format: Option<DownloadVideoFormat>,
/// ClientType type for fetching videos /// Client types for fetching videos
client_type: Option<ClientType>, client_types: Option<Vec<ClientType>>,
/// Pot token to circumvent bot detection /// Pot token to circumvent bot detection
pot: Option<String>, pot: Option<String>,
} }
@ -292,6 +295,7 @@ impl Default for DownloaderBuilder {
audio_tag: false, audio_tag: false,
#[cfg(feature = "audiotag")] #[cfg(feature = "audiotag")]
crop_cover: false, crop_cover: false,
client_types: None,
pot: None, pot: None,
} }
} }
@ -390,6 +394,23 @@ impl DownloaderBuilder {
self self
} }
/// Set the [`ClientType`] used to fetch the YT player
#[must_use]
pub fn client_type(mut self, client_type: ClientType) -> Self {
self.client_types = Some(vec![client_type]);
self
}
/// Set a list of client types used to fetch the YT player
///
/// The clients are used in the given order. If a client cannot fetch the requested video,
/// an attempt is made with the next one.
#[must_use]
pub fn client_types<T: Into<Vec<ClientType>>>(mut self, client_types: T) -> Self {
self.client_types = Some(client_types.into());
self
}
/// Set the `pot` token to circumvent bot detection /// Set the `pot` token to circumvent bot detection
/// ///
/// YouTube has implemented the token to prevent other clients from downloading YouTube videos. /// YouTube has implemented the token to prevent other clients from downloading YouTube videos.
@ -438,6 +459,7 @@ impl DownloaderBuilder {
audio_tag: self.audio_tag, audio_tag: self.audio_tag,
#[cfg(feature = "audiotag")] #[cfg(feature = "audiotag")]
crop_cover: self.crop_cover, crop_cover: self.crop_cover,
client_types: self.client_types,
pot: self.pot, pot: self.pot,
}), }),
} }
@ -472,7 +494,7 @@ impl Downloader {
progress: None, progress: None,
filter: None, filter: None,
video_format: None, video_format: None,
client_type: None, client_types: None,
pot: None, pot: None,
} }
} }
@ -609,7 +631,17 @@ impl DownloadQuery {
/// Set the [`ClientType`] used to fetch the YT player /// Set the [`ClientType`] used to fetch the YT player
#[must_use] #[must_use]
pub fn client_type(mut self, client_type: ClientType) -> Self { pub fn client_type(mut self, client_type: ClientType) -> Self {
self.client_type = Some(client_type); self.client_types = Some(vec![client_type]);
self
}
/// Set a list of client types used to fetch the YT player
///
/// The clients are used in the given order. If a client cannot fetch the requested video,
/// an attempt is made with the next one.
#[must_use]
pub fn client_types<T: Into<Vec<ClientType>>>(mut self, client_types: T) -> Self {
self.client_types = Some(client_types.into());
self self
} }
@ -710,16 +742,20 @@ impl DownloadQuery {
}; };
#[cfg(feature = "indicatif")] #[cfg(feature = "indicatif")]
if let Some(pb) = pb { if let Some(pb) = pb {
pb.set_message(format!( if let Some(n) = &self.video.name {
"Fetching player data for {}{}", pb.set_message(format!("Fetching player data for {n}{attempt_suffix}"));
self.video.name.as_deref().unwrap_or_default(), } else {
attempt_suffix pb.set_message(format!("Fetching player data{attempt_suffix}"));
)) }
} }
let q = self.dl.i.rp.query(); let q = self.dl.i.rp.query();
let player_data = match self.client_type { let player_data = match self
Some(client_type) => q.player_from_client(&self.video.id, client_type).await?, .client_types
.as_ref()
.or(self.dl.i.client_types.as_ref())
{
Some(client_types) => q.player_from_clients(&self.video.id, client_types).await?,
None => q.player(&self.video.id).await?, None => q.player(&self.video.id).await?,
}; };
let user_agent = q.user_agent(player_data.client_type); let user_agent = q.user_agent(player_data.client_type);