support 50fps videos; enable +faststart
This commit is contained in:
parent
0865076849
commit
2882fb286a
3 changed files with 65 additions and 26 deletions
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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")));
|
||||
|
|
Loading…
Reference in a new issue