support 50fps videos; enable +faststart

This commit is contained in:
Dominic 2023-11-15 15:44:58 +01:00
parent 0865076849
commit 2882fb286a
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
3 changed files with 65 additions and 26 deletions

View file

@ -9,8 +9,8 @@ end = "12"
fast = [["5", "7"], ["9", "11"]]
[source.metadata]
source_duration = "12.533330"
source_fps = "25/1"
source_duration = "12.53333"
source_fps = "50/1"
source_tbn = "1/12800"
source_res = "FullHD"
source_sample_rate = 48000

View file

@ -50,25 +50,60 @@ impl FfmpegInput {
}
}
pub(crate) struct FfmpegOutput {
pub(crate) fps: Option<Rational>,
pub(crate) duration: Option<Time>,
pub(crate) time_base: Option<Rational>,
pub(crate) fps_mode_vfr: bool,
pub(crate) path: PathBuf
}
impl FfmpegOutput {
pub(crate) fn new(path: PathBuf) -> Self {
Self {
fps: None,
duration: None,
time_base: None,
fps_mode_vfr: false,
path
}
}
fn append_to_cmd(self, cmd: &mut Command) {
if let Some(fps) = self.fps {
cmd.arg("-r").arg(fps.to_string());
}
if let Some(duration) = self.duration {
cmd.arg("-t").arg(format_time(duration));
}
if let Some(time_base) = self.time_base {
cmd.arg("-enc_time_base").arg(time_base.to_string());
}
if self.fps_mode_vfr {
cmd.arg("-fps_mode").arg("vfr");
}
cmd.arg("-movflags").arg("+faststart");
cmd.arg(self.path);
}
}
pub(crate) struct Ffmpeg {
inputs: Vec<FfmpegInput>,
filters: Vec<Filter>,
filters_output: Cow<'static, str>,
loudnorm: bool,
duration: Option<Time>,
output: PathBuf,
output: FfmpegOutput,
filter_idx: usize
}
impl Ffmpeg {
pub fn new(output: PathBuf) -> Self {
pub fn new(output: FfmpegOutput) -> Self {
Self {
inputs: Vec::new(),
filters: Vec::new(),
filters_output: "0".into(),
loudnorm: false,
duration: None,
output,
filter_idx: 0
@ -100,11 +135,6 @@ impl Ffmpeg {
self
}
pub fn set_duration(&mut self, duration: Time) -> &mut Self {
self.duration = Some(duration);
self
}
pub fn run(mut self) -> anyhow::Result<()> {
let mut cmd = cmd();
cmd.arg("ffmpeg").arg("-hide_banner").arg("-y");
@ -157,13 +187,14 @@ impl Ffmpeg {
}
// append encoding options
const QUALITY: &str = "24";
if vaapi {
cmd.arg("-c:v").arg("h264_vaapi");
cmd.arg("-rc_mode").arg("CQP");
cmd.arg("-global_quality").arg("24");
cmd.arg("-global_quality").arg(QUALITY);
} else if venc {
cmd.arg("-c:v").arg("libx264");
cmd.arg("-crf").arg("22");
cmd.arg("-crf").arg(QUALITY);
} else {
cmd.arg("-c:v").arg("copy");
}
@ -174,11 +205,7 @@ impl Ffmpeg {
cmd.arg("-c:a").arg("copy");
}
if let Some(duration) = self.duration {
cmd.arg("-t").arg(format_time(duration));
}
cmd.arg("-movflags").arg("+faststart");
cmd.arg(&self.output);
self.output.append_to_cmd(&mut cmd);
let status = cmd.status()?;
if status.success() {

View file

@ -3,7 +3,7 @@
pub mod ffmpeg;
mod filter;
use self::filter::Filter;
use self::{ffmpeg::FfmpegOutput, filter::Filter};
use crate::{
iotro::{intro, outro},
render::ffmpeg::{Ffmpeg, FfmpegInput},
@ -117,10 +117,22 @@ pub(crate) struct Renderer<'a> {
target: PathBuf
}
fn svg2mp4(svg: PathBuf, mp4: PathBuf, duration: Time) -> anyhow::Result<()> {
let mut ffmpeg = Ffmpeg::new(mp4);
fn svg2mp4(
meta: &ProjectSourceMetadata,
svg: PathBuf,
mp4: 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,
path: mp4
});
ffmpeg.add_input(FfmpegInput {
loop_input: true,
fps: Some(meta.source_fps),
..FfmpegInput::new(svg)
});
ffmpeg.add_filter(Filter::GenerateSilence {
@ -128,7 +140,6 @@ fn svg2mp4(svg: PathBuf, mp4: PathBuf, duration: Time) -> anyhow::Result<()> {
output: "out".into()
});
ffmpeg.set_filter_output("out");
ffmpeg.set_duration(duration);
ffmpeg.run()
}
@ -185,7 +196,7 @@ impl<'a> Renderer<'a> {
let source_sample_rate =
ffprobe_audio("stream=sample_rate", &recording_txt)?.parse()?;
let recording_mp4 = self.recording_mp4();
let mut ffmpeg = Ffmpeg::new(recording_mp4.clone());
let mut ffmpeg = Ffmpeg::new(FfmpegOutput::new(recording_mp4.clone()));
ffmpeg.add_input(FfmpegInput {
concat: true,
..FfmpegInput::new(recording_txt)
@ -210,6 +221,7 @@ impl<'a> Renderer<'a> {
source_res,
source_sample_rate
});
let metadata = project.source.metadata.as_ref().unwrap();
println!("\x1B[1m ==> Preparing assets ...\x1B[0m");
@ -222,7 +234,7 @@ impl<'a> Renderer<'a> {
.into_bytes()
)?;
let intro_mp4 = self.target.join("intro.mp4");
svg2mp4(intro_svg, intro_mp4, INTRO_LEN)?;
svg2mp4(metadata, intro_svg, intro_mp4, INTRO_LEN)?;
// render outro to svg then mp4
let outro_svg = self.target.join("outro.svg");
@ -231,7 +243,7 @@ impl<'a> Renderer<'a> {
outro(source_res).to_string_pretty().into_bytes()
)?;
let outro_mp4 = self.target.join("outro.mp4");
svg2mp4(outro_svg, outro_mp4, OUTRO_LEN)?;
svg2mp4(metadata, outro_svg, outro_mp4, OUTRO_LEN)?;
// copy logo then render to png
let logo_svg = self.target.join("logo.svg");
@ -272,7 +284,7 @@ impl<'a> Renderer<'a> {
pub(crate) fn render(&self, project: &mut Project) -> anyhow::Result<PathBuf> {
let output = self.video_mp4(project);
let mut ffmpeg = Ffmpeg::new(output.clone());
let mut ffmpeg = Ffmpeg::new(FfmpegOutput::new(output.clone()));
// add all of our inputs
let intro = ffmpeg.add_input(FfmpegInput::new(self.target.join("intro.mp4")));