fix: map track/mv type correctly for album items
This commit is contained in:
parent
38bc12f695
commit
01a2717c11
11 changed files with 104 additions and 30 deletions
|
|
@ -418,10 +418,12 @@ impl MusicListMapper {
|
|||
// List item
|
||||
MusicResponseItem::MusicResponsiveListItemRenderer(item) => {
|
||||
let mut columns = item.flex_columns.into_iter();
|
||||
let title = columns.next().map(|col| col.renderer.text.to_string());
|
||||
let c1 = columns.next();
|
||||
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
|
||||
|
|
@ -433,27 +435,54 @@ impl MusicListMapper {
|
|||
.navigation_endpoint
|
||||
.and_then(|ne| ne.music_page())
|
||||
.or_else(|| {
|
||||
item.playlist_item_data
|
||||
.map(|d| (MusicPageType::Track, d.video_id))
|
||||
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,
|
||||
)
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
first_tn.and_then(|tn| {
|
||||
util::video_id_from_thumbnail_url(&tn.url)
|
||||
.map(|id| (MusicPageType::Track, id))
|
||||
util::video_id_from_thumbnail_url(&tn.url).map(|id| {
|
||||
(
|
||||
MusicPageType::Track {
|
||||
is_video: self.album.is_none() && tn.width != tn.height,
|
||||
},
|
||||
id,
|
||||
)
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
match pt_id {
|
||||
// Track
|
||||
Some((MusicPageType::Track, id)) => {
|
||||
Some((MusicPageType::Track { is_video }, 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
|
||||
|
|
@ -519,15 +548,14 @@ impl MusicListMapper {
|
|||
}),
|
||||
),
|
||||
(_, false) => (
|
||||
album_p
|
||||
.and_then(|p| {
|
||||
p.0.into_iter().find_map(|c| AlbumId::try_from(c).ok())
|
||||
})
|
||||
.or_else(|| self.album.clone()),
|
||||
album_p.and_then(|p| {
|
||||
p.0.into_iter().find_map(|c| AlbumId::try_from(c).ok())
|
||||
}),
|
||||
None,
|
||||
),
|
||||
(FlexColumnDisplayStyle::Default, true) => (None, None),
|
||||
};
|
||||
let album = album.or_else(|| self.album.clone());
|
||||
|
||||
let (mut artists, _) = map_artists(artists_p);
|
||||
|
||||
|
|
@ -640,7 +668,8 @@ impl MusicListMapper {
|
|||
// There may be broken YT channels from the artist search. They can be skipped.
|
||||
Ok(None)
|
||||
}
|
||||
MusicPageType::Track => unreachable!(),
|
||||
// Tracks were already handled above
|
||||
MusicPageType::Track { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
None => Err("could not determine item type".to_owned()),
|
||||
|
|
@ -655,7 +684,7 @@ impl MusicListMapper {
|
|||
|
||||
match item.navigation_endpoint.music_page() {
|
||||
Some((page_type, id)) => match page_type {
|
||||
MusicPageType::Track => {
|
||||
MusicPageType::Track { is_video } => {
|
||||
let artists = map_artists(subtitle_p1).0;
|
||||
|
||||
self.items.push(MusicItem::Track(TrackItem {
|
||||
|
|
@ -669,7 +698,7 @@ impl MusicListMapper {
|
|||
view_count: subtitle_p2.and_then(|c| {
|
||||
util::parse_large_numstr(c.first_str(), self.lang)
|
||||
}),
|
||||
is_video: true,
|
||||
is_video,
|
||||
track_nr: None,
|
||||
}));
|
||||
Ok(Some(MusicEntityType::Track))
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ pub(crate) struct WatchEndpoint {
|
|||
pub playlist_id: Option<String>,
|
||||
#[serde(default)]
|
||||
pub start_time_seconds: u32,
|
||||
#[serde(default)]
|
||||
pub watch_endpoint_music_supported_configs: WatchEndpointConfigWrap,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -118,6 +120,30 @@ 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(
|
||||
|
|
@ -152,7 +178,7 @@ pub(crate) enum MusicPageType {
|
|||
Artist,
|
||||
Album,
|
||||
Playlist,
|
||||
Track,
|
||||
Track { is_video: bool },
|
||||
None,
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +215,16 @@ impl NavigationEndpoint {
|
|||
// Genre radios (e.g. "pop radio") will be skipped
|
||||
(MusicPageType::None, watch.video_id)
|
||||
} else {
|
||||
(MusicPageType::Track, watch.video_id)
|
||||
(
|
||||
MusicPageType::Track {
|
||||
is_video: watch
|
||||
.watch_endpoint_music_supported_configs
|
||||
.watch_endpoint_music_config
|
||||
.music_video_type
|
||||
== MusicVideoType::Video,
|
||||
},
|
||||
watch.video_id,
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ MusicAlbum(
|
|||
name: "25",
|
||||
)),
|
||||
view_count: None,
|
||||
is_video: false,
|
||||
is_video: true,
|
||||
track_nr: Some(1),
|
||||
),
|
||||
TrackItem(
|
||||
|
|
@ -76,7 +76,7 @@ MusicAlbum(
|
|||
name: "25",
|
||||
)),
|
||||
view_count: None,
|
||||
is_video: false,
|
||||
is_video: true,
|
||||
track_nr: Some(2),
|
||||
),
|
||||
TrackItem(
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ MusicAlbum(
|
|||
name: "Der Himmel reißt auf",
|
||||
)),
|
||||
view_count: None,
|
||||
is_video: false,
|
||||
is_video: true,
|
||||
track_nr: Some(1),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ MusicAlbum(
|
|||
name: "<Queendom2> FINAL",
|
||||
)),
|
||||
view_count: None,
|
||||
is_video: false,
|
||||
is_video: true,
|
||||
track_nr: Some(1),
|
||||
),
|
||||
TrackItem(
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
text::TextComponent::Video { text: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned(), start_time: 0, is_video: true },
|
||||
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() },
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ 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: ",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer};
|
|||
use serde_with::{serde_as, DeserializeAs};
|
||||
|
||||
use crate::{
|
||||
client::response::url_endpoint::{NavigationEndpoint, PageType},
|
||||
client::response::url_endpoint::{MusicVideoType, NavigationEndpoint, PageType},
|
||||
model::UrlTarget,
|
||||
util,
|
||||
};
|
||||
|
|
@ -94,6 +94,8 @@ 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,
|
||||
|
|
@ -164,6 +166,11 @@ 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 {
|
||||
|
|
@ -365,6 +372,7 @@ impl From<TextComponent> for crate::model::richtext::TextComponent {
|
|||
text,
|
||||
video_id,
|
||||
start_time,
|
||||
..
|
||||
} => Self::YouTube {
|
||||
text,
|
||||
target: UrlTarget::Video {
|
||||
|
|
@ -581,6 +589,7 @@ mod tests {
|
|||
text: "DEEP",
|
||||
video_id: "wZIoIgz5mbs",
|
||||
start_time: 0,
|
||||
is_video: true,
|
||||
},
|
||||
}
|
||||
"###);
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ MusicAlbum(
|
|||
name: "Waldbrand",
|
||||
)),
|
||||
view_count: None,
|
||||
is_video: false,
|
||||
is_video: true,
|
||||
track_nr: Some(2),
|
||||
),
|
||||
TrackItem(
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ MusicAlbum(
|
|||
name: "Der Himmel reißt auf",
|
||||
)),
|
||||
view_count: None,
|
||||
is_video: false,
|
||||
is_video: true,
|
||||
track_nr: Some(1),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ MusicAlbum(
|
|||
name: "<Queendom2> FINAL",
|
||||
)),
|
||||
view_count: None,
|
||||
is_video: false,
|
||||
is_video: true,
|
||||
track_nr: Some(1),
|
||||
),
|
||||
TrackItem(
|
||||
|
|
|
|||
Reference in a new issue