Last active
July 7, 2021 10:34
-
-
Save will-hart/768fd6349a790654a95f2c23e9e1e70e to your computer and use it in GitHub Desktop.
FFMPEG based video clipper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Public Domain |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:[email protected]: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