aboutsummaryrefslogtreecommitdiff
path: root/stream/types/src
diff options
context:
space:
mode:
Diffstat (limited to 'stream/types/src')
-rw-r--r--stream/types/src/lib.rs281
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(()),
+ })
+ }
+}