diff options
author | metamuffin <metamuffin@disroot.org> | 2025-04-16 20:06:01 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-04-16 20:06:01 +0200 |
commit | d26849375c70c795fdf664f9dfea68c273b6d483 (patch) | |
tree | 53ad4f0eff3604e80b27ff0abf0438ea6c69d432 /common/src/stream.rs | |
parent | 1cd966f7454f052fda6c6c9ae1597479f05e23d9 (diff) | |
parent | cdf95d7b80bd2b78895671da8f462145bb5db522 (diff) | |
download | jellything-d26849375c70c795fdf664f9dfea68c273b6d483.tar jellything-d26849375c70c795fdf664f9dfea68c273b6d483.tar.bz2 jellything-d26849375c70c795fdf664f9dfea68c273b6d483.tar.zst |
Merge branch 'rewrite-stream'
Diffstat (limited to 'common/src/stream.rs')
-rw-r--r-- | common/src/stream.rs | 262 |
1 files changed, 204 insertions, 58 deletions
diff --git a/common/src/stream.rs b/common/src/stream.rs index 1c285b3..55f2f49 100644 --- a/common/src/stream.rs +++ b/common/src/stream.rs @@ -3,80 +3,226 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use bincode::{Decode, Encode}; -#[cfg(feature = "rocket")] -use rocket::{FromForm, FromFormField, UriDisplayQuery}; 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, + }, +} + +#[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)] -#[cfg_attr(feature = "rocket", derive(FromForm, UriDisplayQuery))] -pub struct StreamSpec { - pub track: Vec<usize>, - pub format: StreamFormat, - pub webm: Option<bool>, - pub profile: Option<usize>, - pub index: Option<usize>, +pub struct StreamTrackInfo { + pub name: Option<String>, + pub kind: TrackKind, + pub formats: Vec<StreamFormatInfo>, } -#[rustfmt::skip] -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, Encode, Decode)] +#[derive(Debug, Copy, Clone, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] -#[cfg_attr(feature = "rocket", derive(FromFormField, UriDisplayQuery))] -pub enum StreamFormat { - #[cfg_attr(feature = "rocket", field(value = "original"))] Original, - #[cfg_attr(feature = "rocket", field(value = "matroska"))] Matroska, - #[cfg_attr(feature = "rocket", field(value = "hlsmaster"))] HlsMaster, - #[cfg_attr(feature = "rocket", field(value = "hlsvariant"))] HlsVariant, - #[cfg_attr(feature = "rocket", field(value = "jhlsi"))] JhlsIndex, - #[cfg_attr(feature = "rocket", field(value = "frag"))] Fragment, - #[cfg_attr(feature = "rocket", field(value = "webvtt"))] Webvtt, - #[cfg_attr(feature = "rocket", field(value = "jvtt"))] Jvtt, +pub enum TrackKind { + Video, + Audio, + Subtitle, } -impl Default for StreamSpec { - fn default() -> Self { - Self { - track: Vec::new(), - format: StreamFormat::Matroska, - webm: Some(true), - profile: None, - index: None, - } - } +#[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 { - use std::fmt::Write; - let mut u = String::new(); - write!(u, "format={}", self.format.ident()).unwrap(); - for t in &self.track { - write!(u, "&track={}", t).unwrap(); - } - if let Some(profile) = self.profile { - write!(u, "&profile={profile}").unwrap(); - } - if let Some(index) = self.index { - write!(u, "&index={index}").unwrap(); + match self { + StreamSpec::Whep { track, seek } => format!("?whep&track={track}&seek={seek}"), + StreamSpec::WhepControl { token } => format!("?whepcontrol&token={token}"), + 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 } => format!("?info"), + 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}"), } - if let Some(webm) = self.webm { - write!(u, "&webm={webm}").unwrap(); + } + pub fn from_query_kv(query: &BTreeMap<String, String>) -> Result<Self, &'static str> { + let get_num = |k: &'static str| { + query + .get(k) + .ok_or(k) + .and_then(|a| a.parse().map_err(|_| "invalid number")) + }; + let get_container = || { + query + .get("container") + .ok_or("container") + .and_then(|s| s.parse().map_err(|()| "unknown container")) + }; + if query.contains_key("info") { + Ok(Self::Info { + segment: get_num("segment").ok(), + }) + } else if query.contains_key("hlsmultivariant") { + Ok(Self::HlsMultiVariant { + segment: get_num("segment")? as SegmentNum, + container: get_container()?, + }) + } else if query.contains_key("hlsvariant") { + Ok(Self::HlsVariant { + segment: get_num("segment")? as SegmentNum, + track: get_num("track")? as TrackNum, + format: get_num("format")? as FormatNum, + container: get_container()?, + }) + } else if query.contains_key("fragment") { + Ok(Self::Fragment { + segment: get_num("segment")? as SegmentNum, + track: get_num("track")? as TrackNum, + format: get_num("format")? as FormatNum, + index: get_num("index")? as IndexNum, + container: get_container()?, + }) + } else if query.contains_key("fragmentindex") { + Ok(Self::FragmentIndex { + segment: get_num("segment")? as SegmentNum, + track: get_num("track")? as TrackNum, + }) + } else { + Err("invalid stream spec") } - u } } -impl StreamFormat { - pub fn ident(&self) -> &'static str { - match self { - StreamFormat::Jvtt => "jvtt", - StreamFormat::Original => "original", - StreamFormat::Matroska => "matroska", - StreamFormat::HlsMaster => "hlsmaster", - StreamFormat::HlsVariant => "hlsvariant", - StreamFormat::JhlsIndex => "jhlsi", - StreamFormat::Fragment => "frag", - StreamFormat::Webvtt => "webvtt", - } +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(()), + }) } } |