feat: add channel_videos

refactor: unify VideoListItem
This commit is contained in:
ThetaDev 2022-09-22 00:01:09 +02:00
parent 86a348f210
commit 67ae1eb21d
19 changed files with 39591 additions and 100 deletions

View file

@ -2,13 +2,15 @@ use serde::Deserialize;
use serde_with::serde_as;
use serde_with::VecSkipError;
use super::TimeOverlay;
use super::{ContentRenderer, ContentsRenderer, Thumbnails, VideoListItem};
use crate::serializer::text::Text;
use super::ChannelBadge;
use super::Thumbnails;
use super::{ContentRenderer, ContentsRenderer, VideoListItem};
use crate::serializer::{text::Text, MapResult, VecLogError};
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Channel {
pub header: Header,
pub contents: Contents,
}
@ -18,6 +20,8 @@ pub struct Contents {
pub two_column_browse_results_renderer: TabsRenderer,
}
/// YouTube channel tab view. Contains multiple tabs
/// (Home, Videos, Playlists, About...). We can ignore unknown tabs.
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -35,7 +39,14 @@ pub struct TabRendererWrap {
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SectionListRendererWrap {
pub section_list_renderer: ContentsRenderer<ItemSectionRendererWrap>,
pub section_list_renderer: SectionListRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SectionListRenderer {
pub contents: Vec<ItemSectionRendererWrap>,
pub target_id: String,
}
#[derive(Clone, Debug, Deserialize)]
@ -54,22 +65,31 @@ pub struct GridRendererWrap {
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GridRenderer {
#[serde_as(as = "VecSkipError<_>")]
pub items: Vec<VideoListItem<ChannelVideo>>,
#[serde_as(as = "VecLogError<_>")]
pub items: MapResult<Vec<VideoListItem>>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Header {
pub c4_tabbed_header_renderer: HeaderRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChannelVideo {
pub video_id: String,
pub thumbnail: Thumbnails,
#[serde_as(as = "Text")]
pub struct HeaderRenderer {
pub channel_id: String,
/// Channel name
pub title: String,
#[serde_as(as = "Option<Text>")]
pub published_time_text: Option<String>,
/// Approximate subscriber count (e.g. `880K subscribers`), depends on language
#[serde_as(as = "Text")]
pub view_count_text: String,
pub subscriber_count_text: String,
pub avatar: Thumbnails,
#[serde_as(as = "VecSkipError<_>")]
pub thumbnail_overlays: Vec<TimeOverlay>,
pub badges: Vec<ChannelBadge>,
pub banner: Thumbnails,
pub mobile_banner: Thumbnails,
/// Fullscreen (16:9) channel banner
pub tv_banner: Thumbnails,
}

View file

@ -14,7 +14,7 @@ pub use video_details::VideoDetails;
pub use video_details::VideoRecommendations;
use serde::Deserialize;
use serde_with::{serde_as, DefaultOnError, VecSkipError};
use serde_with::{json::JsonString, serde_as, DefaultOnError, VecSkipError};
use crate::serializer::{
ignore_any,
@ -40,6 +40,8 @@ pub struct ThumbnailsWrap {
pub thumbnail: Thumbnails,
}
/// List of images in different resolutions.
/// Not only used for thumbnails, but also for avatars and banners.
#[derive(Default, Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Thumbnails {
@ -54,27 +56,107 @@ pub struct Thumbnail {
pub height: u32,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum VideoListItem<T> {
#[serde(alias = "playlistVideoRenderer", alias = "compactVideoRenderer")]
GridVideoRenderer {
#[serde(flatten)]
video: T,
},
pub enum VideoListItem {
GridVideoRenderer(GridVideoRenderer),
CompactVideoRenderer(CompactVideoRenderer),
PlaylistVideoRenderer(PlaylistVideoRenderer),
GridPlaylistRenderer(GridPlaylistRenderer),
/// Continauation items are located at the end of a list
/// and contain the continuation token for progressive loading
#[serde(rename_all = "camelCase")]
ContinuationItemRenderer {
continuation_endpoint: ContinuationEndpoint,
},
/// No video list item (e.g. ad)
/// No video list item (e.g. ad) or unimplemented item
///
/// Note that there are sometimes playlists among the recommended
/// videos. They are currently ignored.
/// Unimplemented:
/// - compactPlaylistRenderer (recommended playlists)
/// - compactRadioRenderer (recommended mix)
#[serde(other, deserialize_with = "ignore_any")]
None,
}
/// Video displayed on a channel page
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GridVideoRenderer {
pub video_id: String,
pub thumbnail: Thumbnails,
#[serde_as(as = "Text")]
pub title: String,
#[serde_as(as = "Option<Text>")]
pub published_time_text: Option<String>,
#[serde_as(as = "Option<Text>")]
pub view_count_text: Option<String>,
#[serde_as(as = "VecSkipError<_>")]
pub thumbnail_overlays: Vec<TimeOverlay>,
}
/// Video displayed in recommendations
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompactVideoRenderer {
pub video_id: String,
pub thumbnail: Thumbnails,
#[serde_as(as = "Text")]
pub title: String,
#[serde(rename = "shortBylineText")]
pub channel: TextComponent,
pub channel_thumbnail: Thumbnails,
/// Channel verification badge
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub owner_badges: Vec<ChannelBadge>,
#[serde_as(as = "Option<Text>")]
pub length_text: Option<String>,
/// (e.g. `11 months ago`)
#[serde_as(as = "Option<Text>")]
pub published_time_text: Option<String>,
#[serde_as(as = "Option<Text>")]
pub view_count_text: Option<String>,
/// Badges are displayed on the video thumbnail and
/// show certain video properties (e.g. active livestream)
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub badges: Vec<VideoBadge>,
}
/// Video displayed in a playlist
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub 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 = "JsonString")]
pub length_seconds: u32,
}
/// Playlist displayed on a channel page
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GridPlaylistRenderer {
pub playlist_id: String,
#[serde_as(as = "Text")]
pub title: String,
pub thumbnail: Thumbnails,
#[serde_as(as = "Text")]
pub published_time_text: String,
#[serde_as(as = "Text")]
pub video_count_short_text: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContinuationItemRenderer {

View file

@ -1,11 +1,11 @@
use serde::Deserialize;
use serde_with::serde_as;
use serde_with::{json::JsonString, DefaultOnError, VecSkipError};
use serde_with::{DefaultOnError, VecSkipError};
use crate::serializer::text::{Text, TextComponent};
use crate::serializer::{MapResult, VecLogError};
use super::{ContentRenderer, ContentsRenderer, Thumbnails, ThumbnailsWrap, VideoListItem};
use super::{ContentRenderer, ContentsRenderer, ThumbnailsWrap, VideoListItem};
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -58,21 +58,7 @@ pub struct PlaylistVideoListRenderer {
#[serde(rename_all = "camelCase")]
pub struct PlaylistVideoList {
#[serde_as(as = "VecLogError<_>")]
pub contents: MapResult<Vec<VideoListItem<PlaylistVideo>>>,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistVideo {
pub video_id: String,
pub thumbnail: Thumbnails,
#[serde_as(as = "Text")]
pub title: String,
#[serde(rename = "shortBylineText")]
pub channel: TextComponent,
#[serde_as(as = "JsonString")]
pub length_seconds: u32,
pub contents: MapResult<Vec<VideoListItem>>,
}
#[derive(Clone, Debug, Deserialize)]
@ -172,5 +158,5 @@ pub struct OnResponseReceivedAction {
#[serde(rename_all = "camelCase")]
pub struct AppendAction {
#[serde_as(as = "VecLogError<_>")]
pub continuation_items: MapResult<Vec<VideoListItem<PlaylistVideo>>>,
pub continuation_items: MapResult<Vec<VideoListItem>>,
}

View file

@ -8,13 +8,12 @@ use crate::serializer::text::TextComponents;
use crate::serializer::MapResult;
use crate::serializer::{
ignore_any,
text::{AccessibilityText, Text, TextComponent},
text::{AccessibilityText, Text},
VecLogError,
};
use super::{
ChannelBadge, ContinuationEndpoint, ContinuationItemRenderer, Icon, Thumbnails, VideoBadge,
VideoListItem, VideoOwner,
ContinuationEndpoint, ContinuationItemRenderer, Icon, Thumbnails, VideoListItem, VideoOwner,
};
/*
@ -283,37 +282,7 @@ pub struct RecommendationResultsWrap {
pub struct RecommendationResults {
/// Can be `None` for age-restricted videos
#[serde_as(as = "Option<VecLogError<_>>")]
pub results: Option<MapResult<Vec<VideoListItem<RecommendedVideo>>>>,
}
/// Video recommendation item
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecommendedVideo {
pub video_id: String,
pub thumbnail: Thumbnails,
#[serde_as(as = "Text")]
pub title: String,
#[serde(rename = "shortBylineText")]
pub channel: TextComponent,
pub channel_thumbnail: Thumbnails,
/// Channel verification badge
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub owner_badges: Vec<ChannelBadge>,
#[serde_as(as = "Option<Text>")]
pub length_text: Option<String>,
/// (e.g. `11 months ago`)
#[serde_as(as = "Option<Text>")]
pub published_time_text: Option<String>,
#[serde_as(as = "Option<Text>")]
pub view_count_text: Option<String>,
/// Badges are displayed on the video thumbnail and
/// show certain video properties (e.g. active livestream)
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub badges: Vec<VideoBadge>,
pub results: Option<MapResult<Vec<VideoListItem>>>,
}
/// The engagement panels are displayed below the video and contain chapter markers
@ -468,7 +437,7 @@ pub struct RecommendationsContItem {
#[serde(rename_all = "camelCase")]
pub struct AppendRecommendations {
#[serde_as(as = "VecLogError<_>")]
pub continuation_items: MapResult<Vec<VideoListItem<RecommendedVideo>>>,
pub continuation_items: MapResult<Vec<VideoListItem>>,
}
/*