mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-09 11:31:27 +00:00
fbx2gltf should work - just needs endpoint
This commit is contained in:
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>>,
|
||||
|
||||
+105
-150
@@ -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,
|
||||
}
|
||||
Reference in new issue
Block a user