use std::collections::HashMap; use serde::{Deserialize, Deserializer}; #[derive(Debug, Deserialize)] pub struct InfoJson { pub id: String, pub title: String, pub formats: Vec<Format>, pub thumbnails: Vec<ThumbNail>, pub thumbnail: String, pub description: String, pub channel_id: String, pub channel_url: String, pub duration: u32, pub view_count: u32, pub age_limit: u32, pub webpage_url: String, pub categories: Vec<String>, pub tags: Vec<String>, pub playable_in_embed: bool, pub live_status: String, _format_sort_fields: Vec<String>, pub automatic_captions: HashMap<String, Vec<Caption>>, pub subtitles: Subtitles, pub comment_count: u32, pub like_count: u32, pub channel: String, pub channel_follower_count: u32, pub channel_is_verified: bool, pub uploader: String, pub uploader_id: String, pub uploader_url: String, pub upload_date: String, pub availability: String, pub webpage_url_basename: String, pub webpage_url_domain: String, pub extractor: String, pub extractor_key: String, pub display_id: String, pub fulltitle: String, pub duration_string: String, pub is_live: bool, pub was_live: bool, pub epoch: u32, pub comments: Vec<Comment>, pub sponsorblock_chapters: Vec<SponsorblockChapter>, pub format: String, pub format_id: String, pub ext: String, pub protocol: String, pub language: Option<String>, pub format_note: String, pub filesize_approx: u32, pub tbr: f64, pub width: u32, pub height: u32, pub resolution: String, pub fps: f64, pub dynamic_range: String, pub vcodec: String, pub vbr: f64, pub aspect_ratio: f64, pub acodec: String, pub abr: f64, pub asr: u32, pub audio_channels: u32, _type: String, _version: Version, } #[derive(Debug, Deserialize)] pub struct Subtitles {} #[derive(Debug, Deserialize)] pub struct Version { pub version: String, pub release_git_head: String, pub repository: String, } #[derive(Debug, Deserialize)] pub struct SponsorblockChapter {} #[derive(Debug, Deserialize, Clone)] #[serde(from = "String")] pub enum Parent { Root, Id(String), } impl Parent { pub fn id(&self) -> Option<&str> { if let Self::Id(id) = self { Some(&id) } else { None } } } impl From<String> for Parent { fn from(value: String) -> Self { if value == "root" { Self::Root } else { Self::Id(value) } } } #[derive(Debug, Deserialize, Clone)] #[serde(from = "String")] pub struct Id { pub id: String, } impl From<String> for Id { fn from(value: String) -> Self { Self { // Take the last element if the string is split with dots, otherwise take the full id id: value.split('.').last().unwrap_or(&value).to_owned(), } } } #[derive(Debug, Deserialize, Clone)] pub struct Comment { pub id: Id, pub text: String, #[serde(default = "zero")] pub like_count: u32, pub author_id: String, pub author: String, pub author_thumbnail: String, pub parent: Parent, #[serde(deserialize_with = "edited_from_time_text", alias = "_time_text")] pub edited: bool, // Can't also be deserialized, as it's already used in 'edited' // _time_text: String, pub timestamp: i64, pub author_url: String, pub author_is_uploader: bool, pub is_favorited: bool, } fn zero() -> u32 { 0 } fn edited_from_time_text<'de, D>(d: D) -> Result<bool, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(d)?; if s.contains(" (edited)") { Ok(true) } else { Ok(false) } } #[derive(Debug, Deserialize)] pub struct Caption { pub ext: String, pub url: String, pub name: String, } #[derive(Debug, Deserialize)] pub struct ThumbNail { pub url: String, pub preference: i32, pub id: String, pub height: Option<u32>, pub width: Option<u32>, pub resolution: Option<String>, } #[derive(Debug, Deserialize)] pub struct Format { pub format_id: String, pub format_note: Option<String>, pub ext: String, pub protocol: String, pub acodec: Option<String>, pub vcodec: String, pub url: String, pub width: Option<u32>, pub height: Option<u32>, pub fps: Option<f64>, pub rows: Option<u32>, pub columns: Option<u32>, pub fragments: Option<Vec<Fragment>>, pub resolution: String, pub aspect_ratio: Option<f64>, pub http_headers: HttpHeader, pub audio_ext: String, pub video_ext: String, pub vbr: Option<f64>, pub abr: Option<f64>, pub format: String, } #[derive(Debug, Deserialize)] pub struct HttpHeader { #[serde(alias = "User-Agent")] pub user_agent: String, #[serde(alias = "Accept")] pub accept: String, #[serde(alias = "Accept-Language")] pub accept_language: String, #[serde(alias = "Sec-Fetch-Mode")] pub sec_fetch_mode: String, } #[derive(Debug, Deserialize)] pub struct Fragment { pub url: String, pub duration: f64, }