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 | |
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')
-rw-r--r-- | common/src/config.rs | 119 | ||||
-rw-r--r-- | common/src/jhls.rs | 27 | ||||
-rw-r--r-- | common/src/lib.rs | 2 | ||||
-rw-r--r-- | common/src/seek_index.rs | 33 | ||||
-rw-r--r-- | common/src/stream.rs | 262 | ||||
-rw-r--r-- | common/src/user.rs | 46 |
6 files changed, 259 insertions, 230 deletions
diff --git a/common/src/config.rs b/common/src/config.rs index d7682df..df16ef0 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -4,27 +4,53 @@ Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::{jhls::EncodingProfile, user::PermissionSet}; +use crate::user::PermissionSet; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, path::PathBuf}; -#[rustfmt::skip] #[derive(Debug, Deserialize, Serialize, Default)] pub struct GlobalConfig { pub hostname: String, pub brand: String, pub slogan: String, - #[serde(default = "return_true" )] pub tls: bool, - #[serde(default = "default::asset_path")] pub asset_path: PathBuf, - #[serde(default = "default::database_path")] pub database_path: PathBuf, - #[serde(default = "default::cache_path")] pub cache_path: PathBuf, - #[serde(default = "default::media_path")] pub media_path: PathBuf, - #[serde(default = "default::secrets_path")] pub secrets_path: PathBuf, - #[serde(default = "default::transcoding_profiles")] pub transcoding_profiles: Vec<EncodingProfile>, - #[serde(default = "default::max_in_memory_cache_size")] pub max_in_memory_cache_size: usize, - #[serde(default)] pub admin_username: Option<String>, - #[serde(default = "default::login_expire")] pub login_expire: i64, - #[serde(default)] pub default_permission_set: PermissionSet, + #[serde(default = "return_true")] + pub tls: bool, + pub asset_path: PathBuf, + pub database_path: PathBuf, + pub cache_path: PathBuf, + pub media_path: PathBuf, + pub secrets_path: PathBuf, + #[serde(default = "max_in_memory_cache_size")] + pub max_in_memory_cache_size: usize, + #[serde(default)] + pub admin_username: Option<String>, + #[serde(default = "login_expire")] + pub login_expire: i64, + #[serde(default)] + pub default_permission_set: PermissionSet, + #[serde(default)] + pub encoders: EncoderArgs, +} + +#[derive(Debug, Deserialize, Serialize, Default)] +pub struct EncoderArgs { + pub avc: Option<String>, + pub hevc: Option<String>, + pub vp8: Option<String>, + pub vp9: Option<String>, + pub av1: Option<String>, + pub generic: Option<String>, +} + +#[derive(Debug, Deserialize, Serialize, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum EncoderClass { + Aom, + Svt, + X26n, + Vpx, + Vaapi, + Rkmpp, } #[derive(Serialize, Deserialize, Debug, Default)] @@ -58,68 +84,11 @@ pub struct ApiSecrets { pub trakt: Option<String>, } -mod default { - use crate::jhls::EncodingProfile; - use std::path::PathBuf; - - pub fn login_expire() -> i64 { - 60 * 60 * 24 - } - pub fn asset_path() -> PathBuf { - "data/assets".into() - } - pub fn database_path() -> PathBuf { - "data/database".into() - } - pub fn cache_path() -> PathBuf { - "data/cache".into() - } - pub fn media_path() -> PathBuf { - "data/media".into() - } - pub fn secrets_path() -> PathBuf { - "data/secrets.yaml".into() - } - pub fn max_in_memory_cache_size() -> usize { - 50_000_000 - } - pub fn transcoding_profiles() -> Vec<EncodingProfile> { - vec![ - EncodingProfile::Video { - codec: "libsvtav1".to_string(), - preset: Some(8), - bitrate: 2_000_000, - width: Some(1920), - }, - EncodingProfile::Video { - codec: "libsvtav1".to_string(), - preset: Some(8), - bitrate: 1_200_000, - width: Some(1280), - }, - EncodingProfile::Video { - codec: "libsvtav1".to_string(), - preset: Some(8), - bitrate: 300_000, - width: Some(640), - }, - EncodingProfile::Audio { - codec: "libopus".to_string(), - bitrate: 128_000, - sample_rate: None, - channels: Some(2), - }, - EncodingProfile::Audio { - codec: "libopus".to_string(), - bitrate: 64_000, - sample_rate: None, - channels: Some(2), - }, - EncodingProfile::Subtitles { - codec: "webvtt".to_string(), - }, - ] - } +fn login_expire() -> i64 { + 60 * 60 * 24 +} +fn max_in_memory_cache_size() -> usize { + 50_000_000 } fn return_true() -> bool { diff --git a/common/src/jhls.rs b/common/src/jhls.rs index 6dc976b..90f48f5 100644 --- a/common/src/jhls.rs +++ b/common/src/jhls.rs @@ -5,33 +5,6 @@ */ use bincode::{Decode, Encode}; use serde::{Deserialize, Serialize}; -use std::ops::Range; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct JhlsTrackIndex { - pub extra_profiles: Vec<EncodingProfile>, - pub fragments: Vec<Range<f64>>, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum EncodingProfile { - Video { - codec: String, - preset: Option<u8>, - bitrate: usize, - width: Option<usize>, - }, - Audio { - codec: String, - bitrate: usize, - channels: Option<usize>, - sample_rate: Option<f64>, - }, - Subtitles { - codec: String, - }, -} #[derive(Debug, Serialize, Deserialize, Encode, Decode)] pub struct SubtitleCue { diff --git a/common/src/lib.rs b/common/src/lib.rs index 7685027..003a798 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -9,7 +9,6 @@ pub mod config; pub mod helpers; pub mod r#impl; pub mod jhls; -pub mod seek_index; pub mod stream; pub mod user; @@ -173,7 +172,6 @@ pub type TrackID = usize; pub struct LocalTrack { pub path: PathBuf, pub track: TrackID, - pub codec_private: Option<Vec<u8>>, } #[derive(Debug, Clone, Deserialize, Serialize, Encode, Decode)] diff --git a/common/src/seek_index.rs b/common/src/seek_index.rs deleted file mode 100644 index 20cf394..0000000 --- a/common/src/seek_index.rs +++ /dev/null @@ -1,33 +0,0 @@ -/* - 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 bincode::{Decode, Encode}; - -pub const SEEK_INDEX_VERSION: u32 = 0x5eef1de4; - -#[derive(Debug, Clone, Decode, Encode)] -pub struct SeekIndex { - pub version: u32, - pub blocks: Vec<BlockIndex>, - pub keyframes: Vec<usize>, -} - -#[derive(Debug, Clone, Decode, Encode)] -pub struct BlockIndex { - pub pts: u64, - // pub duration: Option<u64>, - pub source_off: u64, // points to start of SimpleBlock or BlockGroup (not the Block inside it) - pub size: usize, -} - -impl Default for SeekIndex { - fn default() -> Self { - Self { - version: SEEK_INDEX_VERSION, - blocks: Vec::new(), - keyframes: Vec::new(), - } - } -} 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(()), + }) } } diff --git a/common/src/user.rs b/common/src/user.rs index ef78eca..e0e7a0d 100644 --- a/common/src/user.rs +++ b/common/src/user.rs @@ -3,7 +3,6 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::{stream::StreamFormat, user}; use bincode::{Decode, Encode}; #[cfg(feature = "rocket")] use rocket::{FromFormField, UriDisplayQuery}; @@ -99,7 +98,6 @@ pub enum UserPermission { ManageSelf, AccessNode(String), - StreamFormat(StreamFormat), Transcode, FederatedContent, } @@ -107,33 +105,11 @@ pub enum UserPermission { impl UserPermission { pub const ALL_ENUMERABLE: &'static [UserPermission] = { use UserPermission::*; - &[ - Admin, - Transcode, - ManageSelf, - FederatedContent, - StreamFormat(user::StreamFormat::Original), - ] + &[Admin, Transcode, ManageSelf, FederatedContent] }; pub fn default_value(&self) -> bool { - use user::StreamFormat::*; use UserPermission::*; - matches!( - self, - Transcode - | ManageSelf - | FederatedContent - | StreamFormat( - JhlsIndex - | Jvtt - | HlsMaster - | HlsVariant - | Matroska - | Fragment - | Webvtt - | Original // TODO remove later - ) - ) + matches!(self, Transcode | ManageSelf | FederatedContent) } } @@ -143,15 +119,15 @@ impl Display for UserPermission { UserPermission::ManageSelf => "manage self (password, display name, etc.)".to_string(), UserPermission::FederatedContent => "access to federated content".to_string(), UserPermission::Admin => "administrative rights".to_string(), - UserPermission::StreamFormat(StreamFormat::Original) => { - "downloading the original media".to_string() - } - UserPermission::StreamFormat(StreamFormat::Matroska) => { - "downloading a remuxed WebM/Matroska version of the media ".to_string() - } - UserPermission::StreamFormat(x) => { - format!("downloading media via {x:?}") - } + // UserPermission::StreamFormat(StreamFormat::Original) => { + // "downloading the original media".to_string() + // } + // UserPermission::StreamFormat(StreamFormat::Matroska) => { + // "downloading a remuxed WebM/Matroska version of the media ".to_string() + // } + // UserPermission::StreamFormat(x) => { + // format!("downloading media via {x:?}") + // } UserPermission::Transcode => "transcoding".to_string(), // UserPermission::ManageUsers => format!("management of all users"), // UserPermission::GenerateInvite => format!("inviting new users"), |