From 45338beeef3731390b762bcd29f76d1a612ccf49 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 17 Apr 2026 02:15:31 -0400 Subject: [PATCH] refactor execution --- .../generate/video/execute/execute_fal.rs | 53 +++++++++ .../video/execute/execute_generation.rs | 61 ++++++++++ .../execute_kinovi.rs} | 112 +++--------------- .../omni_gen/generate/video/execute/mod.rs | 3 + .../endpoints/omni_gen/generate/video/mod.rs | 2 +- .../video/omni_gen_video_generate_handler.rs | 7 +- 6 files changed, 136 insertions(+), 102 deletions(-) create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_fal.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_generation.rs rename crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/{execute_generation.rs => execute/execute_kinovi.rs} (71%) create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/mod.rs diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_fal.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_fal.rs new file mode 100644 index 0000000000..f1f7d5acba --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_fal.rs @@ -0,0 +1,53 @@ +//! Fal provider execution path. + +use log::warn; + +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; +use artcraft_router::generate::generate_video::generate_video_response::GenerateVideoResponse; +use enums::common::generation::common_generation_mode::CommonGenerationMode; + +use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError; +use crate::state::server_state::ServerState; + +use super::super::distill_video_request::DistilledVideoRequest; +use super::execute_generation::GenerationResult; + +/// Execute generation via Fal (the existing path). +pub(super) async fn execute_generation_fal( + distilled: &DistilledVideoRequest, + request: &OmniGenVideoCostAndGenerateRequest, + server_state: &ServerState, +) -> Result { + let fal_client = artcraft_router::client::router_fal_client::RouterFalClient::new( + server_state.fal.api_key.clone(), + server_state.fal.webhook_url.clone(), + ); + let router_client = artcraft_router::client::router_client::RouterClient::Fal(fal_client); + + let generation_response = distilled.plan().generate_video(&router_client) + .await + .map_err(|e| { + warn!("Video generation failed (Fal): {:?}", e); + AdvancedCommonWebError::from_error(e) + })?; + + let external_job_id = match &generation_response { + GenerateVideoResponse::Artcraft(p) => p.inference_job_token.as_str().to_string(), + GenerateVideoResponse::Muapi(p) => p.request_id.as_str().to_string(), + GenerateVideoResponse::Seedance2Pro(p) => p.order_id.clone(), + GenerateVideoResponse::Fal(p) => p.request_id.clone().unwrap_or_default(), + }; + + let generation_mode = if request.start_frame_image_media_token.is_some() { + CommonGenerationMode::Keyframe + } else { + CommonGenerationMode::Text + }; + + Ok(GenerationResult { + external_job_id, + is_seedance2pro: false, + maybe_seedance_order_ids: None, + generation_mode, + }) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_generation.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_generation.rs new file mode 100644 index 0000000000..d134147573 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_generation.rs @@ -0,0 +1,61 @@ +//! Main entrypoint for video generation execution. +//! +//! Routes to the appropriate provider (Fal or Seedance2Pro/Kinovi) +//! based on the distilled request's `execution_provider`. + +use std::collections::HashMap; + +use url::Url; + +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; +use artcraft_router::api::provider::Provider; +use enums::common::generation::common_generation_mode::CommonGenerationMode; +use tokens::tokens::media_files::MediaFileToken; + +use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError; +use crate::state::server_state::ServerState; + +use super::super::distill_video_request::DistilledVideoRequest; +use super::execute_fal::execute_generation_fal; +use super::execute_kinovi::execute_generation_kinovi; + +/// Result of a successful generation, regardless of provider. +pub struct GenerationResult { + /// The external job ID used to track the generation (first order_id for Seedance). + pub external_job_id: String, + + /// Whether this is a Seedance2Pro generation (changes DB insertion path). + pub is_seedance2pro: bool, + + /// For Seedance2Pro batch jobs, the list of all order IDs. + pub maybe_seedance_order_ids: Option>, + + /// The generation mode (Text, Keyframe, Reference). + pub generation_mode: CommonGenerationMode, +} + +/// Execute video generation via the appropriate provider. +pub async fn execute_generation( + distilled: &DistilledVideoRequest, + request: &OmniGenVideoCostAndGenerateRequest, + server_state: &ServerState, + media_file_hydration_map: Option<&HashMap>, +) -> Result { + match distilled.execution_provider { + Provider::Seedance2Pro => { + execute_generation_kinovi( + distilled, + request, + server_state, + media_file_hydration_map, + ).await + } + _ => { + execute_generation_fal( + distilled, + request, + server_state, + ).await + } + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute_generation.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_kinovi.rs similarity index 71% rename from crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute_generation.rs rename to crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_kinovi.rs index 94673cc2a6..4c655830ab 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute_generation.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/execute_kinovi.rs @@ -1,7 +1,7 @@ -//! Execution helpers for the omni-gen video generation handler. +//! Seedance2Pro / Kinovi provider execution path. //! -//! Routes generation to the appropriate provider (Fal or Seedance2Pro/Kinovi) -//! based on the distilled request's `execution_provider`. +//! Downloads all referenced media from our CDN, re-uploads to Seedance2Pro's +//! CDN, then calls the Seedance2Pro generate endpoint. use std::collections::HashMap; @@ -9,14 +9,12 @@ use log::{info, warn}; use url::Url; use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; -use artcraft_router::api::provider::Provider; -use artcraft_router::generate::generate_video::generate_video_response::GenerateVideoResponse; use enums::common::generation::common_aspect_ratio::CommonAspectRatio; use enums::common::generation::common_generation_mode::CommonGenerationMode; use enums::common::generation::common_video_model::CommonVideoModel; use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession; use seedance2pro_client::requests::generate_video::generate_video::{ - generate_video, KinoviBatchCount, GenerateVideoArgs, KinoviModelType, KinoviResolution, + generate_video, GenerateVideoArgs, KinoviBatchCount, KinoviModelType, KinoviResolution, }; use seedance2pro_client::requests::prepare_file_upload::prepare_file_upload::{ prepare_file_upload, PrepareFileUploadArgs, @@ -31,95 +29,11 @@ use crate::http_server::common_responses::advanced_common_web_error::AdvancedCom use crate::state::server_state::ServerState; use crate::util::http_download_url_to_bytes::http_download_url_to_bytes; -use super::distill_video_request::DistilledVideoRequest; +use super::super::distill_video_request::DistilledVideoRequest; +use super::execute_generation::GenerationResult; -/// Result of a successful generation, regardless of provider. -pub struct GenerationResult { - /// The external job ID used to track the generation (first order_id for Seedance). - pub external_job_id: String, - - /// Whether this is a Seedance2Pro generation (changes DB insertion path). - pub is_seedance2pro: bool, - - /// For Seedance2Pro batch jobs, the list of all order IDs. - pub maybe_seedance_order_ids: Option>, - - /// The generation mode (Text, Keyframe, Reference). - pub generation_mode: CommonGenerationMode, -} - -/// Execute video generation via the appropriate provider. -pub async fn execute_generation( - distilled: &DistilledVideoRequest, - request: &OmniGenVideoCostAndGenerateRequest, - server_state: &ServerState, - media_file_hydration_map: Option<&HashMap>, -) -> Result { - match distilled.execution_provider { - Provider::Seedance2Pro => { - execute_generation_kinovi( - distilled, - request, - server_state, - media_file_hydration_map, - ).await - } - _ => { - execute_generation_fal( - distilled, - request, - server_state, - ).await - } - } -} - -/// Execute generation via Fal (the existing path). -async fn execute_generation_fal( - distilled: &DistilledVideoRequest, - request: &OmniGenVideoCostAndGenerateRequest, - server_state: &ServerState, -) -> Result { - let fal_client = artcraft_router::client::router_fal_client::RouterFalClient::new( - server_state.fal.api_key.clone(), - server_state.fal.webhook_url.clone(), - ); - let router_client = artcraft_router::client::router_client::RouterClient::Fal(fal_client); - - let generation_response = distilled.plan().generate_video(&router_client) - .await - .map_err(|e| { - warn!("Video generation failed (Fal): {:?}", e); - AdvancedCommonWebError::from_error(e) - })?; - - let external_job_id = match &generation_response { - GenerateVideoResponse::Artcraft(p) => p.inference_job_token.as_str().to_string(), - GenerateVideoResponse::Muapi(p) => p.request_id.as_str().to_string(), - GenerateVideoResponse::Seedance2Pro(p) => p.order_id.clone(), - GenerateVideoResponse::Fal(p) => p.request_id.clone().unwrap_or_default(), - }; - - let generation_mode = if request.start_frame_image_media_token.is_some() { - CommonGenerationMode::Keyframe - } else { - CommonGenerationMode::Text - }; - - Ok(GenerationResult { - external_job_id, - is_seedance2pro: false, - maybe_seedance_order_ids: None, - generation_mode, - }) -} - -/// Execute generation via Seedance2Pro/Kinovi. -/// -/// Downloads all referenced media from our CDN, re-uploads to Seedance2Pro's -/// CDN, then calls the Seedance2Pro generate endpoint. -async fn execute_generation_kinovi( - distilled: &DistilledVideoRequest, +pub(super) async fn execute_generation_kinovi( + _distilled: &DistilledVideoRequest, request: &OmniGenVideoCostAndGenerateRequest, server_state: &ServerState, media_file_hydration_map: Option<&HashMap>, @@ -128,6 +42,8 @@ async fn execute_generation_kinovi( server_state.seedance2pro.cookies.clone() ); + // TODO(bt): Move this logic to `artcraft_router` as an intermediate step between plan and execute. + let empty_map = HashMap::new(); let hydration_map = media_file_hydration_map.unwrap_or(&empty_map); @@ -159,6 +75,8 @@ async fn execute_generation_kinovi( || request.reference_video_media_tokens.as_ref().map_or(false, |t| !t.is_empty()) || request.reference_audio_media_tokens.as_ref().map_or(false, |t| !t.is_empty()); + // TODO(bt): Move this logic to `artcraft_router` to the `execute` phase. + let generation_mode = if is_keyframe { CommonGenerationMode::Keyframe } else if is_reference { @@ -169,9 +87,9 @@ async fn execute_generation_kinovi( // Map aspect ratio / duration / batch from the request. let resolution = map_common_aspect_ratio_to_kinovi_resolution(request.aspect_ratio); - + let duration_seconds = request.duration_seconds.unwrap_or(5).clamp(4, 15) as u8; - + let batch_count = match request.video_batch_count { Some(2) => KinoviBatchCount::Two, Some(4) => KinoviBatchCount::Four, @@ -313,7 +231,7 @@ async fn upload_url_to_seedance2pro( Ok(upload_result.public_url) } -/// Map a CommonAspectRatio to the Seedance2Pro Resolution enum. +/// Map a CommonAspectRatio to the Seedance2Pro KinoviResolution enum. fn map_common_aspect_ratio_to_kinovi_resolution(aspect_ratio: Option) -> KinoviResolution { match aspect_ratio { Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => KinoviResolution::Landscape16x9, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/mod.rs new file mode 100644 index 0000000000..b4f3645e64 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/execute/mod.rs @@ -0,0 +1,3 @@ +mod execute_fal; +mod execute_kinovi; +pub mod execute_generation; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/mod.rs index 5511c5297c..f9e82fe0b9 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/mod.rs @@ -1,7 +1,7 @@ pub mod omni_gen_video_generate_handler; pub mod distill_video_request; pub(crate) mod distill_helper; -mod execute_generation; +mod execute; #[cfg(test)] mod tests; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs index 8e0dcb3060..f4e6685eb6 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs @@ -10,10 +10,10 @@ use url::Url; use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; use artcraft_api_defs::omni_gen::generate_response::omni_gen_video_generate_response::OmniGenVideoGenerateResponse; use artcraft_router::api::provider::Provider; -use enums::common::generation::common_video_model::CommonVideoModel; use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType; use enums::by_table::prompts::prompt_type::PromptType; use enums::common::generation::common_model_type::CommonModelType; +use enums::common::generation::common_video_model::CommonVideoModel; use enums::common::generation_provider::GenerationProvider; use enums::common::visibility::Visibility; use http_server_common::request::get_request_ip::get_request_ip; @@ -37,13 +37,12 @@ use tokens::tokens::media_files::MediaFileToken; use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error; use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError; use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test; +use crate::http_server::endpoints::omni_gen::generate::video::distill_video_request::distill_video_request; +use crate::http_server::endpoints::omni_gen::generate::video::execute::execute_generation::execute_generation; use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; use crate::state::server_state::ServerState; use crate::util::lookup::lookup_image_urls_as_map::lookup_image_urls_as_map; -use super::distill_video_request::distill_video_request; -use super::execute_generation::execute_generation; - /// Generate a video using the omni-gen unified endpoint. #[utoipa::path( post,