ytextract 0.11.2

A library for getting YouTube metadata
Documentation
use std::time::Duration;

use serde::Deserialize;
use serde_with::serde_as;

use crate::youtube::{ChannelNameRuns, ContinuationItemRenderer, Thumbnails, TitleRun, TitleRuns};

pub type Result = super::Result<Ok>;

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Ok {
    pub contents: Contents,
    pub microformat: Microformat,
    pub sidebar: Sidebar,
}

////////////////////////////////////////////////////////////////////////////////

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Contents {
    pub two_column_browse_results_renderer: TwoColumnBrowseResultsRenderer,
}

impl Contents {
    pub fn into_videos(self) -> impl Iterator<Item = PlaylistItem> {
        self.two_column_browse_results_renderer
            .tabs
            .0
            .tab_renderer
            .content
            .section_list_renderer
            .contents
            .0
            .item_section_renderer
            .contents
            .0
            .playlist_video_list_renderer
            .contents
            .into_iter()
    }
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TwoColumnBrowseResultsRenderer {
    pub tabs: (Tab,),
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Tab {
    pub tab_renderer: TabRenderer,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TabRenderer {
    pub content: Content,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Content {
    pub section_list_renderer: SectionListRenderer,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SectionListRenderer {
    pub contents: (Content2,),
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Content2 {
    pub item_section_renderer: ItemSectionRenderer,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ItemSectionRenderer {
    pub contents: (Content3,),
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Content3 {
    #[serde(default)]
    pub playlist_video_list_renderer: PlaylistVideoListRenderer,
}

#[derive(Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistVideoListRenderer {
    pub contents: Vec<PlaylistItem>,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub enum PlaylistItem {
    PlaylistVideoRenderer(PlaylistVideoRenderer),
    ContinuationItemRenderer(ContinuationItemRenderer),
}

#[serde_as]
#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase", untagged)]
pub enum PlaylistVideoRenderer {
    Ok(PlaylistVideo),
    Err {
        title: TitleRuns,
        #[serde(rename = "videoId")]
        video_id: crate::video::Id,
    },
}

#[serde_as]
#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistVideo {
    pub video_id: crate::video::Id,

    pub thumbnail: Thumbnails,
    pub title: TitleRuns,
    pub short_byline_text: ChannelNameRuns,

    #[serde_as(as = "serde_with::DurationSeconds<String>")]
    pub length_seconds: Duration,
}

////////////////////////////////////////////////////////////////////////////////
#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Microformat {
    pub microformat_data_renderer: MicroformatDataRenderer,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct MicroformatDataRenderer {
    pub url_canonical: String,
    pub title: String,
    pub description: String,
    pub thumbnail: Thumbnails,
    pub unlisted: bool,
}
////////////////////////////////////////////////////////////////////////////////

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Sidebar {
    pub playlist_sidebar_renderer: PlaylistSidebarRenderer,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistSidebarRenderer {
    pub items: PlaylistSidebarItems,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistSidebarItems(
    pub PlaylistSidebarPrimaryInfo,
    #[serde(default)] pub Option<PlaylistSidebarSecondaryInfo>,
);

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistSidebarPrimaryInfo {
    pub playlist_sidebar_primary_info_renderer: PlaylistSidebarPrimaryInfoRenderer,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistSidebarSecondaryInfo {
    pub playlist_sidebar_secondary_info_renderer: PlaylistSidebarSecondaryInfoRenderer,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistSidebarPrimaryInfoRenderer {
    pub stats: (VideoStats, ViewsStats, DateStats),
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct VideoStats {
    pub runs: Vec<TitleRun>,
}

impl VideoStats {
    pub fn as_number(&self) -> u64 {
        match self.runs[0].text.as_str() {
            "No videos" => 0,
            o => o
                .replace(',', "")
                .parse()
                .expect("VideoStats text was not a number"),
        }
    }
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ViewsStats {
    pub simple_text: String,
}

impl ViewsStats {
    pub fn as_number(&self) -> u64 {
        self.simple_text
            .as_str()
            .split_once(' ')
            .expect("No space in ViewsStats text")
            .0
            .replace(',', "")
            .parse()
            .unwrap_or_default()
    }
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DateStats {}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistSidebarSecondaryInfoRenderer {
    pub video_owner: VideoOwner,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct VideoOwner {
    pub video_owner_renderer: VideoOwnerRenderer,
}

#[derive(Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct VideoOwnerRenderer {
    pub title: ChannelNameRuns,
}

impl VideoOwnerRenderer {
    pub fn name(&self) -> &str {
        &self.title.runs[0].text
    }

    pub fn id(&self) -> crate::channel::Id {
        self.title.runs[0]
            .navigation_endpoint
            .browse_endpoint
            .browse_id
    }
}
////////////////////////////////////////////////////////////////////////////////
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Continuation {
    pub on_response_received_actions: (OnResponseReceivedAction,),
}

impl Continuation {
    pub fn into_videos(self) -> impl Iterator<Item = super::playlist::PlaylistItem> {
        self.on_response_received_actions
            .0
            .append_continuation_items_action
            .continuation_items
            .into_iter()
    }
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnResponseReceivedAction {
    pub append_continuation_items_action: AppendContinuationItemsAction,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AppendContinuationItemsAction {
    pub continuation_items: Vec<super::playlist::PlaylistItem>,
}