Compare commits
	
		
			5 commits
		
	
	
		
			
				6c41ef2fb2
			
			...
			
				b4a6658e33
			
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b4a6658e33 | |||
| 16e0e28c48 | |||
| 8fbd6b95b6 | |||
| 77ee923778 | |||
| a8fb337fae | 
					 11 changed files with 112 additions and 82 deletions
				
			
		|  | @ -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(); | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|  |  | ||||||
|  | @ -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() | ||||||
|         { |         { | ||||||
|  |  | ||||||
|  | @ -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), | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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), | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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( | ||||||
|  |  | ||||||
|  | @ -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( | ||||||
|  |  | ||||||
|  | @ -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", | ||||||
|  |  | ||||||
|  | @ -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]", | ||||||
|  |  | ||||||
|  | @ -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( | ||||||
|  |  | ||||||
|  | @ -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( | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue