use std::ops::Range; use serde::Deserialize; use serde_with::serde_as; use serde_with::{DefaultOnError, DisplayFromStr, VecSkipError}; use super::{Empty, ResponseContext, Thumbnails}; use crate::serializer::{text::Text, MapResult}; #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Player { pub playability_status: PlayabilityStatus, pub streaming_data: Option, pub captions: Option, pub video_details: Option, #[serde(default)] #[serde_as(deserialize_as = "DefaultOnError")] pub storyboards: Option, pub response_context: ResponseContext, #[serde(default)] pub player_config: PlayerConfig, #[serde(default)] pub heartbeat_params: HeartbeatParams, } #[serde_as] #[derive(Debug, Deserialize)] #[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] pub(crate) enum PlayabilityStatus { #[serde(rename_all = "camelCase")] Ok { live_streamability: Option }, /// Video cant be played because of DRM / Geoblock #[serde(rename_all = "camelCase")] Unplayable { #[serde(default)] reason: String, #[serde(default)] error_screen: ErrorScreen, }, /// Age limit / Private video #[serde(rename_all = "camelCase")] LoginRequired { #[serde(default)] reason: String, #[serde(default)] messages: Vec, }, #[serde(rename_all = "camelCase")] LiveStreamOffline { #[serde(default)] reason: String, }, /// Video was censored / deleted #[serde(rename_all = "camelCase")] Error { #[serde(default)] reason: String, }, } #[serde_as] #[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ErrorScreen { #[serde(default)] #[serde_as(deserialize_as = "DefaultOnError")] pub player_error_message_renderer: Option, pub player_captcha_view_model: Option, } #[serde_as] #[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ErrorMessage { #[serde_as(as = "Text")] pub subreason: String, } #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct StreamingData { #[serde_as(as = "DisplayFromStr")] pub expires_in_seconds: u32, #[serde(default)] pub formats: MapResult>, #[serde(default)] pub adaptive_formats: MapResult>, /// Only on livestreams pub dash_manifest_url: Option, /// Only on livestreams pub hls_manifest_url: Option, pub drm_params: Option, #[serde(default)] #[serde_as(deserialize_as = "VecSkipError<_>")] pub initial_authorized_drm_track_types: Vec, } #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Format { pub itag: u32, pub url: Option, #[serde(default, rename = "type")] pub format_type: FormatType, pub mime_type: String, pub bitrate: u32, pub width: Option, pub height: Option, #[serde_as(as = "Option")] pub approx_duration_ms: Option, #[serde_as(as = "Option")] pub index_range: Option>, #[serde_as(as = "Option")] pub init_range: Option>, #[serde_as(as = "Option")] pub content_length: Option, #[serde(default)] #[serde_as(deserialize_as = "DefaultOnError")] pub quality: Option, pub fps: Option, pub quality_label: Option, pub average_bitrate: Option, pub color_info: Option, // Audio only #[serde(default)] #[serde_as(deserialize_as = "DefaultOnError")] pub audio_quality: Option, #[serde_as(as = "Option")] pub audio_sample_rate: Option, pub audio_channels: Option, pub loudness_db: Option, pub audio_track: Option, pub signature_cipher: Option, #[serde(default)] #[serde_as(deserialize_as = "VecSkipError<_>")] pub drm_families: Vec, pub drm_track_type: Option, } impl Format { pub fn is_audio(&self) -> bool { self.audio_quality.is_some() && self.audio_sample_rate.is_some() } pub fn is_video(&self) -> bool { self.quality.is_some() && self.quality_label.is_some() && self.fps.is_some() && self.height.is_some() && self.width.is_some() } } #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "lowercase")] pub(crate) enum Quality { Tiny, Small, Medium, Large, Highres, Hd720, Hd1080, Hd1440, Hd2160, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum AudioQuality { #[serde(rename = "AUDIO_QUALITY_ULTRALOW")] UltraLow, #[serde(rename = "AUDIO_QUALITY_LOW")] Low, #[serde(rename = "AUDIO_QUALITY_MEDIUM")] Medium, #[serde(rename = "AUDIO_QUALITY_HIGH")] High, } #[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub(crate) enum FormatType { #[default] Default, /// This stream only works via DASH and not via progressive HTTP. FormatStreamTypeOtf, } #[derive(Default, Debug, Deserialize)] #[serde(default, rename_all = "camelCase")] pub(crate) struct ColorInfo { pub primaries: Primaries, } #[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub(crate) enum Primaries { #[default] ColorPrimariesBt709, ColorPrimariesBt2020, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[allow(clippy::enum_variant_names)] pub(crate) enum DrmTrackType { DrmTrackTypeAudio, DrmTrackTypeSd, DrmTrackTypeHd, DrmTrackTypeUhd1, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub(crate) enum DrmFamily { Widevine, Playready, Fairplay, } #[derive(Default, Debug, Deserialize)] #[serde(default, rename_all = "camelCase")] pub(crate) struct AudioTrack { pub id: String, pub display_name: String, pub audio_is_default: bool, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Captions { pub player_captions_tracklist_renderer: PlayerCaptionsTracklistRenderer, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct PlayerCaptionsTracklistRenderer { pub caption_tracks: Vec, } #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct CaptionTrack { pub base_url: String, #[serde_as(as = "Text")] pub name: String, pub language_code: String, } #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct VideoDetails { pub video_id: String, pub title: Option, #[serde_as(as = "DisplayFromStr")] pub length_seconds: u32, #[serde(default)] pub keywords: Vec, pub channel_id: String, pub short_description: Option, #[serde(default)] pub thumbnail: Thumbnails, #[serde_as(as = "Option")] pub view_count: Option, pub author: Option, pub is_live_content: bool, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Storyboards { pub player_storyboard_spec_renderer: StoryboardRenderer, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct StoryboardRenderer { pub spec: String, } #[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct PlayerConfig { pub web_drm_config: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct WebDrmConfig { pub widevine_service_cert: Option, } #[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct HeartbeatParams { pub drm_session_id: Option, } impl From for crate::model::DrmTrackType { fn from(value: DrmTrackType) -> Self { match value { DrmTrackType::DrmTrackTypeAudio => Self::Audio, DrmTrackType::DrmTrackTypeSd => Self::Sd, DrmTrackType::DrmTrackTypeHd => Self::Hd, DrmTrackType::DrmTrackTypeUhd1 => Self::Uhd1, } } } impl From for crate::model::DrmSystem { fn from(value: DrmFamily) -> Self { match value { DrmFamily::Widevine => Self::Widevine, DrmFamily::Playready => Self::Playready, DrmFamily::Fairplay => Self::Fairplay, } } } #[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct DrmLicense { pub status: String, pub license: String, pub authorized_formats: Vec, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct AuthorizedFormat { pub track_type: DrmTrackType, pub key_id: String, }