Compare commits

...
Author SHA1 Message Date
Brandon Thomas b3da60aa3a cleanup pathing (still wip) 2024-07-10 20:58:39 -04:00
Brandon Thomas 342ab2302c refactor worker (wip) 2024-07-10 19:11:28 -04:00
14 changed files with 332 additions and 163 deletions
@@ -1,5 +1,3 @@
pub mod process_job;
pub mod steps;
pub mod video_paths;
pub mod write_workflow_prompt;
pub mod util;
@@ -52,10 +52,11 @@ use crate::job::job_types::workflow::comfy_ui::video_style_transfer::steps::post
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::steps::preprocess_save_audio::{preprocess_save_audio, ProcessSaveAudioArgs};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::steps::preprocess_trim_and_resample_videos::{preprocess_trim_and_resample_videos, ProcessTrimAndResampleVideoArgs};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::steps::validate_and_save_results::{SaveResultsArgs, validate_and_save_results};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::comfy_dirs::ComfyDirs;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::comfy_ui_inference_command::{InferenceArgs, InferenceDetails};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing::{InputVideoAndPaths, VideoDownloads};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::video_paths::VideoPaths;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::write_workflow_prompt::{WorkflowPromptArgs, write_workflow_prompt};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing::{PrimaryInputVideoAndPaths, SecondaryInputVideoAndPaths, VideoDownloads};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing_deprecated::VideoPaths;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::write_workflow_prompt::{WorkflowPromptArgs, write_workflow_prompt};
use crate::job_dependencies::JobDependencies;
use crate::util::common_commands::ffmpeg_audio_replace_args::FfmpegAudioReplaceArgs;
use crate::util::common_commands::ffmpeg_logo_watermark_command::WatermarkArgs;
@@ -70,7 +71,7 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
.new_generic_inference(job.inference_job_token.as_str())
.map_err(|e| ProcessSingleJobError::Other(anyhow!(e)))?;
let model_dependencies = args
let comfy_deps = args
.job_dependencies
.job
.job_specific_dependencies
@@ -96,7 +97,10 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
// ===================== DOWNLOAD REQUIRED MODELS IF NOT EXIST ===================== //
let root_comfy_path = model_dependencies.inference_command.mounts_directory.clone();
// TODO: Replace all other paths with this
let comfy_dirs = ComfyDirs::new(&comfy_deps);
let root_comfy_path = comfy_deps.inference_command.mounts_directory.clone();
let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await;
let remote_cloud_file_client = match remote_cloud_file_client {
@@ -159,7 +163,7 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
workflow_path = write_workflow_prompt(WorkflowPromptArgs {
workflow_path: &workflow_path,
comfy_args: &comfy_args,
model_dependencies: &model_dependencies,
model_dependencies: &comfy_deps,
maybe_positive_prompt: comfy_args.positive_prompt.as_deref(),
maybe_negative_prompt: comfy_args.negative_prompt.as_deref(),
})?;
@@ -216,7 +220,7 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
info!("Root comfy path: {:?}", &root_comfy_path);
info!("Job output path will be (fix this code! the job shouldn't set this path!): {:?}", &job_args.output_path);
let mut videos = VideoPaths::new(&root_comfy_path, job_args.output_path);
//let mut videos = VideoPaths::new(&root_comfy_path, job_args.output_path);
// TODO(bt,2024-04-20): Clean up this mess.
@@ -239,18 +243,18 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
// ==================== DOWNLOAD VIDEOS ==================== //
let mut download_videos = download_input_videos(DownloadInputVideoArgs {
let mut videos = download_input_videos(DownloadInputVideoArgs {
job_args: &job_args,
videos: &videos,
comfy_dirs: &comfy_dirs,
mysql_pool: &deps.db.mysql_pool,
remote_cloud_file_client: &remote_cloud_file_client,
}).await?;
info!("Downloaded video!");
videos.debug_print_paths_after_download();
//videos.debug_print_paths_after_download();
if let Ok(Some(dimensions)) = ffprobe_get_dimensions(&download_videos.input_video.original_download_path) {
if let Ok(Some(dimensions)) = ffprobe_get_dimensions(&videos.input_video.original_download_path) {
info!("Download video dimensions: {}x{}", dimensions.width, dimensions.height);
}
@@ -258,16 +262,16 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
preprocess_trim_and_resample_videos(ProcessTrimAndResampleVideoArgs {
comfy_args,
comfy_deps: model_dependencies,
primary_video_paths: &videos,
download_videos: &mut download_videos,
comfy_deps,
comfy_dirs: &comfy_dirs,
videos: &mut videos,
})?;
// ========================= PREPROCESS AUDIO ======================== //
let mut lipsync_enabled = comfy_args.lipsync_enabled.unwrap_or(false);
if let Err(err) = preprocess_save_audio(ProcessSaveAudioArgs {
comfy_deps: model_dependencies,
comfy_deps,
videos: &mut videos,
}) {
error!("Audio extraction failed: {:?}", err);
@@ -336,7 +340,7 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
info!("Running ComfyUI inference...");
let command_exit_status = model_dependencies
let command_exit_status = comfy_deps
.inference_command
.execute_inference(InferenceArgs {
stderr_output_file: &stderr_output_file,
@@ -353,13 +357,13 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
.as_ref()
.map(|image| path_to_string(&image.ipa_image_path)),
global_ipa_strength: None, // TODO: Expose a UI slider
depth_video_path: download_videos.maybe_depth.as_ref()
depth_video_path: videos.maybe_depth.as_ref()
.map(|v| v.maybe_processed_path.as_deref())
.flatten(),
normal_video_path: download_videos.maybe_normal.as_ref()
normal_video_path: videos.maybe_normal.as_ref()
.map(|v| v.maybe_processed_path.as_deref())
.flatten(),
outline_video_path: download_videos.maybe_outline.as_ref()
outline_video_path: videos.maybe_outline.as_ref()
.map(|v| v.maybe_processed_path.as_deref())
.flatten(),
});
@@ -375,15 +379,15 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
info!("Captured stduout output: {}", contents);
}
videos.debug_print_paths_after_comfy();
//videos.debug_print_paths_after_comfy();
if let Ok(Some(dimensions)) = ffprobe_get_dimensions(&videos.comfy_output_video_path) {
if let Ok(Some(dimensions)) = ffprobe_get_dimensions(&videos.input_video.comfy_output_video_path) {
info!("Comfy output video dimensions: {}x{}", dimensions.width, dimensions.height);
}
// ==================== CHECK OUTPUT FILE ======================== //
if let Err(err) = check_file_exists(&videos.comfy_output_video_path) {
if let Err(err) = check_file_exists(&videos.input_video.comfy_output_video_path) {
error!("Output file does not exist: {:?}", err);
error!("Inference failed: {:?}", command_exit_status);
@@ -396,7 +400,7 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
safe_delete_temp_file(&stdout_output_file);
safe_delete_temp_directory(&work_temp_dir);
safe_delete_temp_file(&workflow_path);
safe_delete_all_input_videos(&download_videos);
safe_delete_all_input_videos(&videos);
// TODO(bt,2024-04-21): Not sure we want to delete the LoRA?
if let Some(lora_path) = maybe_lora_path {
@@ -407,37 +411,31 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
safe_delete_temp_file(ipa_path.ipa_image_path);
}
safe_delete_temp_file(&videos.comfy_input_video_path);
safe_delete_temp_file(&videos.trimmed_resampled_video_path);
safe_delete_temp_file(&videos.trimmed_audio_path);
//safe_delete_temp_file(&videos.original_video_path);
let output_dir = root_comfy_path.join("output");
safe_recursively_delete_files(&output_dir);
safe_recursively_delete_files(&comfy_dirs.comfy_output_dir);
return Err(ProcessSingleJobError::Other(anyhow!("Output file did not exist: {:?}",
&videos.comfy_output_video_path)));
&videos.input_video.comfy_output_video_path)));
}
// ==================== COPY BACK AUDIO ==================== //
post_process_restore_audio(PostProcessRestoreVideoArgs {
comfy_deps: model_dependencies,
comfy_deps,
videos: &mut videos,
});
// ==================== OPTIONAL WATERMARK ==================== //
post_process_add_watermark(PostProcessAddWatermarkArgs {
comfy_deps: model_dependencies,
comfy_deps,
videos: &mut videos,
});
// ==================== DEBUG ======================== //
videos.debug_print_paths_after_post_processing();
//videos.debug_print_paths_after_post_processing();
if let Ok(Some(dimensions)) = ffprobe_get_dimensions(&videos.get_final_video_to_upload()) {
if let Ok(Some(dimensions)) = ffprobe_get_dimensions(&videos.input_video.get_final_video_to_upload()) {
info!("Final video upload dimensions: {}x{}", dimensions.width, dimensions.height);
}
@@ -447,11 +445,10 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
job,
deps: &deps,
job_args: &job_args,
comfy_deps: model_dependencies,
comfy_deps,
comfy_args,
videos: &videos,
job_progress_reporter: &mut job_progress_reporter,
download_videos: &download_videos,
inference_duration,
}).await?;
@@ -468,13 +465,7 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
safe_delete_temp_file(&stderr_output_file);
safe_delete_temp_file(&stdout_output_file);
//safe_delete_temp_file(&videos.original_video_path);
safe_delete_temp_file(&videos.trimmed_resampled_video_path);
safe_delete_temp_file(&videos.comfy_output_video_path);
safe_delete_temp_file(videos.video_to_watermark());
safe_delete_temp_file(videos.get_final_video_to_upload());
safe_delete_temp_file(videos.get_non_watermarked_video_to_upload());
safe_delete_all_input_videos(&download_videos);
safe_delete_all_input_videos(&videos);
// TODO(bt,2024-03-01): Do we really want to delete the workflow, models, etc.?
@@ -516,22 +507,33 @@ pub async fn process_job(args: ComfyProcessJobArgs<'_>) -> Result<JobSuccessResu
}
fn safe_delete_all_input_videos(videos: &VideoDownloads) {
safe_delete_videos(&videos.input_video);
safe_delete_primary_videos(&videos.input_video);
if let Some(depth) = &videos.maybe_depth {
safe_delete_videos(depth);
safe_delete_secondary_videos(depth);
}
if let Some(normal) = &videos.maybe_normal {
safe_delete_videos(normal);
safe_delete_secondary_videos(normal);
}
if let Some(outline) = &videos.maybe_outline {
safe_delete_videos(outline);
safe_delete_secondary_videos(outline);
}
}
fn safe_delete_videos(video: &InputVideoAndPaths) {
fn safe_delete_primary_videos(video: &PrimaryInputVideoAndPaths) {
safe_delete_temp_file(&video.original_download_path);
safe_delete_temp_file(&video.comfy_output_video_path);
safe_delete_temp_file(video.video_to_watermark());
safe_delete_temp_file(video.get_final_video_to_upload());
safe_delete_temp_file(video.get_non_watermarked_video_to_upload());
if let Some(processed_path) = &video.maybe_processed_path {
safe_delete_temp_file(processed_path);
}
}
fn safe_delete_secondary_videos(video: &SecondaryInputVideoAndPaths) {
safe_delete_temp_file(&video.original_download_path);
if let Some(processed_path) = &video.maybe_processed_path {
safe_delete_temp_file(processed_path);
@@ -13,7 +13,7 @@ use videos::ffprobe_get_dimensions::ffprobe_get_dimensions;
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::steps::check_and_validate_job::JobArgs;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::video_paths::VideoPaths;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing_deprecated::VideoPaths;
const DEFAULT_SUFFIX : &str = ".jpg";
@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use anyhow::anyhow;
use log::{error, info};
@@ -14,13 +14,15 @@ use tokens::tokens::media_files::MediaFileToken;
use videos::ffprobe_get_dimensions::ffprobe_get_dimensions;
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job::job_types::workflow::comfy_ui::comfy_ui_dependencies::ComfyDependencies;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::steps::check_and_validate_job::JobArgs;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing::{InputVideoAndPaths, VideoDownloads, VideoMediaFileRecord};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::video_paths::VideoPaths;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::comfy_dirs::ComfyDirs;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing::{PrimaryInputVideoAndPaths, SecondaryInputVideoAndPaths, VideoDownloads};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing_deprecated::VideoPaths;
pub struct DownloadInputVideoArgs<'a> {
pub job_args: &'a JobArgs<'a>,
pub videos: &'a VideoPaths,
pub comfy_dirs: &'a ComfyDirs,
pub mysql_pool: &'a MySqlPool,
pub remote_cloud_file_client: &'a RemoteCloudFileClient,
}
@@ -35,7 +37,7 @@ pub async fn download_input_videos(
// TODO: Consolidate primary video download with secondary download logic.
async fn download_primary_video(
args: &DownloadInputVideoArgs<'_>
args: &DownloadInputVideoArgs<'_>,
) -> Result<VideoDownloads, ProcessSingleJobError> {
let input_media_file_token = match args.job_args.maybe_input_file {
None => return Err(ProcessSingleJobError::InvalidJob(anyhow!("No primary input video file provided"))),
@@ -81,7 +83,7 @@ async fn download_primary_video(
// NB(bt,2024-07-09): This convention is muddled with the python side.
// We may not have flexibility to change this pathing for a while.
let download_path = args.videos.comfy_input_dir.join("video.mp4");
let download_path = args.comfy_dirs.comfy_input_dir.join("video.mp4");
info!("Downloading primary input file to {:?}", download_path);
@@ -92,12 +94,13 @@ async fn download_primary_video(
info!("Downloaded primary input video!");
// TODO: This monumentally sucks.
// The upstream shouldn't be telling us what to do about this at all.
let job_output_path = args.job_args.output_path;
Ok(VideoDownloads {
input_video: InputVideoAndPaths {
record: VideoMediaFileRecord::Single(input_media_file),
original_download_path: download_path,
maybe_processed_path: None,
},
input_video: PrimaryInputVideoAndPaths::new(
input_media_file, &args.comfy_dirs, job_output_path),
maybe_depth: None,
maybe_normal: None,
maybe_outline: None,
@@ -164,7 +167,7 @@ async fn download_secondary_video(
desired_token: &MediaFileToken,
all_video_media_files: &[MediaFilesByTokensRecord],
args: &DownloadInputVideoArgs<'_>
) -> Result<Option<InputVideoAndPaths>, ProcessSingleJobError> {
) -> Result<Option<SecondaryInputVideoAndPaths>, ProcessSingleJobError> {
let file_description = match secondary_video_type {
SecondaryVideoType::Depth => "depth video",
@@ -173,9 +176,9 @@ async fn download_secondary_video(
};
let download_path = match secondary_video_type {
SecondaryVideoType::Depth => args.videos.comfy_input_dir.join("depth_download.mp4"),
SecondaryVideoType::Normal => args.videos.comfy_input_dir.join("normal_download.mp4"),
SecondaryVideoType::Outline => args.videos.comfy_input_dir.join("outline_download.mp4"),
SecondaryVideoType::Depth => args.comfy_dirs.comfy_input_dir.join("depth_download.mp4"),
SecondaryVideoType::Normal => args.comfy_dirs.comfy_input_dir.join("normal_download.mp4"),
SecondaryVideoType::Outline => args.comfy_dirs.comfy_input_dir.join("outline_download.mp4"),
};
let maybe_media_file = all_video_media_files
@@ -205,8 +208,8 @@ async fn download_secondary_video(
path_to_string(&download_path)
).await?;
Ok(Some(InputVideoAndPaths {
record: VideoMediaFileRecord::Bulk(media_file.clone()),
Ok(Some(SecondaryInputVideoAndPaths {
record: media_file.clone(),
original_download_path: download_path,
maybe_processed_path: None,
}))
@@ -3,12 +3,13 @@ use log::{error, info};
use filesys::check_file_exists::check_file_exists;
use crate::job::job_types::workflow::comfy_ui::comfy_ui_dependencies::ComfyDependencies;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::video_paths::VideoPaths;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing::VideoDownloads;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing_deprecated::VideoPaths;
use crate::util::common_commands::ffmpeg_logo_watermark_command::WatermarkArgs;
pub struct PostProcessAddWatermarkArgs<'a> {
pub comfy_deps: &'a ComfyDependencies,
pub videos: &'a mut VideoPaths,
pub videos: &'a mut VideoDownloads,
}
/// NB: Purposefully infallible.
@@ -21,13 +22,14 @@ pub fn post_process_add_watermark(
info!("Adding watermark...");
let output_video_fs_path_watermark = args.videos
.input_video
.comfy_output_video_path
.with_extension("_watermark.mp4");
let command_exit_status = args.comfy_deps
.ffmpeg_watermark_command
.execute(WatermarkArgs {
video_path: args.videos.video_to_watermark(),
video_path: args.videos.input_video.video_to_watermark(),
maybe_override_logo_path: None,
alpha: 0.6,
scale: 0.1, // NB: 0.1 is good for the Storyteller logo @ 2653x512 placed on 1024x576 output.
@@ -49,6 +51,6 @@ pub fn post_process_add_watermark(
if use_watermarked_file {
info!("Success generating watermarked file: {:?}", output_video_fs_path_watermark);
args.videos.watermarked_video_path = Some(output_video_fs_path_watermark);
args.videos.input_video.watermarked_video_path = Some(output_video_fs_path_watermark);
}
}
@@ -6,12 +6,13 @@ use subprocess_common::command_runner::command_runner_args::{RunAsSubprocessArgs
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job::job_types::workflow::comfy_ui::comfy_ui_dependencies::ComfyDependencies;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::video_paths::VideoPaths;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing::VideoDownloads;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing_deprecated::VideoPaths;
use crate::util::common_commands::ffmpeg_audio_replace_args::FfmpegAudioReplaceArgs;
pub struct PostProcessRestoreVideoArgs<'a> {
pub comfy_deps: &'a ComfyDependencies,
pub videos: &'a mut VideoPaths,
pub videos: &'a mut VideoDownloads,
}
/// NB: Purposefully infallible.
@@ -20,7 +21,18 @@ pub fn post_process_restore_audio(
) -> () {
info!("Restoring audio...");
// Use the original downloaded video if we didn't trim and resample it.
let input_video_file = args
.videos
.input_video
.maybe_processed_path
.as_deref()
.unwrap_or(
&args.videos.input_video.original_download_path
);
let output_video_fs_path_restored = args.videos
.input_video
.comfy_output_video_path
.with_extension("_restored.mp4");
@@ -29,8 +41,8 @@ pub fn post_process_restore_audio(
.ffmpeg_command_runner
.run_with_subprocess(RunAsSubprocessArgs {
args: Box::new(&FfmpegAudioReplaceArgs {
input_video_file: &args.videos.comfy_output_video_path,
input_audio_file: &args.videos.trimmed_resampled_video_path,
input_video_file: &args.videos.input_video.comfy_output_video_path,
input_audio_file: &input_video_file,
output_video_file: &output_video_fs_path_restored,
}),
stderr: StreamRedirection::None,
@@ -52,6 +64,6 @@ pub fn post_process_restore_audio(
if use_restored_audio {
info!("Success generating restored audio file: {:?}", output_video_fs_path_restored);
args.videos.audio_restored_video_path = Some(output_video_fs_path_restored);
args.videos.input_video.audio_restored_video_path = Some(output_video_fs_path_restored);
}
}
@@ -7,13 +7,14 @@ use subprocess_common::command_runner::command_runner_args::{RunAsSubprocessArgs
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job::job_types::workflow::comfy_ui::comfy_ui_dependencies::ComfyDependencies;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::video_paths::VideoPaths;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing::VideoDownloads;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing_deprecated::VideoPaths;
use crate::util::common_commands::ffmpeg_audio_replace_args::FfmpegAudioReplaceArgs;
use crate::util::common_commands::ffmpeg_extract_audio_args::FfmpegExtractAudioArgs;
pub struct ProcessSaveAudioArgs<'a> {
pub comfy_deps: &'a ComfyDependencies,
pub videos: &'a mut VideoPaths,
pub videos: &'a mut VideoDownloads,
}
pub fn preprocess_save_audio(
@@ -21,14 +22,23 @@ pub fn preprocess_save_audio(
) -> Result<(), ProcessSingleJobError> {
info!("Extracting audio...");
// Use the original downloaded video if we didn't trim and resample it.
let input_video_file = args
.videos
.input_video
.maybe_processed_path
.as_deref()
.unwrap_or(
&args.videos.input_video.original_download_path
);
let command_exit_status = args
.comfy_deps
.ffmpeg_command_runner
.run_with_subprocess(RunAsSubprocessArgs {
args: Box::new(&FfmpegExtractAudioArgs {
input_video_file: &args.videos.trimmed_resampled_video_path,
output_file: &args.videos.trimmed_audio_path
input_video_file,
output_file: &args.videos.input_video.trimmed_audio_path
}),
stderr: StreamRedirection::None,
stdout: StreamRedirection::None,
@@ -13,21 +13,15 @@ use videos::ffprobe_get_dimensions::ffprobe_get_dimensions;
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job::job_types::workflow::comfy_ui::comfy_ui_dependencies::ComfyDependencies;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing::{InputVideoAndPaths, VideoDownloads};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::video_paths::VideoPaths;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::comfy_dirs::ComfyDirs;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing::{SecondaryInputVideoAndPaths, VideoDownloads};
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing_deprecated::VideoPaths;
pub struct ProcessTrimAndResampleVideoArgs<'a> {
pub comfy_args: &'a WorkflowArgs,
pub comfy_deps: &'a ComfyDependencies,
// TODO(bt,2024-07-09): This is going to need refatoring and consolidation for clarity.
// 'VideoPaths' was for the original video and all of its resamplings, outputs,
// watermarkings, etc. Now that we have depth, normal, and outline map videos (and
// possibly more), we're going to need to group these. Those files are referenced by
// `VideoDownloadDetails`, which is a better system to keep track of the videos as they
// pass through the system.
pub primary_video_paths: &'a VideoPaths,
pub download_videos: &'a mut VideoDownloads,
pub comfy_dirs: &'a ComfyDirs,
pub videos: &'a mut VideoDownloads,
}
pub fn preprocess_trim_and_resample_videos(
@@ -55,10 +49,18 @@ pub fn preprocess_trim_and_resample_videos(
let skip_resampling_video = args.comfy_args.skip_process_video.unwrap_or(false);
preprocess_trim_and_resample_primary_video(&args, &resample_details, skip_resampling_video)?;
preprocess_trim_and_resample_primary_video(
&args.comfy_deps,
&args.comfy_dirs,
&resample_details,
skip_resampling_video,
args.videos)?;
if !skip_resampling_video {
preprocess_trim_and_resample_secondary_videos(&args.comfy_deps, &resample_details, args.download_videos)?;
preprocess_trim_and_resample_secondary_videos(
&args.comfy_deps,
&resample_details,
args.videos)?;
}
Ok(())
@@ -71,22 +73,26 @@ struct ResampleDetails {
}
fn preprocess_trim_and_resample_primary_video(
args: &ProcessTrimAndResampleVideoArgs<'_>,
comfy_deps: &ComfyDependencies,
comfy_dirs: &ComfyDirs,
resample_details: &ResampleDetails,
skip_process_video: bool,
videos: &mut VideoDownloads,
) -> Result<(), ProcessSingleJobError> {
let resampled_path = comfy_dirs.comfy_input_dir.join("trimmed.mp4");
if skip_process_video {
info!("Skipping video trim / resample...");
info!("(This might break if we need to copy the video path. Salt's code implicitly expects videos to be in certain places, but doesn't allow passing of config, and that's horrible.)");
std::fs::copy(&args.download_videos.input_video.original_download_path, &args.primary_video_paths.trimmed_resampled_video_path)
std::fs::copy(&videos.input_video.original_download_path, &resampled_path)
.map_err(|err| {
error!("Error copying video (1): {:?}", err);
ProcessSingleJobError::IoError(err)
})?;
std::fs::copy(&args.download_videos.input_video.original_download_path, &args.primary_video_paths.comfy_output_video_path)
std::fs::copy(&videos.input_video.original_download_path, &videos.input_video.comfy_output_video_path)
.map_err(|err| {
error!("Error copying video (2): {:?}", err);
ProcessSingleJobError::IoError(err)
@@ -94,23 +100,23 @@ fn preprocess_trim_and_resample_primary_video(
} else {
info!("Calling video trim / resample...");
info!("Script: {:?}", &args.comfy_deps.inference_command.processing_script);
info!("Script: {:?}", &comfy_deps.inference_command.processing_script);
// NB(bt,2024-07-09): Despite what the comments on this field say, the script `format_video.py` writes
// to a file named 'input.mp4', not 'trimmed.mp4'. This pathing really needs to be cleaned up.
let comfy_input_video_path = args.primary_video_paths.comfy_input_dir.join("input.mp4");
let comfy_input_video_path = comfy_dirs.comfy_input_dir.join("input.mp4");
// shell out to python script
let output = Command::new("python3")
.stdout(Stdio::inherit()) // NB: This should emit to the rust job's stdout
.stderr(Stdio::inherit()) // NB: This should emit to the rust job's stderr
.arg(path_to_string(&args.comfy_deps.inference_command.processing_script))
.arg(path_to_string(&comfy_deps.inference_command.processing_script))
.arg("DO_NOT_USE_THIS_ARG_ANYMORE")
.arg(format!("{:?}", resample_details.trim_start_millis))
.arg(format!("{:?}", resample_details.trim_end_millis))
.arg(format!("{:?}", resample_details.target_fps))
.arg("--input")
.arg(path_to_string(&args.download_videos.input_video.original_download_path))
.arg(path_to_string(&videos.input_video.original_download_path))
.arg("--output")
// NB(bt,2024-07-09): Despite what the comments on this field say, the script `format_video.py` writes
// to a file named 'input.mp4', not 'trimmed.mp4'. This pathing really needs to be cleaned up.
@@ -134,19 +140,22 @@ fn preprocess_trim_and_resample_primary_video(
// NB: The process video script implicitly saves the above video as "input.mp4"
// Comfy sometimes overwrites this, so we need to make a copy.
std::fs::copy(&comfy_input_video_path, &args.primary_video_paths.trimmed_resampled_video_path)
std::fs::copy(&comfy_input_video_path, &resampled_path)
.map_err(|err| {
error!("Error copying trimmed video: {:?}", err);
ProcessSingleJobError::IoError(err)
})?;
}
args.primary_video_paths.debug_print_paths_after_trim();
//primary_video_paths.debug_print_paths_after_trim();
if let Ok(Some(dimensions)) = ffprobe_get_dimensions(&args.primary_video_paths.trimmed_resampled_video_path) {
if let Ok(Some(dimensions)) = ffprobe_get_dimensions(&resampled_path) {
info!("Trimmed / resampled video dimensions: {}x{}", dimensions.width, dimensions.height);
}
// NB(bt,2024-07-10): Even if we don't resample, the python side still expects certain pathing for now
videos.input_video.maybe_processed_path = Some(resampled_path);
Ok(())
}
@@ -177,7 +186,7 @@ fn preprocess_trim_and_resample_secondary_videos(
fn preprocess_trim_and_resample_secondary_video(
comfy_deps: &ComfyDependencies,
resample_details: &ResampleDetails,
video: &InputVideoAndPaths,
video: &SecondaryInputVideoAndPaths,
) -> Result<PathBuf, ProcessSingleJobError> {
info!("Calling video trim / resample...");
info!("Script: {:?}", &comfy_deps.inference_command.processing_script);
@@ -34,7 +34,7 @@ use crate::job::job_types::workflow::comfy_ui::comfy_process_job_args::ComfyProc
use crate::job::job_types::workflow::comfy_ui::comfy_ui_dependencies::ComfyDependencies;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::steps::check_and_validate_job::JobArgs;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing::VideoDownloads;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::video_paths::VideoPaths;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::video_pathing_deprecated::VideoPaths;
use crate::job_dependencies::JobDependencies;
fn get_file_extension(mimetype: &str) -> anyhow::Result<&'static str> {
@@ -55,8 +55,7 @@ pub struct SaveResultsArgs<'a> {
pub comfy_deps: &'a ComfyDependencies,
pub job_progress_reporter: &'a mut Box<dyn JobProgressReporter>,
pub download_videos: &'a VideoDownloads,
pub videos: &'a VideoPaths,
pub videos: &'a VideoDownloads,
pub inference_duration: Duration,
pub comfy_args: &'a WorkflowArgs,
@@ -71,7 +70,7 @@ pub async fn validate_and_save_results(args: SaveResultsArgs<'_>) -> Result<Medi
info!("Interrogating result file size ...");
let final_video = args.videos.get_final_video_to_upload();
let final_video = args.videos.input_video.get_final_video_to_upload();
let file_size_bytes = file_size(final_video)
.map_err(|err| ProcessSingleJobError::Other(err))?;
@@ -175,7 +174,7 @@ pub async fn validate_and_save_results(args: SaveResultsArgs<'_>) -> Result<Medi
let result = args.deps.buckets.public_bucket_client.upload_filename_with_content_type(
&result_bucket_object_pathbuf.with_extension("no_watermark.mp4"),
&args.videos.get_non_watermarked_video_to_upload(),
&args.videos.input_video.get_non_watermarked_video_to_upload(),
&mimetype) // TODO: We should check the mimetype to make sure bad payloads can't get uploaded
.await;
@@ -200,9 +199,9 @@ pub async fn validate_and_save_results(args: SaveResultsArgs<'_>) -> Result<Medi
// This also lets us hide the engine renders from users.
// This shouldn't ever become a deeply nested tree of children, but rather a single root
// with potentially many direct children.
let style_transfer_source_media_file_token = args.download_videos
.input_video.record.maybe_style_transfer_source_media_file_token()?
.unwrap_or_else(|| args.download_videos.input_video.record.token());
let style_transfer_source_media_file_token = args.videos
.input_video.record.maybe_style_transfer_source_media_file_token.as_ref()
.unwrap_or_else(|| &args.videos.input_video.record.token);
let prompt_token = PromptToken::generate();
@@ -210,10 +209,10 @@ pub async fn validate_and_save_results(args: SaveResultsArgs<'_>) -> Result<Medi
pool: &args.deps.db.mysql_pool,
job: &args.job,
maybe_mime_type: Some(&mimetype),
maybe_title: args.download_videos.input_video.record.maybe_title(),
maybe_title: args.videos.input_video.record.maybe_title.as_deref(),
maybe_style_transfer_source_media_file_token: Some(&style_transfer_source_media_file_token),
maybe_scene_source_media_file_token: args.download_videos
.input_video.record.maybe_scene_source_media_file_token()?,
maybe_scene_source_media_file_token: args.videos
.input_video.record.maybe_scene_source_media_file_token.as_ref(),
file_size_bytes,
sha256_checksum: &file_checksum,
maybe_prompt_token: Some(&prompt_token),
@@ -0,0 +1,24 @@
use std::path::PathBuf;
use crate::job::job_types::workflow::comfy_ui::comfy_ui_dependencies::ComfyDependencies;
/// This is a temporary measure until we start using tempdirs for intermediate inputs and outputs.
pub struct ComfyDirs {
pub comfy_input_dir: PathBuf,
pub comfy_output_dir: PathBuf,
}
impl ComfyDirs {
pub fn new(comfy_deps: &ComfyDependencies) -> Self {
// TODO(bt,2024-04-21): The pathing for this job is complicated. ComfyUI
// was set up with some of these expectations, which is bad. Worse, the
// jobs enqueue the expected output path -- no idea why that was done.
// This should all be fixed.
let root_comfy_path = comfy_deps.inference_command.mounts_directory.as_path();
Self {
comfy_input_dir: root_comfy_path.join("input"),
comfy_output_dir: root_comfy_path.join("output"),
}
}
}
@@ -1,2 +1,5 @@
pub mod comfy_dirs;
pub mod comfy_ui_inference_command;
pub mod video_pathing;
pub mod video_pathing;
pub mod video_pathing_deprecated;
pub mod write_workflow_prompt;
@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use anyhow::anyhow;
@@ -7,63 +7,171 @@ use mysql_queries::queries::media_files::get::get_media_file::MediaFile;
use tokens::tokens::media_files::MediaFileToken;
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job::job_types::workflow::comfy_ui::video_style_transfer::util::comfy_dirs::ComfyDirs;
/// Keep track of where we download videos to and where they end up after processing.
pub struct VideoDownloads {
/// The main video
pub input_video: InputVideoAndPaths,
pub input_video: PrimaryInputVideoAndPaths,
// Secondary videos that provide enrichment signals.
pub maybe_depth: Option<InputVideoAndPaths>,
pub maybe_normal: Option<InputVideoAndPaths>,
pub maybe_outline: Option<InputVideoAndPaths>,
pub maybe_depth: Option<SecondaryInputVideoAndPaths>,
pub maybe_normal: Option<SecondaryInputVideoAndPaths>,
pub maybe_outline: Option<SecondaryInputVideoAndPaths>,
}
/// This is the primary input video for style transfer.
pub struct PrimaryInputVideoAndPaths {
/// Media file record.
pub record: MediaFile,
/// Filesystem path of the downloaded original video
/// This is the input.
pub original_download_path: PathBuf,
// TODO: Rename back to `trimmed_resampled...`
/// Filesystem path of the trimmed and resampled video
/// This is the first output we generate.
/// We'll use this downstream once it's available.
pub maybe_processed_path: Option<PathBuf>, //pub trimmed_resampled_video_path: PathBuf,
/// Filesystem path of the trimmed audio
pub trimmed_audio_path: PathBuf,
/// This is the input into Comfy.
/// This is typically the `trimmed_resampled_video_path`, but since Comfy
/// can overwrite the source, we'll keep a separate copy of that file for
/// later downstream sound restoration (since comfy wipes sound).
pub comfy_input_video_path: PathBuf,
/// Filesystem path of the style transfer output
/// This is the main purpose of the job, and the second output we generate.
pub comfy_output_video_path: PathBuf,
/// Filesystem path of the audio-restored output
/// This is the third output we generate.
/// We'll want to upload this as a result, if available.
pub audio_restored_video_path: Option<PathBuf>,
/// Watermarked final result
/// This is the fourth output we generate.
/// We'll want to upload this as a result, if available.
pub watermarked_video_path: Option<PathBuf>,
// Nasty hack to reuse this elsewhere.
pub comfy_input_dir: PathBuf,
}
/// This is for the secondary depth, normal, and outline videos.
/// We attach metadata as these videos progress through the system (if they're present).
pub struct InputVideoAndPaths {
pub record: VideoMediaFileRecord,
pub struct SecondaryInputVideoAndPaths {
/// Media file record.
pub record: MediaFilesByTokensRecord,
/// Filesystem path of the downloaded original video
pub original_download_path: PathBuf,
/// Filesystem path of the trimmed and resampled video
pub maybe_processed_path: Option<PathBuf>,
}
// Adapter enum
pub enum VideoMediaFileRecord {
// Records returned by single lookup
Single(MediaFile),
// Records returned by batch query
Bulk(MediaFilesByTokensRecord),
// pub enum VideoMediaFileRecord {
// // Records returned by single lookup
// Single(MediaFile),
// // Records returned by batch query
// Bulk(MediaFilesByTokensRecord),
// }
pub trait CommonVideoPathing {
fn original_video_path(&self) -> &Path;
}
impl VideoMediaFileRecord {
pub fn token(&self) -> &MediaFileToken {
match self {
VideoMediaFileRecord::Single(m) => &m.token,
VideoMediaFileRecord::Bulk(m) => &m.token,
}
}
pub fn maybe_title(&self) -> Option<&str> {
match self {
VideoMediaFileRecord::Single(m) => m.maybe_title.as_deref(),
VideoMediaFileRecord::Bulk(m) => m.maybe_title.as_deref(),
}
}
pub fn maybe_style_transfer_source_media_file_token(&self) -> Result<Option<&MediaFileToken>, ProcessSingleJobError> {
// TODO(bt,2024-07-09): Future proofing this to deliberately explode in case I query the
// primary input media files with the bulk query.
match self {
VideoMediaFileRecord::Single(m) => Ok(m.maybe_style_transfer_source_media_file_token.as_ref()),
VideoMediaFileRecord::Bulk(_m) => Err(ProcessSingleJobError::Other(anyhow!("bad refactor?: failed to query foreign key"))),
}
}
pub fn maybe_scene_source_media_file_token(&self) -> Result<Option<&MediaFileToken>, ProcessSingleJobError> {
// TODO(bt,2024-07-09): Future proofing this to deliberately explode in case I query the
// primary input media files with the bulk query.
match self {
VideoMediaFileRecord::Single(m) => Ok(m.maybe_scene_source_media_file_token.as_ref()),
VideoMediaFileRecord::Bulk(_m) => Err(ProcessSingleJobError::Other(anyhow!("bad refactor?: failed to query foreign key"))),
}
impl CommonVideoPathing for PrimaryInputVideoAndPaths {
fn original_video_path(&self) -> &Path {
self.original_download_path.as_path()
}
}
impl CommonVideoPathing for SecondaryInputVideoAndPaths {
fn original_video_path(&self) -> &Path {
self.original_download_path.as_path()
}
}
impl PrimaryInputVideoAndPaths {
pub fn new(media_file_record: MediaFile, comfy_dirs: &ComfyDirs, job_output_path: &str) -> Self {
let original_video_path = comfy_dirs.comfy_input_dir.join("video.mp4");
let trimmed_resampled_video_path = comfy_dirs.comfy_input_dir.join("trimmed.mp4");
let trimmed_audio_path = comfy_dirs.comfy_input_dir.join("trimmed.wav");
let comfy_input_video_path = comfy_dirs.comfy_input_dir.join("input.mp4");
let comfy_output_video_path = comfy_dirs.comfy_output_dir.join(job_output_path); // TODO: This sucks.
Self {
record: media_file_record,
original_download_path: original_video_path,
maybe_processed_path: Some(trimmed_resampled_video_path),
trimmed_audio_path,
comfy_input_video_path,
comfy_output_video_path,
audio_restored_video_path: None,
watermarked_video_path: None,
comfy_input_dir: comfy_dirs.comfy_input_dir.clone(),
}
}
pub fn video_to_watermark(&self) -> &PathBuf {
// Try to use the audio-restored video if it's available
self.audio_restored_video_path.as_ref()
.unwrap_or(&self.comfy_output_video_path)
}
pub fn get_final_video_to_upload(&self) -> &PathBuf {
// This is the video to upload as the result and save in the media_files table.
self.watermarked_video_path.as_ref()
.or(self.audio_restored_video_path.as_ref())
.unwrap_or(&self.comfy_output_video_path)
}
pub fn get_non_watermarked_video_to_upload(&self) -> &PathBuf {
// We'll upload this for internal use and for premium users.
// Same as "video_to_watermark()"
self.audio_restored_video_path.as_ref()
.unwrap_or(&self.comfy_output_video_path)
}
}
// impl VideoMediaFileRecord {
// pub fn token(&self) -> &MediaFileToken {
// match self {
// VideoMediaFileRecord::Single(m) => &m.token,
// VideoMediaFileRecord::Bulk(m) => &m.token,
// }
// }
//
// pub fn maybe_title(&self) -> Option<&str> {
// match self {
// VideoMediaFileRecord::Single(m) => m.maybe_title.as_deref(),
// VideoMediaFileRecord::Bulk(m) => m.maybe_title.as_deref(),
// }
// }
//
// pub fn maybe_style_transfer_source_media_file_token(&self) -> Result<Option<&MediaFileToken>, ProcessSingleJobError> {
// // TODO(bt,2024-07-09): Future proofing this to deliberately explode in case I query the
// // primary input media files with the bulk query.
// match self {
// VideoMediaFileRecord::Single(m) => Ok(m.maybe_style_transfer_source_media_file_token.as_ref()),
// VideoMediaFileRecord::Bulk(_m) => Err(ProcessSingleJobError::Other(anyhow!("bad refactor?: failed to query foreign key"))),
// }
// }
//
// pub fn maybe_scene_source_media_file_token(&self) -> Result<Option<&MediaFileToken>, ProcessSingleJobError> {
// // TODO(bt,2024-07-09): Future proofing this to deliberately explode in case I query the
// // primary input media files with the bulk query.
// match self {
// VideoMediaFileRecord::Single(m) => Ok(m.maybe_scene_source_media_file_token.as_ref()),
// VideoMediaFileRecord::Bulk(_m) => Err(ProcessSingleJobError::Other(anyhow!("bad refactor?: failed to query foreign key"))),
// }
// }
// }
@@ -4,10 +4,10 @@ use log::info;
use filesys::file_exists::file_exists;
#[deprecated]
pub struct VideoPaths {
/// Filesystem path of the downloaded original video
/// This is the input.
#[deprecated(note = "Use InputVideoAndPaths")]
original_video_path: PathBuf,
/// Filesystem path of the trimmed and resampled video
@@ -22,7 +22,6 @@ pub struct VideoPaths {
/// This is typically the `trimmed_resampled_video_path`, but since Comfy
/// can overwrite the source, we'll keep a separate copy of that file for
/// later downstream sound restoration (since comfy wipes sound).
#[deprecated(note = "Use InputVideoAndPaths")]
pub comfy_input_video_path: PathBuf,
/// Filesystem path of the style transfer output