/* 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 */ 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, 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, }, 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, pub segments: Vec, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct StreamSegmentInfo { pub name: Option, pub duration: f64, pub tracks: Vec, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct StreamTrackInfo { pub name: Option, pub kind: TrackKind, pub formats: Vec, } #[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, pub width: Option, pub height: Option, pub samplerate: Option, pub channels: Option, pub bit_depth: Option, } #[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::>() .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::>() .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) -> Result { 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 { Ok(match s { "webm" => StreamContainer::WebM, "matroska" => StreamContainer::Matroska, "webvtt" => StreamContainer::WebVTT, "jvtt" => StreamContainer::JVTT, "mpeg4" => StreamContainer::MPEG4, _ => return Err(()), }) } }