aboutsummaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-04-16 20:06:01 +0200
committermetamuffin <metamuffin@disroot.org>2025-04-16 20:06:01 +0200
commitd26849375c70c795fdf664f9dfea68c273b6d483 (patch)
tree53ad4f0eff3604e80b27ff0abf0438ea6c69d432 /common
parent1cd966f7454f052fda6c6c9ae1597479f05e23d9 (diff)
parentcdf95d7b80bd2b78895671da8f462145bb5db522 (diff)
downloadjellything-d26849375c70c795fdf664f9dfea68c273b6d483.tar
jellything-d26849375c70c795fdf664f9dfea68c273b6d483.tar.bz2
jellything-d26849375c70c795fdf664f9dfea68c273b6d483.tar.zst
Merge branch 'rewrite-stream'
Diffstat (limited to 'common')
-rw-r--r--common/src/config.rs119
-rw-r--r--common/src/jhls.rs27
-rw-r--r--common/src/lib.rs2
-rw-r--r--common/src/seek_index.rs33
-rw-r--r--common/src/stream.rs262
-rw-r--r--common/src/user.rs46
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"),