diff --git a/Cargo.lock b/Cargo.lock index 99739046ab..3b69872523 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2996,6 +2996,7 @@ dependencies = [ "serde_json", "sha2", "sqlx", + "subprocess", "tempdir", "time 0.2.26", "tokio 0.2.25", @@ -3012,6 +3013,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "subprocess" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334b801f9ca2529ba9605109f1d2835f15aff94cd6cfe5afe6ce8cf71e5fd3c4" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "subtle" version = "2.4.0" diff --git a/Cargo.toml b/Cargo.toml index 0897048b18..b303005d4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ ring = "0.17.0-alpha.10" #rust-s3 = { version = "0.27.0-rc4", features = ["with-async-std"], default-features = false } rust-s3 = { version = "0.26" } +subprocess = "0.2.6" tempdir = "0.3.7" # Web diff --git a/src/script_execution/mod.rs b/src/script_execution/mod.rs index 49f2e9d2cf..41465de59d 100644 --- a/src/script_execution/mod.rs +++ b/src/script_execution/mod.rs @@ -1 +1,2 @@ pub mod google_drive_download_command; +pub mod wav2lip_preprocess_command; diff --git a/src/script_execution/wav2lip_preprocess_command.rs b/src/script_execution/wav2lip_preprocess_command.rs new file mode 100644 index 0000000000..1ab44b1d9c --- /dev/null +++ b/src/script_execution/wav2lip_preprocess_command.rs @@ -0,0 +1,154 @@ +use crate::AnyhowResult; +use log::{info,warn}; +use std::process::{Command, Stdio}; +use subprocess::{Popen, PopenConfig, Redirection}; +use std::fs::OpenOptions; + +/// This command is used to preprocess the face detection frames from user-submitted video. +/// This should only ever need to run once. The frames can then be uploaded to Buckets and saved. +#[derive(Clone)] +pub struct Wav2LipPreprocessClient { + w2l_directory: String, + script_name: String, + checkpoint_path: String, + temp_directory: String, + audio_start_pad_millis: Option, + audio_end_pad_millis: Option, + end_bump_filename: Option, +} + +impl Wav2LipPreprocessClient { + pub fn new( + w2l_directory: &str, + script_name: &str, + checkpoint_path: &str, + temp_directory: &str, + audio_start_pad_millis: Option, + audio_end_pad_millis: Option, + end_bump_filename: Option, + ) -> Self { + Self { + w2l_directory: w2l_directory.to_string(), + script_name: script_name.to_string(), + checkpoint_path: checkpoint_path.to_string(), + temp_directory: temp_directory.to_string(), + audio_start_pad_millis, + audio_end_pad_millis, + end_bump_filename, + } + } + + pub fn execute(&self, + audio_filename: &str, + image_or_video_filename: &str, + output_video_filename: &str, + is_image: bool, + disable_end_bump: bool, + spawn_process: bool) -> AnyhowResult<()> + { + let mut command = String::new(); + + command.push_str("echo 'test'"); + command.push_str(" && "); + command.push_str(&format!("cd {}", self.w2l_directory)); + command.push_str(" && "); + command.push_str("source python/bin/activate"); + command.push_str(" && "); + command.push_str("python "); + command.push_str(&self.script_name); + command.push_str(" --checkpoint_path "); + command.push_str(&self.checkpoint_path); + command.push_str(" --face "); + command.push_str(image_or_video_filename); + command.push_str(" --audio "); + command.push_str(audio_filename); + command.push_str(" --outfile "); + command.push_str(output_video_filename); + + if let Some(millis) = self.audio_start_pad_millis { + command.push_str(" --audio_start_pad_millis "); + command.push_str(&millis.to_string()); + } + + if let Some(millis) = self.audio_end_pad_millis { + command.push_str(" --audio_end_pad_millis "); + command.push_str(&millis.to_string()); + } + + if !disable_end_bump { + if let Some(end_bump_filename) = self.end_bump_filename.as_ref() { + command.push_str(" --end_bump_file "); + command.push_str(end_bump_filename); + } + } + + if is_image { + command.push_str(" --is_image "); + } + + info!("Command: {:?}", command); + + let command_parts = [ + "bash", + "-c", + &command + ]; + + if spawn_process { + // NB: This forks and returns immediately. + //let _child_pid = command_builder.spawn()?; + + let stdout_file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open("/tmp/stdout.txt")?; + + let stderr_file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open("/tmp/stderr.txt")?; + + let mut p = Popen::create(&command_parts, PopenConfig { + //stdout: Redirection::Pipe, + //stderr: Redirection::Pipe, + stdout: Redirection::File(stdout_file), + stderr: Redirection::File(stderr_file), + ..Default::default() + })?; + + info!("Pid : {:?}", p.pid()); + + p.detach(); + + } else { + // NB: This is a blocking call. + /*let output = command_builder.output()?; + + info!("Output status: {}", output.status); + info!("Stdout: {:?}", String::from_utf8(output.stdout)); + error!("Stderr: {:?}", String::from_utf8(output.stderr)); + + if !output.status.success() { + bail!("Bad error code: {:?}", output.status); + }*/ + + let mut p = Popen::create(&command_parts, PopenConfig { + //stdout: Redirection::Pipe, + //stderr: Redirection::Pipe, + ..Default::default() + })?; + + info!("Pid : {:?}", p.pid()); + + let exit_status = p.wait()?; + + info!("Exit status: {:?}", exit_status); + } + + Ok(()) + } +}