//! YouTube API response models mod convert; mod frameset; mod ordering; pub mod paginator; pub mod richtext; pub mod traits; pub use frameset::{Frameset, FramesetUrls}; use std::{collections::BTreeSet, ops::Range}; use serde::{Deserialize, Serialize}; use time::{Date, OffsetDateTime}; use self::{paginator::Paginator, richtext::RichText}; use crate::{client::ClientType, error::Error, param::Country, validate}; /* #COMMON */ /// Video thumbnail or other image #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[non_exhaustive] pub struct Thumbnail { /// Thumbnail URL pub url: String, /// Thumbnail image width pub width: u32, /// Thumbnail image height pub height: u32, } /// Entities extracted from a YouTube URL #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum UrlTarget { /// YouTube video /// /// Example: Video { /// Unique YouTube video ID id: String, /// Video start time in seconds start_time: u32, }, /// YouTube channel /// /// Example: Channel { /// Unique YouTube channel ID id: String, }, /// YouTube playlist /// /// Example: Playlist { /// Unique YouTube playlist ID id: String, }, /// YouTube Music album /// /// Example: Album { /// Unique YouTube album ID id: String, }, } impl std::fmt::Display for UrlTarget { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.to_url()) } } impl UrlTarget { /// Convert the URL target to a YouTube URL /// /// Is equivalent to `url_target.to_string()` pub fn to_url(&self) -> String { self.to_url_yt_host("https://www.youtube.com") } /// Convert the URL target to a YouTube URL with a specified YouTube host. /// /// Used to redirect to alternative YouTube frontends like Piped or Invidious. /// /// **Note:** Music album URL targets are still converted to `music.youtube.com/browse/*`, /// since these URLs are not supported by Piped or Invidious. pub fn to_url_yt_host(&self, yt_host: &str) -> String { match self { UrlTarget::Video { id, start_time, .. } => match start_time { 0 => format!("{yt_host}/watch?v={id}"), n => format!("{yt_host}/watch?v={id}&t={n}s"), }, UrlTarget::Channel { id } => { format!("{yt_host}/channel/{id}") } UrlTarget::Playlist { id } => { format!("{yt_host}/playlist?list={id}") } UrlTarget::Album { id } => { format!("https://music.youtube.com/browse/{id}") } } } /// Validate the YouTube ID from the URL target pub(crate) fn validate(&self) -> Result<(), Error> { match self { UrlTarget::Video { id, .. } => validate::video_id(id), UrlTarget::Channel { id } => validate::channel_id(id), UrlTarget::Playlist { id } => validate::playlist_id(id), UrlTarget::Album { id } => validate::album_id(id), } } } /* #PLAYER */ /// Video player data #[derive(Clone, Debug, Serialize, Deserialize)] #[non_exhaustive] pub struct VideoPlayer { /// Video metadata pub details: VideoPlayerDetails, /// List of streams containing both audio and video pub video_streams: Vec, /// List of streams containing video only pub video_only_streams: Vec, /// List of streams containing audio only pub audio_streams: Vec, /// List of subtitles pub subtitles: Vec, /// Lifetime of the stream URLs in seconds pub expires_in_seconds: u32, /// HLS manifest URL (for livestreams) pub hls_manifest_url: Option, /// Dash manifest URL (for livestreams) pub dash_manifest_url: Option, /// Video frames for seek preview pub preview_frames: Vec, /// Video player DRM config /// /// [`None`] if the video is not DRM-protected pub drm: Option, /// Client type with which the player was fetched pub client_type: ClientType, /// YouTube visitor data cookie pub visitor_data: Option, } /// Video metadata from the player #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[non_exhaustive] pub struct VideoPlayerDetails { /// Unique YouTube video ID pub id: String, /// Video title pub name: Option, /// Video description in plaintext format pub description: Option, /// Video duration in seconds /// /// Is zero for livestreams pub duration: u32, /// Video thumbnail pub thumbnail: Vec, /// Channel ID of the video pub channel_id: String, /// Channel name of the video pub channel_name: Option, /// Number of views / current viewers in case of a livestream. pub view_count: Option, /// List of words that describe the topic of the video pub keywords: Vec, /// True if the video is an active livestream pub is_live: bool, /// True if the video is/was livestreamed pub is_live_content: bool, } /// Video stream #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[non_exhaustive] pub struct VideoStream { /// Video stream URL pub url: String, /// YouTube stream format identifier pub itag: u32, /// Stream bitrate (in bits/second) pub bitrate: u32, /// Average stream bitrate (in bits/second) pub average_bitrate: u32, /// Video file size in bytes pub size: Option, /// Index range (used for DASH streaming) pub index_range: Option>, /// Init range (used for DASH streaming) pub init_range: Option>, /// Video duration in milliseconds pub duration_ms: Option, /// Video width in pixels pub width: u32, /// Video height in pixels pub height: u32, /// Video frames per second pub fps: u8, /// Quality text (e.g. "1080p60") pub quality: String, /// True if the video is HDR pub hdr: bool, /// MIME file type pub mime: String, /// Video file format pub format: VideoFormat, /// Video codec pub codec: VideoCodec, /// DRM track type /// /// [`None`] if the track is not DRM-protected pub drm_track_type: Option, /// List of DRM systems that can decrypt this track pub drm_systems: Vec, } /// Audio stream #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[non_exhaustive] pub struct AudioStream { /// Audio stream URL pub url: String, /// YouTube stream format identifier pub itag: u32, /// Stream bitrate (in bits/second) pub bitrate: u32, /// Average stream bitrate (in bits/second) pub average_bitrate: u32, /// Audio file size in bytes pub size: u64, /// Index range (used for DASH streaming) pub index_range: Option>, /// Init range (used for DASH streaming) pub init_range: Option>, /// Audio duration in milliseconds pub duration_ms: Option, /// MIME file type pub mime: String, /// Audio file format pub format: AudioFormat, /// Audio codec pub codec: AudioCodec, /// Number of audio channels pub channels: Option, /// Audio loudness for volume normalization /// /// The track volume correction factor (0-1) can be calculated using this formula /// /// `10^(-loudness_db/20)` /// /// Note that the `loudness_db` value is the inverse of the usual ReplayGain track gain /// parameter, i.e. a value of 6 means the volume should be reduced by 6dB and the /// track gain parameter would be -6. /// /// More information about ReplayGain and how to apply this infomation to audio files /// can be found here: . /// /// The loudness parameter is not available when using the Android client. pub loudness_db: Option, /// Audio track information /// /// Videos can have multiple audio tracks (different languages). /// In this case, this object shows to which track the stream belongs to. /// /// This is None if the video contains only 1 audio track. pub track: Option, /// DRM track type /// /// [`None`] if the track is not DRM-protected pub drm_track_type: Option, /// List of DRM systems that can decrypt this track pub drm_systems: Vec, } /// Video player DRM parameters #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub struct VideoPlayerDrm { /// Widevine service certificate pub widevine_service_cert: Option>, /// DRM parameters for the license API pub drm_params: String, /// DRM session id parameter for the license API pub drm_session_id: String, /// List of track types available for playback pub authorized_track_types: Vec, } /// Video player DRM parameters #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub struct DrmLicense { /// DRM license pub license: Vec, /// List of authorized formats with track type and 16-byte key ID pub authorized_formats: Vec<(DrmTrackType, [u8; 16])>, } /// Video codec #[derive( Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[serde(rename_all = "snake_case")] #[non_exhaustive] pub enum VideoCodec { /// Unknown codec #[default] Unknown, /// MPEG-4 Part 14 Mp4v, /// avc1 aka H.264: Avc1, /// VP9: Vp9, /// AV1, the latest codec: Av01, } /// Audio codec #[derive( Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[serde(rename_all = "snake_case")] #[non_exhaustive] pub enum AudioCodec { /// Unknown codec #[default] Unknown, /// MP4A aka AAC: Mp4a, /// Opus: Opus, /// Dolby Digital / AC-3: #[serde(rename = "ac-3")] Ac3, /// Dolby Digital Plus / EC-3: #[serde(rename = "ec-3")] Ec3, } /// Video file type #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(rename_all = "snake_case")] #[non_exhaustive] pub enum VideoFormat { /// `*.3gp` #[serde(rename = "3gp")] ThreeGp, /// `*.mp4` Mp4, /// `*.webm` Webm, } /// DRM track type /// /// Depending on the purchased video and the device's DRM capabilites, only a subset of track /// types may be available for playback. #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] pub enum DrmTrackType { /// Audio track Audio, /// Standard definition video (max. 480p) Sd, /// High definition video (max. 1080p) Hd, /// Ultra high definition video (2160p) Uhd1, } /// DRM system used to protect tracks #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] pub enum DrmSystem { /// Google Widevine /// /// Widevine, /// Microsoft PlayReady /// /// Playready, /// Apple FairPlay /// /// Fairplay, } impl DrmSystem { pub(crate) fn req_param(self) -> &'static str { match self { DrmSystem::Widevine => "DRM_SYSTEM_WIDEVINE", DrmSystem::Playready => "DRM_SYSTEM_PLAYREADY", DrmSystem::Fairplay => "DRM_SYSTEM_FAIRPLAY", } } } /// Audio track information /// /// Videos can have multiple audio tracks (different languages). /// In this case, this object shows to which track the stream belongs to. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[non_exhaustive] pub struct AudioTrack { /// Track ID (e.g. `en.0`) pub id: String, /// Language code (e.g. `en-US`, `de`) pub lang: Option, /// Language name (e.g. "English") pub lang_name: String, /// True if this is the default audio track chosen by YouTube /// /// Note that YouTube's selection depends on the client type used to fetch the player. /// Some players pub is_default: bool, /// Audio track type (e.g. *Original*, *Dubbed*) pub track_type: Option, } /// Audio file type #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(rename_all = "snake_case")] #[non_exhaustive] pub enum AudioFormat { /// `*.m4a` M4a, /// `*.webm` Webm, } /// Audio track type #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] #[serde(rename_all = "kebab-case")] #[non_exhaustive] pub enum AudioTrackType { /// An original audio track of the video Original, /// An audio track with the original voices replaced, typically in a different language Dubbed, /// An audio track dubbed using YouTube's AI powered AutoDub feature DubbedAuto, /// A descriptive audio track /// /// A descriptive audio track is an audio track in which descriptions of visual elements of /// a video are added to the original audio, with the goal to make a video more accessible to /// blind and visually impaired people. /// /// See Descriptive, } /// YouTube provides subtitles in different formats. /// /// srv1 (XML) is the default format, to request a different format you have /// to append `&fmt=` to the URL. /// /// # Subtitle formats /// /// ### `srv1` (default) /// /// ```xml /// /// /// - [Mr Beast] I built two massive circles /// and put 100 boys in one /// and 100 girls in the other /// /// ``` /// /// ### `srv2` /// /// ```xml /// /// /// - [Mr Beast] I built two massive circles /// and put 100 boys in one /// and 100 girls in the other /// /// ``` /// /// ### `srv3` /// /// ```xml /// /// /// ///

- [Mr Beast] I built two massive circles

///

and put 100 boys in one /// and 100 girls in the other

/// ///
/// ``` /// /// ### `json3` /// /// ```json /// { /// "wireMagic": "pb3", /// "pens": [{}], /// "wsWinStyles": [{}], /// "wpWinPositions": [{}], /// "events": [ /// { /// "tStartMs": 120, /// "dDurationMs": 1590, /// "segs": [ /// { /// "utf8": "- [Mr Beast] I built two massive circles" /// } /// ] /// }, /// { /// "tStartMs": 1710, /// "dDurationMs": 3390, /// "segs": [ /// { /// "utf8": "and put 100 boys in one\nand 100 girls in the other" /// } /// ] /// } /// ] /// } /// ``` /// /// ### Timed Text Markup Language (`ttml`) /// /// ```xml /// /// /// /// ///