diff --git a/src/client/music_details.rs b/src/client/music_details.rs index cd4717c..1a48e67 100644 --- a/src/client/music_details.rs +++ b/src/client/music_details.rs @@ -99,10 +99,7 @@ impl RustyPipeQuery { radio_id: S, ) -> Result, Error> { let radio_id = radio_id.as_ref(); - let visitor_data = self.get_ytm_visitor_data().await?; - let context = self - .get_context(ClientType::DesktopMusic, true, Some(&visitor_data)) - .await; + let context = self.get_context(ClientType::DesktopMusic, true, None).await; let request_body = QRadio { context, playlist_id: radio_id, diff --git a/src/client/music_playlist.rs b/src/client/music_playlist.rs index 6fdd6ef..6db4c7e 100644 --- a/src/client/music_playlist.rs +++ b/src/client/music_playlist.rs @@ -45,55 +45,14 @@ impl RustyPipeQuery { browse_id: album_id, }; - let mut album = self - .execute_request::( - ClientType::DesktopMusic, - "music_album", - album_id, - "browse", - &request_body, - ) - .await?; - - // YouTube Music is replacing album tracks with their respective music videos. To get the original - // tracks, we have to fetch the album as a playlist and replace the offending track ids. - if let Some(playlist_id) = &album.playlist_id { - // Get a list of music videos in the album - let to_replace = album - .tracks - .iter() - .enumerate() - .filter_map(|(i, track)| { - if track.is_video { - Some((i, track.title.to_owned())) - } else { - None - } - }) - .collect::>(); - - if !to_replace.is_empty() { - let playlist = self.music_playlist(playlist_id).await?; - - for (i, title) in to_replace { - let found_track = playlist.tracks.items.iter().find_map(|track| { - if track.title == title && !track.is_video { - Some((track.id.to_owned(), track.duration)) - } else { - None - } - }); - if let Some((track_id, duration)) = found_track { - album.tracks[i].id = track_id; - if let Some(duration) = duration { - album.tracks[i].duration = Some(duration); - } - album.tracks[i].is_video = false; - } - } - } - } - Ok(album) + self.execute_request::( + ClientType::DesktopMusic, + "music_album", + album_id, + "browse", + &request_body, + ) + .await } } diff --git a/src/client/response/music_item.rs b/src/client/response/music_item.rs index d29ebd2..c505b02 100644 --- a/src/client/response/music_item.rs +++ b/src/client/response/music_item.rs @@ -418,12 +418,10 @@ impl MusicListMapper { // List item MusicResponseItem::MusicResponsiveListItemRenderer(item) => { let mut columns = item.flex_columns.into_iter(); - let c1 = columns.next(); + let title = columns.next().map(|col| col.renderer.text.to_string()); let c2 = columns.next(); let c3 = columns.next(); - let title = c1.as_ref().map(|col| col.renderer.text.to_string()); - let first_tn = item .thumbnail .music_thumbnail_renderer @@ -435,54 +433,27 @@ impl MusicListMapper { .navigation_endpoint .and_then(|ne| ne.music_page()) .or_else(|| { - c1.and_then(|c1| { - c1.renderer.text.0.into_iter().next().and_then(|t| match t { - crate::serializer::text::TextComponent::Video { - video_id, - is_video, - .. - } => Some((MusicPageType::Track { is_video }, video_id)), - crate::serializer::text::TextComponent::Browse { - page_type, - browse_id, - .. - } => Some((page_type.into(), browse_id)), - _ => None, - }) - }) - }) - .or_else(|| { - item.playlist_item_data.map(|d| { - ( - MusicPageType::Track { - is_video: self.album.is_none() - && !first_tn - .map(|tn| tn.height == tn.width) - .unwrap_or_default(), - }, - d.video_id, - ) - }) + item.playlist_item_data + .map(|d| (MusicPageType::Track, d.video_id)) }) .or_else(|| { first_tn.and_then(|tn| { - util::video_id_from_thumbnail_url(&tn.url).map(|id| { - ( - MusicPageType::Track { - is_video: self.album.is_none() && tn.width != tn.height, - }, - id, - ) - }) + util::video_id_from_thumbnail_url(&tn.url) + .map(|id| (MusicPageType::Track, id)) }) }); match pt_id { // Track - Some((MusicPageType::Track { is_video }, id)) => { + Some((MusicPageType::Track, id)) => { let title = title.ok_or_else(|| format!("track {}: could not get title", id))?; + // Videos have rectangular thumbnails, YTM tracks have square covers + // Exception: there are no thumbnails on album items + let is_video = self.album.is_none() + && !first_tn.map(|tn| tn.height == tn.width).unwrap_or_default(); + let (artists_p, album_p, duration_p) = match item.flex_column_display_style { // Search result @@ -548,14 +519,15 @@ impl MusicListMapper { }), ), (_, false) => ( - album_p.and_then(|p| { - p.0.into_iter().find_map(|c| AlbumId::try_from(c).ok()) - }), + album_p + .and_then(|p| { + p.0.into_iter().find_map(|c| AlbumId::try_from(c).ok()) + }) + .or_else(|| self.album.clone()), None, ), (FlexColumnDisplayStyle::Default, true) => (None, None), }; - let album = album.or_else(|| self.album.clone()); let (mut artists, _) = map_artists(artists_p); @@ -668,8 +640,7 @@ impl MusicListMapper { // There may be broken YT channels from the artist search. They can be skipped. Ok(None) } - // Tracks were already handled above - MusicPageType::Track { .. } => unreachable!(), + MusicPageType::Track => unreachable!(), } } None => Err("could not determine item type".to_owned()), @@ -684,7 +655,7 @@ impl MusicListMapper { match item.navigation_endpoint.music_page() { Some((page_type, id)) => match page_type { - MusicPageType::Track { is_video } => { + MusicPageType::Track => { let artists = map_artists(subtitle_p1).0; self.items.push(MusicItem::Track(TrackItem { @@ -698,7 +669,7 @@ impl MusicListMapper { view_count: subtitle_p2.and_then(|c| { util::parse_large_numstr(c.first_str(), self.lang) }), - is_video, + is_video: true, track_nr: None, })); Ok(Some(MusicEntityType::Track)) diff --git a/src/client/response/url_endpoint.rs b/src/client/response/url_endpoint.rs index 03d9043..09713e1 100644 --- a/src/client/response/url_endpoint.rs +++ b/src/client/response/url_endpoint.rs @@ -35,8 +35,6 @@ pub(crate) struct WatchEndpoint { pub playlist_id: Option, #[serde(default)] pub start_time_seconds: u32, - #[serde(default)] - pub watch_endpoint_music_supported_configs: WatchEndpointConfigWrap, } #[derive(Debug)] @@ -120,30 +118,6 @@ pub(crate) struct WebCommandMetadata { pub web_page_type: PageType, } -#[derive(Default, Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct WatchEndpointConfigWrap { - pub watch_endpoint_music_config: WatchEndpointConfig, -} - -#[serde_as] -#[derive(Default, Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct WatchEndpointConfig { - #[serde(default)] - #[serde_as(deserialize_as = "DefaultOnError")] - pub music_video_type: MusicVideoType, -} - -#[derive(Default, Debug, Clone, Copy, Deserialize, PartialEq, Eq)] -pub(crate) enum MusicVideoType { - #[default] - #[serde(rename = "MUSIC_VIDEO_TYPE_OMV")] - Video, - #[serde(rename = "MUSIC_VIDEO_TYPE_ATV")] - Track, -} - #[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)] pub(crate) enum PageType { #[serde( @@ -178,7 +152,7 @@ pub(crate) enum MusicPageType { Artist, Album, Playlist, - Track { is_video: bool }, + Track, None, } @@ -215,16 +189,7 @@ impl NavigationEndpoint { // Genre radios (e.g. "pop radio") will be skipped (MusicPageType::None, watch.video_id) } else { - ( - MusicPageType::Track { - is_video: watch - .watch_endpoint_music_supported_configs - .watch_endpoint_music_config - .music_video_type - == MusicVideoType::Video, - }, - watch.video_id, - ) + (MusicPageType::Track, watch.video_id) } }) }) diff --git a/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_description.snap b/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_description.snap index 19a4cd1..8f2bb6c 100644 --- a/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_description.snap +++ b/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_description.snap @@ -56,7 +56,7 @@ MusicAlbum( name: "25", )), view_count: None, - is_video: true, + is_video: false, track_nr: Some(1), ), TrackItem( @@ -76,7 +76,7 @@ MusicAlbum( name: "25", )), view_count: None, - is_video: true, + is_video: false, track_nr: Some(2), ), TrackItem( diff --git a/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_single.snap b/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_single.snap index 3e86ad8..fa429ce 100644 --- a/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_single.snap +++ b/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_single.snap @@ -64,7 +64,7 @@ MusicAlbum( name: "Der Himmel reißt auf", )), view_count: None, - is_video: true, + is_video: false, track_nr: Some(1), ), ], diff --git a/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_various_artists.snap b/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_various_artists.snap index 78bfbc7..83f0652 100644 --- a/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_various_artists.snap +++ b/src/client/snapshots/rustypipe__client__music_playlist__tests__map_music_album_various_artists.snap @@ -51,7 +51,7 @@ MusicAlbum( name: "<Queendom2> FINAL", )), view_count: None, - is_video: true, + is_video: false, track_nr: Some(1), ), TrackItem( diff --git a/src/model/richtext.rs b/src/model/richtext.rs index 1947e48..c4609be 100644 --- a/src/model/richtext.rs +++ b/src/model/richtext.rs @@ -122,7 +122,7 @@ mod tests { text::TextComponent::Text { text: "🎧Listen and download aespa's debut single \"Black Mamba\": ".to_owned() }, text::TextComponent::Web { text: "https://smarturl.it/aespa_BlackMamba".to_owned(), url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbFY1QmpQamJPSms0Z1FnVTlQUS00ZFhBZnBJZ3xBQ3Jtc0tuRGJBanludGoyRnphb2dZWVd3cUNnS3dEd0FnNHFOZEY1NHBJaHFmLXpaWUJwX3ZucDZxVnpGeHNGX1FpMzFkZW9jQkI2Mi1wNGJ1UVFNN3h1MnN3R3JLMzdxU01nZ01POHBGcmxHU2puSUk1WHRzQQ&q=https%3A%2F%2Fsmarturl.it%2Faespa_BlackMamba&v=ZeerrnuLi5E".to_owned() }, text::TextComponent::Text { text: "\n🐍The Debut Stage ".to_owned() }, - text::TextComponent::Video { text: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned(), start_time: 0, is_video: true }, + text::TextComponent::Video { text: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned(), start_time: 0 }, text::TextComponent::Text { text: "\n\n🎟️ aespa Showcase SYNK in LA! Tickets now on sale: ".to_owned() }, text::TextComponent::Web { text: "https://www.ticketmaster.com/event/0A...".to_owned(), url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbFpUMEZiaXJWWkszaVZXaEM0emxWU1JQV3NoQXxBQ3Jtc0tuU2g4VWNPNE5UY3hoSWYtamFzX0h4bUVQLVJiRy1ubDZrTnh3MUpGdDNSaUo0ZlMyT3lUM28ycUVBdHJLMndGcDhla3BkOFpxSVFfOS1QdVJPVHBUTEV1LXpOV0J2QXdhV05lV210cEJtZUJMeHdaTQ&q=https%3A%2F%2Fwww.ticketmaster.com%2Fevent%2F0A005CCD9E871F6E&v=ZeerrnuLi5E".to_owned() }, text::TextComponent::Text { text: "\n\nSubscribe to aespa Official YouTube Channel!\n".to_owned() }, diff --git a/src/serializer/snapshots/rustypipe__serializer__text__tests__t_attributed_description.snap b/src/serializer/snapshots/rustypipe__serializer__text__tests__t_attributed_description.snap index e937bcc..8ba3dfa 100644 --- a/src/serializer/snapshots/rustypipe__serializer__text__tests__t_attributed_description.snap +++ b/src/serializer/snapshots/rustypipe__serializer__text__tests__t_attributed_description.snap @@ -19,7 +19,6 @@ SAttributed { text: "aespa 에스파 'Black ...", video_id: "Ky5RT5oGg0w", start_time: 0, - is_video: true, }, Text { text: "\n\n🎟\u{fe0f} aespa Showcase SYNK in LA! Tickets now on sale: ", diff --git a/src/serializer/text.rs b/src/serializer/text.rs index abe8c80..587daf0 100644 --- a/src/serializer/text.rs +++ b/src/serializer/text.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer}; use serde_with::{serde_as, DeserializeAs}; use crate::{ - client::response::url_endpoint::{MusicVideoType, NavigationEndpoint, PageType}, + client::response::url_endpoint::{NavigationEndpoint, PageType}, model::UrlTarget, util, }; @@ -94,8 +94,6 @@ pub(crate) enum TextComponent { text: String, video_id: String, start_time: u32, - /// True if the item is a video, false if it is a YTM track - is_video: bool, }, Browse { text: String, @@ -166,11 +164,6 @@ fn map_text_component(text: String, nav: NavigationEndpoint) -> TextComponent { text, video_id: w.video_id, start_time: w.start_time_seconds, - is_video: w - .watch_endpoint_music_supported_configs - .watch_endpoint_music_config - .music_video_type - == MusicVideoType::Video, }, None => match nav.browse_endpoint { Some(b) => TextComponent::Browse { @@ -372,7 +365,6 @@ impl From for crate::model::richtext::TextComponent { text, video_id, start_time, - .. } => Self::YouTube { text, target: UrlTarget::Video { @@ -589,7 +581,6 @@ mod tests { text: "DEEP", video_id: "wZIoIgz5mbs", start_time: 0, - is_video: true, }, } "###); diff --git a/tests/snapshots/youtube__music_album_ep.snap b/tests/snapshots/youtube__music_album_ep.snap index 26256f6..dd465da 100644 --- a/tests/snapshots/youtube__music_album_ep.snap +++ b/tests/snapshots/youtube__music_album_ep.snap @@ -39,7 +39,7 @@ MusicAlbum( track_nr: Some(1), ), TrackItem( - id: "Jz-26iiDuYs", + id: "lhPOMUjV4rE", title: "Waldbrand", duration: Some(208), cover: [], diff --git a/tests/snapshots/youtube__music_album_single.snap b/tests/snapshots/youtube__music_album_single.snap index aeba890..befe8c2 100644 --- a/tests/snapshots/youtube__music_album_single.snap +++ b/tests/snapshots/youtube__music_album_single.snap @@ -23,7 +23,7 @@ MusicAlbum( by_va: false, tracks: [ TrackItem( - id: "VU6lEv0PKAo", + id: "XX0epju-YvY", title: "Der Himmel reißt auf", duration: Some(183), cover: [], diff --git a/tests/snapshots/youtube__music_album_various_artists.snap b/tests/snapshots/youtube__music_album_various_artists.snap index b04f5ea..8da3db0 100644 --- a/tests/snapshots/youtube__music_album_various_artists.snap +++ b/tests/snapshots/youtube__music_album_various_artists.snap @@ -14,7 +14,7 @@ MusicAlbum( by_va: true, tracks: [ TrackItem( - id: "Tzai7JXo45w", + id: "8IqLxg0GqXc", title: "Waka Boom (My Way) (feat. Lee Young Ji)", duration: Some(274), cover: [], diff --git a/tests/youtube.rs b/tests/youtube.rs index 4bf178f..bd48b4a 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -1600,8 +1600,6 @@ async fn music_search_videos() { assert_next(res.items, rp.query(), 15, 2).await; } -/* -This podcast was removed from YouTube Music and I could not find another one #[tokio::test] async fn music_search_episode() { let rp = RustyPipe::builder().strict().build(); @@ -1626,7 +1624,6 @@ async fn music_search_episode() { ); assert!(!track.cover.is_empty(), "got no cover"); } -*/ #[rstest] #[case::single(