aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-04-21 20:36:34 +0200
committermetamuffin <metamuffin@disroot.org>2025-04-21 20:36:34 +0200
commitb9d8e20b6bff6b51f73106b688bd6e5d98e08215 (patch)
tree0e6a82ff9fd346d79a0879c373c838f4e2a49e8a
parent30c3bb552aba8794d57eb08688e0c1c751ab27a0 (diff)
downloadjellything-b9d8e20b6bff6b51f73106b688bd6e5d98e08215.tar
jellything-b9d8e20b6bff6b51f73106b688bd6e5d98e08215.tar.bz2
jellything-b9d8e20b6bff6b51f73106b688bd6e5d98e08215.tar.zst
new transcoding args system
-rw-r--r--common/src/config.rs35
-rw-r--r--common/src/stream.rs2
-rw-r--r--remuxer/src/fragment.rs2
-rw-r--r--remuxer/src/matroska_to_mpeg4.rs2
-rw-r--r--remuxer/src/metadata.rs2
-rw-r--r--stream/src/fragment.rs9
-rw-r--r--stream/src/stream_info.rs14
-rw-r--r--transcoder/src/fragment.rs140
8 files changed, 124 insertions, 82 deletions
diff --git a/common/src/config.rs b/common/src/config.rs
index 522fa0f..4ec43eb 100644
--- a/common/src/config.rs
+++ b/common/src/config.rs
@@ -29,28 +29,25 @@ pub struct GlobalConfig {
#[serde(default)]
pub default_permission_set: PermissionSet,
#[serde(default)]
- pub encoders: EncoderArgs,
+ pub transcoder: TranscoderConfig,
}
+#[rustfmt::skip]
#[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,
+pub struct TranscoderConfig {
+ #[serde(default)] pub offer_avc: bool,
+ #[serde(default)] pub offer_hevc: bool,
+ #[serde(default)] pub offer_vp8: bool,
+ #[serde(default)] pub offer_vp9: bool,
+ #[serde(default)] pub offer_av1: bool,
+ #[serde(default)] pub enable_rkmpp: bool,
+ #[serde(default)] pub enable_rkrga: bool,
+ #[serde(default)] pub use_svtav1: bool,
+ #[serde(default)] pub use_rav1e: bool,
+ pub svtav1_preset: Option<u8>, // 0..=13, high is fast
+ pub rav1e_preset: Option<u8>, // 0..=10
+ pub aom_preset: Option<u8>, // 0..=8, high is fast
+ pub x264_preset: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Default)]
diff --git a/common/src/stream.rs b/common/src/stream.rs
index 79cb380..81dd298 100644
--- a/common/src/stream.rs
+++ b/common/src/stream.rs
@@ -82,7 +82,7 @@ pub struct StreamTrackInfo {
pub formats: Vec<StreamFormatInfo>,
}
-#[derive(Debug, Copy, Clone, Deserialize, Serialize)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TrackKind {
Video,
diff --git a/remuxer/src/fragment.rs b/remuxer/src/fragment.rs
index 439c700..45a671f 100644
--- a/remuxer/src/fragment.rs
+++ b/remuxer/src/fragment.rs
@@ -20,7 +20,7 @@ use std::{
path::Path,
};
-const FRAGMENT_LENGTH: f64 = 2.;
+const FRAGMENT_LENGTH: f64 = 4.;
pub fn fragment_index(path: &Path, track: u64) -> Result<Vec<Range<f64>>> {
let meta = matroska_metadata(path)?;
diff --git a/remuxer/src/matroska_to_mpeg4.rs b/remuxer/src/matroska_to_mpeg4.rs
index e8268e7..ab7d5ab 100644
--- a/remuxer/src/matroska_to_mpeg4.rs
+++ b/remuxer/src/matroska_to_mpeg4.rs
@@ -17,7 +17,7 @@ pub fn matroska_to_mpeg4(
) -> Result<()> {
let path = format!("/tmp/jellything-tc-hack-{:016x}", random::<u64>());
let args = format!(
- "-hide_banner -loglevel warning -f matroska -i pipe:0 -copyts -c copy -f mp4 -movflags frag_keyframe+empty_moov {path}"
+ "-hide_banner -loglevel warning -f matroska -i pipe:0 -c copy -f mp4 -movflags frag_keyframe+empty_moov {path}"
);
let mut child = Command::new("ffmpeg")
.args(args.split(" "))
diff --git a/remuxer/src/metadata.rs b/remuxer/src/metadata.rs
index 4a496fe..c2931f4 100644
--- a/remuxer/src/metadata.rs
+++ b/remuxer/src/metadata.rs
@@ -23,6 +23,8 @@ use std::{
sync::Arc,
};
+pub use ebml_struct::matroska::TrackEntry as MatroskaTrackEntry;
+
#[derive(Debug, Encode, Decode, Clone)]
pub struct MatroskaMetadata {
pub info: Option<Info>,
diff --git a/stream/src/fragment.rs b/stream/src/fragment.rs
index 0652df2..9e8c3bd 100644
--- a/stream/src/fragment.rs
+++ b/stream/src/fragment.rs
@@ -40,6 +40,14 @@ pub async fn fragment_stream(
.formats
.get(format_num)
.ok_or(anyhow!("format not found"))?;
+ let orig_track = iinfo.metadata[file_index]
+ .tracks
+ .as_ref()
+ .unwrap()
+ .entries
+ .iter()
+ .find(|t| t.track_number == track_num)
+ .unwrap();
if format.remux {
match container {
@@ -81,6 +89,7 @@ pub async fn fragment_stream(
} else {
let location = transcode(
track.kind,
+ orig_track,
format,
&format!("{path:?} {track_num} {index}"),
move |b| {
diff --git a/stream/src/stream_info.rs b/stream/src/stream_info.rs
index 3a33b46..1005edc 100644
--- a/stream/src/stream_info.rs
+++ b/stream/src/stream_info.rs
@@ -34,7 +34,7 @@ async fn async_get_track_sizes(path: PathBuf) -> Result<BTreeMap<u64, usize>> {
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)>,
}
@@ -76,7 +76,7 @@ pub(crate) async fn stream_info(info: Arc<SMediaInfo>) -> Result<(InternalStream
};
Ok((
InternalStreamInfo {
- _metadata: metadata_arr,
+ metadata: metadata_arr,
paths,
track_to_file,
},
@@ -119,11 +119,11 @@ fn stream_formats(t: &TrackEntry, remux_bitrate: f64) -> Vec<StreamFormatInfo> {
// most codecs use chroma subsampling that requires even dims
let h = ((w * sh) / sw) & !1; // clear last bit to ensure even height.
for (cid, enable) in [
- ("V_AV1", CONF.encoders.av1.is_some()),
- ("V_VP8", CONF.encoders.vp8.is_some()),
- ("V_VP9", CONF.encoders.vp9.is_some()),
- ("V_MPEG4/ISO/AVC", CONF.encoders.avc.is_some()),
- ("V_MPEGH/ISO/HEVC", CONF.encoders.hevc.is_some()),
+ ("V_AV1", CONF.transcoder.offer_av1),
+ ("V_VP8", CONF.transcoder.offer_vp8),
+ ("V_VP9", CONF.transcoder.offer_vp9),
+ ("V_MPEG4/ISO/AVC", CONF.transcoder.offer_avc),
+ ("V_MPEGH/ISO/HEVC", CONF.transcoder.offer_hevc),
] {
if enable {
formats.push(StreamFormatInfo {
diff --git a/transcoder/src/fragment.rs b/transcoder/src/fragment.rs
index 7c5cadd..027e80f 100644
--- a/transcoder/src/fragment.rs
+++ b/transcoder/src/fragment.rs
@@ -4,12 +4,18 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use crate::LOCAL_VIDEO_TRANSCODING_TASKS;
+use anyhow::Result;
use jellybase::{
cache::{async_cache_file, CachePath},
- common::stream::{StreamFormatInfo, TrackKind},
+ common::{
+ config::TranscoderConfig,
+ stream::{StreamFormatInfo, TrackKind},
+ },
CONF,
};
-use log::{debug, info};
+use jellyremuxer::metadata::MatroskaTrackEntry;
+use log::info;
+use std::fmt::Write;
use std::process::Stdio;
use tokio::{
io::copy,
@@ -21,62 +27,16 @@ use tokio::{
pub async fn transcode(
kind: TrackKind,
+ orig_metadata: &MatroskaTrackEntry,
format: &StreamFormatInfo,
input_key: &str,
input: impl FnOnce(ChildStdin),
) -> anyhow::Result<CachePath> {
- let template = match format.codec.as_str() {
- "V_MPEG4/ISO/AVC" => CONF.encoders.avc.as_ref(),
- "V_MPEGH/ISO/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 %a".to_owned());
-
- let filter = match kind {
- TrackKind::Video => "-vf scale=%w:%h",
- TrackKind::Audio => "",
- TrackKind::Subtitle => "",
- };
- let typechar = match kind {
- TrackKind::Video => "v",
- TrackKind::Audio => "a",
- TrackKind::Subtitle => "s",
- };
- let fallback_encoder = match format.codec.as_str() {
- "A_OPUS" => "libopus",
- "V_VP8" => "libvpx",
- "V_VP9" => "libvpx-vp9",
- "V_AV1" => "libaom", // svtav1 is x86 only :(
- "V_MPEG4/ISO/AVC" => "libx264",
- "V_MPEGH/ISO/HEVC" => "libx265",
- _ => "",
- };
-
- let args = template
- .replace("%a", "-hide_banner %i %f %e %o")
- .replace("%i", "-f matroska -i pipe:0 -copyts")
- .replace("%o", "-f matroska pipe:1")
- .replace("%f", &filter)
- .replace("%w", &format.width.unwrap_or_default().to_string())
- .replace("%h", &format.height.unwrap_or_default().to_string())
- .replace("%e", "-c:%t %c -b:%t %r")
- .replace("%t", typechar)
- .replace("%c", fallback_encoder)
- .replace("%r", &(format.bitrate as i64).to_string())
- .replace(" ", " ");
-
- async_cache_file("frag-tc", (input_key, &args), async |mut output| {
+ let command = transcode_command(kind, orig_metadata, format, &CONF.transcoder).unwrap();
+ async_cache_file("frag-tc", (input_key, &command), async |mut output| {
let _permit = LOCAL_VIDEO_TRANSCODING_TASKS.acquire().await?;
- debug!("transcoding fragment with {format:?}");
-
- info!("encoding with {:?}", args);
-
- let mut args = args.split(" ");
+ info!("encoding with {command:?}");
+ let mut args = command.split(" ");
let mut proc = Command::new(args.next().unwrap())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
@@ -95,3 +55,77 @@ pub async fn transcode(
})
.await
}
+
+fn transcode_command(
+ kind: TrackKind,
+ orig_metadata: &MatroskaTrackEntry,
+ format: &StreamFormatInfo,
+ config: &TranscoderConfig,
+) -> Result<String> {
+ let br = format.bitrate as u64;
+ let w = format.width.unwrap_or(0);
+ let h = format.height.unwrap_or(0);
+ let mut o = String::new();
+ write!(o, "ffmpeg -hide_banner ")?;
+
+ if kind == TrackKind::Video {
+ if config.enable_rkmpp {
+ write!(o, "-hwaccel rkmpp -hwaccel_output_format drm_prime ")?;
+ }
+ if config.enable_rkrga {
+ write!(o, "-afbc rga ")?;
+ }
+
+ write!(o, "-f matroska -i pipe:0 -copyts ")?;
+
+ if config.enable_rkrga {
+ write!(o, "-vf scale_rkrga=w={w}:h={h}:format=nv12:afbc=1 ")?;
+ } else {
+ write!(o, "-vf scale={w}:{h} ")?;
+ }
+
+ match format.codec.as_str() {
+ "V_MPEG4/ISO/AVC" if config.enable_rkmpp => {
+ write!(o, "-c:v h264_rkmpp -profile:v high -b:v {br} ")?
+ }
+ "V_MPEGH/ISO/HEVC" if config.enable_rkmpp => {
+ write!(o, "-c:v h265_rkmpp -profile:v high -b:v {br} ")?
+ }
+ "A_AV1" if config.use_svtav1 => {
+ let p = config.svtav1_preset.unwrap_or(8);
+ write!(o, "-c:v libsvtav1 -preset {p} -b:v {br} ")?
+ }
+ "A_AV1" if config.use_rav1e => {
+ let p = config.rav1e_preset.unwrap_or(8);
+ write!(o, "-c:v librav1e -speed {p} -b:v {br} ")?
+ }
+ "V_MPEG4/ISO/AVC" => {
+ let p = config.x264_preset.clone().unwrap_or("fast".to_string());
+ write!(o, "-c:v libx264 -preset {p} -b:v {br} ")?
+ }
+ "V_MPEGH/ISO/HEVC" => write!(o, "-c:v libx265 -b:v {br} ")?,
+ "V_VP8" => write!(o, "-c:v libvpx -b:v {br} ")?,
+ "V_VP9" => write!(o, "-c:v libvpx-vp9 -b:v {br} ")?,
+ "V_AV1" => {
+ let p = config.aom_preset.unwrap_or(1);
+ write!(o, "-c:v libaom -cpu-used {p} -row-mt 1 -b:v {br} ")?
+ }
+ _ => todo!(),
+ };
+ } else if kind == TrackKind::Audio {
+ write!(o, "-f matroska -i pipe:0 -copyts ")?;
+ if format.codec == "A_OPUS" && orig_metadata.audio.as_ref().unwrap().channels > 2 {
+ write!(o, "-ac 2 ")?;
+ }
+ match format.codec.as_str() {
+ "A_OPUS" => write!(o, "-c:a libopus -b:a {br} ")?,
+ _ => todo!(),
+ }
+ } else {
+ write!(o, "-f matroska -i pipe:0 -copyts ")?;
+ todo!()
+ }
+
+ write!(o, "-f matroska pipe:1")?;
+ Ok(o)
+}