Compare commits

..

5 commits

Author SHA1 Message Date
b4a6658e33
test: update track durations
All checks were successful
CI / Test (push) Successful in 4m25s
2024-05-02 14:14:22 +02:00
16e0e28c48
feat: CLI: setting player type 2024-04-26 16:09:13 +02:00
8fbd6b95b6
fix: parsing error when no music_related content available 2024-04-18 19:50:06 +02:00
77ee923778
test: update channel ID for L. R. Eswari 2024-04-18 17:37:50 +02:00
a8fb337fae
fix: remove Innertube API keys, update android player params 2024-04-16 15:18:29 +02:00
11 changed files with 112 additions and 82 deletions

View file

@ -8,7 +8,7 @@ use futures::stream::{self, StreamExt};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use reqwest::{Client, ClientBuilder}; use reqwest::{Client, ClientBuilder};
use rustypipe::{ use rustypipe::{
client::RustyPipe, client::{ClientType, RustyPipe},
model::{UrlTarget, VideoId, YouTubeItem}, model::{UrlTarget, VideoId, YouTubeItem},
param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter}, param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter},
}; };
@ -81,6 +81,8 @@ enum Commands {
/// Get the player /// Get the player
#[clap(long)] #[clap(long)]
player: bool, player: bool,
#[clap(long)]
player_type: Option<PlayerType>,
}, },
/// Search YouTube /// Search YouTube
Search { Search {
@ -189,6 +191,14 @@ enum MusicSearchCategory {
PlaylistsCommunity, PlaylistsCommunity,
} }
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
enum PlayerType {
Desktop,
Tv,
Android,
Ios,
}
impl From<SearchItemType> for search_filter::ItemType { impl From<SearchItemType> for search_filter::ItemType {
fn from(value: SearchItemType) -> Self { fn from(value: SearchItemType) -> Self {
match value { match value {
@ -231,6 +241,17 @@ impl From<SearchOrder> for search_filter::Order {
} }
} }
impl From<PlayerType> for ClientType {
fn from(value: PlayerType) -> Self {
match value {
PlayerType::Desktop => Self::Desktop,
PlayerType::Tv => Self::TvHtml5Embed,
PlayerType::Android => Self::Android,
PlayerType::Ios => Self::Ios,
}
}
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
async fn download_single_video( async fn download_single_video(
video_id: &str, video_id: &str,
@ -540,6 +561,7 @@ async fn main() {
comments, comments,
lyrics, lyrics,
player, player,
player_type,
} => { } => {
let target = rp.query().resolve_string(&id, false).await.unwrap(); let target = rp.query().resolve_string(&id, false).await.unwrap();
@ -558,7 +580,12 @@ async fn main() {
let details = rp.query().music_details(&id).await.unwrap(); let details = rp.query().music_details(&id).await.unwrap();
print_data(&details, format, pretty); print_data(&details, format, pretty);
} else if player { } else if player {
let player = rp.query().player(&id).await.unwrap(); let player = if let Some(player_type) = player_type {
rp.query().player_from_client(&id, player_type.into()).await
} else {
rp.query().player(&id).await
}
.unwrap();
print_data(&player, format, pretty); print_data(&player, format, pretty);
} else { } else {
let mut details = rp.query().video_details(&id).await.unwrap(); let mut details = rp.query().video_details(&id).await.unwrap();

View file

@ -192,19 +192,15 @@ const YOUTUBE_MUSIC_V1_URL: &str = "https://music.youtube.com/youtubei/v1/";
const YOUTUBE_HOME_URL: &str = "https://www.youtube.com/"; 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";
// Desktop client // Desktop client
const DESKTOP_CLIENT_VERSION: &str = "2.20230126.00.00"; const DESKTOP_CLIENT_VERSION: &str = "2.20230126.00.00";
const DESKTOP_API_KEY: &str = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
const TVHTML5_CLIENT_VERSION: &str = "2.0"; const TVHTML5_CLIENT_VERSION: &str = "2.0";
const DESKTOP_MUSIC_API_KEY: &str = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30";
const DESKTOP_MUSIC_CLIENT_VERSION: &str = "1.20230123.01.01"; const DESKTOP_MUSIC_CLIENT_VERSION: &str = "1.20230123.01.01";
// Mobile client // Mobile client
const MOBILE_CLIENT_VERSION: &str = "18.03.33"; const MOBILE_CLIENT_VERSION: &str = "18.03.33";
const ANDROID_API_KEY: &str = "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w";
const IOS_API_KEY: &str = "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc";
const IOS_DEVICE_MODEL: &str = "iPhone14,5"; const IOS_DEVICE_MODEL: &str = "iPhone14,5";
static CLIENT_VERSION_REGEX: Lazy<Regex> = static CLIENT_VERSION_REGEX: Lazy<Regex> =
@ -1189,7 +1185,7 @@ impl RustyPipeQuery {
.inner .inner
.http .http
.post(format!( .post(format!(
"{YOUTUBEI_V1_URL}{endpoint}?key={DESKTOP_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}" "{YOUTUBEI_V1_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
)) ))
.header(header::ORIGIN, YOUTUBE_HOME_URL) .header(header::ORIGIN, YOUTUBE_HOME_URL)
.header(header::REFERER, YOUTUBE_HOME_URL) .header(header::REFERER, YOUTUBE_HOME_URL)
@ -1204,7 +1200,7 @@ impl RustyPipeQuery {
.inner .inner
.http .http
.post(format!( .post(format!(
"{YOUTUBE_MUSIC_V1_URL}{endpoint}?key={DESKTOP_MUSIC_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}" "{YOUTUBE_MUSIC_V1_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
)) ))
.header(header::ORIGIN, YOUTUBE_MUSIC_HOME_URL) .header(header::ORIGIN, YOUTUBE_MUSIC_HOME_URL)
.header(header::REFERER, YOUTUBE_MUSIC_HOME_URL) .header(header::REFERER, YOUTUBE_MUSIC_HOME_URL)
@ -1212,14 +1208,14 @@ impl RustyPipeQuery {
.header("X-YouTube-Client-Name", "67") .header("X-YouTube-Client-Name", "67")
.header( .header(
"X-YouTube-Client-Version", "X-YouTube-Client-Version",
self.client.get_music_client_version().await self.client.get_music_client_version().await,
), ),
ClientType::TvHtml5Embed => self ClientType::TvHtml5Embed => self
.client .client
.inner .inner
.http .http
.post(format!( .post(format!(
"{YOUTUBEI_V1_URL}{endpoint}?key={DESKTOP_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}" "{YOUTUBEI_V1_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
)) ))
.header(header::ORIGIN, YOUTUBE_HOME_URL) .header(header::ORIGIN, YOUTUBE_HOME_URL)
.header(header::REFERER, YOUTUBE_HOME_URL) .header(header::REFERER, YOUTUBE_HOME_URL)
@ -1230,7 +1226,7 @@ impl RustyPipeQuery {
.inner .inner
.http .http
.post(format!( .post(format!(
"{YOUTUBEI_V1_GAPIS_URL}{endpoint}?key={ANDROID_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}" "{YOUTUBEI_V1_GAPIS_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
)) ))
.header( .header(
header::USER_AGENT, header::USER_AGENT,
@ -1245,7 +1241,7 @@ impl RustyPipeQuery {
.inner .inner
.http .http
.post(format!( .post(format!(
"{YOUTUBEI_V1_GAPIS_URL}{endpoint}?key={IOS_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}" "{YOUTUBEI_V1_GAPIS_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
)) ))
.header( .header(
header::USER_AGENT, header::USER_AGENT,

View file

@ -306,19 +306,14 @@ impl MapResponse<Lyrics> for response::MusicLyrics {
) -> Result<MapResult<Lyrics>, ExtractionError> { ) -> Result<MapResult<Lyrics>, ExtractionError> {
let lyrics = self let lyrics = self
.contents .contents
.section_list_renderer .into_res()
.and_then(|sl| { .map_err(|msg| ExtractionError::NotFound {
sl.contents id: id.to_owned(),
.into_iter() msg: msg.into(),
.find_map(|item| item.music_description_shelf_renderer) })?
}) .into_iter()
.ok_or(match self.contents.message_renderer { .find_map(|item| item.music_description_shelf_renderer)
Some(msg) => ExtractionError::NotFound { .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no content")))?;
id: id.to_owned(),
msg: msg.text.into(),
},
None => ExtractionError::InvalidData(Cow::Borrowed("no content")),
})?;
Ok(MapResult { Ok(MapResult {
c: Lyrics { c: Lyrics {
@ -333,36 +328,39 @@ impl MapResponse<Lyrics> for response::MusicLyrics {
impl MapResponse<MusicRelated> for response::MusicRelated { impl MapResponse<MusicRelated> for response::MusicRelated {
fn map_response( fn map_response(
self, self,
_id: &str, id: &str,
lang: Language, lang: Language,
_deobf: Option<&crate::deobfuscate::DeobfData>, _deobf: Option<&crate::deobfuscate::DeobfData>,
_vdata: Option<&str>, _vdata: Option<&str>,
) -> Result<MapResult<MusicRelated>, ExtractionError> { ) -> Result<MapResult<MusicRelated>, ExtractionError> {
let contents = self
.contents
.into_res()
.map_err(|msg| ExtractionError::NotFound {
id: id.to_owned(),
msg: msg.into(),
})?;
// Find artist // Find artist
let artist_id = self let artist_id = contents.iter().find_map(|section| match section {
.contents response::music_item::ItemSection::MusicCarouselShelfRenderer(shelf) => {
.section_list_renderer shelf.header.as_ref().and_then(|h| {
.contents h.music_carousel_shelf_basic_header_renderer
.iter() .title
.find_map(|section| match section { .0
response::music_item::ItemSection::MusicCarouselShelfRenderer(shelf) => { .iter()
shelf.header.as_ref().and_then(|h| { .find_map(|c| {
h.music_carousel_shelf_basic_header_renderer let artist = ArtistId::from(c.clone());
.title if artist.id.is_some() {
.0 Some(artist)
.iter() } else {
.find_map(|c| { None
let artist = ArtistId::from(c.clone()); }
if artist.id.is_some() { })
Some(artist) })
} else { }
None _ => None,
} });
})
})
}
_ => None,
});
let mut mapper_tracks = MusicListMapper::new(lang); let mut mapper_tracks = MusicListMapper::new(lang);
let mut mapper = match artist_id { let mut mapper = match artist_id {
@ -370,7 +368,7 @@ impl MapResponse<MusicRelated> for response::MusicRelated {
None => MusicListMapper::new(lang), None => MusicListMapper::new(lang),
}; };
let mut sections = self.contents.section_list_renderer.contents.into_iter(); let mut sections = contents.into_iter();
if let Some(response::music_item::ItemSection::MusicCarouselShelfRenderer(shelf)) = if let Some(response::music_item::ItemSection::MusicCarouselShelfRenderer(shelf)) =
sections.next() sections.next()
{ {

View file

@ -128,8 +128,8 @@ impl RustyPipeQuery {
video_id, video_id,
content_check_ok: true, content_check_ok: true,
racy_check_ok: true, racy_check_ok: true,
// Source: https://github.com/TeamNewPipe/NewPipeExtractor/pull/1084 // Source: https://github.com/TeamNewPipe/NewPipeExtractor/pull/1168
params: Some("CgIQBg").filter(|_| client_type == ClientType::Android), params: Some("CgIIAQ%3D%3D").filter(|_| client_type == ClientType::Android),
} }
}; };

View file

@ -7,7 +7,7 @@ use super::AlertRenderer;
use super::ContentsRenderer; use super::ContentsRenderer;
use super::{ use super::{
music_item::{ItemSection, PlaylistPanelRenderer}, music_item::{ItemSection, PlaylistPanelRenderer},
ContentRenderer, SectionList, ContentRenderer,
}; };
/// Response model for YouTube Music track details /// Response model for YouTube Music track details
@ -108,14 +108,14 @@ pub(crate) struct PlaylistPanel {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct MusicLyrics { pub(crate) struct MusicLyrics {
pub contents: LyricsContents, pub contents: ListOrMessage<LyricsSection>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct LyricsContents { pub(crate) enum ListOrMessage<T> {
pub message_renderer: Option<AlertRenderer>, SectionListRenderer(ContentsRenderer<T>),
pub section_list_renderer: Option<ContentsRenderer<LyricsSection>>, MessageRenderer(AlertRenderer),
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -137,5 +137,14 @@ pub(crate) struct LyricsRenderer {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub(crate) struct MusicRelated { pub(crate) struct MusicRelated {
pub contents: SectionList<ItemSection>, pub contents: ListOrMessage<ItemSection>,
}
impl<T> ListOrMessage<T> {
pub fn into_res(self) -> Result<Vec<T>, String> {
match self {
ListOrMessage::SectionListRenderer(c) => Ok(c.contents),
ListOrMessage::MessageRenderer(msg) => Err(msg.text),
}
}
} }

View file

@ -22,7 +22,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "aGd3VKSOTxY", id: "aGd3VKSOTxY",
name: "Ich wache auf", name: "Ich wache auf",
duration: Some(221), duration: Some(222),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -43,7 +43,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "Jz-26iiDuYs", id: "Jz-26iiDuYs",
name: "Waldbrand", name: "Waldbrand",
duration: Some(208), duration: Some(209),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -64,7 +64,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "Bu26uFtpt58", id: "Bu26uFtpt58",
name: "Verlernt", name: "Verlernt",
duration: Some(223), duration: Some(224),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -85,7 +85,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "RgwNqqiVqdY", id: "RgwNqqiVqdY",
name: "In Farbe", name: "In Farbe",
duration: Some(221), duration: Some(222),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -106,7 +106,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "2TuOh30XbCI", id: "2TuOh30XbCI",
name: "Stadt im Hinterland", name: "Stadt im Hinterland",
duration: Some(197), duration: Some(198),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(

View file

@ -22,7 +22,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "aGd3VKSOTxY", id: "aGd3VKSOTxY",
name: "[name]", name: "[name]",
duration: Some(221), duration: Some(222),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -43,7 +43,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "Jz-26iiDuYs", id: "Jz-26iiDuYs",
name: "[name]", name: "[name]",
duration: Some(208), duration: Some(209),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -64,7 +64,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "Bu26uFtpt58", id: "Bu26uFtpt58",
name: "[name]", name: "[name]",
duration: Some(223), duration: Some(224),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -85,7 +85,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "RgwNqqiVqdY", id: "RgwNqqiVqdY",
name: "[name]", name: "[name]",
duration: Some(221), duration: Some(222),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -106,7 +106,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "2TuOh30XbCI", id: "2TuOh30XbCI",
name: "[name]", name: "[name]",
duration: Some(197), duration: Some(198),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(

View file

@ -63,11 +63,11 @@ MusicAlbum(
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
id: Some("UCUhWwvF6gIPWTYlYb4-icLA"), id: Some("UC_KQPMiRQl3CFAIKTVfCHwA"),
name: "L.r. Eswari", name: "L.r. Eswari",
), ),
], ],
artist_id: Some("UCUhWwvF6gIPWTYlYb4-icLA"), artist_id: Some("UC_KQPMiRQl3CFAIKTVfCHwA"),
album: Some(AlbumId( album: Some(AlbumId(
id: "MPREb_bqWA6mAZFWS", id: "MPREb_bqWA6mAZFWS",
name: "Pedha Rasi Peddamma Katha", name: "Pedha Rasi Peddamma Katha",

View file

@ -63,11 +63,11 @@ MusicAlbum(
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
id: Some("UCUhWwvF6gIPWTYlYb4-icLA"), id: Some("UC_KQPMiRQl3CFAIKTVfCHwA"),
name: "[name]", name: "[name]",
), ),
], ],
artist_id: Some("UCUhWwvF6gIPWTYlYb4-icLA"), artist_id: Some("UC_KQPMiRQl3CFAIKTVfCHwA"),
album: Some(AlbumId( album: Some(AlbumId(
id: "MPREb_bqWA6mAZFWS", id: "MPREb_bqWA6mAZFWS",
name: "[name]", name: "[name]",

View file

@ -26,7 +26,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "AKJ3IJZKPWc", id: "AKJ3IJZKPWc",
name: "Oh Javaraala", name: "Oh Javaraala",
duration: Some(228), duration: Some(229),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -51,7 +51,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "WnpZuHNB33E", id: "WnpZuHNB33E",
name: "Siva Manoranjani", name: "Siva Manoranjani",
duration: Some(266), duration: Some(267),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -72,7 +72,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "pRqoDGXg1-I", id: "pRqoDGXg1-I",
name: "Gulabi Buggalunna", name: "Gulabi Buggalunna",
duration: Some(154), duration: Some(155),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -93,7 +93,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "20vIKLJxjBY", id: "20vIKLJxjBY",
name: "Kuluku Nadakula", name: "Kuluku Nadakula",
duration: Some(178), duration: Some(179),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(

View file

@ -26,7 +26,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "AKJ3IJZKPWc", id: "AKJ3IJZKPWc",
name: "[name]", name: "[name]",
duration: Some(228), duration: Some(229),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -51,7 +51,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "WnpZuHNB33E", id: "WnpZuHNB33E",
name: "[name]", name: "[name]",
duration: Some(266), duration: Some(267),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -72,7 +72,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "pRqoDGXg1-I", id: "pRqoDGXg1-I",
name: "[name]", name: "[name]",
duration: Some(154), duration: Some(155),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(
@ -93,7 +93,7 @@ MusicAlbum(
TrackItem( TrackItem(
id: "20vIKLJxjBY", id: "20vIKLJxjBY",
name: "[name]", name: "[name]",
duration: Some(178), duration: Some(179),
cover: [], cover: [],
artists: [ artists: [
ArtistId( ArtistId(