1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
/*
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 crate::{Config, CONF, LOCAL_VIDEO_TRANSCODING_TASKS};
use anyhow::Result;
use jellycache::{async_cache_file, CachePath};
use jellycommon::stream::{StreamFormatInfo, TrackKind};
use jellyremuxer::metadata::MatroskaTrackEntry;
use log::info;
use std::fmt::Write;
use std::process::Stdio;
use tokio::{
io::copy,
process::{ChildStdin, Command},
};
// TODO odd video resolutions can cause errors when transcoding to YUV42{0,2}
// TODO with an implementation that cant handle it (SVT-AV1 is such an impl).
pub async fn transcode(
kind: TrackKind,
orig_metadata: &MatroskaTrackEntry,
format: &StreamFormatInfo,
input_key: &str,
input: impl FnOnce(ChildStdin),
) -> anyhow::Result<CachePath> {
let command = transcode_command(kind, orig_metadata, format, &*CONF).unwrap();
async_cache_file("frag-tc", (input_key, &command), async |mut output| {
let _permit = LOCAL_VIDEO_TRANSCODING_TASKS.acquire().await?;
info!("encoding with {command:?}");
let mut args = command.split(" ");
let mut proc = Command::new(args.next().unwrap())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.args(args)
.spawn()?;
let stdin = proc.stdin.take().unwrap();
let mut stdout = proc.stdout.take().unwrap();
input(stdin);
copy(&mut stdout, &mut output).await?;
proc.wait().await.unwrap().exit_ok()?;
info!("done");
Ok(())
})
.await
}
fn transcode_command(
kind: TrackKind,
orig_metadata: &MatroskaTrackEntry,
format: &StreamFormatInfo,
config: &Config,
) -> 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)
}
|