fbx2gltf should work - just needs endpoint

This commit is contained in:
Brandon Thomas committed 2024-01-28 05:29:57 -05:00
1 parent 2b0ebd5350
commit d8fbc09867
3 files changed
+115 -153

No files matched your search

@@ -45,7 +45,14 @@ pub struct CommandRunner {
impl CommandRunner {
/// Run the command with the `subprocess` crate utilities
pub fn run_with_subprocess(&self, args: RunAsSubprocessArgs<'_>) -> AnyhowResult<CommandExitStatus> {
pub fn run_with_subprocess(&self, args: RunAsSubprocessArgs<'_>) -> CommandExitStatus {
self.do_run_with_subprocess(args)
.unwrap_or_else(|err| {
CommandExitStatus::FailureWithReason { reason: format!("error: {:?}", err) }
})
}
fn do_run_with_subprocess(&self, args: RunAsSubprocessArgs<'_>) -> AnyhowResult<CommandExitStatus> {
let command = self.build_command_string(&args.args);
info!("Command: {:?}", command);
@@ -132,7 +139,7 @@ impl CommandRunner {
}
}
fn build_command_string(&self, command_args: &Box<dyn CommandArgs>) -> String {
fn build_command_string(&self, command_args: &Box<&dyn CommandArgs>) -> String {
let mut command = String::new();
if let Some(execution_directory) = self.maybe_execution_directory.as_deref() {
@@ -17,7 +17,7 @@ pub enum FileOrCreate<'a> {
pub struct RunAsSubprocessArgs<'a> {
/// The args for the process we're going to call
pub args: Box<dyn CommandArgs>,
pub args: Box<&'a dyn CommandArgs>,
/// If set, write stderr to this file.
pub maybe_stderr_output_file: Option<FileOrCreate<'a>>,
@@ -1,14 +1,30 @@
use std::fs::read_to_string;
use std::time::Instant;
use anyhow::anyhow;
use log::info;
use log::{error, info, warn};
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
use buckets::public::media_uploads::bucket_file_path::MediaUploadOriginalFilePath;
use enums::by_table::generic_inference_jobs::inference_result_type::InferenceResultType;
use enums::by_table::generic_synthetic_ids::id_category::IdCategory;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use filesys::check_file_exists::check_file_exists;
use filesys::file_size::file_size;
use filesys::safe_delete_temp_directory::safe_delete_temp_directory;
use filesys::safe_delete_temp_file::safe_delete_temp_file;
use hashing::sha256::sha256_hash_file::sha256_hash_file;
use mimetypes::mimetype_for_file::get_mimetype_for_file;
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use mysql_queries::queries::media_files::create::insert_media_file_generic::{insert_media_file_generic, InsertArgs};
use mysql_queries::queries::media_files::get_media_file_for_inference::MediaFileForInference;
use subprocess_common::command_runner::command_runner_args::{FileOrCreate, RunAsSubprocessArgs};
use crate::job::job_loop::job_success_result::JobSuccessResult;
use crate::job::job_loop::job_success_result::{JobSuccessResult, ResultEntity};
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job::job_types::format_conversion::fbx_to_gltf::command_args::FbxToGltfCommandArgs;
use crate::job_dependencies::JobDependencies;
use crate::util::maybe_download_file_from_bucket::{maybe_download_file_from_bucket, MaybeDownloadArgs};
@@ -30,6 +46,14 @@ pub async fn process_job(args: FbxToGltfJobArgs<'_>) -> Result<JobSuccessResult,
.new_generic_inference(job.inference_job_token.as_str())
.map_err(|e| ProcessSingleJobError::Other(anyhow!(e)))?;
let model_dependencies = args
.job_dependencies
.job
.job_specific_dependencies
.maybe_convert_fbx_to_gltf_dependencies
.as_ref()
.ok_or_else(|| ProcessSingleJobError::JobSystemMisconfiguration(Some("missing ConvertFbx2Gltf dependencies".to_string())))?;
// ==================== TEMP DIR ==================== //
let work_temp_dir = format!("temp_file_convert_{}", job.id.0);
@@ -45,7 +69,7 @@ pub async fn process_job(args: FbxToGltfJobArgs<'_>) -> Result<JobSuccessResult,
info!("Download media for conversion...");
let _original_media_upload_fs_path = {
let original_media_upload_fs_path = {
let original_media_upload_fs_path = work_temp_dir.path().join("original.fbx");
let media_upload_bucket_path =
@@ -70,83 +94,39 @@ pub async fn process_job(args: FbxToGltfJobArgs<'_>) -> Result<JobSuccessResult,
original_media_upload_fs_path
};
// TODO: No duration. Replace this.
let inference_duration = Instant::now().duration_since(Instant::now());
/* // TODO
// ==================== SETUP FOR CONVERSION ==================== //
job_progress_reporter.log_status("running conversion")
.map_err(|e| ProcessSingleJobError::Other(e))?;
//let config_path = PathBuf::from("/models/voice_conversion/so-vits-svc/example_config.json"); // TODO: This could be variable.
let input_wav_path = original_media_upload_fs_path;
let output_audio_fs_path = work_temp_dir.path().join("output.wav");
let stderr_output_file = work_temp_dir.path().join("stderr.txt");
info!("Running VC inference...");
info!("Expected output audio filename: {:?}", &output_audio_fs_path);
// TODO: Limit output length for premium.
let maybe_args = job.maybe_inference_args
.as_ref()
.map(|args| args.args.as_ref())
.flatten();
// If not specified by the user, turn off auto prediction. It sounds awful.
let auto_predict_f0 = maybe_args
.map(|args| match args {
PolymorphicInferenceArgs::Tts { .. } => None,
PolymorphicInferenceArgs::La(_) => None,
PolymorphicInferenceArgs::Vc { auto_predict_f0, .. } => *auto_predict_f0,
PolymorphicInferenceArgs::Rr(_) => None,
PolymorphicInferenceArgs::Ig(_) => None,
PolymorphicInferenceArgs::Mc(_) => None,
PolymorphicInferenceArgs::Cu(_) => None,
})
.flatten()
.unwrap_or(false);
let maybe_transpose = maybe_args
.map(|args| match args {
PolymorphicInferenceArgs::Tts { .. } => None,
PolymorphicInferenceArgs::La(_) => None,
PolymorphicInferenceArgs::Vc { transpose, .. } => *transpose,
PolymorphicInferenceArgs::Rr(_) => None,
PolymorphicInferenceArgs::Ig(_) => None,
PolymorphicInferenceArgs::Mc(_) => None,
PolymorphicInferenceArgs::Cu(_) => None,
})
.flatten();
// ==================== RUN INFERENCE SCRIPT ==================== //
let inference_start_time = Instant::now();
let stderr_output_file = work_temp_dir.path().join("stderr.txt");
let output_directory = work_temp_dir.path().join("output");
let output_directory_actual = work_temp_dir.path().join("output_out"); // NB: FBX2GLTF changes the directory name
let command_exit_status = model_dependencies
.inference_command
.execute_inference(InferenceArgs {
model_path: &so_vits_svc_fs_path,
input_path: &input_wav_path,
output_path: &output_audio_fs_path,
stderr_output_file: &stderr_output_file,
maybe_config_path: None,
device: Device::Cuda,
auto_predict_f0,
maybe_transpose,
});
let execution_start_time = Instant::now();
let inference_duration = Instant::now().duration_since(inference_start_time);
let command_exit_status = {
model_dependencies
.command_runner
.run_with_subprocess(RunAsSubprocessArgs {
args: Box::new(&FbxToGltfCommandArgs {
input_file: &original_media_upload_fs_path,
output_directory: &output_directory,
}),
maybe_stderr_output_file: Some(FileOrCreate::NewFileWithName(&stderr_output_file)),
maybe_stdout_output_file: None,
})
};
info!("Inference took duration to complete: {:?}", &inference_duration);
let execution_duration = Instant::now().duration_since(execution_start_time);
info!("Execution took duration to complete: {:?}", &execution_duration);
if !command_exit_status.is_success() {
error!("Inference failed: {:?}", command_exit_status);
error!("Execution failed: {:?}", command_exit_status);
let error = ProcessSingleJobError::Other(anyhow!("CommandExitStatus: {:?}", command_exit_status));
@@ -162,8 +142,8 @@ pub async fn process_job(args: FbxToGltfJobArgs<'_>) -> Result<JobSuccessResult,
//}
}
safe_delete_temp_file(&input_wav_path);
safe_delete_temp_file(&output_audio_fs_path);
safe_delete_temp_file(&original_media_upload_fs_path);
safe_delete_temp_directory(&output_directory_actual);
safe_delete_temp_directory(&work_temp_dir);
return Err(error);
@@ -171,68 +151,53 @@ pub async fn process_job(args: FbxToGltfJobArgs<'_>) -> Result<JobSuccessResult,
// ==================== CHECK ALL FILES EXIST AND GET METADATA ==================== //
// NB: The actual name of the file will be this:
let output_gltf_file = output_directory_actual.join("output.gltf"); // NB: FBX2GLTF creates multiple files in the directory
info!("Checking that output files exist...");
check_file_exists(&output_audio_fs_path).map_err(|e| ProcessSingleJobError::Other(e))?;
check_file_exists(&output_gltf_file).map_err(|e| ProcessSingleJobError::Other(e))?;
info!("Interrogating result file properties...");
let file_size_bytes = file_size(&output_audio_fs_path)
let file_size_bytes = file_size(&output_gltf_file)
.map_err(|err| ProcessSingleJobError::Other(err))?;
let maybe_mimetype = get_mimetype_for_file(&output_audio_fs_path)
let maybe_mimetype = get_mimetype_for_file(&output_gltf_file)
.map_err(|err| ProcessSingleJobError::from_io_error(err))?
.map(|mime| mime.to_string());
let audio_info = decode_basic_audio_file_info(&output_audio_fs_path, maybe_mimetype.as_deref(), None)
.map_err(|err| ProcessSingleJobError::Other(err))?;
if audio_info.required_full_decode {
warn!("Required a full decode of the output data to get duration! That's inefficient!");
}
info!("Calculating sha256...");
let file_checksum = sha256_hash_file(&output_audio_fs_path)
let file_checksum = sha256_hash_file(&output_gltf_file)
.map_err(|err| {
ProcessSingleJobError::Other(anyhow!("Error hashing file: {:?}", err))
})?;
let file_metadata = FileMetadata {
duration_millis: audio_info.duration_millis,
mimetype: maybe_mimetype.clone(),
file_size_bytes,
};
// ==================== UPLOAD AUDIO TO BUCKET ==================== //
job_progress_reporter.log_status("uploading result")
.map_err(|e| ProcessSingleJobError::Other(e))?;
// let result_bucket_location = VoiceConversionResultOriginalFilePath::generate_new();
//
// let result_bucket_object_pathbuf = result_bucket_location.to_full_object_pathbuf();
//
// info!("Audio destination bucket path: {:?}", &result_bucket_object_pathbuf);
let result_bucket_location = MediaFileBucketPath::generate_new(
Some(BUCKET_FILE_PREFIX),
None,
Some(BUCKET_FILE_EXTENSION));
let result_bucket_object_pathbuf = result_bucket_location.to_full_object_pathbuf();
info!("Audio destination bucket path: {:?}", &result_bucket_object_pathbuf);
info!("Destination bucket path: {:?}", &result_bucket_object_pathbuf);
info!("Uploading audio media file...");
info!("Uploading media file...");
args.job_dependencies.buckets.public_bucket_client.upload_filename_with_content_type(
&result_bucket_object_pathbuf,
&output_audio_fs_path,
&output_gltf_file,
"audio/wav")
.await
.map_err(|e| ProcessSingleJobError::Other(e))?;
safe_delete_temp_file(&output_audio_fs_path);
safe_delete_temp_file(&output_gltf_file);
safe_delete_temp_directory(&output_directory_actual);
// ==================== DELETE DOWNLOADED FILE ==================== //
@@ -241,77 +206,67 @@ pub async fn process_job(args: FbxToGltfJobArgs<'_>) -> Result<JobSuccessResult,
// ==================== SAVE RECORDS ==================== //
info!("Saving vc inference record...");
info!("Saving record...");
// let (inference_result_token, id) = insert_voice_conversion_result(InsertArgs {
// pool: &args.job_dependencies.mysql_pool,
// job: &job,
// public_bucket_hash: result_bucket_location.get_object_hash(),
// file_size_bytes: file_metadata.file_size_bytes,
// duration_millis: file_metadata.duration_millis.unwrap_or(0),
// is_on_prem: args.job_dependencies.container.is_on_prem,
// worker_hostname: &args.job_dependencies.container.hostname,
// worker_cluster: &args.job_dependencies.container.cluster_name,
// is_debug_worker: args.job_dependencies.worker_details.is_debug_worker
// })
// .await
// .map_err(|e| ProcessSingleJobError::Other(e))?;
let (inference_result_token, id) = insert_media_file_from_voice_conversion(InsertMediaFileArgs {
let (inference_result_token, id) = insert_media_file_generic(InsertArgs {
pool: &args.job_dependencies.db.mysql_pool,
job: &job,
voice_conversion_type: VoiceConversionModelType::SoVitsSvc,
media_type: MediaFileType::Mocap,
origin_category: MediaFileOriginCategory::Processed,
origin_product_category: MediaFileOriginProductCategory::Mocap,
maybe_origin_model_type: None,
maybe_origin_model_token: None,
maybe_origin_filename: None,
maybe_mime_type: maybe_mimetype.as_deref(),
file_size_bytes: file_metadata.file_size_bytes,
duration_millis: file_metadata.duration_millis.unwrap_or(0),
sha256_checksum: &file_checksum,
file_size_bytes,
maybe_duration_millis: None,
maybe_audio_encoding: None,
maybe_video_encoding: None,
maybe_frame_width: None,
maybe_frame_height: None,
public_bucket_directory_hash: result_bucket_location.get_object_hash(),
maybe_public_bucket_prefix: Some(BUCKET_FILE_PREFIX),
maybe_public_bucket_prefix: None,
maybe_public_bucket_extension: Some(BUCKET_FILE_EXTENSION),
is_on_prem: args.job_dependencies.job.info.container.is_on_prem,
worker_hostname: &args.job_dependencies.job.info.container.hostname,
worker_cluster: &args.job_dependencies.job.info.container.cluster_name,
extra_file_modification_info: None,
maybe_creator_user_token: None,
maybe_creator_anonymous_visitor_token: None,
creator_ip_address: &job.creator_ip_address,
creator_set_visibility: job.creator_set_visibility,
maybe_creator_file_synthetic_id_category: IdCategory::MediaFile,
maybe_creator_category_synthetic_id_category: IdCategory::MediaFile,
maybe_mod_user_token: None,
is_batch_generated: false,
is_generated_on_prem: false,
checksum_sha2: &file_checksum,
generated_by_worker: Some(&args.job_dependencies.job.info.container.hostname),
generated_by_cluster: Some(&args.job_dependencies.job.info.container.cluster_name),
})
.await
.map_err(|e| ProcessSingleJobError::Other(e))?;
info!("VC Done.");
// TODO: Update upstream to be strongly typed
let maybe_user_token = job.maybe_creator_user_token.as_deref()
.map(|token| UserToken::new_from_str(token));
args.job_dependencies.clients.firehose_publisher.vc_inference_finished(
maybe_user_token.as_ref(),
&job.inference_job_token,
inference_result_token.as_str())
.await
.map_err(|e| {
error!("error publishing event: {:?}", e);
ProcessSingleJobError::Other(anyhow!("error publishing event"))
})?;
//args.job_dependencies.clients.firehose_publisher.vc_inference_finished(
// maybe_user_token.as_ref(),
// &job.inference_job_token,
// inference_result_token.as_str())
// .await
// .map_err(|e| {
// error!("error publishing event: {:?}", e);
// ProcessSingleJobError::Other(anyhow!("error publishing event"))
// })?;
job_progress_reporter.log_status("done")
.map_err(|e| ProcessSingleJobError::Other(e))?;
info!("Job {:?} complete success! Downloaded, ran inference, and uploaded. Saved model record: {}, Result Token: {}",
info!("Job {:?} complete success! Downloaded, executed successfully, and uploaded. Saved record: {}, Result Token: {}",
job.id, id, &inference_result_token);
*/
Ok(JobSuccessResult {
//maybe_result_entity: Some(ResultEntity {
// entity_type: InferenceResultType::MediaFile,
// entity_token: inference_result_token.to_string(),
//}),
maybe_result_entity: None,
inference_duration,
maybe_result_entity: Some(ResultEntity {
entity_type: InferenceResultType::MediaFile,
entity_token: inference_result_token.to_string(),
}),
inference_duration: execution_duration,
})
}
#[derive(Deserialize, Default)]
struct FileMetadata {
pub duration_millis: Option<u64>,
pub mimetype: Option<String>,
pub file_size_bytes: u64,
}