mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-07 18:42:10 +00:00
model downloader trait wip
This commit is contained in:
1 parent
e41f72305b
commit
dd7bd71587
9 files changed
+508
-20
No files matched your search
Generated
+1
@@ -2831,6 +2831,7 @@ dependencies = [
|
||||
"actix-web 4.0.1",
|
||||
"actix_helpers",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bootstrap",
|
||||
"buckets",
|
||||
"chrono",
|
||||
|
||||
@@ -77,3 +77,9 @@ actix-web = "4.0.0-beta.10"
|
||||
serde = "1.0.125"
|
||||
serde_derive = "1.0.125"
|
||||
serde_json = "1.0.64"
|
||||
|
||||
# Async traits
|
||||
# NB: note: `async` trait functions are not currently supported
|
||||
# The initial round of stabilizations for the async/await language feature in Rust 1.39 did not
|
||||
# include support for async fn in traits.
|
||||
async-trait = "0.1.58"
|
||||
+7
-19
@@ -1,31 +1,20 @@
|
||||
use anyhow::anyhow;
|
||||
use crate::job::job_loop::job_success_result::JobSuccessResult;
|
||||
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
|
||||
use crate::job::job_types::vc::rvc_v2::process_job::RvcV2ProcessJobArgs;
|
||||
use crate::job::job_types::vc::so_vits_svc::process_job::SoVitsSvcProcessJobArgs;
|
||||
use crate::job::job_types::vc::{rvc_v2, so_vits_svc};
|
||||
use crate::job::job_types::lipsync::sad_talker::process_job::SadTalkerProcessJobArgs;
|
||||
use crate::job::job_types::lipsync::sad_talker;
|
||||
use crate::job_dependencies::JobDependencies;
|
||||
use enums::by_table::voice_conversion_models::voice_conversion_model_type::VoiceConversionModelType;
|
||||
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
|
||||
use mysql_queries::queries::voice_conversion::inference::get_voice_conversion_model_for_inference::get_voice_conversion_model_for_inference;
|
||||
use std::time::Duration;
|
||||
use log::{error, info};
|
||||
use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType;
|
||||
use mysql_queries::queries::media_uploads::get_media_upload_for_inference::get_media_upload_for_inference;
|
||||
use tokens::files::media_upload::MediaUploadToken;
|
||||
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
|
||||
|
||||
pub async fn process_single_lipsync_job(job_dependencies: &JobDependencies, job: &AvailableInferenceJob) -> Result<JobSuccessResult, ProcessSingleJobError> {
|
||||
|
||||
let job_success_result = match job.maybe_model_type {
|
||||
Some(InferenceModelType::SadTalker) => {
|
||||
//rvc_v2::process_job::process_job(RvcV2ProcessJobArgs {
|
||||
// job_dependencies,
|
||||
// job,
|
||||
// vc_model: &vc_model,
|
||||
// media_upload_token: &media_upload_token,
|
||||
// media_upload: &media_upload,
|
||||
//}).await?
|
||||
return Err(ProcessSingleJobError::Other(anyhow!("not yet implemented")))
|
||||
sad_talker::process_job::process_job(SadTalkerProcessJobArgs {
|
||||
job_dependencies,
|
||||
job,
|
||||
}).await?
|
||||
}
|
||||
Some(model_type) => return Err(ProcessSingleJobError::Other(anyhow!("wrong model type: {:?}", model_type))),
|
||||
None => return Err(ProcessSingleJobError::Other(anyhow!("no model type in record"))),
|
||||
@@ -33,4 +22,3 @@ pub async fn process_single_lipsync_job(job_dependencies: &JobDependencies, job:
|
||||
|
||||
Ok(job_success_result)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod process_job;
|
||||
pub mod model_downloaders;
|
||||
Whitespace-only changes.
@@ -0,0 +1,362 @@
|
||||
use anyhow::anyhow;
|
||||
use buckets::public::media_uploads::original_file::MediaUploadOriginalFilePath;
|
||||
use buckets::public::voice_conversion_results::original_file::VoiceConversionResultOriginalFilePath;
|
||||
use container_common::filesystem::check_file_exists::check_file_exists;
|
||||
use container_common::filesystem::safe_delete_temp_directory::safe_delete_temp_directory;
|
||||
use container_common::filesystem::safe_delete_temp_file::safe_delete_temp_file;
|
||||
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::vc::rvc_v2::rvc_v2_inference_command::InferenceArgs;
|
||||
use crate::job_dependencies::JobDependencies;
|
||||
use crate::util::maybe_download_file_from_bucket::maybe_download_file_from_bucket;
|
||||
use enums::by_table::generic_inference_jobs::inference_result_type::InferenceResultType;
|
||||
use filesys::create_dir_all_if_missing::create_dir_all_if_missing;
|
||||
use filesys::file_size::file_size;
|
||||
use log::{error, info, warn};
|
||||
use media::decode_basic_audio_info::decode_basic_audio_file_info;
|
||||
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::voice_conversion::results::insert_voice_conversion_result::{insert_voice_conversion_result, InsertArgs};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
use tokens::users::user::UserToken;
|
||||
|
||||
pub struct SadTalkerProcessJobArgs<'a> {
|
||||
pub job_dependencies: &'a JobDependencies,
|
||||
pub job: &'a AvailableInferenceJob,
|
||||
}
|
||||
|
||||
pub async fn process_job(args: SadTalkerProcessJobArgs<'_>) -> Result<JobSuccessResult, ProcessSingleJobError> {
|
||||
let job = args.job;
|
||||
|
||||
/*
|
||||
let mut job_progress_reporter = args.job_dependencies
|
||||
.job_progress_reporter
|
||||
.new_generic_inference(job.inference_job_token.as_str())
|
||||
.map_err(|e| ProcessSingleJobError::Other(anyhow!(e)))?;
|
||||
|
||||
// ==================== DOWNLOAD HUBERT ==================== //
|
||||
|
||||
info!("Download RVC hubert model (if not present)...");
|
||||
|
||||
args.job_dependencies.pretrained_models.rvc_v2_hubert.download_if_not_on_filesystem(
|
||||
&args.job_dependencies.private_bucket_client,
|
||||
&args.job_dependencies.fs.scoped_temp_dir_creator_for_downloads)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("could not download hubert: {:?}", e);
|
||||
ProcessSingleJobError::from_anyhow_error(e)
|
||||
})?;
|
||||
|
||||
// ==================== CONFIRM OR DOWNLOAD RVC (v2) MODEL ==================== //
|
||||
|
||||
//info!("Download RVC model (if not present)...");
|
||||
|
||||
let rvc_v2_model_fs_path = {
|
||||
let filename = format!("{}.pt", vc_model.token.as_str());
|
||||
let fs_path = args.job_dependencies.fs.semi_persistent_cache.voice_conversion_model_path(&filename);
|
||||
|
||||
create_dir_all_if_missing(args.job_dependencies.fs.semi_persistent_cache.voice_conversion_model_directory())
|
||||
.map_err(|e| {
|
||||
error!("could not create model storage directory: {:?}", e);
|
||||
ProcessSingleJobError::from_io_error(e)
|
||||
})?;
|
||||
|
||||
let model_object_path = args.job_dependencies.bucket_path_unifier.rvc_v2_model_path(&vc_model.private_bucket_hash);
|
||||
|
||||
maybe_download_file_from_bucket(
|
||||
"rvc (v2) model",
|
||||
&fs_path,
|
||||
&model_object_path,
|
||||
&args.job_dependencies.private_bucket_client,
|
||||
&mut job_progress_reporter,
|
||||
"downloading rvc (v2) model",
|
||||
job.id.0,
|
||||
&args.job_dependencies.fs.scoped_temp_dir_creator_for_downloads,
|
||||
).await?;
|
||||
|
||||
fs_path
|
||||
};
|
||||
|
||||
// ==================== CONFIRM OR DOWNLOAD RVC (v2) MODEL INDICES ==================== //
|
||||
|
||||
// Index files are optional
|
||||
let mut maybe_rvc_v2_model_index_fs_path: Option<PathBuf> = None;
|
||||
|
||||
if vc_model.has_index_file {
|
||||
info!("Download RVC index (if not present)...");
|
||||
|
||||
maybe_rvc_v2_model_index_fs_path = {
|
||||
let filename = format!("{}.index", vc_model.token.as_str());
|
||||
let fs_path = args.job_dependencies.fs.semi_persistent_cache.voice_conversion_model_path(&filename);
|
||||
|
||||
create_dir_all_if_missing(args.job_dependencies.fs.semi_persistent_cache.voice_conversion_model_directory())
|
||||
.map_err(|e| {
|
||||
error!("could not create model storage directory: {:?}", e);
|
||||
ProcessSingleJobError::from_io_error(e)
|
||||
})?;
|
||||
|
||||
let model_index_object_path = args.job_dependencies.bucket_path_unifier.rvc_v2_model_index_path(&vc_model.private_bucket_hash);
|
||||
|
||||
maybe_download_file_from_bucket(
|
||||
"rvc (v2) model index",
|
||||
&fs_path,
|
||||
&model_index_object_path,
|
||||
&args.job_dependencies.private_bucket_client,
|
||||
&mut job_progress_reporter,
|
||||
"downloading rvc (v2) model index",
|
||||
job.id.0,
|
||||
&args.job_dependencies.fs.scoped_temp_dir_creator_for_downloads,
|
||||
).await?;
|
||||
|
||||
Some(fs_path)
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== TEMP DIR ==================== //
|
||||
|
||||
let work_temp_dir = format!("temp_rvc_v2_inference_{}", job.id.0);
|
||||
|
||||
// NB: TempDir exists until it goes out of scope, at which point it should delete from filesystem.
|
||||
let work_temp_dir = args.job_dependencies
|
||||
.fs
|
||||
.scoped_temp_dir_creator_for_work
|
||||
.new_tempdir(&work_temp_dir)
|
||||
.map_err(|e| ProcessSingleJobError::from_io_error(e))?;
|
||||
|
||||
// ==================== DOWNLOAD MEDIA FILE ==================== //
|
||||
|
||||
info!("Download media for RVC voice conversion...");
|
||||
|
||||
// TODO: If already transcoded, download the transcoded file instead.
|
||||
// TODO: Turn this into a general utility.
|
||||
|
||||
let original_media_upload_fs_path = {
|
||||
let original_media_upload_fs_path = work_temp_dir.path().join("original.bin");
|
||||
|
||||
let media_upload_bucket_path =
|
||||
MediaUploadOriginalFilePath::from_object_hash(&args.media_upload.public_bucket_directory_hash);
|
||||
|
||||
let bucket_object_path = media_upload_bucket_path.to_full_object_pathbuf();
|
||||
|
||||
info!("Downloading media from bucket path: {:?}", &bucket_object_path);
|
||||
|
||||
maybe_download_file_from_bucket(
|
||||
"media upload (original file)",
|
||||
&original_media_upload_fs_path,
|
||||
&bucket_object_path,
|
||||
&args.job_dependencies.public_bucket_client,
|
||||
&mut job_progress_reporter,
|
||||
"downloading",
|
||||
job.id.0,
|
||||
&args.job_dependencies.fs.scoped_temp_dir_creator_for_work,
|
||||
).await?;
|
||||
|
||||
original_media_upload_fs_path
|
||||
};
|
||||
|
||||
// ==================== TRANSCODE MEDIA (IF NECESSARY) ==================== //
|
||||
|
||||
// TODO
|
||||
|
||||
// ==================== SETUP FOR INFERENCE ==================== //
|
||||
|
||||
info!("Ready for RVC (v2) inference...");
|
||||
|
||||
job_progress_reporter.log_status("running inference")
|
||||
.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 output_metadata_fs_path = temp_dir.path().join("metadata.json");
|
||||
//let output_spectrogram_fs_path = temp_dir.path().join("spectrogram.json");
|
||||
|
||||
info!("Running RVC (v2) 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();
|
||||
|
||||
// ==================== RUN INFERENCE SCRIPT ==================== //
|
||||
|
||||
let inference_start_time = Instant::now();
|
||||
|
||||
let command_exit_status = args.job_dependencies
|
||||
.job_type_details
|
||||
.rvc_v2
|
||||
.inference_command
|
||||
.execute_inference(InferenceArgs {
|
||||
model_path: &rvc_v2_model_fs_path,
|
||||
maybe_model_index_path: maybe_rvc_v2_model_index_fs_path,
|
||||
hubert_path: &args.job_dependencies.pretrained_models.rvc_v2_hubert.filesystem_path,
|
||||
input_path: &input_wav_path,
|
||||
output_path: &output_audio_fs_path,
|
||||
});
|
||||
|
||||
let inference_duration = Instant::now().duration_since(inference_start_time);
|
||||
|
||||
info!("Inference took duration to complete: {:?}", &inference_duration);
|
||||
|
||||
if !command_exit_status.is_success() {
|
||||
error!("Inference failed: {:?}", command_exit_status);
|
||||
safe_delete_temp_file(&input_wav_path);
|
||||
safe_delete_temp_file(&output_audio_fs_path);
|
||||
safe_delete_temp_directory(&work_temp_dir);
|
||||
return Err(ProcessSingleJobError::Other(anyhow!("CommandExitStatus: {:?}", command_exit_status)));
|
||||
}
|
||||
|
||||
// ==================== CHECK ALL FILES EXIST AND GET METADATA ==================== //
|
||||
|
||||
info!("Checking that output files exist...");
|
||||
|
||||
check_file_exists(&output_audio_fs_path).map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
//check_file_exists(&output_metadata_fs_path).map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
//check_file_exists(&output_spectrogram_fs_path).map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
info!("Interrogating result file properties...");
|
||||
|
||||
let file_size_bytes = file_size(&output_audio_fs_path)
|
||||
.map_err(|err| ProcessSingleJobError::Other(err))?;
|
||||
|
||||
let maybe_mimetype = get_mimetype_for_file(&output_audio_fs_path)
|
||||
.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!");
|
||||
}
|
||||
|
||||
// TODO: Make a new python image that generates spectrograms from any audio file.
|
||||
|
||||
let file_metadata = FileMetadata {
|
||||
duration_millis: audio_info.duration_millis,
|
||||
mimetype: maybe_mimetype,
|
||||
file_size_bytes,
|
||||
};
|
||||
|
||||
//safe_delete_temp_file(&output_metadata_fs_path);
|
||||
|
||||
// ==================== 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);
|
||||
|
||||
info!("Uploading audio...");
|
||||
|
||||
args.job_dependencies.public_bucket_client.upload_filename_with_content_type(
|
||||
&result_bucket_object_pathbuf,
|
||||
&output_audio_fs_path,
|
||||
"audio/wav")
|
||||
.await
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
safe_delete_temp_file(&output_audio_fs_path);
|
||||
|
||||
// // ==================== UPLOAD SPECTROGRAM TO BUCKETS ==================== //
|
||||
//
|
||||
// let spectrogram_result_object_path = args.job_dependencies.bucket_path_unifier.tts_inference_spectrogram_output_path(
|
||||
// &job.uuid_idempotency_token); // TODO: Don't use this!
|
||||
//
|
||||
// info!("Spectrogram destination bucket path: {:?}", &spectrogram_result_object_path);
|
||||
//
|
||||
// info!("Uploading spectrogram...");
|
||||
//
|
||||
// args.job_dependencies.public_bucket_client.upload_filename_with_content_type(
|
||||
// &spectrogram_result_object_path,
|
||||
// &output_spectrogram_fs_path,
|
||||
// "application/json")
|
||||
// .await
|
||||
// .map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
//
|
||||
// safe_delete_temp_file(&output_spectrogram_fs_path);
|
||||
|
||||
// ==================== DELETE DOWNLOADED FILE ==================== //
|
||||
|
||||
// NB: We should be using a tempdir, but to make absolutely certain we don't overflow the disk...
|
||||
safe_delete_temp_directory(&work_temp_dir);
|
||||
|
||||
// ==================== SAVE RECORDS ==================== //
|
||||
|
||||
info!("Saving vc inference 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))?;
|
||||
|
||||
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.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: {}",
|
||||
job.id, id, &inference_result_token);
|
||||
|
||||
Ok(JobSuccessResult {
|
||||
maybe_result_entity: Some(ResultEntity {
|
||||
entity_type: InferenceResultType::VoiceConversion,
|
||||
entity_token: inference_result_token.to_string(),
|
||||
}),
|
||||
inference_duration,
|
||||
})
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// TODO - REMOVE -
|
||||
let inference_start_time = Instant::now();
|
||||
let inference_duration = Instant::now().duration_since(inference_start_time);
|
||||
Ok(JobSuccessResult {
|
||||
maybe_result_entity: Some(ResultEntity {
|
||||
entity_type: InferenceResultType::VoiceConversion,
|
||||
entity_token: "todo".to_string(),
|
||||
}),
|
||||
inference_duration,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
struct FileMetadata {
|
||||
pub duration_millis: Option<u64>,
|
||||
pub mimetype: Option<String>,
|
||||
pub file_size_bytes: u64,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod audiowmark;
|
||||
pub mod scoped_execution;
|
||||
pub mod maybe_download_file_from_bucket;
|
||||
pub mod model_downloader;
|
||||
pub mod scoped_execution;
|
||||
pub mod scoped_temp_dir_creator;
|
||||
mod from_env;
|
||||
@@ -0,0 +1,126 @@
|
||||
use anyhow::anyhow;
|
||||
use cloud_storage::bucket_client::BucketClient;
|
||||
use container_common::filesystem::safe_delete_temp_directory::safe_delete_temp_directory;
|
||||
use crate::util::scoped_temp_dir_creator::ScopedTempDirCreator;
|
||||
use errors::AnyhowResult;
|
||||
use filesys::create_dir_all_if_missing::create_dir_all_if_missing;
|
||||
use filesys::file_exists::file_exists;
|
||||
use log::{error, info};
|
||||
use std::path::{Path, PathBuf};
|
||||
use async_trait::async_trait;
|
||||
use filesys::rename_across_devices::rename_across_devices;
|
||||
|
||||
// /// Implementing this implies we can return an instance constructed from environment variables.
|
||||
// /// This isn't to be manually used, but rather with a macro.
|
||||
// trait FromEnv {
|
||||
// fn from_env() -> Box<dyn Self>;
|
||||
// }
|
||||
|
||||
|
||||
#[async_trait]
|
||||
trait ModelDownloader {
|
||||
//fn from_env() -> Self {
|
||||
// // NB: For now rvc-v2 is all that uses this hubert, but other models
|
||||
// // may (re)use it in the future.
|
||||
// let cloud_bucket_path = easyenv::get_env_string_or_default(
|
||||
// "RVC_V2_PRETRAINED_HUBERT_BUCKET_PATH",
|
||||
// "/hubert_pretrained/rvc_v2_hubert_base.pt");
|
||||
|
||||
// // NB: For now rvc-v2 is all that uses this hubert, but other models
|
||||
// // may (re)use it in the future.
|
||||
// let filesystem_path = easyenv::get_env_pathbuf_or_default(
|
||||
// "RVC_V2_PRETRAINED_HUBERT_FILESYSTEM_PATH",
|
||||
// // NB: For now this path is on the shared SMB drive in GKE K8S
|
||||
// "/tmp/downloads/hubert/rvc_v2_hubert_base.pt");
|
||||
|
||||
// Self {
|
||||
// cloud_bucket_path,
|
||||
// filesystem_path,
|
||||
// }
|
||||
//}
|
||||
|
||||
fn get_model_name(&self) -> &str;
|
||||
|
||||
fn get_cloud_bucket_path(&self) -> &str;
|
||||
|
||||
fn get_filesystem_path(&self) -> &Path;
|
||||
|
||||
async fn download_if_not_on_filesystem(
|
||||
&self,
|
||||
bucket_client: &BucketClient,
|
||||
scoped_tempdir_creator: &ScopedTempDirCreator,
|
||||
) -> AnyhowResult<()> {
|
||||
let filesystem_path = self.get_filesystem_path();
|
||||
|
||||
if file_exists(filesystem_path) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(parent_directory_path) = filesystem_path.parent() {
|
||||
create_dir_all_if_missing(parent_directory_path)?;
|
||||
}
|
||||
|
||||
// NB: Download to temp directory to stop concurrent writes and race conditions from other
|
||||
// workers writing to a shared volume.
|
||||
// NB: TempDir exists until it goes out of scope, at which point it should delete from filesystem.
|
||||
let temp_dir = scoped_tempdir_creator.new_tempdir("model_download")
|
||||
.map_err(|e| anyhow!("problem creating tempdir: {:?}", e))?;
|
||||
|
||||
let temp_path = temp_dir.path().join("download.part");
|
||||
|
||||
let model_name = self.get_model_name();
|
||||
let cloud_bucket_path = self.get_cloud_bucket_path();
|
||||
|
||||
info!("Downloading {} from bucket path: {:?}", model_name, cloud_bucket_path);
|
||||
|
||||
bucket_client.download_file_to_disk(cloud_bucket_path, &temp_path)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("could not download {} to disk: {:?}", model_name, e);
|
||||
safe_delete_temp_directory(&temp_dir);
|
||||
anyhow!("couldn't download {} cloud object to disk: {:?}", model_name, e)
|
||||
})?;
|
||||
|
||||
info!("Downloaded {} from bucket", model_name);
|
||||
|
||||
info!("Renaming {} file from {:?} to {:?}!", model_name, &temp_path, filesystem_path);
|
||||
|
||||
rename_across_devices(&temp_path, filesystem_path)
|
||||
.map_err(|e| {
|
||||
error!("could not rename on disk: {:?}", e);
|
||||
safe_delete_temp_directory(&temp_dir);
|
||||
anyhow!("couldn't rename disk files: {:?}", e)
|
||||
})?;
|
||||
|
||||
info!("Finished downloading {} file to {:?}", model_name, filesystem_path);
|
||||
|
||||
safe_delete_temp_directory(&temp_dir);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
macro_rules! impl_model_downloader {
|
||||
($struct_name:ident) => {
|
||||
#[derive(Debug, Clone)]
|
||||
struct $struct_name {
|
||||
pub model_name: String,
|
||||
pub cloud_bucket_path: String,
|
||||
pub filesystem_path: PathBuf,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ModelDownloader for $struct_name {
|
||||
fn get_model_name(&self) -> &str {
|
||||
&self.model_name
|
||||
}
|
||||
fn get_cloud_bucket_path(&self) -> &str {
|
||||
&self.cloud_bucket_path
|
||||
}
|
||||
fn get_filesystem_path(&self) -> &Path {
|
||||
&self.filesystem_path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user