diff --git a/src/render/ffmpeg.rs b/src/render/ffmpeg.rs index 05dbf4b..bf3836d 100644 --- a/src/render/ffmpeg.rs +++ b/src/render/ffmpeg.rs @@ -1,12 +1,17 @@ -use super::{cmd, filter::Filter}; +use super::{filter::Filter, new_cmd}; use crate::{ render::filter::channel, time::{format_time, Time} }; use anyhow::bail; -use camino::Utf8PathBuf as PathBuf; +use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf}; use rational::Rational; -use std::{borrow::Cow, fmt::Write as _, process::Command}; +use std::{ + borrow::Cow, + fmt::Write as _, + io::Write as _, + process::{Command, Stdio} +}; pub(crate) struct FfmpegInput { pub(crate) concat: bool, @@ -29,7 +34,7 @@ impl FfmpegInput { } } - fn append_to_cmd(self, cmd: &mut Command) { + fn append_to_cmd(&self, cmd: &mut Command) { if self.concat { cmd.arg("-f").arg("concat").arg("-safe").arg("0"); } @@ -45,7 +50,7 @@ impl FfmpegInput { if let Some(duration) = self.duration { cmd.arg("-t").arg(format_time(duration)); } - cmd.arg("-i").arg(self.path); + cmd.arg("-i").arg(&self.path); } } @@ -104,8 +109,8 @@ impl Ffmpeg { self } - pub fn run(mut self) -> anyhow::Result<()> { - let mut cmd = cmd(); + pub fn run(mut self, filter_graph: Option<&Path>) -> anyhow::Result<()> { + let mut cmd = new_cmd(); cmd.arg("ffmpeg").arg("-hide_banner").arg("-y"); // determine whether the video need to be re-encoded @@ -120,7 +125,7 @@ impl Ffmpeg { } // append all the inputs - for i in self.inputs { + for i in &self.inputs { i.append_to_cmd(&mut cmd); } @@ -149,6 +154,37 @@ impl Ffmpeg { } else { write!(complex, "{}null[v]", channel('v', &self.filters_output)); } + + if let Some(filter_graph) = filter_graph { + let mut graph2dot = new_cmd(); + graph2dot.stdin(Stdio::piped()); + graph2dot.stdout(Stdio::piped()); + graph2dot.arg("graph2dot"); + let mut graph2dot = graph2dot.spawn()?; + + let mut dot = new_cmd(); + dot.stdin(Stdio::from(graph2dot.stdout.take().unwrap())); + dot.arg("dot").arg("-Tpng").arg("-o").arg(filter_graph); + let mut dot = dot.spawn()?; + + let mut stdin = graph2dot.stdin.take().unwrap(); + for (i, _) in self.inputs.iter().enumerate() { + writeln!(stdin, "nullsrc[{i}];[{i}]nullsink;"); + } + writeln!(stdin, "{complex};")?; + writeln!(stdin, "[v]nullsink"); + drop(stdin); + + let status = graph2dot.wait()?; + if !status.success() { + bail!("graph2dot failed with status code {:?}", status.code()); + } + let status = dot.wait()?; + if !status.success() { + bail!("graphviz dot failed with status code {:?}", status.code()); + } + } + cmd.arg("-filter_complex").arg(complex); cmd.arg("-map").arg("[v]"); cmd.arg("-map").arg(channel('a', &self.filters_output)); diff --git a/src/render/mod.rs b/src/render/mod.rs index 1c963e0..b47b689 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -38,7 +38,7 @@ const FF_MULTIPLIER: usize = 8; const FF_LOGO_SIZE: usize = 128; const LOGO_SIZE: usize = 96; -fn cmd() -> Command { +fn new_cmd() -> Command { let mut cmd = Command::new("busybox"); cmd.arg("ash") .arg("-exuo") @@ -49,7 +49,7 @@ fn cmd() -> Command { } fn ffprobe() -> Command { - let mut cmd = cmd(); + let mut cmd = new_cmd(); cmd.arg("ffprobe") .arg("-v") .arg("error") @@ -119,11 +119,11 @@ fn svg2mp4(svg: PathBuf, mp4: PathBuf, duration: Time) -> anyhow::Result<()> { }); ffmpeg.set_filter_output("out"); ffmpeg.set_duration(duration); - ffmpeg.run() + ffmpeg.run(None) } fn svg2png(svg: &Path, png: &Path, size: usize) -> anyhow::Result<()> { - let mut cmd = cmd(); + let mut cmd = new_cmd(); let size = size.to_string(); cmd.arg("inkscape") .arg("-w") @@ -181,7 +181,7 @@ impl<'a> Renderer<'a> { ..FfmpegInput::new(recording_txt) }); ffmpeg.enable_loudnorm(); - ffmpeg.run()?; + ffmpeg.run(None)?; let width = ffprobe_video("stream=width", &recording_mp4)?.parse()?; let height = ffprobe_video("stream=height", &recording_mp4)?.parse()?; @@ -425,7 +425,7 @@ impl<'a> Renderer<'a> { // we're done :) ffmpeg.set_filter_output(overlay); - ffmpeg.run()?; + ffmpeg.run(Some(&self.target.join("filter_graph.png")))?; Ok(output) }