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 reqwest::{Client, ClientBuilder};
use rustypipe::{
client::RustyPipe,
client::{ClientType, RustyPipe},
model::{UrlTarget, VideoId, YouTubeItem},
param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter},
};
@ -81,6 +81,8 @@ enum Commands {
/// Get the player
#[clap(long)]
player: bool,
#[clap(long)]
player_type: Option<PlayerType>,
},
/// Search YouTube
Search {
@ -189,6 +191,14 @@ enum MusicSearchCategory {
PlaylistsCommunity,
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
enum PlayerType {
Desktop,
Tv,
Android,
Ios,
}
impl From<SearchItemType> for search_filter::ItemType {
fn from(value: SearchItemType) -> Self {
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)]
async fn download_single_video(
video_id: &str,
@ -540,6 +561,7 @@ async fn main() {
comments,
lyrics,
player,
player_type,
} => {
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();
print_data(&details, format, pretty);
} 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);
} else {
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_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
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 DESKTOP_MUSIC_API_KEY: &str = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30";
const DESKTOP_MUSIC_CLIENT_VERSION: &str = "1.20230123.01.01";
// Mobile client
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";
static CLIENT_VERSION_REGEX: Lazy<Regex> =
@ -1189,7 +1185,7 @@ impl RustyPipeQuery {
.inner
.http
.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::REFERER, YOUTUBE_HOME_URL)
@ -1204,7 +1200,7 @@ impl RustyPipeQuery {
.inner
.http
.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::REFERER, YOUTUBE_MUSIC_HOME_URL)
@ -1212,14 +1208,14 @@ impl RustyPipeQuery {
.header("X-YouTube-Client-Name", "67")
.header(
"X-YouTube-Client-Version",
self.client.get_music_client_version().await
self.client.get_music_client_version().await,
),
ClientType::TvHtml5Embed => self
.client
.inner
.http
.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::REFERER, YOUTUBE_HOME_URL)
@ -1230,7 +1226,7 @@ impl RustyPipeQuery {
.inner
.http
.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::USER_AGENT,
@ -1245,7 +1241,7 @@ impl RustyPipeQuery {
.inner
.http
.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::USER_AGENT,

View file

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

View file

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

View file

@ -7,7 +7,7 @@ use super::AlertRenderer;
use super::ContentsRenderer;
use super::{
music_item::{ItemSection, PlaylistPanelRenderer},
ContentRenderer, SectionList,
ContentRenderer,
};
/// Response model for YouTube Music track details
@ -108,14 +108,14 @@ pub(crate) struct PlaylistPanel {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MusicLyrics {
pub contents: LyricsContents,
pub contents: ListOrMessage<LyricsSection>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LyricsContents {
pub message_renderer: Option<AlertRenderer>,
pub section_list_renderer: Option<ContentsRenderer<LyricsSection>>,
pub(crate) enum ListOrMessage<T> {
SectionListRenderer(ContentsRenderer<T>),
MessageRenderer(AlertRenderer),
}
#[derive(Debug, Deserialize)]
@ -137,5 +137,14 @@ pub(crate) struct LyricsRenderer {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
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(
id: "aGd3VKSOTxY",
name: "Ich wache auf",
duration: Some(221),
duration: Some(222),
cover: [],
artists: [
ArtistId(
@ -43,7 +43,7 @@ MusicAlbum(
TrackItem(
id: "Jz-26iiDuYs",
name: "Waldbrand",
duration: Some(208),
duration: Some(209),
cover: [],
artists: [
ArtistId(
@ -64,7 +64,7 @@ MusicAlbum(
TrackItem(
id: "Bu26uFtpt58",
name: "Verlernt",
duration: Some(223),
duration: Some(224),
cover: [],
artists: [
ArtistId(
@ -85,7 +85,7 @@ MusicAlbum(
TrackItem(
id: "RgwNqqiVqdY",
name: "In Farbe",
duration: Some(221),
duration: Some(222),
cover: [],
artists: [
ArtistId(
@ -106,7 +106,7 @@ MusicAlbum(
TrackItem(
id: "2TuOh30XbCI",
name: "Stadt im Hinterland",
duration: Some(197),
duration: Some(198),
cover: [],
artists: [
ArtistId(

View file

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

View file

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

View file

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

View file

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

View file

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