diff options
Diffstat (limited to 'stream/types/src/lib.rs')
-rw-r--r-- | stream/types/src/lib.rs | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/stream/types/src/lib.rs b/stream/types/src/lib.rs new file mode 100644 index 0000000..a90db03 --- /dev/null +++ b/stream/types/src/lib.rs @@ -0,0 +1,281 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2025 metamuffin <metamuffin.org> +*/ +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, fmt::Display, str::FromStr}; + +pub type SegmentNum = usize; +pub type TrackNum = usize; +pub type FormatNum = usize; +pub type IndexNum = usize; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum StreamSpec { + // Whep { + // track: TrackNum, + // seek: u64, + // }, + // WhepControl { + // token: String, + // }, + Remux { + tracks: Vec<usize>, + container: StreamContainer, + }, + Original { + track: TrackNum, + }, + HlsSuperMultiVariant { + container: StreamContainer, + }, + HlsMultiVariant { + segment: SegmentNum, + container: StreamContainer, + }, + HlsVariant { + segment: SegmentNum, + track: TrackNum, + container: StreamContainer, + format: FormatNum, + }, + Info { + segment: Option<u64>, + }, + FragmentIndex { + segment: SegmentNum, + track: TrackNum, + }, + Fragment { + segment: SegmentNum, + track: TrackNum, + index: IndexNum, + container: StreamContainer, + format: FormatNum, + }, + // Track { + // segment: SegmentNum, + // track: TrackNum, + // container: StreamContainer, + // foramt: FormatNum, + // }, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct StreamInfo { + pub name: Option<String>, + pub segments: Vec<StreamSegmentInfo>, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct StreamSegmentInfo { + pub name: Option<String>, + pub duration: f64, + pub tracks: Vec<StreamTrackInfo>, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct StreamTrackInfo { + pub name: Option<String>, + pub kind: TrackKind, + pub formats: Vec<StreamFormatInfo>, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum TrackKind { + Video, + Audio, + Subtitle, +} + +#[derive(Debug, Clone, Deserialize, Serialize, Default)] +pub struct StreamFormatInfo { + pub codec: String, + pub bitrate: f64, + pub remux: bool, + pub containers: Vec<StreamContainer>, + + pub width: Option<u64>, + pub height: Option<u64>, + pub samplerate: Option<f64>, + pub channels: Option<usize>, + pub bit_depth: Option<u8>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum StreamContainer { + WebM, + Matroska, + WebVTT, + MPEG4, + JVTT, +} + +impl StreamSpec { + pub fn to_query(&self) -> String { + match self { + StreamSpec::Remux { tracks, container } => { + format!( + "?remux&tracks={}&container={container}", + tracks + .iter() + .map(|t| t.to_string()) + .collect::<Vec<String>>() + .join(",") + ) + } + StreamSpec::Original { track } => format!("?original&track={track}"), + StreamSpec::HlsSuperMultiVariant { container } => { + format!("?hlssupermultivariant&container={container}") + } + StreamSpec::HlsMultiVariant { segment, container } => { + format!("?hlsmultivariant&segment={segment}&container={container}") + } + StreamSpec::HlsVariant { + segment, + track, + container, + format, + } => format!( + "?hlsvariant&segment={segment}&track={track}&container={container}&format={format}" + ), + StreamSpec::Info { + segment: Some(segment), + } => format!("?info&segment={segment}"), + StreamSpec::Info { segment: None } => "?info".to_string(), + StreamSpec::FragmentIndex { segment, track } => { + format!("?fragmentindex&segment={segment}&track={track}") + } + StreamSpec::Fragment { + segment, + track, + index, + container, + format, + } => format!( + "?fragment&segment={segment}&track={track}&index={index}&container={container}&format={format}" + ), + } + } + pub fn to_query_short(&self) -> String { + match self { + StreamSpec::Remux { tracks, container } => { + format!( + "?remux&ts={}&c={container}", + tracks + .iter() + .map(|t| t.to_string()) + .collect::<Vec<String>>() + .join(",") + ) + } + StreamSpec::Original { track } => format!("?original&t={track}"), + StreamSpec::HlsSuperMultiVariant { container } => { + format!("?hlssupermultivariant&c={container}") + } + StreamSpec::HlsMultiVariant { segment, container } => { + format!("?hlsmultivariant&s={segment}&c={container}") + } + StreamSpec::HlsVariant { + segment, + track, + container, + format, + } => format!("?hlsvariant&s={segment}&t={track}&c={container}&f={format}"), + StreamSpec::Info { + segment: Some(segment), + } => format!("?info&s={segment}"), + StreamSpec::Info { segment: None } => "?info".to_string(), + StreamSpec::FragmentIndex { segment, track } => { + format!("?fragmentindex&s={segment}&t={track}") + } + StreamSpec::Fragment { + segment, + track, + index, + container, + format, + } => format!("?fragment&s={segment}&t={track}&i={index}&c={container}&f={format}"), + } + } + pub fn from_query_kv(query: &BTreeMap<String, String>) -> Result<Self, &'static str> { + let get_num = |k: &'static str, ks: &'static str| { + query + .get(k) + .or(query.get(ks)) + .ok_or(k) + .and_then(|a| a.parse().map_err(|_| "invalid number")) + }; + let get_container = || { + query + .get("container") + .or(query.get("c")) + .ok_or("container") + .and_then(|s| s.parse().map_err(|()| "unknown container")) + }; + if query.contains_key("info") { + Ok(Self::Info { + segment: get_num("segment", "s").ok(), + }) + } else if query.contains_key("hlssupermultivariant") { + Ok(Self::HlsSuperMultiVariant { + container: get_container().ok().unwrap_or(StreamContainer::Matroska), + }) + } else if query.contains_key("hlsmultivariant") { + Ok(Self::HlsMultiVariant { + segment: get_num("segment", "s")? as SegmentNum, + container: get_container()?, + }) + } else if query.contains_key("hlsvariant") { + Ok(Self::HlsVariant { + segment: get_num("segment", "s")? as SegmentNum, + track: get_num("track", "t")? as TrackNum, + format: get_num("format", "f")? as FormatNum, + container: get_container()?, + }) + } else if query.contains_key("fragment") { + Ok(Self::Fragment { + segment: get_num("segment", "s")? as SegmentNum, + track: get_num("track", "t")? as TrackNum, + format: get_num("format", "f")? as FormatNum, + index: get_num("index", "i")? as IndexNum, + container: get_container()?, + }) + } else if query.contains_key("fragmentindex") { + Ok(Self::FragmentIndex { + segment: get_num("segment", "s")? as SegmentNum, + track: get_num("track", "t")? as TrackNum, + }) + } else { + Err("invalid stream spec") + } + } +} + +impl Display for StreamContainer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + StreamContainer::WebM => "webm", + StreamContainer::Matroska => "matroska", + StreamContainer::WebVTT => "webvtt", + StreamContainer::JVTT => "jvtt", + StreamContainer::MPEG4 => "mpeg4", + }) + } +} +impl FromStr for StreamContainer { + type Err = (); + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(match s { + "webm" => StreamContainer::WebM, + "matroska" => StreamContainer::Matroska, + "webvtt" => StreamContainer::WebVTT, + "jvtt" => StreamContainer::JVTT, + "mpeg4" => StreamContainer::MPEG4, + _ => return Err(()), + }) + } +} |