From 2e8a02cb7a47daf11e6eb2f0c35e40f806da0cbb Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 7 Apr 2026 15:53:41 -0400 Subject: [PATCH] kling generation --- .../estimate_video_cost_fal_kling_1_6_pro.rs | 18 ++ ...stimate_video_cost_fal_kling_2_1_master.rs | 18 ++ .../estimate_video_cost_fal_kling_2_1_pro.rs | 18 ++ ...mate_video_cost_fal_kling_2_5_turbo_pro.rs | 18 ++ .../estimate_video_cost_fal_kling_2_6_pro.rs | 26 +++ .../estimate_video_cost_fal_kling_3p0_pro.rs | 23 +++ ...imate_video_cost_fal_kling_3p0_standard.rs | 22 +++ .../generate/generate_video/cost/fal/mod.rs | 7 + .../fal/generate_video_fal_kling_1_6_pro.rs | 38 +++++ .../generate_video_fal_kling_2_1_master.rs | 33 ++++ .../fal/generate_video_fal_kling_2_1_pro.rs | 36 ++++ .../generate_video_fal_kling_2_5_turbo_pro.rs | 79 +++++++++ .../fal/generate_video_fal_kling_2_6_pro.rs | 79 +++++++++ .../fal/generate_video_fal_kling_3p0_pro.rs | 115 +++++++++++++ .../generate_video_fal_kling_3p0_standard.rs | 116 +++++++++++++ .../generate_video/execute/fal/mod.rs | 7 + .../generate_video/generate_video_request.rs | 14 ++ .../generate/generate_video/plan/fal/mod.rs | 7 + .../plan_generate_video_fal_kling_1_6_pro.rs | 122 ++++++++++++++ ...lan_generate_video_fal_kling_2_1_master.rs | 97 +++++++++++ .../plan_generate_video_fal_kling_2_1_pro.rs | 93 +++++++++++ ..._generate_video_fal_kling_2_5_turbo_pro.rs | 126 ++++++++++++++ .../plan_generate_video_fal_kling_2_6_pro.rs | 126 ++++++++++++++ .../plan_generate_video_fal_kling_3p0_pro.rs | 156 ++++++++++++++++++ ...n_generate_video_fal_kling_3p0_standard.rs | 40 +++++ .../generate_video/video_generation_plan.rs | 63 +++++++ .../src/configs/omni_gen/video_models.rs | 147 +++++++++++++++++ 27 files changed, 1644 insertions(+) create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_1_6_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_1_master.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_1_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_5_turbo_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_6_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_3p0_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_3p0_standard.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_1_6_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_1_master.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_1_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_5_turbo_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_6_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_3p0_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_3p0_standard.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_1_6_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_1_master.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_1_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_5_turbo_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_6_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_3p0_pro.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_3p0_standard.rs diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_1_6_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_1_6_pro.rs new file mode 100644 index 0000000000..38a76fcf03 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_1_6_pro.rs @@ -0,0 +1,18 @@ +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::PlanFalKling16Pro; +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; + +pub(crate) fn estimate_video_cost_fal_kling_1_6_pro( + plan: &PlanFalKling16Pro, +) -> VideoGenerationCostEstimate { + // Mirrors fal_client kling_v1p6_pro: 5s = 48¢, 10s = 95¢ (Default → 5s). + let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 95 } else { 48 }; + + VideoGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_1_master.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_1_master.rs new file mode 100644 index 0000000000..f5ae50db9c --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_1_master.rs @@ -0,0 +1,18 @@ +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_master::PlanFalKling21Master; +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; + +pub(crate) fn estimate_video_cost_fal_kling_2_1_master( + plan: &PlanFalKling21Master, +) -> VideoGenerationCostEstimate { + // Mirrors fal_client kling_v2p1_master: 5s = $1.40, 10s = $2.80. + let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 280 } else { 140 }; + + VideoGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_1_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_1_pro.rs new file mode 100644 index 0000000000..24fae129fc --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_1_pro.rs @@ -0,0 +1,18 @@ +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_pro::PlanFalKling21Pro; +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; + +pub(crate) fn estimate_video_cost_fal_kling_2_1_pro( + plan: &PlanFalKling21Pro, +) -> VideoGenerationCostEstimate { + // Mirrors fal_client kling_v2p1_pro: 5s = 45¢, 10s = 90¢. + let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 90 } else { 45 }; + + VideoGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_5_turbo_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_5_turbo_pro.rs new file mode 100644 index 0000000000..b94fac6db4 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_5_turbo_pro.rs @@ -0,0 +1,18 @@ +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_5_turbo_pro::PlanFalKling2p5TurboPro; +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; + +pub(crate) fn estimate_video_cost_fal_kling_2_5_turbo_pro( + plan: &PlanFalKling2p5TurboPro, +) -> VideoGenerationCostEstimate { + // Mirrors fal_client kling_v2p5_turbo_pro: 5s = 35¢, 10s = 70¢. None → 5s. + let cost_in_usd_cents: u64 = if plan.is_ten_seconds() { 70 } else { 35 }; + + VideoGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_6_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_6_pro.rs new file mode 100644 index 0000000000..a418d341bd --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_2_6_pro.rs @@ -0,0 +1,26 @@ +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_6_pro::PlanFalKling2p6Pro; +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; + +pub(crate) fn estimate_video_cost_fal_kling_2_6_pro( + plan: &PlanFalKling2p6Pro, +) -> VideoGenerationCostEstimate { + // Mirrors fal_client kling_v2p6_pro: + // audio off: $0.07/sec → 5s=35¢, 10s=70¢ + // audio on: $0.14/sec → 5s=70¢, 10s=140¢ + // Default duration is 5s; default generate_audio is true. + let cost_in_usd_cents: u64 = match (plan.generate_audio_for_cost(), plan.is_ten_seconds()) { + (false, false) => 35, + (false, true) => 70, + (true, false) => 70, + (true, true) => 140, + }; + + VideoGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_3p0_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_3p0_pro.rs new file mode 100644 index 0000000000..2ae5e5aec7 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_3p0_pro.rs @@ -0,0 +1,23 @@ +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_pro::PlanFalKling3p0Pro; +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; + +pub(crate) fn estimate_video_cost_fal_kling_3p0_pro( + plan: &PlanFalKling3p0Pro, +) -> VideoGenerationCostEstimate { + // Mirrors fal_client kling_3p0_pro: + // audio off: $0.224/sec (rate=224 in tenths-of-cents) + // audio on: $0.336/sec (rate=336) + // ceiling-divided to whole cents. + let seconds = plan.duration_seconds_for_cost(); + let rate: u64 = if plan.generate_audio_for_cost() { 336 } else { 224 }; + let cost_in_usd_cents = (rate * seconds + 9) / 10; + + VideoGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_3p0_standard.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_3p0_standard.rs new file mode 100644 index 0000000000..a538d6a662 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/estimate_video_cost_fal_kling_3p0_standard.rs @@ -0,0 +1,22 @@ +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_standard::PlanFalKling3p0Standard; +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; + +pub(crate) fn estimate_video_cost_fal_kling_3p0_standard( + plan: &PlanFalKling3p0Standard, +) -> VideoGenerationCostEstimate { + // Mirrors fal_client kling_3p0_standard: + // audio off: $0.168/sec (rate=168) + // audio on: $0.252/sec (rate=252) + let seconds = plan.duration_seconds_for_cost(); + let rate: u64 = if plan.generate_audio_for_cost() { 252 } else { 168 }; + let cost_in_usd_cents = (rate * seconds + 9) / 10; + + VideoGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/mod.rs index bd34d80da2..63f8cdb48c 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/cost/fal/mod.rs @@ -1,3 +1,10 @@ +pub(crate) mod estimate_video_cost_fal_kling_1_6_pro; +pub(crate) mod estimate_video_cost_fal_kling_2_1_master; +pub(crate) mod estimate_video_cost_fal_kling_2_1_pro; +pub(crate) mod estimate_video_cost_fal_kling_2_5_turbo_pro; +pub(crate) mod estimate_video_cost_fal_kling_2_6_pro; +pub(crate) mod estimate_video_cost_fal_kling_3p0_pro; +pub(crate) mod estimate_video_cost_fal_kling_3p0_standard; pub(crate) mod estimate_video_cost_fal_veo_2; pub(crate) mod estimate_video_cost_fal_veo_3; pub(crate) mod estimate_video_cost_fal_veo_3_fast; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_1_6_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_1_6_pro.rs new file mode 100644 index 0000000000..2f3639e9f3 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_1_6_pro.rs @@ -0,0 +1,38 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video_response::{ + FalVideoResponsePayload, GenerateVideoResponse, +}; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::PlanFalKling16Pro; +use fal_client::requests::webhook::video::image::enqueue_kling_v1p6_pro_image_to_video_webhook::{ + enqueue_kling_v1p6_pro_image_to_video_webhook, Kling1p6ProArgs, +}; + +pub async fn execute_fal_kling_1_6_pro( + plan: &PlanFalKling16Pro, + fal_client: &RouterFalClient, +) -> Result { + // The Fal arg type is generic over `Option` for the end frame URL — + // explicitly bind T to String here. + let end_frame_image_url: Option = plan.end_image_url.clone(); + + let args = Kling1p6ProArgs { + image_url: plan.image_url.as_str(), + end_frame_image_url, + webhook_url: fal_client.webhook_url.as_str(), + prompt: plan.prompt.as_str(), + api_key: &fal_client.api_key, + duration: plan.duration, + aspect_ratio: plan.aspect_ratio, + }; + + let webhook_response = enqueue_kling_v1p6_pro_image_to_video_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?; + + Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_1_master.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_1_master.rs new file mode 100644 index 0000000000..f34a9c387d --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_1_master.rs @@ -0,0 +1,33 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video_response::{ + FalVideoResponsePayload, GenerateVideoResponse, +}; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_master::PlanFalKling21Master; +use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_master_image_to_video_webhook::{ + enqueue_kling_v2p1_master_image_to_video_webhook, Kling2p1MasterArgs, +}; + +pub async fn execute_fal_kling_2_1_master( + plan: &PlanFalKling21Master, + fal_client: &RouterFalClient, +) -> Result { + let args = Kling2p1MasterArgs { + image_url: plan.image_url.as_str(), + webhook_url: fal_client.webhook_url.as_str(), + prompt: plan.prompt.as_str(), + api_key: &fal_client.api_key, + duration: plan.duration, + aspect_ratio: plan.aspect_ratio, + }; + + let webhook_response = enqueue_kling_v2p1_master_image_to_video_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?; + + Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_1_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_1_pro.rs new file mode 100644 index 0000000000..0ead4f170c --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_1_pro.rs @@ -0,0 +1,36 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video_response::{ + FalVideoResponsePayload, GenerateVideoResponse, +}; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_pro::PlanFalKling21Pro; +use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_pro_image_to_video_webhook::{ + enqueue_kling_v2p1_pro_image_to_video_webhook, Kling2p1ProArgs, +}; + +pub async fn execute_fal_kling_2_1_pro( + plan: &PlanFalKling21Pro, + fal_client: &RouterFalClient, +) -> Result { + let end_frame_image_url: Option = plan.end_image_url.clone(); + + let args = Kling2p1ProArgs { + image_url: plan.image_url.as_str(), + end_frame_image_url, + webhook_url: fal_client.webhook_url.as_str(), + prompt: plan.prompt.as_str(), + api_key: &fal_client.api_key, + duration: plan.duration, + aspect_ratio: plan.aspect_ratio, + }; + + let webhook_response = enqueue_kling_v2p1_pro_image_to_video_webhook(args) + .await + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?; + + Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_5_turbo_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_5_turbo_pro.rs new file mode 100644 index 0000000000..a276c1a8be --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_5_turbo_pro.rs @@ -0,0 +1,79 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video_response::{ + FalVideoResponsePayload, GenerateVideoResponse, +}; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_5_turbo_pro::{ + FalKling2p5TurboProAspectRatio, FalKling2p5TurboProDuration, FalKling2p5TurboProMode, + PlanFalKling2p5TurboPro, +}; +use fal_client::requests::webhook::video::image::enqueue_kling_v2p5_turbo_pro_image_to_video_webhook::{ + enqueue_kling_v2p5_turbo_pro_image_to_video_webhook, EnqueueKlingV2p5TurboProImageToVideoArgs, + EnqueueKlingV2p5TurboProImageToVideoDurationSeconds, +}; +use fal_client::requests::webhook::video::text::enqueue_kling_v2p5_turbo_pro_text_to_video_webhook::{ + enqueue_kling_v2p5_turbo_pro_text_to_video_webhook, EnqueueKlingV2p5TurboProTextToVideoArgs, + EnqueueKlingV2p5TurboProTextToVideoAspectRatio, EnqueueKlingV2p5TurboProTextToVideoDurationSeconds, +}; + +pub async fn execute_fal_kling_2_5_turbo_pro( + plan: &PlanFalKling2p5TurboPro, + fal_client: &RouterFalClient, +) -> Result { + let webhook_response = match &plan.mode { + FalKling2p5TurboProMode::TextToVideo => { + let args = EnqueueKlingV2p5TurboProTextToVideoArgs { + prompt: plan.prompt.clone(), + negative_prompt: plan.negative_prompt.clone(), + duration: plan.duration.map(to_t2v_duration), + aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio), + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_kling_v2p5_turbo_pro_text_to_video_webhook(args).await + } + FalKling2p5TurboProMode::ImageToVideo { image_url, end_image_url } => { + let args = EnqueueKlingV2p5TurboProImageToVideoArgs { + prompt: plan.prompt.clone(), + image_url: image_url.clone(), + tail_image_url: end_image_url.clone(), + negative_prompt: plan.negative_prompt.clone(), + duration: plan.duration.map(to_i2v_duration), + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_kling_v2p5_turbo_pro_image_to_video_webhook(args).await + } + }; + + let webhook_response = webhook_response + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?; + + Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} + +fn to_t2v_duration(d: FalKling2p5TurboProDuration) -> EnqueueKlingV2p5TurboProTextToVideoDurationSeconds { + match d { + FalKling2p5TurboProDuration::Five => EnqueueKlingV2p5TurboProTextToVideoDurationSeconds::Five, + FalKling2p5TurboProDuration::Ten => EnqueueKlingV2p5TurboProTextToVideoDurationSeconds::Ten, + } +} + +fn to_t2v_aspect_ratio(a: FalKling2p5TurboProAspectRatio) -> EnqueueKlingV2p5TurboProTextToVideoAspectRatio { + match a { + FalKling2p5TurboProAspectRatio::Square => EnqueueKlingV2p5TurboProTextToVideoAspectRatio::Square, + FalKling2p5TurboProAspectRatio::SixteenByNine => EnqueueKlingV2p5TurboProTextToVideoAspectRatio::SixteenByNine, + FalKling2p5TurboProAspectRatio::NineBySixteen => EnqueueKlingV2p5TurboProTextToVideoAspectRatio::NineBySixteen, + } +} + +fn to_i2v_duration(d: FalKling2p5TurboProDuration) -> EnqueueKlingV2p5TurboProImageToVideoDurationSeconds { + match d { + FalKling2p5TurboProDuration::Five => EnqueueKlingV2p5TurboProImageToVideoDurationSeconds::Five, + FalKling2p5TurboProDuration::Ten => EnqueueKlingV2p5TurboProImageToVideoDurationSeconds::Ten, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_6_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_6_pro.rs new file mode 100644 index 0000000000..6970b0daaa --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_2_6_pro.rs @@ -0,0 +1,79 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video_response::{ + FalVideoResponsePayload, GenerateVideoResponse, +}; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_6_pro::{ + FalKling2p6ProAspectRatio, FalKling2p6ProDuration, FalKling2p6ProMode, PlanFalKling2p6Pro, +}; +use fal_client::requests::webhook::video::image::enqueue_kling_v2p6_pro_image_to_video_webhook::{ + enqueue_kling_v2p6_pro_image_to_video_webhook, EnqueueKlingV2p6ProImageToVideoArgs, + EnqueueKlingV2p6ProImageToVideoDurationSeconds, +}; +use fal_client::requests::webhook::video::text::enqueue_kling_v2p6_pro_text_to_video_webhook::{ + enqueue_kling_v2p6_pro_text_to_video_webhook, EnqueueKlingV2p6ProTextToVideoArgs, + EnqueueKlingV2p6ProTextToVideoAspectRatio, EnqueueKlingV2p6ProTextToVideoDurationSeconds, +}; + +pub async fn execute_fal_kling_2_6_pro( + plan: &PlanFalKling2p6Pro, + fal_client: &RouterFalClient, +) -> Result { + let webhook_response = match &plan.mode { + FalKling2p6ProMode::TextToVideo => { + let args = EnqueueKlingV2p6ProTextToVideoArgs { + prompt: plan.prompt.clone(), + generate_audio: plan.generate_audio, + negative_prompt: plan.negative_prompt.clone(), + duration: plan.duration.map(to_t2v_duration), + aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio), + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_kling_v2p6_pro_text_to_video_webhook(args).await + } + FalKling2p6ProMode::ImageToVideo { image_url } => { + let args = EnqueueKlingV2p6ProImageToVideoArgs { + prompt: plan.prompt.clone(), + image_url: image_url.clone(), + generate_audio: plan.generate_audio, + negative_prompt: plan.negative_prompt.clone(), + duration: plan.duration.map(to_i2v_duration), + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_kling_v2p6_pro_image_to_video_webhook(args).await + } + }; + + let webhook_response = webhook_response + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?; + + Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} + +fn to_t2v_duration(d: FalKling2p6ProDuration) -> EnqueueKlingV2p6ProTextToVideoDurationSeconds { + match d { + FalKling2p6ProDuration::Five => EnqueueKlingV2p6ProTextToVideoDurationSeconds::Five, + FalKling2p6ProDuration::Ten => EnqueueKlingV2p6ProTextToVideoDurationSeconds::Ten, + } +} + +fn to_t2v_aspect_ratio(a: FalKling2p6ProAspectRatio) -> EnqueueKlingV2p6ProTextToVideoAspectRatio { + match a { + FalKling2p6ProAspectRatio::Square => EnqueueKlingV2p6ProTextToVideoAspectRatio::Square, + FalKling2p6ProAspectRatio::SixteenByNine => EnqueueKlingV2p6ProTextToVideoAspectRatio::SixteenByNine, + FalKling2p6ProAspectRatio::NineBySixteen => EnqueueKlingV2p6ProTextToVideoAspectRatio::NineBySixteen, + } +} + +fn to_i2v_duration(d: FalKling2p6ProDuration) -> EnqueueKlingV2p6ProImageToVideoDurationSeconds { + match d { + FalKling2p6ProDuration::Five => EnqueueKlingV2p6ProImageToVideoDurationSeconds::Five, + FalKling2p6ProDuration::Ten => EnqueueKlingV2p6ProImageToVideoDurationSeconds::Ten, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_3p0_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_3p0_pro.rs new file mode 100644 index 0000000000..aea96c5373 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_3p0_pro.rs @@ -0,0 +1,115 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video_response::{ + FalVideoResponsePayload, GenerateVideoResponse, +}; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_pro::{ + FalKling3p0AspectRatio, FalKling3p0Duration, FalKling3p0Mode, PlanFalKling3p0Pro, +}; +use fal_client::requests::webhook::video::image::enqueue_kling_3p0_pro_image_to_video_webhook::{ + enqueue_kling_3p0_pro_image_to_video_webhook, EnqueueKling3p0ProImageToVideoArgs, + EnqueueKling3p0ProImageToVideoAspectRatio, EnqueueKling3p0ProImageToVideoDuration, +}; +use fal_client::requests::webhook::video::text::enqueue_kling_3p0_pro_text_to_video_webhook::{ + enqueue_kling_3p0_pro_text_to_video_webhook, EnqueueKling3p0ProTextToVideoArgs, + EnqueueKling3p0ProTextToVideoAspectRatio, EnqueueKling3p0ProTextToVideoDuration, +}; + +pub async fn execute_fal_kling_3p0_pro( + plan: &PlanFalKling3p0Pro, + fal_client: &RouterFalClient, +) -> Result { + let webhook_response = match &plan.mode { + FalKling3p0Mode::TextToVideo => { + let args = EnqueueKling3p0ProTextToVideoArgs { + prompt: plan.prompt.clone(), + generate_audio: plan.generate_audio, + negative_prompt: plan.negative_prompt.clone(), + duration: plan.duration.map(to_t2v_duration), + aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio), + shot_type: None, + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_kling_3p0_pro_text_to_video_webhook(args).await + } + FalKling3p0Mode::ImageToVideo { image_url, end_image_url } => { + let args = EnqueueKling3p0ProImageToVideoArgs { + prompt: plan.prompt.clone(), + image_url: image_url.clone(), + end_image_url: end_image_url.clone(), + generate_audio: plan.generate_audio, + negative_prompt: plan.negative_prompt.clone(), + duration: plan.duration.map(to_i2v_duration), + aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio), + shot_type: None, + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_kling_3p0_pro_image_to_video_webhook(args).await + } + }; + + let webhook_response = webhook_response + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?; + + Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} + +fn to_t2v_duration(d: FalKling3p0Duration) -> EnqueueKling3p0ProTextToVideoDuration { + use EnqueueKling3p0ProTextToVideoDuration as D; + match d.0 { + 3 => D::ThreeSeconds, + 4 => D::FourSeconds, + 5 => D::FiveSeconds, + 6 => D::SixSeconds, + 7 => D::SevenSeconds, + 8 => D::EightSeconds, + 9 => D::NineSeconds, + 10 => D::TenSeconds, + 11 => D::ElevenSeconds, + 12 => D::TwelveSeconds, + 13 => D::ThirteenSeconds, + 14 => D::FourteenSeconds, + _ => D::FifteenSeconds, + } +} + +fn to_i2v_duration(d: FalKling3p0Duration) -> EnqueueKling3p0ProImageToVideoDuration { + use EnqueueKling3p0ProImageToVideoDuration as D; + match d.0 { + 3 => D::ThreeSeconds, + 4 => D::FourSeconds, + 5 => D::FiveSeconds, + 6 => D::SixSeconds, + 7 => D::SevenSeconds, + 8 => D::EightSeconds, + 9 => D::NineSeconds, + 10 => D::TenSeconds, + 11 => D::ElevenSeconds, + 12 => D::TwelveSeconds, + 13 => D::ThirteenSeconds, + 14 => D::FourteenSeconds, + _ => D::FifteenSeconds, + } +} + +fn to_t2v_aspect_ratio(a: FalKling3p0AspectRatio) -> EnqueueKling3p0ProTextToVideoAspectRatio { + match a { + FalKling3p0AspectRatio::Square => EnqueueKling3p0ProTextToVideoAspectRatio::Square, + FalKling3p0AspectRatio::SixteenByNine => EnqueueKling3p0ProTextToVideoAspectRatio::SixteenByNine, + FalKling3p0AspectRatio::NineBySixteen => EnqueueKling3p0ProTextToVideoAspectRatio::NineBySixteen, + } +} + +fn to_i2v_aspect_ratio(a: FalKling3p0AspectRatio) -> EnqueueKling3p0ProImageToVideoAspectRatio { + match a { + FalKling3p0AspectRatio::Square => EnqueueKling3p0ProImageToVideoAspectRatio::Square, + FalKling3p0AspectRatio::SixteenByNine => EnqueueKling3p0ProImageToVideoAspectRatio::SixteenByNine, + FalKling3p0AspectRatio::NineBySixteen => EnqueueKling3p0ProImageToVideoAspectRatio::NineBySixteen, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_3p0_standard.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_3p0_standard.rs new file mode 100644 index 0000000000..54523e3634 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/generate_video_fal_kling_3p0_standard.rs @@ -0,0 +1,116 @@ +use crate::client::router_fal_client::RouterFalClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video_response::{ + FalVideoResponsePayload, GenerateVideoResponse, +}; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_pro::{ + FalKling3p0AspectRatio, FalKling3p0Duration, FalKling3p0Mode, +}; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_standard::PlanFalKling3p0Standard; +use fal_client::requests::webhook::video::image::enqueue_kling_3p0_standard_image_to_video_webhook::{ + enqueue_kling_3p0_standard_image_to_video_webhook, EnqueueKling3p0StandardImageToVideoArgs, + EnqueueKling3p0StandardImageToVideoAspectRatio, EnqueueKling3p0StandardImageToVideoDuration, +}; +use fal_client::requests::webhook::video::text::enqueue_kling_3p0_standard_text_to_video_webhook::{ + enqueue_kling_3p0_standard_text_to_video_webhook, EnqueueKling3p0StandardTextToVideoArgs, + EnqueueKling3p0StandardTextToVideoAspectRatio, EnqueueKling3p0StandardTextToVideoDuration, +}; + +pub async fn execute_fal_kling_3p0_standard( + plan: &PlanFalKling3p0Standard, + fal_client: &RouterFalClient, +) -> Result { + let webhook_response = match &plan.mode { + FalKling3p0Mode::TextToVideo => { + let args = EnqueueKling3p0StandardTextToVideoArgs { + prompt: plan.prompt.clone(), + generate_audio: plan.generate_audio, + negative_prompt: plan.negative_prompt.clone(), + duration: plan.duration.map(to_t2v_duration), + aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio), + shot_type: None, + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_kling_3p0_standard_text_to_video_webhook(args).await + } + FalKling3p0Mode::ImageToVideo { image_url, end_image_url } => { + let args = EnqueueKling3p0StandardImageToVideoArgs { + prompt: plan.prompt.clone(), + image_url: image_url.clone(), + end_image_url: end_image_url.clone(), + generate_audio: plan.generate_audio, + negative_prompt: plan.negative_prompt.clone(), + duration: plan.duration.map(to_i2v_duration), + aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio), + shot_type: None, + webhook_url: fal_client.webhook_url.as_str(), + api_key: &fal_client.api_key, + }; + enqueue_kling_3p0_standard_image_to_video_webhook(args).await + } + }; + + let webhook_response = webhook_response + .map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?; + + Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload { + request_id: webhook_response.request_id, + gateway_request_id: webhook_response.gateway_request_id, + })) +} + +fn to_t2v_duration(d: FalKling3p0Duration) -> EnqueueKling3p0StandardTextToVideoDuration { + use EnqueueKling3p0StandardTextToVideoDuration as D; + match d.0 { + 3 => D::ThreeSeconds, + 4 => D::FourSeconds, + 5 => D::FiveSeconds, + 6 => D::SixSeconds, + 7 => D::SevenSeconds, + 8 => D::EightSeconds, + 9 => D::NineSeconds, + 10 => D::TenSeconds, + 11 => D::ElevenSeconds, + 12 => D::TwelveSeconds, + 13 => D::ThirteenSeconds, + 14 => D::FourteenSeconds, + _ => D::FifteenSeconds, + } +} + +fn to_i2v_duration(d: FalKling3p0Duration) -> EnqueueKling3p0StandardImageToVideoDuration { + use EnqueueKling3p0StandardImageToVideoDuration as D; + match d.0 { + 3 => D::ThreeSeconds, + 4 => D::FourSeconds, + 5 => D::FiveSeconds, + 6 => D::SixSeconds, + 7 => D::SevenSeconds, + 8 => D::EightSeconds, + 9 => D::NineSeconds, + 10 => D::TenSeconds, + 11 => D::ElevenSeconds, + 12 => D::TwelveSeconds, + 13 => D::ThirteenSeconds, + 14 => D::FourteenSeconds, + _ => D::FifteenSeconds, + } +} + +fn to_t2v_aspect_ratio(a: FalKling3p0AspectRatio) -> EnqueueKling3p0StandardTextToVideoAspectRatio { + match a { + FalKling3p0AspectRatio::Square => EnqueueKling3p0StandardTextToVideoAspectRatio::Square, + FalKling3p0AspectRatio::SixteenByNine => EnqueueKling3p0StandardTextToVideoAspectRatio::SixteenByNine, + FalKling3p0AspectRatio::NineBySixteen => EnqueueKling3p0StandardTextToVideoAspectRatio::NineBySixteen, + } +} + +fn to_i2v_aspect_ratio(a: FalKling3p0AspectRatio) -> EnqueueKling3p0StandardImageToVideoAspectRatio { + match a { + FalKling3p0AspectRatio::Square => EnqueueKling3p0StandardImageToVideoAspectRatio::Square, + FalKling3p0AspectRatio::SixteenByNine => EnqueueKling3p0StandardImageToVideoAspectRatio::SixteenByNine, + FalKling3p0AspectRatio::NineBySixteen => EnqueueKling3p0StandardImageToVideoAspectRatio::NineBySixteen, + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/mod.rs index 745852a205..946a2dcd63 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/execute/fal/mod.rs @@ -1,3 +1,10 @@ +pub mod generate_video_fal_kling_1_6_pro; +pub mod generate_video_fal_kling_2_1_master; +pub mod generate_video_fal_kling_2_1_pro; +pub mod generate_video_fal_kling_2_5_turbo_pro; +pub mod generate_video_fal_kling_2_6_pro; +pub mod generate_video_fal_kling_3p0_pro; +pub mod generate_video_fal_kling_3p0_standard; pub mod generate_video_fal_veo_2; pub mod generate_video_fal_veo_3; pub mod generate_video_fal_veo_3_fast; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request.rs index 1373fced1e..c25f36acd5 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request.rs @@ -23,6 +23,13 @@ use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraf use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3_fast::plan_generate_video_artcraft_veo_3_fast; use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3p1::plan_generate_video_artcraft_veo_3p1; use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3p1_fast::plan_generate_video_artcraft_veo_3p1_fast; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::plan_generate_video_fal_kling_1_6_pro; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_master::plan_generate_video_fal_kling_2_1_master; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_pro::plan_generate_video_fal_kling_2_1_pro; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_5_turbo_pro::plan_generate_video_fal_kling_2_5_turbo_pro; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_6_pro::plan_generate_video_fal_kling_2_6_pro; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_pro::plan_generate_video_fal_kling_3p0_pro; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_standard::plan_generate_video_fal_kling_3p0_standard; use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_2::plan_generate_video_fal_veo_2; use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3::plan_generate_video_fal_veo_3; use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3_fast::plan_generate_video_fal_veo_3_fast; @@ -132,6 +139,13 @@ impl<'a> GenerateVideoRequest<'a> { CommonVideoModel::Veo3Fast => plan_generate_video_fal_veo_3_fast(self), CommonVideoModel::Veo3p1 => plan_generate_video_fal_veo_3p1(self), CommonVideoModel::Veo3p1Fast => plan_generate_video_fal_veo_3p1_fast(self), + CommonVideoModel::Kling16Pro => plan_generate_video_fal_kling_1_6_pro(self), + CommonVideoModel::Kling21Pro => plan_generate_video_fal_kling_2_1_pro(self), + CommonVideoModel::Kling21Master => plan_generate_video_fal_kling_2_1_master(self), + CommonVideoModel::Kling2p5TurboPro => plan_generate_video_fal_kling_2_5_turbo_pro(self), + CommonVideoModel::Kling2p6Pro => plan_generate_video_fal_kling_2_6_pro(self), + CommonVideoModel::Kling3p0Pro => plan_generate_video_fal_kling_3p0_pro(self), + CommonVideoModel::Kling3p0Standard => plan_generate_video_fal_kling_3p0_standard(self), _ => Err(ArtcraftRouterError::UnsupportedModel(format!("{:?}", self.model))), } } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/mod.rs index 64e267315d..235ce88b06 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/mod.rs @@ -1,3 +1,10 @@ +pub mod plan_generate_video_fal_kling_1_6_pro; +pub mod plan_generate_video_fal_kling_2_1_master; +pub mod plan_generate_video_fal_kling_2_1_pro; +pub mod plan_generate_video_fal_kling_2_5_turbo_pro; +pub mod plan_generate_video_fal_kling_2_6_pro; +pub mod plan_generate_video_fal_kling_3p0_pro; +pub mod plan_generate_video_fal_kling_3p0_standard; pub mod plan_generate_video_fal_veo_2; pub mod plan_generate_video_fal_veo_3; pub mod plan_generate_video_fal_veo_3_fast; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_1_6_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_1_6_pro.rs new file mode 100644 index 0000000000..2d500ce580 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_1_6_pro.rs @@ -0,0 +1,122 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::api::image_ref::ImageRef; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_video::generate_video_request::GenerateVideoRequest; +use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan; +use fal_client::requests::webhook::video::image::enqueue_kling_v1p6_pro_image_to_video_webhook::{ + Kling1p6ProAspectRatio, Kling1p6ProDuration, +}; + +#[derive(Debug, Clone)] +pub struct PlanFalKling16Pro { + pub prompt: String, + pub image_url: String, + pub end_image_url: Option, + pub aspect_ratio: Kling1p6ProAspectRatio, + pub duration: Kling1p6ProDuration, +} + +pub fn plan_generate_video_fal_kling_1_6_pro<'a>( + request: &'a GenerateVideoRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + + let image_url = require_url(request.start_frame, "start_frame", "Kling 1.6 Pro requires a starting frame")?; + let end_image_url = optional_url(request.end_frame)?; + let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?; + let duration = plan_duration(request.duration_seconds, strategy)?; + + Ok(VideoGenerationPlan::FalKling16Pro(PlanFalKling16Pro { + prompt: request.prompt.unwrap_or("").to_string(), + image_url, + end_image_url, + aspect_ratio, + duration, + })) +} + +pub(crate) fn require_url( + image_ref: Option>, + field: &'static str, + reason: &str, +) -> Result { + match image_ref { + Some(ImageRef::Url(url)) => Ok(url.to_string()), + Some(ImageRef::MediaFileToken(_)) => { + Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls)) + } + None => Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field, + value: reason.to_string(), + })), + } +} + +pub(crate) fn optional_url( + image_ref: Option>, +) -> Result, ArtcraftRouterError> { + match image_ref { + None => Ok(None), + Some(ImageRef::Url(url)) => Ok(Some(url.to_string())), + Some(ImageRef::MediaFileToken(_)) => { + Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls)) + } + } +} + +fn plan_aspect_ratio( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + use Kling1p6ProAspectRatio as Ar; + match aspect_ratio { + None => Ok(Ar::WideSixteenNine), + + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Ar::Square), + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Ar::WideSixteenNine), + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Ar::TallNineSixteen), + + Some(CommonAspectRatio::Auto) + | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto4k) => Ok(Ar::WideSixteenNine), + + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Ar::WideSixteenNine), + }, + } +} + +fn plan_duration( + duration_seconds: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + match duration_seconds { + None => Ok(Kling1p6ProDuration::Default), + Some(5) => Ok(Kling1p6ProDuration::FiveSeconds), + Some(10) => Ok(Kling1p6ProDuration::TenSeconds), + Some(other) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "duration_seconds", + value: format!("{}", other), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade => Ok(Kling1p6ProDuration::TenSeconds), + RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(Kling1p6ProDuration::FiveSeconds), + }, + } +} + +impl PlanFalKling16Pro { + pub fn is_ten_seconds(&self) -> bool { + matches!(self.duration, Kling1p6ProDuration::TenSeconds) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_1_master.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_1_master.rs new file mode 100644 index 0000000000..14249ea292 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_1_master.rs @@ -0,0 +1,97 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_video::generate_video_request::GenerateVideoRequest; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::require_url; +use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan; +use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_master_image_to_video_webhook::{ + Kling2p1MasterAspectRatio, Kling2p1MasterDuration, +}; + +#[derive(Debug, Clone)] +pub struct PlanFalKling21Master { + pub prompt: String, + pub image_url: String, + pub aspect_ratio: Kling2p1MasterAspectRatio, + pub duration: Kling2p1MasterDuration, +} + +pub fn plan_generate_video_fal_kling_2_1_master<'a>( + request: &'a GenerateVideoRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + + let image_url = require_url(request.start_frame, "start_frame", "Kling 2.1 Master requires a starting frame")?; + if request.end_frame.is_some() { + return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "end_frame", + value: "Kling 2.1 Master does not support an ending frame".to_string(), + })); + } + + let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?; + let duration = plan_duration(request.duration_seconds, strategy)?; + + Ok(VideoGenerationPlan::FalKling21Master(PlanFalKling21Master { + prompt: request.prompt.unwrap_or("").to_string(), + image_url, + aspect_ratio, + duration, + })) +} + +fn plan_aspect_ratio( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + use Kling2p1MasterAspectRatio as Ar; + match aspect_ratio { + None => Ok(Ar::WideSixteenNine), + + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Ar::Square), + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Ar::WideSixteenNine), + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Ar::TallNineSixteen), + + Some(CommonAspectRatio::Auto) + | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto4k) => Ok(Ar::WideSixteenNine), + + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Ar::WideSixteenNine), + }, + } +} + +fn plan_duration( + duration_seconds: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + match duration_seconds { + None => Ok(Kling2p1MasterDuration::Default), + Some(5) => Ok(Kling2p1MasterDuration::FiveSeconds), + Some(10) => Ok(Kling2p1MasterDuration::TenSeconds), + Some(other) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "duration_seconds", + value: format!("{}", other), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade => Ok(Kling2p1MasterDuration::TenSeconds), + RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(Kling2p1MasterDuration::FiveSeconds), + }, + } +} + +impl PlanFalKling21Master { + pub fn is_ten_seconds(&self) -> bool { + matches!(self.duration, Kling2p1MasterDuration::TenSeconds) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_1_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_1_pro.rs new file mode 100644 index 0000000000..cbb6d22e5c --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_1_pro.rs @@ -0,0 +1,93 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_video::generate_video_request::GenerateVideoRequest; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::{optional_url, require_url}; +use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan; +use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_pro_image_to_video_webhook::{ + Kling2p1ProAspectRatio, Kling2p1ProDuration, +}; + +#[derive(Debug, Clone)] +pub struct PlanFalKling21Pro { + pub prompt: String, + pub image_url: String, + pub end_image_url: Option, + pub aspect_ratio: Kling2p1ProAspectRatio, + pub duration: Kling2p1ProDuration, +} + +pub fn plan_generate_video_fal_kling_2_1_pro<'a>( + request: &'a GenerateVideoRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + + let image_url = require_url(request.start_frame, "start_frame", "Kling 2.1 Pro requires a starting frame")?; + let end_image_url = optional_url(request.end_frame)?; + let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?; + let duration = plan_duration(request.duration_seconds, strategy)?; + + Ok(VideoGenerationPlan::FalKling21Pro(PlanFalKling21Pro { + prompt: request.prompt.unwrap_or("").to_string(), + image_url, + end_image_url, + aspect_ratio, + duration, + })) +} + +fn plan_aspect_ratio( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + use Kling2p1ProAspectRatio as Ar; + match aspect_ratio { + None => Ok(Ar::WideSixteenNine), + + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Ar::Square), + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Ar::WideSixteenNine), + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Ar::TallNineSixteen), + + Some(CommonAspectRatio::Auto) + | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto4k) => Ok(Ar::WideSixteenNine), + + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Ar::WideSixteenNine), + }, + } +} + +fn plan_duration( + duration_seconds: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + match duration_seconds { + None => Ok(Kling2p1ProDuration::Default), + Some(5) => Ok(Kling2p1ProDuration::FiveSeconds), + Some(10) => Ok(Kling2p1ProDuration::TenSeconds), + Some(other) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "duration_seconds", + value: format!("{}", other), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade => Ok(Kling2p1ProDuration::TenSeconds), + RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(Kling2p1ProDuration::FiveSeconds), + }, + } +} + +impl PlanFalKling21Pro { + pub fn is_ten_seconds(&self) -> bool { + matches!(self.duration, Kling2p1ProDuration::TenSeconds) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_5_turbo_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_5_turbo_pro.rs new file mode 100644 index 0000000000..4ec8d9db17 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_5_turbo_pro.rs @@ -0,0 +1,126 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_video::generate_video_request::GenerateVideoRequest; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::optional_url; +use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan; + +#[derive(Debug, Clone, Copy)] +pub enum FalKling2p5TurboProAspectRatio { + Square, + SixteenByNine, + NineBySixteen, +} + +#[derive(Debug, Clone, Copy)] +pub enum FalKling2p5TurboProDuration { + Five, + Ten, +} + +#[derive(Debug, Clone)] +pub enum FalKling2p5TurboProMode { + TextToVideo, + ImageToVideo { + image_url: String, + end_image_url: Option, + }, +} + +#[derive(Debug, Clone)] +pub struct PlanFalKling2p5TurboPro { + pub prompt: String, + pub negative_prompt: Option, + pub mode: FalKling2p5TurboProMode, + pub aspect_ratio: Option, + pub duration: Option, +} + +pub fn plan_generate_video_fal_kling_2_5_turbo_pro<'a>( + request: &'a GenerateVideoRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + + let mode = match optional_url(request.start_frame)? { + None => { + if request.end_frame.is_some() { + return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "end_frame", + value: "Kling 2.5 Turbo Pro requires a start_frame when end_frame is provided".to_string(), + })); + } + FalKling2p5TurboProMode::TextToVideo + } + Some(image_url) => FalKling2p5TurboProMode::ImageToVideo { + image_url, + end_image_url: optional_url(request.end_frame)?, + }, + }; + + let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?; + let duration = plan_duration(request.duration_seconds, strategy)?; + + Ok(VideoGenerationPlan::FalKling2p5TurboPro(PlanFalKling2p5TurboPro { + prompt: request.prompt.unwrap_or("").to_string(), + negative_prompt: request.negative_prompt.map(|s| s.to_string()), + mode, + aspect_ratio, + duration, + })) +} + +fn plan_aspect_ratio( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + use FalKling2p5TurboProAspectRatio as Ar; + match aspect_ratio { + None => Ok(None), + + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Some(Ar::Square)), + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Some(Ar::SixteenByNine)), + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Some(Ar::NineBySixteen)), + + Some(CommonAspectRatio::Auto) + | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto4k) => Ok(Some(Ar::SixteenByNine)), + + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Some(Ar::SixteenByNine)), + }, + } +} + +fn plan_duration( + duration_seconds: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + match duration_seconds { + None => Ok(None), + Some(5) => Ok(Some(FalKling2p5TurboProDuration::Five)), + Some(10) => Ok(Some(FalKling2p5TurboProDuration::Ten)), + Some(other) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "duration_seconds", + value: format!("{}", other), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade => Ok(Some(FalKling2p5TurboProDuration::Ten)), + RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(Some(FalKling2p5TurboProDuration::Five)), + }, + } +} + +impl PlanFalKling2p5TurboPro { + pub fn is_ten_seconds(&self) -> bool { + matches!(self.duration, Some(FalKling2p5TurboProDuration::Ten)) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_6_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_6_pro.rs new file mode 100644 index 0000000000..6bc32c273b --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_2_6_pro.rs @@ -0,0 +1,126 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_video::generate_video_request::GenerateVideoRequest; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::optional_url; +use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan; + +#[derive(Debug, Clone, Copy)] +pub enum FalKling2p6ProAspectRatio { + Square, + SixteenByNine, + NineBySixteen, +} + +#[derive(Debug, Clone, Copy)] +pub enum FalKling2p6ProDuration { + Five, + Ten, +} + +#[derive(Debug, Clone)] +pub enum FalKling2p6ProMode { + TextToVideo, + ImageToVideo { image_url: String }, +} + +#[derive(Debug, Clone)] +pub struct PlanFalKling2p6Pro { + pub prompt: String, + pub negative_prompt: Option, + pub mode: FalKling2p6ProMode, + pub aspect_ratio: Option, + pub duration: Option, + pub generate_audio: Option, +} + +pub fn plan_generate_video_fal_kling_2_6_pro<'a>( + request: &'a GenerateVideoRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + + if request.end_frame.is_some() { + return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "end_frame", + value: "Kling 2.6 Pro does not support an ending frame".to_string(), + })); + } + + let mode = match optional_url(request.start_frame)? { + None => FalKling2p6ProMode::TextToVideo, + Some(image_url) => FalKling2p6ProMode::ImageToVideo { image_url }, + }; + + let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?; + let duration = plan_duration(request.duration_seconds, strategy)?; + + Ok(VideoGenerationPlan::FalKling2p6Pro(PlanFalKling2p6Pro { + prompt: request.prompt.unwrap_or("").to_string(), + negative_prompt: request.negative_prompt.map(|s| s.to_string()), + mode, + aspect_ratio, + duration, + generate_audio: request.generate_audio, + })) +} + +fn plan_aspect_ratio( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + use FalKling2p6ProAspectRatio as Ar; + match aspect_ratio { + None => Ok(None), + + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Some(Ar::Square)), + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Some(Ar::SixteenByNine)), + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Some(Ar::NineBySixteen)), + + Some(CommonAspectRatio::Auto) + | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto4k) => Ok(Some(Ar::SixteenByNine)), + + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Some(Ar::SixteenByNine)), + }, + } +} + +fn plan_duration( + duration_seconds: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + match duration_seconds { + None => Ok(None), + Some(5) => Ok(Some(FalKling2p6ProDuration::Five)), + Some(10) => Ok(Some(FalKling2p6ProDuration::Ten)), + Some(other) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "duration_seconds", + value: format!("{}", other), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade => Ok(Some(FalKling2p6ProDuration::Ten)), + RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(Some(FalKling2p6ProDuration::Five)), + }, + } +} + +impl PlanFalKling2p6Pro { + pub fn is_ten_seconds(&self) -> bool { + matches!(self.duration, Some(FalKling2p6ProDuration::Ten)) + } + + /// Legacy / Fal client default for `generate_audio` is `true`. + pub fn generate_audio_for_cost(&self) -> bool { + self.generate_audio.unwrap_or(true) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_3p0_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_3p0_pro.rs new file mode 100644 index 0000000000..573879e141 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_3p0_pro.rs @@ -0,0 +1,156 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_video::generate_video_request::GenerateVideoRequest; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::optional_url; +use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan; + +#[derive(Debug, Clone, Copy)] +pub enum FalKling3p0AspectRatio { + Square, + SixteenByNine, + NineBySixteen, +} + +/// 3-15 seconds inclusive. +#[derive(Debug, Clone, Copy)] +pub struct FalKling3p0Duration(pub u8); + +#[derive(Debug, Clone)] +pub enum FalKling3p0Mode { + TextToVideo, + ImageToVideo { + image_url: String, + end_image_url: Option, + }, +} + +#[derive(Debug, Clone)] +pub struct PlanFalKling3p0Pro { + pub prompt: String, + pub negative_prompt: Option, + pub mode: FalKling3p0Mode, + pub aspect_ratio: Option, + pub duration: Option, + pub generate_audio: Option, +} + +pub fn plan_generate_video_fal_kling_3p0_pro<'a>( + request: &'a GenerateVideoRequest<'a>, +) -> Result, ArtcraftRouterError> { + let inner = build_kling_3p0_plan(request, "Kling 3.0 Pro")?; + Ok(VideoGenerationPlan::FalKling3p0Pro(PlanFalKling3p0Pro { + prompt: inner.prompt, + negative_prompt: inner.negative_prompt, + mode: inner.mode, + aspect_ratio: inner.aspect_ratio, + duration: inner.duration, + generate_audio: inner.generate_audio, + })) +} + +pub(crate) struct Kling3p0Common { + pub prompt: String, + pub negative_prompt: Option, + pub mode: FalKling3p0Mode, + pub aspect_ratio: Option, + pub duration: Option, + pub generate_audio: Option, +} + +pub(crate) fn build_kling_3p0_plan<'a>( + request: &'a GenerateVideoRequest<'a>, + _model_label: &'static str, +) -> Result { + let strategy = request.request_mismatch_mitigation_strategy; + + let mode = match optional_url(request.start_frame)? { + None => { + if request.end_frame.is_some() { + return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "end_frame", + value: "Kling 3.0 requires a start_frame when end_frame is provided".to_string(), + })); + } + FalKling3p0Mode::TextToVideo + } + Some(image_url) => FalKling3p0Mode::ImageToVideo { + image_url, + end_image_url: optional_url(request.end_frame)?, + }, + }; + + let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?; + let duration = plan_duration(request.duration_seconds, strategy)?; + + Ok(Kling3p0Common { + prompt: request.prompt.unwrap_or("").to_string(), + negative_prompt: request.negative_prompt.map(|s| s.to_string()), + mode, + aspect_ratio, + duration, + generate_audio: request.generate_audio, + }) +} + +fn plan_aspect_ratio( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + use FalKling3p0AspectRatio as Ar; + match aspect_ratio { + None => Ok(None), + + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Some(Ar::Square)), + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Some(Ar::SixteenByNine)), + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Some(Ar::NineBySixteen)), + + Some(CommonAspectRatio::Auto) + | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto4k) => Ok(Some(Ar::SixteenByNine)), + + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + _ => Ok(Some(Ar::SixteenByNine)), + }, + } +} + +fn plan_duration( + duration_seconds: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + const MIN: u16 = 3; + const MAX: u16 = 15; + match duration_seconds { + None => Ok(None), + Some(d) if (MIN..=MAX).contains(&d) => Ok(Some(FalKling3p0Duration(d as u8))), + Some(d) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "duration_seconds", + value: format!("{}", d), + })) + } + _ => Ok(Some(FalKling3p0Duration(d.clamp(MIN, MAX) as u8))), + }, + } +} + +impl PlanFalKling3p0Pro { + /// Default duration is 5s when None per Fal client. + pub fn duration_seconds_for_cost(&self) -> u64 { + self.duration.map(|d| d.0 as u64).unwrap_or(5) + } + + /// Default `generate_audio` is true per Fal client. + pub fn generate_audio_for_cost(&self) -> bool { + self.generate_audio.unwrap_or(true) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_3p0_standard.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_3p0_standard.rs new file mode 100644 index 0000000000..c0785430c5 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/plan/fal/plan_generate_video_fal_kling_3p0_standard.rs @@ -0,0 +1,40 @@ +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::generate::generate_video::generate_video_request::GenerateVideoRequest; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_pro::{ + build_kling_3p0_plan, FalKling3p0AspectRatio, FalKling3p0Duration, FalKling3p0Mode, +}; +use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan; + +#[derive(Debug, Clone)] +pub struct PlanFalKling3p0Standard { + pub prompt: String, + pub negative_prompt: Option, + pub mode: FalKling3p0Mode, + pub aspect_ratio: Option, + pub duration: Option, + pub generate_audio: Option, +} + +pub fn plan_generate_video_fal_kling_3p0_standard<'a>( + request: &'a GenerateVideoRequest<'a>, +) -> Result, ArtcraftRouterError> { + let inner = build_kling_3p0_plan(request, "Kling 3.0 Standard")?; + Ok(VideoGenerationPlan::FalKling3p0Standard(PlanFalKling3p0Standard { + prompt: inner.prompt, + negative_prompt: inner.negative_prompt, + mode: inner.mode, + aspect_ratio: inner.aspect_ratio, + duration: inner.duration, + generate_audio: inner.generate_audio, + })) +} + +impl PlanFalKling3p0Standard { + pub fn duration_seconds_for_cost(&self) -> u64 { + self.duration.map(|d| d.0 as u64).unwrap_or(5) + } + + pub fn generate_audio_for_cost(&self) -> bool { + self.generate_audio.unwrap_or(true) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_plan.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_plan.rs index e981d8b41a..dfaeae6cc1 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_plan.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_plan.rs @@ -14,6 +14,13 @@ use crate::generate::generate_video::cost::artcraft::estimate_video_cost_artcraf use crate::generate::generate_video::cost::artcraft::estimate_video_cost_artcraft_veo_3_fast::estimate_video_cost_artcraft_veo_3_fast; use crate::generate::generate_video::cost::artcraft::estimate_video_cost_artcraft_veo_3p1::estimate_video_cost_artcraft_veo_3p1; use crate::generate::generate_video::cost::artcraft::estimate_video_cost_artcraft_veo_3p1_fast::estimate_video_cost_artcraft_veo_3p1_fast; +use crate::generate::generate_video::cost::fal::estimate_video_cost_fal_kling_1_6_pro::estimate_video_cost_fal_kling_1_6_pro; +use crate::generate::generate_video::cost::fal::estimate_video_cost_fal_kling_2_1_master::estimate_video_cost_fal_kling_2_1_master; +use crate::generate::generate_video::cost::fal::estimate_video_cost_fal_kling_2_1_pro::estimate_video_cost_fal_kling_2_1_pro; +use crate::generate::generate_video::cost::fal::estimate_video_cost_fal_kling_2_5_turbo_pro::estimate_video_cost_fal_kling_2_5_turbo_pro; +use crate::generate::generate_video::cost::fal::estimate_video_cost_fal_kling_2_6_pro::estimate_video_cost_fal_kling_2_6_pro; +use crate::generate::generate_video::cost::fal::estimate_video_cost_fal_kling_3p0_pro::estimate_video_cost_fal_kling_3p0_pro; +use crate::generate::generate_video::cost::fal::estimate_video_cost_fal_kling_3p0_standard::estimate_video_cost_fal_kling_3p0_standard; use crate::generate::generate_video::cost::fal::estimate_video_cost_fal_veo_2::estimate_video_cost_fal_veo_2; use crate::generate::generate_video::cost::fal::estimate_video_cost_fal_veo_3::estimate_video_cost_fal_veo_3; use crate::generate::generate_video::cost::fal::estimate_video_cost_fal_veo_3_fast::estimate_video_cost_fal_veo_3_fast; @@ -35,6 +42,13 @@ use crate::generate::generate_video::execute::artcraft::generate_video_artcraft_ use crate::generate::generate_video::execute::artcraft::generate_video_artcraft_veo_3_fast::execute_artcraft_veo_3_fast; use crate::generate::generate_video::execute::artcraft::generate_video_artcraft_veo_3p1::execute_artcraft_veo_3p1; use crate::generate::generate_video::execute::artcraft::generate_video_artcraft_veo_3p1_fast::execute_artcraft_veo_3p1_fast; +use crate::generate::generate_video::execute::fal::generate_video_fal_kling_1_6_pro::execute_fal_kling_1_6_pro; +use crate::generate::generate_video::execute::fal::generate_video_fal_kling_2_1_master::execute_fal_kling_2_1_master; +use crate::generate::generate_video::execute::fal::generate_video_fal_kling_2_1_pro::execute_fal_kling_2_1_pro; +use crate::generate::generate_video::execute::fal::generate_video_fal_kling_2_5_turbo_pro::execute_fal_kling_2_5_turbo_pro; +use crate::generate::generate_video::execute::fal::generate_video_fal_kling_2_6_pro::execute_fal_kling_2_6_pro; +use crate::generate::generate_video::execute::fal::generate_video_fal_kling_3p0_pro::execute_fal_kling_3p0_pro; +use crate::generate::generate_video::execute::fal::generate_video_fal_kling_3p0_standard::execute_fal_kling_3p0_standard; use crate::generate::generate_video::execute::fal::generate_video_fal_veo_2::execute_fal_veo_2; use crate::generate::generate_video::execute::fal::generate_video_fal_veo_3::execute_fal_veo_3; use crate::generate::generate_video::execute::fal::generate_video_fal_veo_3_fast::execute_fal_veo_3_fast; @@ -57,6 +71,13 @@ use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraf use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3_fast::PlanArtcraftVeo3Fast; use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3p1::PlanArtcraftVeo3p1; use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3p1_fast::PlanArtcraftVeo3p1Fast; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_1_6_pro::PlanFalKling16Pro; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_master::PlanFalKling21Master; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_1_pro::PlanFalKling21Pro; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_5_turbo_pro::PlanFalKling2p5TurboPro; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_2_6_pro::PlanFalKling2p6Pro; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_pro::PlanFalKling3p0Pro; +use crate::generate::generate_video::plan::fal::plan_generate_video_fal_kling_3p0_standard::PlanFalKling3p0Standard; use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_2::PlanFalVeo2; use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3::PlanFalVeo3; use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3_fast::PlanFalVeo3Fast; @@ -89,6 +110,13 @@ pub enum VideoGenerationPlan<'a> { FalVeo3Fast(PlanFalVeo3Fast), FalVeo3p1(PlanFalVeo3p1), FalVeo3p1Fast(PlanFalVeo3p1Fast), + FalKling16Pro(PlanFalKling16Pro), + FalKling21Pro(PlanFalKling21Pro), + FalKling21Master(PlanFalKling21Master), + FalKling2p5TurboPro(PlanFalKling2p5TurboPro), + FalKling2p6Pro(PlanFalKling2p6Pro), + FalKling3p0Pro(PlanFalKling3p0Pro), + FalKling3p0Standard(PlanFalKling3p0Standard), } impl<'a> VideoGenerationPlan<'a> { @@ -181,6 +209,34 @@ impl<'a> VideoGenerationPlan<'a> { let fal_client = client.get_fal_client_ref()?; execute_fal_veo_3p1_fast(plan, fal_client).await } + VideoGenerationPlan::FalKling16Pro(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_kling_1_6_pro(plan, fal_client).await + } + VideoGenerationPlan::FalKling21Pro(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_kling_2_1_pro(plan, fal_client).await + } + VideoGenerationPlan::FalKling21Master(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_kling_2_1_master(plan, fal_client).await + } + VideoGenerationPlan::FalKling2p5TurboPro(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_kling_2_5_turbo_pro(plan, fal_client).await + } + VideoGenerationPlan::FalKling2p6Pro(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_kling_2_6_pro(plan, fal_client).await + } + VideoGenerationPlan::FalKling3p0Pro(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_kling_3p0_pro(plan, fal_client).await + } + VideoGenerationPlan::FalKling3p0Standard(plan) => { + let fal_client = client.get_fal_client_ref()?; + execute_fal_kling_3p0_standard(plan, fal_client).await + } } } @@ -219,6 +275,13 @@ impl<'a> VideoGenerationPlan<'a> { VideoGenerationPlan::FalVeo3Fast(plan) => estimate_video_cost_fal_veo_3_fast(plan), VideoGenerationPlan::FalVeo3p1(plan) => estimate_video_cost_fal_veo_3p1(plan), VideoGenerationPlan::FalVeo3p1Fast(plan) => estimate_video_cost_fal_veo_3p1_fast(plan), + VideoGenerationPlan::FalKling16Pro(plan) => estimate_video_cost_fal_kling_1_6_pro(plan), + VideoGenerationPlan::FalKling21Pro(plan) => estimate_video_cost_fal_kling_2_1_pro(plan), + VideoGenerationPlan::FalKling21Master(plan) => estimate_video_cost_fal_kling_2_1_master(plan), + VideoGenerationPlan::FalKling2p5TurboPro(plan) => estimate_video_cost_fal_kling_2_5_turbo_pro(plan), + VideoGenerationPlan::FalKling2p6Pro(plan) => estimate_video_cost_fal_kling_2_6_pro(plan), + VideoGenerationPlan::FalKling3p0Pro(plan) => estimate_video_cost_fal_kling_3p0_pro(plan), + VideoGenerationPlan::FalKling3p0Standard(plan) => estimate_video_cost_fal_kling_3p0_standard(plan), } } } diff --git a/crates/service/web/storyteller_web/src/configs/omni_gen/video_models.rs b/crates/service/web/storyteller_web/src/configs/omni_gen/video_models.rs index 55f565b998..4d7c51757d 100644 --- a/crates/service/web/storyteller_web/src/configs/omni_gen/video_models.rs +++ b/crates/service/web/storyteller_web/src/configs/omni_gen/video_models.rs @@ -185,6 +185,125 @@ fn build_omni_gen_video_models() -> Vec { ..Default::default() }); + models.push(OmniGenVideoModelDetails { + model: CommonVideoModel::Kling16Pro, + full_name: Some("Kling 1.6 Pro".to_string()), + starting_keyframe_supported: Some(true), + ending_keyframe_supported: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Square, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine), + duration_seconds_options: Some(vec![5, 10]), + duration_seconds_default: Some(5), + ..Default::default() + }); + + models.push(OmniGenVideoModelDetails { + model: CommonVideoModel::Kling21Pro, + full_name: Some("Kling 2.1 Pro".to_string()), + starting_keyframe_supported: Some(true), + ending_keyframe_supported: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Square, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine), + duration_seconds_options: Some(vec![5, 10]), + duration_seconds_default: Some(5), + ..Default::default() + }); + + models.push(OmniGenVideoModelDetails { + model: CommonVideoModel::Kling21Master, + full_name: Some("Kling 2.1 Master".to_string()), + starting_keyframe_supported: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Square, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine), + duration_seconds_options: Some(vec![5, 10]), + duration_seconds_default: Some(5), + ..Default::default() + }); + + models.push(OmniGenVideoModelDetails { + model: CommonVideoModel::Kling2p5TurboPro, + full_name: Some("Kling 2.5 Turbo Pro".to_string()), + text_prompt_supported: Some(true), + starting_keyframe_supported: Some(true), + ending_keyframe_supported: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Square, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine), + duration_seconds_options: Some(vec![5, 10]), + duration_seconds_default: Some(5), + ..Default::default() + }); + + models.push(OmniGenVideoModelDetails { + model: CommonVideoModel::Kling2p6Pro, + full_name: Some("Kling 2.6 Pro".to_string()), + text_prompt_supported: Some(true), + starting_keyframe_supported: Some(true), + show_generate_with_sound_toggle: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Square, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine), + duration_seconds_options: Some(vec![5, 10]), + duration_seconds_default: Some(5), + ..Default::default() + }); + + models.push(OmniGenVideoModelDetails { + model: CommonVideoModel::Kling3p0Pro, + full_name: Some("Kling 3.0 Pro".to_string()), + text_prompt_supported: Some(true), + starting_keyframe_supported: Some(true), + ending_keyframe_supported: Some(true), + show_generate_with_sound_toggle: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Square, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine), + duration_seconds_min: Some(3), + duration_seconds_max: Some(15), + duration_seconds_default: Some(5), + ..Default::default() + }); + + models.push(OmniGenVideoModelDetails { + model: CommonVideoModel::Kling3p0Standard, + full_name: Some("Kling 3.0 Standard".to_string()), + text_prompt_supported: Some(true), + starting_keyframe_supported: Some(true), + ending_keyframe_supported: Some(true), + show_generate_with_sound_toggle: Some(true), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::Square, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine), + duration_seconds_min: Some(3), + duration_seconds_max: Some(15), + duration_seconds_default: Some(5), + ..Default::default() + }); + models } @@ -222,6 +341,34 @@ fn build_omni_gen_video_model_providers() -> Vec