/* 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) 2026 metamuffin */ use serde::{Deserialize, Serialize}; use std::{fmt::Display, fmt::Write, str::FromStr}; pub mod path; pub type TrackNum = usize; pub type FormatNum = usize; pub type IndexNum = usize; #[derive(Debug, Clone, Deserialize, Serialize)] pub enum StreamSpec { /// stream.m3u8 HlsMultiVariant, /// stream.mpd Dash, /// stream.json Info, /// //variant.m3u8 HlsVariant { track: TrackNum, format: FormatNum, }, /// /fragindex.json FragmentIndex { track: TrackNum, }, /// //init. FragmentInit { track: TrackNum, container: StreamContainer, format: FormatNum, }, /// //frag. Fragment { track: TrackNum, index: IndexNum, container: StreamContainer, format: FormatNum, }, Remux { tracks: Vec, container: StreamContainer, }, Original { track: TrackNum, }, // Track { // segment: SegmentNum, // track: TrackNum, // container: StreamContainer, // foramt: FormatNum, // }, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct StreamInfo { 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 codec_param: String, pub bitrate: f64, pub remux: bool, pub containers: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub width: Option, #[serde(skip_serializing_if = "Option::is_none")] pub height: Option, #[serde(skip_serializing_if = "Option::is_none")] pub samplerate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub channels: Option, #[serde(skip_serializing_if = "Option::is_none")] pub bit_depth: Option, } impl std::hash::Hash for StreamFormatInfo { fn hash(&self, state: &mut H) { self.codec.hash(state); self.codec_param.hash(state); (self.bitrate as i64).hash(state); self.remux.hash(state); self.containers.hash(state); self.width.hash(state); self.height.hash(state); (self.samplerate.unwrap_or(0.) as i64).hash(state); self.channels.hash(state); self.bit_depth.hash(state); } } impl StreamFormatInfo { pub fn metadata_str(&self) -> String { let mut o = String::new(); if let Some(w) = self.width { write!(o, "w{w}").unwrap(); } if let Some(h) = self.height { write!(o, "h{h}").unwrap(); } if let Some(r) = self.samplerate { write!(o, "r{r:.02}").unwrap(); } if let Some(b) = self.bit_depth { write!(o, "b{b}").unwrap(); } if let Some(c) = self.channels { write!(o, "c{c}").unwrap(); } o } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Hash)] #[serde(rename_all = "lowercase")] pub enum StreamContainer { WebM, Matroska, WebVTT, MP4, JVTT, } impl StreamContainer { pub fn mime_type(&self, kind: TrackKind) -> &'static str { match (self, kind) { (Self::WebM, TrackKind::Audio) => "audio/webm", (Self::WebM, _) => "video/webm", (Self::Matroska, TrackKind::Audio) => "audio/x-matroska", (Self::Matroska, _) => "video/x-matroska", (Self::MP4, TrackKind::Audio) => "audio/mp4", (Self::MP4, _) => "video/mp4", (Self::WebVTT, _) => "text/vtt", (Self::JVTT, _) => "application/jellything-vtt+json", } } pub fn from_file_ext(ext: &str) -> Option { Some(match ext { "weba" | "webm" => Self::WebM, "mkv" | "mka" | "mks" => Self::Matroska, "mp4" | "m4a" | "m4s" => Self::MP4, "vtt" => Self::WebVTT, "json" => Self::JVTT, _ => return None, }) } pub fn file_ext(&self, kind: TrackKind) -> &'static str { match (self, kind) { (Self::WebM, TrackKind::Audio) => "weba", (Self::WebM, _) => "webm", (Self::Matroska, TrackKind::Audio) => "mka", (Self::Matroska, _) => "mkv", (Self::MP4, TrackKind::Audio) => "m4a", (Self::MP4, _) => "mp4", (Self::WebVTT, _) => "vtt", (Self::JVTT, _) => "json", } } } impl Display for TrackKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { TrackKind::Video => "video", TrackKind::Audio => "audio", TrackKind::Subtitle => "subtitle", }) } } 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::MP4 => "mp4", }) } } 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, "mp4" => StreamContainer::MP4, _ => return Err(()), }) } }