Skip to content

Instantly share code, notes, and snippets.

@will-hart
Last active July 7, 2021 10:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save will-hart/768fd6349a790654a95f2c23e9e1e70e to your computer and use it in GitHub Desktop.
Save will-hart/768fd6349a790654a95f2c23e9e1e70e to your computer and use it in GitHub Desktop.
FFMPEG based video clipper
Public Domain
pub struct VideoClipper;
impl VideoClipper {
pub fn generate_highlights(project: &Project) -> crate::Result<()> {
let local_path = project.video.local_path.clone();
// ...
// snip!
// the snipped bit just builds up a bunch of tags which contain start/end times
// ...
// write out individual clips to temporary folder
create_dir_all("temp")?;
create_dir_all("output")?;
let mut file_names: Vec<String> = Vec::new();
// generate a clip for each range tag, and use video filters to burn in
// the caption
range_tags.iter().enumerate().for_each(|(idx, tag)| {
let path = format!("temp/{}.mkv", idx + 1);
file_names.push(path.clone());
let caption = "blah a caption";
let mut cmd = VideoClipper::get_os_cmd("ffmpeg".into());
cmd.args(&[
"-ss",
&tag.start_time.to_string()[..], // this is an f32 in seconds
"-t",
&(tag.end_time.unwrap() - tag.start_time).to_string()[..], // the tag duration in seconds, f32
"-i",
&local_path[..], // the path to the video
"-vf",
&format!(
"[in]drawtext=fontfile=OpenSans.ttf:text='{}':fontcolor=white:fontsize=48:x=10:y=10:box=1:boxcolor=black@0.5:boxborderw=5[out]",
caption
)[..],
"-y",
&path[..]
]);
let mut handle = cmd.spawn().expect("Should spawn a clipping handle");
handle.wait().expect("Should exit eventually");
});
// write list of files
let mut write_file = std::fs::File::create("file_list.txt")?;
write_file.write_all(
file_names
.iter()
.map(|f| format!("file '{}'", f))
.collect::<Vec<_>>()
.join("\n")
.as_bytes(),
)?;
// concat the temp video files
let mut cmd = VideoClipper::get_os_cmd("ffmpeg".into());
cmd.args(&[
"-f",
"concat",
"-i",
"file_list.txt",
"-c",
"copy",
"-y",
"output/highlights.mkv",
]);
let mut handle = cmd.spawn().expect("Cannot concat videos");
handle.wait()?;
// tidy up
file_names.iter().for_each(|path| {
let mut handle = VideoClipper::get_os_cmd(if cfg!(target_os = "windows") { "del" } else { "rm" }.into())
.arg(if cfg!(target_os = "windows") { path.replace("/", "\\") } else { path.clone() })
.spawn()
.expect("Should delete file");
handle.wait().expect("Should delete file");
});
let mut handle = VideoClipper::get_os_cmd(if cfg!(target_os = "windows") { "del" } else { "rm" }.into())
.arg("file_list.txt")
.spawn()
.expect("Should delete file");
handle.wait().expect("Should delete file");
Ok(())
}
fn get_os_cmd(command: String) -> Command {
let mut cmd = if cfg!(target_os = "windows") {
let mut cmd = Command::new("cmd");
cmd.arg("/C");
cmd.arg(command);
cmd
} else {
let mut cmd = Command::new("cmd");
cmd.arg("-c");
cmd.arg(command);
cmd
};
cmd.stdout(Stdio::inherit());
cmd
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment