feat: add list of clients to downloader
This commit is contained in:
parent
00fa95d92c
commit
fbecd232e5
2 changed files with 69 additions and 31 deletions
|
|
@ -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?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Reference in a new issue