Skip to content
Snippets Groups Projects
Unverified Commit f4adda91 authored by Dominic Meiser's avatar Dominic Meiser
Browse files

use AV1 for 1440p and higher

parent c288f55e
No related branches found
No related tags found
No related merge requests found
......@@ -7,7 +7,7 @@ mod render;
mod time;
use crate::{
render::Renderer,
render::{ffmpeg::FfmpegOutputFormat, Renderer},
time::{parse_date, parse_time, Date, Time}
};
use camino::Utf8PathBuf as PathBuf;
......@@ -46,7 +46,7 @@ struct Args {
}
macro_rules! resolutions {
($($res:ident: $width:literal x $height:literal at $bitrate:literal),+) => {
($($res:ident: $width:literal x $height:literal at $bitrate:literal in $format:ident),+) => {
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
enum Resolution {
......@@ -84,6 +84,12 @@ macro_rules! resolutions {
$(Self::$res => $bitrate),+
}
}
fn format(self) -> FfmpegOutputFormat {
match self {
$(Self::$res => FfmpegOutputFormat::$format),+
}
}
}
impl FromStr for Resolution {
......@@ -100,12 +106,12 @@ macro_rules! resolutions {
}
resolutions! {
nHD: 640 x 360 at 500_000,
HD: 1280 x 720 at 1_000_000,
FullHD: 1920 x 1080 at 2_000_000,
WQHD: 2560 x 1440 at 3_000_000,
nHD: 640 x 360 at 500_000 in AvcAac,
HD: 1280 x 720 at 1_000_000 in AvcAac,
FullHD: 1920 x 1080 at 2_000_000 in AvcAac,
WQHD: 2560 x 1440 at 3_000_000 in Av1Opus,
// TODO qsx muss mal sagen wieviel bitrate für 4k
UHD: 3840 x 2160 at 4_000_000
UHD: 3840 x 2160 at 4_000_000 in Av1Opus
}
#[derive(Deserialize, Serialize)]
......@@ -234,7 +240,7 @@ fn main() {
println!("{}", toml::to_string(&project).unwrap());
let renderer = Renderer::new(&directory, &project).unwrap();
let recording = renderer.recording_mp4();
let recording = renderer.recording_mkv();
// preprocess the video
if !project.progress.preprocessed {
......@@ -282,7 +288,7 @@ fn main() {
// render the video
let mut videos = Vec::new();
videos.push(if project.progress.rendered {
renderer.video_mp4(&project)
renderer.video_file_output()
} else {
let video = renderer.render(&mut project).unwrap();
project.progress.rendered = true;
......@@ -302,7 +308,7 @@ fn main() {
continue;
}
if !project.progress.transcoded.contains(&res) {
videos.push(renderer.rescale(res, &project).unwrap());
videos.push(renderer.rescale(res).unwrap());
project.progress.transcoded.insert(res);
println!("{}", toml::to_string(&project).unwrap());
......
......@@ -41,7 +41,9 @@ impl FfmpegInput {
cmd.arg("-r").arg(fps.to_string());
}
if let Some(start) = self.start {
cmd.arg("-seek_streams_individually").arg("false");
if self.path.ends_with(".mp4") {
cmd.arg("-seek_streams_individualy").arg("false");
}
cmd.arg("-ss").arg(format_time(start));
}
if let Some(duration) = self.duration {
......@@ -51,18 +53,35 @@ impl FfmpegInput {
}
}
pub(crate) enum FfmpegOutputFormat {
/// AV1 / FLAC
Av1Flac,
/// AV1 / OPUS
Av1Opus,
/// AVC (H.264) / AAC
AvcAac
}
pub(crate) struct FfmpegOutput {
pub(crate) format: FfmpegOutputFormat,
pub(crate) audio_bitrate: Option<u64>,
pub(crate) video_bitrate: Option<u64>,
pub(crate) fps: Option<Rational>,
pub(crate) duration: Option<Time>,
pub(crate) time_base: Option<Rational>,
pub(crate) fps_mode_vfr: bool,
pub(crate) faststart: bool,
pub(crate) path: PathBuf
}
impl FfmpegOutput {
pub(crate) fn new(path: PathBuf) -> Self {
pub(crate) fn new(format: FfmpegOutputFormat, path: PathBuf) -> Self {
Self {
format,
audio_bitrate: None,
video_bitrate: None,
fps: None,
duration: None,
time_base: None,
......@@ -77,7 +96,42 @@ impl FfmpegOutput {
self
}
fn append_to_cmd(self, cmd: &mut Command) {
fn append_to_cmd(self, cmd: &mut Command, venc: bool, _aenc: bool, vaapi: bool) {
// select codec and bitrate
const QUALITY: &str = "22";
if venc {
let mut vcodec: String = match self.format {
FfmpegOutputFormat::Av1Flac | FfmpegOutputFormat::Av1Opus => "av1".into(),
FfmpegOutputFormat::AvcAac => "h264".into()
};
if vaapi {
vcodec = format!("{vcodec}_vaapi");
}
cmd.arg("-c:v").arg(vcodec);
if let Some(bv) = self.video_bitrate {
cmd.arg("-b:v").arg(bv.to_string());
} else if vaapi {
cmd.arg("-rc_mode").arg("CQP");
cmd.arg("-global_quality").arg(QUALITY);
} else {
cmd.arg("-crf").arg(QUALITY);
}
} else {
cmd.arg("-c:v").arg("copy");
}
cmd.arg("-c:a").arg(match self.format {
FfmpegOutputFormat::Av1Flac => "flac",
FfmpegOutputFormat::Av1Opus => "libopus",
FfmpegOutputFormat::AvcAac => "aac"
});
if let Some(ba) = self.audio_bitrate {
cmd.arg("-b:a").arg(ba.to_string());
} else {
cmd.arg("-b:a").arg("128k");
}
// other output options
if let Some(fps) = self.fps {
cmd.arg("-r").arg(fps.to_string());
}
......@@ -110,7 +164,6 @@ enum FfmpegFilter {
pub(crate) struct Ffmpeg {
inputs: Vec<FfmpegInput>,
filter: FfmpegFilter,
video_bitrate: Option<u64>,
output: FfmpegOutput,
filter_idx: usize
......@@ -121,7 +174,6 @@ impl Ffmpeg {
Self {
inputs: Vec::new(),
filter: FfmpegFilter::None,
video_bitrate: None,
output,
filter_idx: 0
......@@ -182,11 +234,6 @@ impl Ffmpeg {
self
}
pub fn set_video_bitrate(&mut self, bitrate: u64) -> &mut Self {
self.video_bitrate = Some(bitrate);
self
}
pub fn run(mut self) -> anyhow::Result<()> {
let mut cmd = cmd();
cmd.arg("ffmpeg").arg("-hide_banner").arg("-y");
......@@ -256,35 +303,7 @@ impl Ffmpeg {
}
}
// append encoding options
const QUALITY: &str = "22";
if venc {
if vaapi {
cmd.arg("-c:v").arg("h264_vaapi");
if self.video_bitrate.is_none() {
cmd.arg("-rc_mode").arg("CQP");
cmd.arg("-global_quality").arg(QUALITY);
}
} else {
cmd.arg("-c:v").arg("libx264");
if self.video_bitrate.is_none() {
cmd.arg("-crf").arg(QUALITY);
}
}
if self.video_bitrate.is_some() {
cmd.arg("-b:v").arg(self.video_bitrate.unwrap().to_string());
}
} else {
cmd.arg("-c:v").arg("copy");
}
if aenc {
cmd.arg("-c:a").arg("aac");
cmd.arg("-b:a").arg("128000");
} else {
cmd.arg("-c:a").arg("copy");
}
self.output.append_to_cmd(&mut cmd);
self.output.append_to_cmd(&mut cmd, venc, aenc, vaapi);
let status = cmd.status()?;
if status.success() {
......
pub mod ffmpeg;
mod filter;
use self::{ffmpeg::FfmpegOutput, filter::Filter};
use self::{
ffmpeg::{FfmpegOutput, FfmpegOutputFormat},
filter::Filter
};
use crate::{
iotro::{intro, outro},
render::ffmpeg::{Ffmpeg, FfmpegInput},
......@@ -114,19 +117,17 @@ pub(crate) struct Renderer<'a> {
target: PathBuf
}
fn svg2mp4(
fn svg2mkv(
meta: &ProjectSourceMetadata,
svg: PathBuf,
mp4: PathBuf,
mkv: PathBuf,
duration: Time
) -> anyhow::Result<()> {
let mut ffmpeg = Ffmpeg::new(FfmpegOutput {
fps: None,
duration: Some(duration),
time_base: Some(meta.source_tbn),
fps_mode_vfr: true,
faststart: false,
path: mp4
..FfmpegOutput::new(FfmpegOutputFormat::Av1Flac, mkv)
});
ffmpeg.add_input(FfmpegInput {
loop_input: true,
......@@ -176,8 +177,16 @@ impl<'a> Renderer<'a> {
})
}
pub(crate) fn recording_mp4(&self) -> PathBuf {
self.target.join("recording.mp4")
pub(crate) fn recording_mkv(&self) -> PathBuf {
self.target.join("recording.mkv")
}
fn intro_mkv(&self) -> PathBuf {
self.target.join("intro.mkv")
}
fn outro_mkv(&self) -> PathBuf {
self.target.join("outro.mkv")
}
pub(crate) fn preprocess(&self, project: &mut Project) -> anyhow::Result<()> {
......@@ -193,8 +202,11 @@ impl<'a> Renderer<'a> {
println!("\x1B[1m ==> Concatenating Video and Normalising Audio ...\x1B[0m");
let source_sample_rate =
ffprobe_audio("stream=sample_rate", &recording_txt)?.parse()?;
let recording_mp4 = self.recording_mp4();
let mut ffmpeg = Ffmpeg::new(FfmpegOutput::new(recording_mp4.clone()));
let recording_mkv = self.recording_mkv();
let mut ffmpeg = Ffmpeg::new(FfmpegOutput::new(
FfmpegOutputFormat::Av1Flac,
recording_mkv.clone()
));
ffmpeg.add_input(FfmpegInput {
concat: true,
..FfmpegInput::new(recording_txt)
......@@ -202,8 +214,8 @@ impl<'a> Renderer<'a> {
ffmpeg.enable_loudnorm();
ffmpeg.run()?;
let width = ffprobe_video("stream=width", &recording_mp4)?.parse()?;
let height = ffprobe_video("stream=height", &recording_mp4)?.parse()?;
let width = ffprobe_video("stream=width", &recording_mkv)?.parse()?;
let height = ffprobe_video("stream=height", &recording_mkv)?.parse()?;
let source_res = match (width, height) {
(3840, 2160) => Resolution::UHD,
(2560, 1440) => Resolution::WQHD,
......@@ -213,9 +225,9 @@ impl<'a> Renderer<'a> {
(width, height) => bail!("Unknown resolution: {width}x{height}")
};
project.source.metadata = Some(ProjectSourceMetadata {
source_duration: ffprobe_video("format=duration", &recording_mp4)?.parse()?,
source_fps: ffprobe_video("stream=r_frame_rate", &recording_mp4)?.parse()?,
source_tbn: ffprobe_video("stream=time_base", &recording_mp4)?.parse()?,
source_duration: ffprobe_video("format=duration", &recording_mkv)?.parse()?,
source_fps: ffprobe_video("stream=r_frame_rate", &recording_mkv)?.parse()?,
source_tbn: ffprobe_video("stream=time_base", &recording_mkv)?.parse()?,
source_res,
source_sample_rate
});
......@@ -231,8 +243,8 @@ impl<'a> Renderer<'a> {
.to_string_pretty()
.into_bytes()
)?;
let intro_mp4 = self.target.join("intro.mp4");
svg2mp4(metadata, intro_svg, intro_mp4, INTRO_LEN)?;
let intro_mkv = self.intro_mkv();
svg2mkv(metadata, intro_svg, intro_mkv, INTRO_LEN)?;
// render outro to svg then mp4
let outro_svg = self.target.join("outro.svg");
......@@ -240,8 +252,8 @@ impl<'a> Renderer<'a> {
&outro_svg,
outro(source_res).to_string_pretty().into_bytes()
)?;
let outro_mp4 = self.target.join("outro.mp4");
svg2mp4(metadata, outro_svg, outro_mp4, OUTRO_LEN)?;
let outro_mkv = self.outro_mkv();
svg2mkv(metadata, outro_svg, outro_mkv, OUTRO_LEN)?;
// copy logo then render to png
let logo_svg = self.target.join("logo.svg");
......@@ -271,25 +283,35 @@ impl<'a> Renderer<'a> {
Ok(())
}
fn video_mp4_res(&self, res: Resolution) -> PathBuf {
/// Get the video file for a specific resolution, completely finished.
fn video_file_res(&self, res: Resolution) -> PathBuf {
let extension = match res.format() {
FfmpegOutputFormat::Av1Flac => "mkv",
FfmpegOutputFormat::Av1Opus => "webm",
FfmpegOutputFormat::AvcAac => "mp4"
};
self.target
.join(format!("{}-{}p.mp4", self.slug, res.height()))
.join(format!("{}-{}p.{extension}", self.slug, res.height()))
}
pub(crate) fn video_mp4(&self, project: &Project) -> PathBuf {
self.video_mp4_res(project.source.metadata.as_ref().unwrap().source_res)
/// Get the video file directly outputed to further transcode.
pub(crate) fn video_file_output(&self) -> PathBuf {
self.target.join(format!("{}.mkv", self.slug))
}
pub(crate) fn render(&self, project: &mut Project) -> anyhow::Result<PathBuf> {
let source_res = project.source.metadata.as_ref().unwrap().source_res;
let output = self.video_mp4(project);
let mut ffmpeg = Ffmpeg::new(FfmpegOutput::new(output.clone()));
let output = self.video_file_output();
let mut ffmpeg = Ffmpeg::new(FfmpegOutput {
video_bitrate: Some(source_res.bitrate() * 3),
..FfmpegOutput::new(FfmpegOutputFormat::Av1Flac, output.clone())
});
// add all of our inputs
let intro = ffmpeg.add_input(FfmpegInput::new(self.target.join("intro.mp4")));
let rec_file = self.target.join("recording.mp4");
let outro = ffmpeg.add_input(FfmpegInput::new(self.target.join("outro.mp4")));
let intro = ffmpeg.add_input(FfmpegInput::new(self.intro_mkv()));
let rec_file = self.recording_mkv();
let outro = ffmpeg.add_input(FfmpegInput::new(self.outro_mkv()));
let logo = ffmpeg.add_input(FfmpegInput::new(self.target.join("logo.png")));
let ff = ffmpeg.add_input(FfmpegInput::new(self.target.join("fastforward.png")));
......@@ -444,22 +466,22 @@ impl<'a> Renderer<'a> {
// we're done :)
ffmpeg.set_filter_output(overlay);
ffmpeg.set_video_bitrate(source_res.bitrate() * 3);
ffmpeg.run()?;
Ok(output)
}
pub fn rescale(&self, res: Resolution, project: &Project) -> anyhow::Result<PathBuf> {
let input = self.video_mp4(project);
let output = self.video_mp4_res(res);
pub fn rescale(&self, res: Resolution) -> anyhow::Result<PathBuf> {
let input = self.video_file_output();
let output = self.video_file_res(res);
println!("\x1B[1m ==> Rescaling to {}p\x1B[0m", res.height());
let mut ffmpeg =
Ffmpeg::new(FfmpegOutput::new(output.clone()).enable_faststart());
let mut ffmpeg = Ffmpeg::new(FfmpegOutput {
video_bitrate: Some(res.bitrate()),
..FfmpegOutput::new(res.format(), output.clone()).enable_faststart()
});
ffmpeg.add_input(FfmpegInput::new(input));
ffmpeg.rescale_video(res);
ffmpeg.set_video_bitrate(res.bitrate());
ffmpeg.run()?;
Ok(output)
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment