fix!: parse full video info from playlist items, remove PlaylistVideo model
This commit is contained in:
parent
54f42bcb54
commit
bf80db8a9a
21 changed files with 105688 additions and 42159 deletions
|
|
@ -9,8 +9,8 @@ use time::OffsetDateTime;
|
|||
use super::{url_endpoint::NavigationEndpoint, ChannelBadge, ContinuationEndpoint, Thumbnails};
|
||||
use crate::{
|
||||
model::{
|
||||
Channel, ChannelId, ChannelInfo, ChannelItem, ChannelTag, PlaylistItem, VideoItem,
|
||||
YouTubeItem,
|
||||
Channel, ChannelId, ChannelInfo, ChannelItem, ChannelTag, PlaylistItem, Verification,
|
||||
VideoItem, YouTubeItem,
|
||||
},
|
||||
param::Language,
|
||||
serializer::{
|
||||
|
|
@ -27,6 +27,7 @@ pub(crate) enum YouTubeListItem {
|
|||
#[serde(alias = "gridVideoRenderer", alias = "compactVideoRenderer")]
|
||||
VideoRenderer(VideoRenderer),
|
||||
ReelItemRenderer(ReelItemRenderer),
|
||||
PlaylistVideoRenderer(PlaylistVideoRenderer),
|
||||
|
||||
#[serde(alias = "gridPlaylistRenderer")]
|
||||
PlaylistRenderer(PlaylistRenderer),
|
||||
|
|
@ -145,6 +146,33 @@ pub(crate) struct ReelItemRenderer {
|
|||
pub navigation_endpoint: Option<ReelNavigationEndpoint>,
|
||||
}
|
||||
|
||||
/// Video displayed in a playlist
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct PlaylistVideoRenderer {
|
||||
pub video_id: String,
|
||||
pub thumbnail: Thumbnails,
|
||||
#[serde_as(as = "Text")]
|
||||
pub title: String,
|
||||
#[serde(rename = "shortBylineText")]
|
||||
pub channel: TextComponent,
|
||||
#[serde_as(as = "Option<JsonString>")]
|
||||
pub length_seconds: Option<u32>,
|
||||
/// Regular video: `["29K views", " • ", "13 years ago"]`
|
||||
/// Livestream: `["66K", " watching"]`
|
||||
/// Upcoming: `["8", " waiting"]`
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "Text")]
|
||||
pub video_info: Vec<String>,
|
||||
/// Contains Short/Live tag
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub thumbnail_overlays: Vec<TimeOverlay>,
|
||||
/// Release date for upcoming videos
|
||||
pub upcoming_event_data: Option<UpcomingEventData>,
|
||||
}
|
||||
|
||||
/// Playlist displayed in search results
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
@ -492,7 +520,7 @@ impl<T> YouTubeListMapper<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn map_short_video(&mut self, video: ReelItemRenderer, lang: Language) -> VideoItem {
|
||||
fn map_short_video(&mut self, video: ReelItemRenderer) -> VideoItem {
|
||||
let pub_date_txt = video.navigation_endpoint.map(|n| {
|
||||
n.reel_watch_endpoint
|
||||
.overlay
|
||||
|
|
@ -505,7 +533,7 @@ impl<T> YouTubeListMapper<T> {
|
|||
let length = video.accessibility.and_then(|acc| {
|
||||
let parts = ACCESSIBILITY_SEP_REGEX.split(&acc).collect::<Vec<_>>();
|
||||
if parts.len() > 2 {
|
||||
let i = match lang {
|
||||
let i = match self.lang {
|
||||
Language::Ru => 1,
|
||||
_ => 2,
|
||||
};
|
||||
|
|
@ -531,9 +559,9 @@ impl<T> YouTubeListMapper<T> {
|
|||
timeago::parse_timeago_dt_or_warn(self.lang, txt, &mut self.warnings)
|
||||
}),
|
||||
publish_date_txt: pub_date_txt,
|
||||
view_count: video
|
||||
.view_count_text
|
||||
.and_then(|txt| util::parse_large_numstr_or_warn(&txt, lang, &mut self.warnings)),
|
||||
view_count: video.view_count_text.and_then(|txt| {
|
||||
util::parse_large_numstr_or_warn(&txt, self.lang, &mut self.warnings)
|
||||
}),
|
||||
is_live: false,
|
||||
is_short: true,
|
||||
is_upcoming: false,
|
||||
|
|
@ -541,6 +569,68 @@ impl<T> YouTubeListMapper<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn map_playlist_video(&mut self, video: PlaylistVideoRenderer) -> VideoItem {
|
||||
let channel = ChannelId::try_from(video.channel)
|
||||
.ok()
|
||||
.map(|ch| ChannelTag {
|
||||
id: ch.id,
|
||||
name: ch.name,
|
||||
avatar: Vec::new(),
|
||||
verification: Verification::None,
|
||||
subscriber_count: None,
|
||||
});
|
||||
|
||||
let mut video_info = video.video_info.into_iter();
|
||||
let video_info1 = video_info
|
||||
.next()
|
||||
.map(|s| match video_info.next().as_deref() {
|
||||
None | Some(util::DOT_SEPARATOR) => s,
|
||||
Some(s2) => s + s2,
|
||||
});
|
||||
let video_info2 = video_info.next();
|
||||
|
||||
// RU: "7 лет назад" " • " "210 млн просмотров" (order flipped)
|
||||
let (view_count_txt, publish_date_txt) =
|
||||
if self.lang == Language::Ru && video_info2.is_some() {
|
||||
(video_info2, video_info1)
|
||||
} else {
|
||||
(video_info1, video_info2)
|
||||
};
|
||||
|
||||
let is_live = video.thumbnail_overlays.is_live();
|
||||
|
||||
let publish_date = video
|
||||
.upcoming_event_data
|
||||
.as_ref()
|
||||
.and_then(|upc| OffsetDateTime::from_unix_timestamp(upc.start_time).ok())
|
||||
.or_else(|| {
|
||||
if is_live {
|
||||
None
|
||||
} else {
|
||||
publish_date_txt.as_ref().and_then(|txt| {
|
||||
timeago::parse_timeago_dt_or_warn(self.lang, txt, &mut self.warnings)
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
VideoItem {
|
||||
id: video.video_id,
|
||||
name: video.title,
|
||||
length: video.length_seconds,
|
||||
thumbnail: video.thumbnail.into(),
|
||||
channel,
|
||||
publish_date,
|
||||
publish_date_txt,
|
||||
view_count: view_count_txt.and_then(|txt| {
|
||||
util::parse_large_numstr_or_warn(&txt, self.lang, &mut self.warnings)
|
||||
}),
|
||||
is_live,
|
||||
is_short: video.thumbnail_overlays.is_short(),
|
||||
is_upcoming: video.upcoming_event_data.is_some(),
|
||||
short_description: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_playlist(&self, playlist: PlaylistRenderer) -> PlaylistItem {
|
||||
PlaylistItem {
|
||||
id: playlist.playlist_id,
|
||||
|
|
@ -607,7 +697,11 @@ impl YouTubeListMapper<YouTubeItem> {
|
|||
self.items.push(mapped);
|
||||
}
|
||||
YouTubeListItem::ReelItemRenderer(video) => {
|
||||
let mapped = self.map_short_video(video, self.lang);
|
||||
let mapped = self.map_short_video(video);
|
||||
self.items.push(YouTubeItem::Video(mapped));
|
||||
}
|
||||
YouTubeListItem::PlaylistVideoRenderer(video) => {
|
||||
let mapped = self.map_playlist_video(video);
|
||||
self.items.push(YouTubeItem::Video(mapped));
|
||||
}
|
||||
YouTubeListItem::PlaylistRenderer(playlist) => {
|
||||
|
|
@ -671,7 +765,11 @@ impl YouTubeListMapper<VideoItem> {
|
|||
self.items.push(mapped);
|
||||
}
|
||||
YouTubeListItem::ReelItemRenderer(video) => {
|
||||
let mapped = self.map_short_video(video, self.lang);
|
||||
let mapped = self.map_short_video(video);
|
||||
self.items.push(mapped);
|
||||
}
|
||||
YouTubeListItem::PlaylistVideoRenderer(video) => {
|
||||
let mapped = self.map_playlist_video(video);
|
||||
self.items.push(mapped);
|
||||
}
|
||||
YouTubeListItem::ContinuationItemRenderer {
|
||||
|
|
|
|||
Reference in a new issue