use serde::Serialize;
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum MediaInfo {
Single(VideoInfo),
Collection(CollectionInfo),
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct VideoInfo {
pub id: String,
pub title: String,
pub description: Option<String>,
pub duration: Option<std::time::Duration>,
pub uploader: Option<String>,
pub uploader_id: Option<String>,
pub channel_id: Option<String>,
pub view_count: Option<u64>,
pub upload_date: Option<String>,
pub thumbnails: Vec<Thumbnail>,
pub webpage_url: String,
pub is_live: bool,
pub formats: Vec<Format>,
}
impl VideoInfo {
pub fn formats(&self) -> crate::format::FormatSelector<'_> {
crate::format::FormatSelector::new(&self.formats)
}
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct Thumbnail {
pub url: String,
pub width: Option<u32>,
pub height: Option<u32>,
}
#[derive(Debug, Clone, Default, Serialize)]
#[non_exhaustive]
pub struct Format {
pub itag: Option<u32>,
pub url: String,
pub mime_type: Option<String>,
pub container: Option<Container>,
pub video: Option<VideoStream>,
pub audio: Option<AudioStream>,
pub filesize: Option<u64>,
pub bitrate: Option<u64>,
}
#[derive(Debug, Clone, Default, Serialize)]
#[non_exhaustive]
pub struct VideoStream {
pub width: Option<u32>,
pub height: Option<u32>,
pub fps: Option<f64>,
pub codec: String,
}
#[derive(Debug, Clone, Default, Serialize)]
#[non_exhaustive]
pub struct AudioStream {
pub codec: String,
pub bitrate: Option<u64>,
pub sample_rate: Option<u32>,
pub channels: Option<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[non_exhaustive]
pub enum Container {
Mp4,
WebM,
M4a,
Weba,
Other(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FormatKind {
Progressive,
VideoOnly,
AudioOnly,
Unknown,
}
impl Format {
pub fn kind(&self) -> FormatKind {
match (&self.video, &self.audio) {
(Some(_), Some(_)) => FormatKind::Progressive,
(Some(_), None) => FormatKind::VideoOnly,
(None, Some(_)) => FormatKind::AudioOnly,
(None, None) => FormatKind::Unknown,
}
}
}
pub struct CollectionInfo {
pub id: String,
pub title: Option<String>,
pub kind: CollectionKind,
pub entries: futures::stream::BoxStream<'static, crate::error::Result<Entry>>,
}
impl std::fmt::Debug for CollectionInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CollectionInfo")
.field("id", &self.id)
.field("title", &self.title)
.field("kind", &self.kind)
.field("entries", &"<stream>")
.finish()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CollectionKind {
Playlist,
Channel,
Search,
}
#[derive(Debug, Clone, Default, Serialize)]
#[non_exhaustive]
pub struct Entry {
pub id: String,
pub title: Option<String>,
pub url: String,
pub duration: Option<std::time::Duration>,
pub thumbnails: Vec<Thumbnail>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_kind_classification() {
let f = Format {
itag: Some(22),
video: Some(VideoStream {
width: Some(1280),
height: Some(720),
fps: Some(30.0),
codec: "avc1.64001F".into(),
}),
audio: Some(AudioStream {
codec: "mp4a.40.2".into(),
bitrate: Some(192_000),
sample_rate: Some(44_100),
channels: Some(2),
}),
..Format::default()
};
assert!(matches!(f.kind(), FormatKind::Progressive));
let v = Format {
video: f.video.clone(),
audio: None,
..Format::default()
};
assert!(matches!(v.kind(), FormatKind::VideoOnly));
}
}