fallback + error handling in fal handlers

This commit is contained in:
Brandon Thomas committed 2026-04-12 05:20:24 -04:00
1 parent 6b5f296048
commit 0cc7a9b434
5 files changed
+322 -113

No files matched your search

@@ -1,12 +1,11 @@
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
use crate::http_server::endpoints::webhooks::process_success::resolve_file_metadata::resolve_file_metadata;
use crate::state::server_state::ServerState;
use crate::util::http_download_url_to_bytes::http_download_url_to_bytes;
use anyhow::anyhow;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use errors::AnyhowResult;
use fal_client::webhook_api::hydrated::hydrated_webhook_contents::ImageData;
use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes;
use images::encoding::webp_bytes_to_png_bytes::webp_bytes_to_png_bytes;
@@ -23,15 +22,21 @@ pub async fn process_image_payload(
image_data: &ImageData,
job: &FalJobDetails,
server_state: &ServerState,
) -> AnyhowResult<MediaFileToken> {
) -> Result<MediaFileToken, AdvancedCommonWebError> {
let image_url = image_data.url
.as_deref()
.ok_or_else(|| anyhow!("no `url` in image payload"))?;
.ok_or_else(|| {
warn!("No `url` in image payload");
AdvancedCommonWebError::server_error_with_message("no `url` in image payload")
})?;
// Download with a retry if the first attempt returns suspiciously few bytes.
let mut file_bytes = http_download_url_to_bytes(image_url)
.await
.map_err(|e| anyhow!("Failed to download image: {:?}", e))?;
.map_err(|err| {
warn!("Failed to download image from {}: {:?}", image_url, err);
AdvancedCommonWebError::from_anyhow_error(err)
})?;
if file_bytes.len() <= 10 {
warn!(
@@ -41,16 +46,24 @@ pub async fn process_image_payload(
);
file_bytes = http_download_url_to_bytes(image_url)
.await
.map_err(|e| anyhow!("Failed to download image on retry: {:?}", e))?;
.map_err(|err| {
warn!("Failed to download image on retry from {}: {:?}", image_url, err);
AdvancedCommonWebError::from_anyhow_error(err)
})?;
}
// Resolve mime type: magic bytes first, fal content_type as fallback.
let metadata = resolve_file_metadata(&file_bytes, image_data.content_type.as_deref())
.ok_or_else(|| anyhow!(
"Could not determine file type for image (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
image_data.content_type,
))?;
.ok_or_else(|| {
warn!(
"Could not determine file type for image (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
image_data.content_type,
);
AdvancedCommonWebError::server_error_with_message(
&format!("Could not determine file type for image (bytes: {}, fal content_type: {:?})",
file_bytes.len(), image_data.content_type))
})?;
info!("File type: {}, extension: {:?}, source: {:?}",
metadata.mime_type, metadata.file_extension, metadata.source);
@@ -59,10 +72,18 @@ pub async fn process_image_payload(
FileExtension::Webp => {
info!("Artcraft can't handle WebP images yet; converting to PNG...");
let converted = webp_bytes_to_png_bytes(&file_bytes)?;
let converted = webp_bytes_to_png_bytes(&file_bytes)
.map_err(|e| {
warn!("Failed to convert WebP to PNG: {:?}", e);
AdvancedCommonWebError::from_error(e)
})?;
let converted_metadata = resolve_file_metadata(&converted, Some("image/png"))
.ok_or_else(|| anyhow!("Failed to determine file type after WebP→PNG conversion"))?;
.ok_or_else(|| {
warn!("Failed to determine file type after WebP→PNG conversion");
AdvancedCommonWebError::server_error_with_message(
"Failed to determine file type after WebP→PNG conversion")
})?;
info!("Converted file type: {}, extension: {:?}",
converted_metadata.mime_type, converted_metadata.file_extension);
@@ -93,16 +114,28 @@ async fn upload_single_image_bytes(
file_bytes: &[u8],
mime_type: &str,
file_extension: FileExtension,
) -> AnyhowResult<MediaFileToken> {
) -> Result<MediaFileToken, AdvancedCommonWebError> {
let media_file_type = MediaFileType::try_from_mime_type(mime_type)
.ok_or_else(|| anyhow!("Unsupported media file type: {}", mime_type))?;
.ok_or_else(|| {
warn!("Unsupported media file type: {}", mime_type);
AdvancedCommonWebError::server_error_with_message(
&format!("Unsupported media file type: {}", mime_type))
})?;
let extension_with_period = file_extension.extension_with_period();
let file_size_bytes = file_bytes.len();
let file_hash = sha256_hash_bytes(&file_bytes)?;
let image_info = ImageInfo::decode_image_from_bytes(&file_bytes)?;
let file_hash = sha256_hash_bytes(&file_bytes)
.map_err(|e| {
warn!("Failed to hash image bytes: {:?}", e);
AdvancedCommonWebError::from_anyhow_error(e)
})?;
let image_info = ImageInfo::decode_image_from_bytes(&file_bytes)
.map_err(|e| {
warn!("Failed to decode image info: {:?}", e);
AdvancedCommonWebError::from_error(e)
})?;
let public_upload_path = MediaFileBucketPath::generate_new(PREFIX, Some(&extension_with_period));
@@ -112,7 +145,11 @@ async fn upload_single_image_bytes(
public_upload_path.get_full_object_path_str(),
file_bytes.as_ref(),
mime_type)
.await?;
.await
.map_err(|e| {
warn!("Failed to upload image to bucket: {:?}", e);
AdvancedCommonWebError::from_anyhow_error(e)
})?;
let media_token = MediaFileInsertBuilder::new()
.maybe_creator_user(job.maybe_creator_user_token.as_ref())
@@ -130,8 +167,12 @@ async fn upload_single_image_bytes(
.frame_height(image_info.height())
.checksum_sha2(&file_hash)
.insert_pool(&server_state.mysql_pool)
.await?;
.await
.map_err(|err| {
warn!("Failed to insert image media file record: {:?}", err);
AdvancedCommonWebError::from_error(err)
})?;
info!("Image media file uploaded with token: {}", media_token);
Ok(media_token)
@@ -1,12 +1,11 @@
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
use crate::http_server::endpoints::webhooks::process_success::resolve_file_metadata::resolve_file_metadata;
use crate::state::server_state::ServerState;
use crate::util::http_download_url_to_bytes::http_download_url_to_bytes;
use anyhow::anyhow;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use errors::AnyhowResult;
use fal_client::webhook_api::hydrated::hydrated_webhook_contents::ImagesData;
use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes;
use images::encoding::webp_bytes_to_png_bytes::webp_bytes_to_png_bytes;
@@ -24,7 +23,7 @@ pub async fn process_images_payload(
images_data: &[ImagesData],
job: &FalJobDetails,
server_state: &ServerState,
) -> AnyhowResult<(Option<MediaFileToken>, Option<BatchGenerationToken>)> {
) -> Result<(Option<MediaFileToken>, Option<BatchGenerationToken>), AdvancedCommonWebError> {
let mut maybe_media_token = None;
@@ -52,15 +51,22 @@ async fn upload_image(
server_state: &ServerState,
image: &ImagesData,
maybe_batch_token: Option<&BatchGenerationToken>,
) -> AnyhowResult<MediaFileToken> {
) -> Result<MediaFileToken, AdvancedCommonWebError> {
let image_url = image.url
.as_deref()
.ok_or_else(|| anyhow!("no `url` in image payload"))?;
.ok_or_else(|| {
warn!("No `url` in image payload");
AdvancedCommonWebError::server_error_with_message("no `url` in image payload")
})?;
// Download with a retry if the first attempt returns suspiciously few bytes.
let mut file_bytes = http_download_url_to_bytes(image_url)
.await
.map_err(|e| anyhow!("Failed to download image: {:?}", e))?;
.map_err(|e| {
warn!("Failed to download image from {}: {:?}", image_url, e);
AdvancedCommonWebError::server_error_with_message(
&format!("Failed to download image: {:?}", e))
})?;
if file_bytes.len() <= 10 {
warn!(
@@ -70,16 +76,25 @@ async fn upload_image(
);
file_bytes = http_download_url_to_bytes(image_url)
.await
.map_err(|e| anyhow!("Failed to download image on retry: {:?}", e))?;
.map_err(|e| {
warn!("Failed to download image on retry from {}: {:?}", image_url, e);
AdvancedCommonWebError::server_error_with_message(
&format!("Failed to download image on retry: {:?}", e))
})?;
}
// Resolve mime type: magic bytes first, fal content_type as fallback.
let metadata = resolve_file_metadata(&file_bytes, image.content_type.as_deref())
.ok_or_else(|| anyhow!(
"Could not determine file type for image (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
image.content_type,
))?;
.ok_or_else(|| {
warn!(
"Could not determine file type for image (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
image.content_type,
);
AdvancedCommonWebError::server_error_with_message(
&format!("Could not determine file type for image (bytes: {}, fal content_type: {:?})",
file_bytes.len(), image.content_type))
})?;
info!("File type: {}, extension: {:?}, source: {:?}",
metadata.mime_type, metadata.file_extension, metadata.source);
@@ -88,10 +103,18 @@ async fn upload_image(
FileExtension::Webp => {
info!("Artcraft can't handle WebP images yet; converting to PNG...");
let converted = webp_bytes_to_png_bytes(&file_bytes)?;
let converted = webp_bytes_to_png_bytes(&file_bytes)
.map_err(|e| {
warn!("Failed to convert WebP to PNG: {:?}", e);
AdvancedCommonWebError::from_error(e)
})?;
let converted_metadata = resolve_file_metadata(&converted, Some("image/png"))
.ok_or_else(|| anyhow!("Failed to determine file type after WebP→PNG conversion"))?;
.ok_or_else(|| {
warn!("Failed to determine file type after WebP→PNG conversion");
AdvancedCommonWebError::server_error_with_message(
"Failed to determine file type after WebP→PNG conversion")
})?;
info!("Converted file type: {}, extension: {:?}",
converted_metadata.mime_type, converted_metadata.file_extension);
@@ -99,7 +122,6 @@ async fn upload_image(
upload_image_bytes(
job,
server_state,
image,
&converted,
&converted_metadata.mime_type,
converted_metadata.file_extension,
@@ -110,7 +132,6 @@ async fn upload_image(
upload_image_bytes(
job,
server_state,
image,
&file_bytes,
&metadata.mime_type,
metadata.file_extension,
@@ -123,23 +144,34 @@ async fn upload_image(
async fn upload_image_bytes(
job: &FalJobDetails,
server_state: &ServerState,
_image: &ImagesData,
file_bytes: &[u8],
mime_type: &str,
file_extension: FileExtension,
maybe_batch_token: Option<&BatchGenerationToken>,
) -> AnyhowResult<MediaFileToken> {
) -> Result<MediaFileToken, AdvancedCommonWebError> {
let media_file_type = MediaFileType::try_from_mime_type(mime_type)
.ok_or_else(|| anyhow!("Unsupported media file type: {}", mime_type))?;
.ok_or_else(|| {
warn!("Unsupported media file type: {}", mime_type);
AdvancedCommonWebError::server_error_with_message(
&format!("Unsupported media file type: {}", mime_type))
})?;
info!("MediaFileType: {:?}", media_file_type);
let extension_with_period = file_extension.extension_with_period();
let file_size_bytes = file_bytes.len();
let file_hash = sha256_hash_bytes(&file_bytes)?;
let image_info = ImageInfo::decode_image_from_bytes(&file_bytes)?;
let file_hash = sha256_hash_bytes(&file_bytes)
.map_err(|e| {
warn!("Failed to hash image bytes: {:?}", e);
AdvancedCommonWebError::from_anyhow_error(e)
})?;
let image_info = ImageInfo::decode_image_from_bytes(&file_bytes)
.map_err(|e| {
warn!("Failed to decode image info: {:?}", e);
AdvancedCommonWebError::from_error(e)
})?;
let public_upload_path = MediaFileBucketPath::generate_new(PREFIX, Some(&extension_with_period));
@@ -149,7 +181,11 @@ async fn upload_image_bytes(
public_upload_path.get_full_object_path_str(),
file_bytes.as_ref(),
mime_type)
.await?;
.await
.map_err(|e| {
warn!("Failed to upload image to bucket: {:?}", e);
AdvancedCommonWebError::from_anyhow_error(e)
})?;
let mut query_builder = MediaFileInsertBuilder::new()
.maybe_creator_user(job.maybe_creator_user_token.as_ref())
@@ -173,7 +209,11 @@ async fn upload_image_bytes(
let media_token = query_builder
.insert_pool(&server_state.mysql_pool)
.await?;
.await
.map_err(|e| {
warn!("Failed to insert image media file record: {:?}", e);
AdvancedCommonWebError::from_error(e)
})?;
info!("Image media file uploaded with token: {} ; possible batch token: {:?}",
media_token, maybe_batch_token);
@@ -1,13 +1,12 @@
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
use crate::http_server::endpoints::webhooks::process_success::resolve_file_metadata::resolve_file_metadata;
use crate::state::server_state::ServerState;
use crate::util::http_download_url_to_bytes::http_download_url_to_bytes;
use anyhow::anyhow;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use errors::AnyhowResult;
use fal_client::webhook_api::hydrated::hydrated_webhook_contents::{ModelGlbData, ThumbnailData};
use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes;
use log::{info, warn};
@@ -20,7 +19,7 @@ const PREFIX : Option<&str> = Some("artcraft_");
/*
Example payload, from Hunyuan 3d 3.0,
{
"model_glb": {
"url": "https://v3b.fal.media/files/b/0a8afeff/8uc6VVLTpmVrupLqxxwOg_model.glb",
@@ -52,7 +51,7 @@ const PREFIX : Option<&str> = Some("artcraft_");
},
"seed": null
}
*/
pub async fn process_model_glb_payload(
@@ -60,15 +59,21 @@ pub async fn process_model_glb_payload(
maybe_thumbnail_data: Option<&ThumbnailData>,
job: &FalJobDetails,
server_state: &ServerState,
) -> AnyhowResult<MediaFileToken> {
) -> Result<MediaFileToken, AdvancedCommonWebError> {
let mesh_url = model_glb_data.url
.as_deref()
.ok_or_else(|| anyhow!("no `url` in model glb payload"))?;
.ok_or_else(|| {
warn!("No `url` in model glb payload");
AdvancedCommonWebError::server_error_with_message("no `url` in model glb payload")
})?;
// Download with a retry if the first attempt returns suspiciously few bytes.
let mut file_bytes = http_download_url_to_bytes(mesh_url)
.await
.map_err(|e| anyhow!("Failed to download mesh: {:?}", e))?;
.map_err(|err| {
warn!("Failed to download mesh from {}: {:?}", mesh_url, err);
AdvancedCommonWebError::from_anyhow_error(err)
})?;
if file_bytes.len() <= 10 {
warn!(
@@ -78,28 +83,45 @@ pub async fn process_model_glb_payload(
);
file_bytes = http_download_url_to_bytes(mesh_url)
.await
.map_err(|e| anyhow!("Failed to download mesh on retry: {:?}", e))?;
.map_err(|e| {
warn!("Failed to download mesh on retry from {}: {:?}", mesh_url, e);
AdvancedCommonWebError::server_error_with_message(
&format!("Failed to download mesh on retry: {:?}", e))
})?;
}
// Resolve mime type: magic bytes first, fal content_type as fallback.
let metadata = resolve_file_metadata(&file_bytes, model_glb_data.content_type.as_deref())
.ok_or_else(|| anyhow!(
"Could not determine file type for mesh (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
model_glb_data.content_type,
))?;
.ok_or_else(|| {
warn!(
"Could not determine file type for mesh (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
model_glb_data.content_type,
);
AdvancedCommonWebError::server_error_with_message(
&format!("Could not determine file type for mesh (bytes: {}, fal content_type: {:?})",
file_bytes.len(), model_glb_data.content_type))
})?;
let mime_type = metadata.mime_type.as_str();
info!("Mime type of mesh: {}, source: {:?}", mime_type, metadata.source);
let media_file_type = MediaFileType::try_from_mime_type(mime_type)
.ok_or_else(|| anyhow!("Unsupported media file type: {}", mime_type))?;
.ok_or_else(|| {
warn!("Unsupported media file type: {}", mime_type);
AdvancedCommonWebError::server_error_with_message(
&format!("Unsupported media file type: {}", mime_type))
})?;
let extension_with_period = metadata.file_extension.extension_with_period();
let file_size_bytes = file_bytes.len();
let file_hash = sha256_hash_bytes(&file_bytes)?;
let file_hash = sha256_hash_bytes(&file_bytes)
.map_err(|e| {
warn!("Failed to hash mesh bytes: {:?}", e);
AdvancedCommonWebError::from_anyhow_error(e)
})?;
let public_upload_path = MediaFileBucketPath::generate_new(PREFIX, Some(&extension_with_period));
@@ -109,7 +131,11 @@ pub async fn process_model_glb_payload(
public_upload_path.get_full_object_path_str(),
file_bytes.as_ref(),
&mime_type)
.await?;
.await
.map_err(|e| {
warn!("Failed to upload mesh to bucket: {:?}", e);
AdvancedCommonWebError::from_anyhow_error(e)
})?;
let media_token = MediaFileInsertBuilder::new()
.maybe_creator_user(job.maybe_creator_user_token.as_ref())
@@ -126,17 +152,21 @@ pub async fn process_model_glb_payload(
.maybe_prompt_token(job.maybe_prompt_token.as_ref())
.checksum_sha2(&file_hash)
.insert_pool(&server_state.mysql_pool)
.await?;
.await
.map_err(|e| {
warn!("Failed to insert glb media file record: {:?}", e);
AdvancedCommonWebError::from_error(e)
})?;
info!("Glb media file uploaded with token: {}", media_token);
let result = try_to_attach_thumbnail(
maybe_thumbnail_data,
job,
server_state,
&media_token,
).await;
// NB: Fail open
if let Err(err) = result {
warn!("Could not attach thumbnail as cover image to media file: {:?}", err);
@@ -150,20 +180,31 @@ async fn try_to_attach_thumbnail(
job: &FalJobDetails,
server_state: &ServerState,
glb_media_token: &MediaFileToken,
) -> AnyhowResult<()> {
) -> Result<(), AdvancedCommonWebError> {
let thumbnail_data = maybe_thumbnail_data
.ok_or_else(|| anyhow!("no thumbnail data in extracted contents"))?;
.ok_or_else(|| {
warn!("No thumbnail data in extracted contents");
AdvancedCommonWebError::server_error_with_message(
"no thumbnail data in extracted contents")
})?;
info!("Fal Thumbnail Data: {:?}", thumbnail_data);
let thumbnail_url = thumbnail_data.url
.as_deref()
.ok_or_else(|| anyhow!("no `url` in thumbnail payload"))?;
.ok_or_else(|| {
warn!("No `url` in thumbnail payload");
AdvancedCommonWebError::server_error_with_message("no `url` in thumbnail payload")
})?;
// Download with a retry if the first attempt returns suspiciously few bytes.
let mut file_bytes = http_download_url_to_bytes(thumbnail_url)
.await
.map_err(|e| anyhow!("Failed to download thumbnail image: {:?}", e))?;
.map_err(|e| {
warn!("Failed to download thumbnail image from {}: {:?}", thumbnail_url, e);
AdvancedCommonWebError::server_error_with_message(
&format!("Failed to download thumbnail image: {:?}", e))
})?;
if file_bytes.len() <= 10 {
warn!(
@@ -173,28 +214,45 @@ async fn try_to_attach_thumbnail(
);
file_bytes = http_download_url_to_bytes(thumbnail_url)
.await
.map_err(|e| anyhow!("Failed to download thumbnail on retry: {:?}", e))?;
.map_err(|e| {
warn!("Failed to download thumbnail on retry from {}: {:?}", thumbnail_url, e);
AdvancedCommonWebError::server_error_with_message(
&format!("Failed to download thumbnail on retry: {:?}", e))
})?;
}
// Resolve mime type: magic bytes first, fal content_type as fallback.
let metadata = resolve_file_metadata(&file_bytes, thumbnail_data.content_type.as_deref())
.ok_or_else(|| anyhow!(
"Could not determine file type for thumbnail (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
thumbnail_data.content_type,
))?;
.ok_or_else(|| {
warn!(
"Could not determine file type for thumbnail (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
thumbnail_data.content_type,
);
AdvancedCommonWebError::server_error_with_message(
&format!("Could not determine file type for thumbnail (bytes: {}, fal content_type: {:?})",
file_bytes.len(), thumbnail_data.content_type))
})?;
let mime_type = metadata.mime_type.as_str();
info!("Mime type of thumbnail image: {}, source: {:?}", mime_type, metadata.source);
let media_file_type = MediaFileType::try_from_mime_type(mime_type)
.ok_or_else(|| anyhow!("Unsupported media file type: {}", mime_type))?;
.ok_or_else(|| {
warn!("Unsupported thumbnail media file type: {}", mime_type);
AdvancedCommonWebError::server_error_with_message(
&format!("Unsupported media file type: {}", mime_type))
})?;
let extension_with_period = metadata.file_extension.extension_with_period();
let file_size_bytes = file_bytes.len();
let file_hash = sha256_hash_bytes(&file_bytes)?;
let file_hash = sha256_hash_bytes(&file_bytes)
.map_err(|e| {
warn!("Failed to hash thumbnail bytes: {:?}", e);
AdvancedCommonWebError::from_anyhow_error(e)
})?;
let public_upload_path = MediaFileBucketPath::generate_new(PREFIX, Some(&extension_with_period));
@@ -204,7 +262,11 @@ async fn try_to_attach_thumbnail(
public_upload_path.get_full_object_path_str(),
file_bytes.as_ref(),
&mime_type)
.await?;
.await
.map_err(|e| {
warn!("Failed to upload thumbnail to bucket: {:?}", e);
AdvancedCommonWebError::from_anyhow_error(e)
})?;
let thumbnail_media_token = MediaFileInsertBuilder::new()
.maybe_creator_user(job.maybe_creator_user_token.as_ref())
@@ -222,7 +284,11 @@ async fn try_to_attach_thumbnail(
.is_intermediate_system_file(true)
.checksum_sha2(&file_hash)
.insert_pool(&server_state.mysql_pool)
.await?;
.await
.map_err(|e| {
warn!("Failed to insert thumbnail media file record: {:?}", e);
AdvancedCommonWebError::from_error(e)
})?;
let query_result = set_media_file_cover_image(UpdateArgs {
media_file_token: glb_media_token,
@@ -230,5 +296,9 @@ async fn try_to_attach_thumbnail(
mysql_pool: &server_state.mysql_pool,
}).await;
if let Err(err) = query_result {
warn!("Failed to set cover image on media file {}: {:?}", glb_media_token, err);
}
Ok(())
}
@@ -1,13 +1,12 @@
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
use crate::http_server::endpoints::webhooks::process_success::resolve_file_metadata::resolve_file_metadata;
use crate::state::server_state::ServerState;
use crate::util::http_download_url_to_bytes::http_download_url_to_bytes;
use anyhow::anyhow;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use errors::AnyhowResult;
use fal_client::webhook_api::hydrated::hydrated_webhook_contents::ModelMeshData;
use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes;
use log::{info, warn};
@@ -21,15 +20,21 @@ pub async fn process_model_mesh_payload(
model_mesh_data: &ModelMeshData,
job: &FalJobDetails,
server_state: &ServerState,
) -> AnyhowResult<MediaFileToken> {
) -> Result<MediaFileToken, AdvancedCommonWebError> {
let mesh_url = model_mesh_data.url
.as_deref()
.ok_or_else(|| anyhow!("no `url` in model mesh payload"))?;
.ok_or_else(|| {
warn!("No `url` in model mesh payload");
AdvancedCommonWebError::server_error_with_message("no `url` in model mesh payload")
})?;
// Download with a retry if the first attempt returns suspiciously few bytes.
let mut file_bytes = http_download_url_to_bytes(mesh_url)
.await
.map_err(|e| anyhow!("Failed to download mesh: {:?}", e))?;
.map_err(|err| {
warn!("Failed to download mesh from {}: {:?}", mesh_url, err);
AdvancedCommonWebError::from_anyhow_error(err)
})?;
if file_bytes.len() <= 10 {
warn!(
@@ -39,28 +44,44 @@ pub async fn process_model_mesh_payload(
);
file_bytes = http_download_url_to_bytes(mesh_url)
.await
.map_err(|e| anyhow!("Failed to download mesh on retry: {:?}", e))?;
.map_err(|err| {
warn!("Failed to download mesh on retry from {}: {:?}", mesh_url, err);
AdvancedCommonWebError::from_anyhow_error(err)
})?;
}
// Resolve mime type: magic bytes first, fal content_type as fallback.
let metadata = resolve_file_metadata(&file_bytes, model_mesh_data.content_type.as_deref())
.ok_or_else(|| anyhow!(
"Could not determine file type for mesh (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
model_mesh_data.content_type,
))?;
.ok_or_else(|| {
warn!(
"Could not determine file type for mesh (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
model_mesh_data.content_type,
);
AdvancedCommonWebError::server_error_with_message(
&format!("Could not determine file type for mesh (bytes: {}, fal content_type: {:?})",
file_bytes.len(), model_mesh_data.content_type))
})?;
let mime_type = metadata.mime_type.as_str();
info!("Mime type of mesh: {}, source: {:?}", mime_type, metadata.source);
let media_file_type = MediaFileType::try_from_mime_type(mime_type)
.ok_or_else(|| anyhow!("Unsupported media file type: {}", mime_type))?;
.ok_or_else(|| {
warn!("Unsupported media file type: {}", mime_type);
AdvancedCommonWebError::server_error_with_message(
&format!("Unsupported media file type: {}", mime_type))
})?;
let extension_with_period = metadata.file_extension.extension_with_period();
let file_size_bytes = file_bytes.len();
let file_hash = sha256_hash_bytes(&file_bytes)?;
let file_hash = sha256_hash_bytes(&file_bytes)
.map_err(|err| {
warn!("Failed to hash mesh bytes: {:?}", err);
AdvancedCommonWebError::from_anyhow_error(err)
})?;
let public_upload_path = MediaFileBucketPath::generate_new(PREFIX, Some(&extension_with_period));
@@ -70,7 +91,11 @@ pub async fn process_model_mesh_payload(
public_upload_path.get_full_object_path_str(),
file_bytes.as_ref(),
&mime_type)
.await?;
.await
.map_err(|err| {
warn!("Failed to upload mesh to bucket: {:?}", err);
AdvancedCommonWebError::from_anyhow_error(err)
})?;
let media_token = MediaFileInsertBuilder::new()
.maybe_creator_user(job.maybe_creator_user_token.as_ref())
@@ -87,8 +112,12 @@ pub async fn process_model_mesh_payload(
.maybe_prompt_token(job.maybe_prompt_token.as_ref())
.checksum_sha2(&file_hash)
.insert_pool(&server_state.mysql_pool)
.await?;
.await
.map_err(|err| {
warn!("Failed to insert mesh media file record: {:?}", err);
AdvancedCommonWebError::from_error(err)
})?;
info!("Mesh media file uploaded with token: {}", media_token);
Ok(media_token)
@@ -1,12 +1,11 @@
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
use crate::http_server::endpoints::webhooks::process_success::resolve_file_metadata::resolve_file_metadata;
use crate::state::server_state::ServerState;
use crate::util::http_download_url_to_bytes::http_download_url_to_bytes;
use anyhow::anyhow;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use errors::AnyhowResult;
use fal_client::webhook_api::hydrated::hydrated_webhook_contents::VideoData;
use filesys::path_to_string::path_to_string;
use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes;
@@ -25,15 +24,21 @@ pub async fn process_video_payload(
video_data: &VideoData,
job: &FalJobDetails,
server_state: &ServerState,
) -> AnyhowResult<MediaFileToken> {
) -> Result<MediaFileToken, AdvancedCommonWebError> {
let video_url = video_data.url
.as_deref()
.ok_or_else(|| anyhow!("no `url` in video payload"))?;
.ok_or_else(|| {
warn!("No `url` in video payload");
AdvancedCommonWebError::server_error_with_message("no `url` in video payload")
})?;
// Download with a retry if the first attempt returns suspiciously few bytes.
let mut file_bytes = http_download_url_to_bytes(video_url)
.await
.map_err(|e| anyhow!("Failed to download video: {:?}", e))?;
.map_err(|err| {
warn!("Failed to download video from {}: {:?}", video_url, err);
AdvancedCommonWebError::from_anyhow_error(err)
})?;
if file_bytes.len() <= 10 {
warn!(
@@ -43,16 +48,24 @@ pub async fn process_video_payload(
);
file_bytes = http_download_url_to_bytes(video_url)
.await
.map_err(|e| anyhow!("Failed to download video on retry: {:?}", e))?;
.map_err(|err| {
warn!("Failed to download video on retry from {}: {:?}", video_url, err);
AdvancedCommonWebError::from_anyhow_error(err)
})?;
}
// Resolve mime type: magic bytes first, fal content_type as fallback.
let metadata = resolve_file_metadata(&file_bytes, video_data.content_type.as_deref())
.ok_or_else(|| anyhow!(
"Could not determine file type for video (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
video_data.content_type,
))?;
.ok_or_else(|| {
warn!(
"Could not determine file type for video (bytes: {}, fal content_type: {:?})",
file_bytes.len(),
video_data.content_type,
);
AdvancedCommonWebError::server_error_with_message(
&format!("Could not determine file type for video (bytes: {}, fal content_type: {:?})",
file_bytes.len(), video_data.content_type))
})?;
info!("File type: {}, extension: {:?}, source: {:?}",
metadata.mime_type, metadata.file_extension, metadata.source);
@@ -60,26 +73,34 @@ pub async fn process_video_payload(
let mime_type = metadata.mime_type.as_str();
let media_file_type = MediaFileType::try_from_mime_type(mime_type)
.ok_or_else(|| anyhow!("Unsupported media file type: {}", mime_type))?;
.ok_or_else(|| {
warn!("Unsupported media file type: {}", mime_type);
AdvancedCommonWebError::server_error_with_message(
&format!("Unsupported media file type: {}", mime_type))
})?;
let extension_with_period = metadata.file_extension.extension_with_period();
let file_size_bytes = file_bytes.len();
let file_hash = sha256_hash_bytes(&file_bytes)?;
let file_hash = sha256_hash_bytes(&file_bytes)
.map_err(|e| {
warn!("Failed to hash video bytes: {:?}", e);
AdvancedCommonWebError::from_anyhow_error(e)
})?;
let mut maybe_duration_millis = None;
let mut maybe_frame_width = None;
let mut maybe_frame_height = None;
if let Ok(mut file) = NamedTempFile::new() {
// NB: Fallible
// NB: Fallible
let _r = file.write_all(&file_bytes);
if let Ok(video_info) = ffprobe_get_info(&file.path()) {
maybe_duration_millis = video_info.duration
.as_ref()
.map(|duration| duration.millis as u64);
maybe_frame_width = video_info.dimensions
.as_ref()
.map(|ref dims| dims.width as u32);
@@ -98,7 +119,11 @@ pub async fn process_video_payload(
public_upload_path.get_full_object_path_str(),
file_bytes.as_ref(),
&mime_type)
.await?;
.await
.map_err(|e| {
warn!("Failed to upload video to bucket: {:?}", e);
AdvancedCommonWebError::from_anyhow_error(e)
})?;
let media_token = MediaFileInsertBuilder::new()
.maybe_creator_user(job.maybe_creator_user_token.as_ref())
@@ -117,8 +142,12 @@ pub async fn process_video_payload(
.checksum_sha2(&file_hash)
.maybe_prompt_token(job.maybe_prompt_token.as_ref())
.insert_pool(&server_state.mysql_pool)
.await?;
.await
.map_err(|e| {
warn!("Failed to insert video media file record: {:?}", e);
AdvancedCommonWebError::from_error(e)
})?;
info!("Video media file uploaded with token: {}", media_token);
let thumbnail_task_result =