aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-04-14 18:42:16 +0200
committermetamuffin <metamuffin@disroot.org>2025-04-14 18:42:16 +0200
commit3b147cb1dfcbd5c7218e0accd5784d992d5ae21c (patch)
tree028e5ba21bd9c7da583b2ead0001c1481ccbae86
parent42e08750a5a9a112d458a5db1d6b169278e953c5 (diff)
downloadjellything-3b147cb1dfcbd5c7218e0accd5784d992d5ae21c.tar
jellything-3b147cb1dfcbd5c7218e0accd5784d992d5ae21c.tar.bz2
jellything-3b147cb1dfcbd5c7218e0accd5784d992d5ae21c.tar.zst
things
-rw-r--r--common/src/config.rs17
-rw-r--r--common/src/stream.rs2
-rw-r--r--server/src/routes/stream.rs4
-rw-r--r--stream/src/fragment.rs1
-rw-r--r--stream/src/hls.rs26
-rw-r--r--stream/src/lib.rs38
-rw-r--r--stream/src/stream_info.rs6
-rw-r--r--transcoder/src/fragment.rs47
8 files changed, 87 insertions, 54 deletions
diff --git a/common/src/config.rs b/common/src/config.rs
index 3a48fea..df16ef0 100644
--- a/common/src/config.rs
+++ b/common/src/config.rs
@@ -29,19 +29,20 @@ pub struct GlobalConfig {
#[serde(default)]
pub default_permission_set: PermissionSet,
#[serde(default)]
- pub encoders: EncoderPreferences,
+ pub encoders: EncoderArgs,
}
#[derive(Debug, Deserialize, Serialize, Default)]
-pub struct EncoderPreferences {
- pub avc: Option<EncoderClass>,
- pub hevc: Option<EncoderClass>,
- pub vp8: Option<EncoderClass>,
- pub vp9: Option<EncoderClass>,
- pub av1: Option<EncoderClass>,
+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)]
+#[derive(Debug, Deserialize, Serialize, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum EncoderClass {
Aom,
diff --git a/common/src/stream.rs b/common/src/stream.rs
index 555a5d0..9fd7daf 100644
--- a/common/src/stream.rs
+++ b/common/src/stream.rs
@@ -76,7 +76,7 @@ pub struct StreamTrackInfo {
pub formats: Vec<StreamFormatInfo>,
}
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TrackKind {
Video,
diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs
index 8f97aec..0fbeb3a 100644
--- a/server/src/routes/stream.rs
+++ b/server/src/routes/stream.rs
@@ -42,8 +42,8 @@ pub async fn r_stream_head(
#[get("/n/<id>/stream?<spec..>")]
pub async fn r_stream(
- session: Session,
- federation: &State<Federation>,
+ _session: Session,
+ _federation: &State<Federation>,
db: &State<Database>,
id: &str,
range: Option<RequestRange>,
diff --git a/stream/src/fragment.rs b/stream/src/fragment.rs
index e0644aa..26746fc 100644
--- a/stream/src/fragment.rs
+++ b/stream/src/fragment.rs
@@ -53,6 +53,7 @@ pub async fn fragment_stream(
} else {
let location = transcode(
&format!("{path:?} {track_num} {index} {format_num} {container}"), // TODO maybe not use the entire source
+ track.kind,
format,
container,
move |b| {
diff --git a/stream/src/hls.rs b/stream/src/hls.rs
index f06ac72..3dfbf01 100644
--- a/stream/src/hls.rs
+++ b/stream/src/hls.rs
@@ -15,7 +15,31 @@ use tokio::{
task::spawn_blocking,
};
-pub async fn hls_master_stream(
+pub async fn hls_supermultivariant_stream(
+ mut b: DuplexStream,
+ info: Arc<SMediaInfo>,
+ container: StreamContainer,
+) -> Result<()> {
+ let (_iinfo, info) = stream_info(info).await?;
+ let mut out = String::new();
+ writeln!(out, "#EXTM3U")?;
+ writeln!(out, "#EXT-X-VERSION:4")?;
+ for (i, _seg) in info.segments.iter().enumerate() {
+ let uri = format!(
+ "stream{}",
+ StreamSpec::HlsMultiVariant {
+ segment: i,
+ container,
+ }
+ .to_query()
+ );
+ writeln!(out, "{uri}")?;
+ }
+ tokio::spawn(async move { b.write_all(out.as_bytes()).await });
+ Ok(())
+}
+
+pub async fn hls_multivariant_stream(
mut b: DuplexStream,
info: Arc<SMediaInfo>,
segment: SegmentNum,
diff --git a/stream/src/lib.rs b/stream/src/lib.rs
index 18ad2a7..4df87ae 100644
--- a/stream/src/lib.rs
+++ b/stream/src/lib.rs
@@ -10,10 +10,10 @@ pub mod hls;
pub mod stream_info;
pub mod webvtt;
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, bail, Context, Result};
use fragment::fragment_stream;
use fragment_index::fragment_index_stream;
-use hls::{hls_master_stream, hls_variant_stream};
+use hls::{hls_multivariant_stream, hls_supermultivariant_stream, hls_variant_stream};
use jellybase::common::{
stream::{StreamContainer, StreamSpec},
Node,
@@ -24,7 +24,6 @@ use tokio::{
fs::File,
io::{duplex, AsyncReadExt, AsyncSeekExt, AsyncWriteExt, DuplexStream},
};
-use tokio_util::io::SyncIoBridge;
#[derive(Debug)]
pub struct SMediaInfo {
@@ -71,13 +70,12 @@ pub async fn stream(
let (a, b) = duplex(4096);
match spec {
- StreamSpec::Whep { track, seek } => todo!(),
- StreamSpec::WhepControl { token } => todo!(),
- StreamSpec::Remux { tracks, container } => todo!(),
StreamSpec::Original { track } => original_stream(info, track, range, b).await?,
- StreamSpec::HlsSuperMultiVariant { container } => todo!(),
+ StreamSpec::HlsSuperMultiVariant { container } => {
+ hls_supermultivariant_stream(b, info, container).await?;
+ }
StreamSpec::HlsMultiVariant { segment, container } => {
- hls_master_stream(b, info, segment, container).await?
+ hls_multivariant_stream(b, info, segment, container).await?
}
StreamSpec::HlsVariant {
segment,
@@ -96,34 +94,12 @@ pub async fn stream(
container,
format,
} => fragment_stream(b, info, track, segment, index, format, container).await?,
+ _ => bail!("todo"),
}
Ok(a)
}
-async fn remux_stream(
- node: Arc<Node>,
- spec: StreamSpec,
- range: Range<usize>,
- b: DuplexStream,
-) -> Result<()> {
- let b = SyncIoBridge::new(b);
-
- // tokio::task::spawn_blocking(move || {
- // jellyremuxer::remux_stream_into(
- // b,
- // range,
- // CONF.media_path.to_owned(),
- // &node,
- // local_tracks,
- // spec.track,
- // spec.webm.unwrap_or(false),
- // )
- // });
-
- Ok(())
-}
-
async fn original_stream(
info: Arc<SMediaInfo>,
track: usize,
diff --git a/stream/src/stream_info.rs b/stream/src/stream_info.rs
index 9d3d741..a8b6989 100644
--- a/stream/src/stream_info.rs
+++ b/stream/src/stream_info.rs
@@ -23,7 +23,7 @@ async fn async_matroska_metadata(path: PathBuf) -> Result<Arc<MatroskaMetadata>>
pub(crate) struct InternalStreamInfo {
pub paths: Vec<PathBuf>,
- pub metadata: Vec<Arc<MatroskaMetadata>>,
+ pub _metadata: Vec<Arc<MatroskaMetadata>>,
pub track_to_file: Vec<(usize, u64)>,
}
@@ -67,7 +67,7 @@ pub(crate) async fn stream_info(info: Arc<SMediaInfo>) -> Result<(InternalStream
};
Ok((
InternalStreamInfo {
- metadata,
+ _metadata: metadata,
paths,
track_to_file,
},
@@ -83,7 +83,7 @@ fn stream_formats(t: &TrackEntry) -> Vec<StreamFormatInfo> {
formats.push(StreamFormatInfo {
codec: t.codec_id.to_string(),
remux: true,
- bitrate: 2_000_000., // TODO
+ bitrate: 10_000_000., // TODO
containers: containers_by_codec(&t.codec_id),
bit_depth: t.audio.as_ref().and_then(|a| a.bit_depth.map(|e| e as u8)),
samplerate: t.audio.as_ref().map(|a| a.sampling_frequency),
diff --git a/transcoder/src/fragment.rs b/transcoder/src/fragment.rs
index 3cb4c40..1d06e9a 100644
--- a/transcoder/src/fragment.rs
+++ b/transcoder/src/fragment.rs
@@ -7,7 +7,8 @@
use crate::LOCAL_VIDEO_TRANSCODING_TASKS;
use jellybase::{
cache::{async_cache_file, CachePath},
- common::stream::{StreamContainer, StreamFormatInfo},
+ common::stream::{StreamContainer, StreamFormatInfo, TrackKind},
+ CONF,
};
use log::{debug, info};
use std::process::Stdio;
@@ -21,6 +22,7 @@ use tokio::{
pub async fn transcode(
key: &str,
+ kind: TrackKind,
format: &StreamFormatInfo,
container: StreamContainer,
input: impl FnOnce(ChildStdin),
@@ -31,15 +33,44 @@ pub async fn transcode(
let _permit = LOCAL_VIDEO_TRANSCODING_TASKS.acquire().await?;
debug!("transcoding fragment with {format:?}");
- let mut args = Vec::<String>::new();
-
- match format.codec.as_str() {
- "V_AVC" => {}
+ let template = match format.codec.as_str() {
+ "V_AVC" => CONF.encoders.avc.as_ref(),
+ "V_HEVC" => CONF.encoders.hevc.as_ref(),
+ "V_VP8" => CONF.encoders.vp8.as_ref(),
+ "V_VP9" => CONF.encoders.vp9.as_ref(),
+ "V_AV1" => CONF.encoders.av1.as_ref(),
+ _ => None,
+ }
+ .or(CONF.encoders.generic.as_ref())
+ .cloned()
+ .unwrap_or("ffmpeg %i %f %e %o".to_owned());
+ let filter = match kind {
+ TrackKind::Video => format!("-vf scale={}:-1", format.width.unwrap()),
+ TrackKind::Audio => format!(""),
+ TrackKind::Subtitle => String::new(),
+ };
+ let typechar = match kind {
+ TrackKind::Video => "v",
+ TrackKind::Audio => "a",
+ TrackKind::Subtitle => "s",
+ };
+ let fallback_encoder = match format.codec.as_str() {
+ "A_OPUS" => "libopus",
_ => unreachable!(),
- }
+ };
+
+ let args = template
+ .replace("%i", "-f matroska -i pipe:0")
+ .replace("%o", "-f %C pipe:1")
+ .replace("%f", &filter)
+ .replace("%e", "-c:%t %c -b:%t %r")
+ .replace("%t", typechar)
+ .replace("%c", fallback_encoder)
+ .replace("%r", &(format.bitrate as i64).to_string())
+ .replace("%C", &container.to_string());
- info!("encoding with {:?}", args.join(" "));
+ info!("encoding with {:?}", args);
let container = match container {
StreamContainer::WebM => "webm",
@@ -53,7 +84,7 @@ pub async fn transcode(
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.args(["-f", "matroska", "-i", "pipe:0"])
- .args(args)
+ .args(args.split(" "))
.args(["-f", container, "pipe:1"])
.spawn()?;