artcraft router: all video is on "v2"; artcraft desktop: all models on "omni"; storyteller-web: omni on video v2 (#1577)

This is a lot of legacy path cleanup. 

These are now the only valid paths:

- artcraft website: storyteller-web omni video gen -> artcraft_router v2
video pipeline

- artcraft desktop: artcraft_router v2 video pipeline (for artcraft
provider) -> storyteller-web omni video gen
This commit is contained in:
Brandon Thomas authored and GitHub committed 2026-05-26 10:25:59 -04:00
1 parent c36c0d283c
commit 370d2fc494
188 files changed
+433 -15220

No files matched your search

Generated
-1
View File
@@ -805,7 +805,6 @@ dependencies = [
"gmicloud_client",
"grok_api_client",
"log",
"muapi_client",
"reqwest 0.12.23",
"seedance2pro_client",
"serde",
@@ -22,7 +22,6 @@ artcraft_client.workspace = true
fal_client.workspace = true
gmicloud_client.workspace = true
grok_api_client.workspace = true
muapi_client.workspace = true
seedance2pro_client.workspace = true
tokens.workspace = true
url_utils.workspace = true
@@ -8,6 +8,5 @@ pub enum Provider {
Fal,
GmiCloud,
GrokApi,
Muapi,
Seedance2Pro,
}
@@ -8,5 +8,4 @@ pub mod router_fal_client;
pub mod router_fal_webhook_optional_client;
pub mod router_gmicloud_client;
pub mod router_grok_api_client;
pub mod router_muapi_client;
pub mod router_seedance2pro_client;
@@ -3,7 +3,6 @@ use crate::client::router_fal_client::RouterFalClient;
use crate::client::router_fal_webhook_optional_client::RouterFalWebhookOptionalClient;
use crate::client::router_gmicloud_client::RouterGmiCloudClient;
use crate::client::router_grok_api_client::RouterGrokApiClient;
use crate::client::router_muapi_client::RouterMuapiClient;
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
use crate::errors::client_error::{ClientError, ClientType};
@@ -13,7 +12,6 @@ pub struct MultiRouterClient {
pub(crate) fal_webhook_optional_client: Option<RouterFalWebhookOptionalClient>,
pub(crate) gmicloud_client: Option<RouterGmiCloudClient>,
pub(crate) grok_api_client: Option<RouterGrokApiClient>,
pub(crate) muapi_client: Option<RouterMuapiClient>,
pub(crate) seedance2pro_client: Option<RouterSeedance2ProClient>,
}
@@ -43,11 +41,6 @@ impl MultiRouterClient {
.ok_or(ClientError::ClientNotConfigured(ClientType::GrokApi))
}
pub fn get_muapi_client_ref(&self) -> Result<&RouterMuapiClient, ClientError> {
self.muapi_client.as_ref()
.ok_or(ClientError::ClientNotConfigured(ClientType::Muapi))
}
pub fn get_seedance2pro_client_ref(&self) -> Result<&RouterSeedance2ProClient, ClientError> {
self.seedance2pro_client.as_ref()
.ok_or(ClientError::ClientNotConfigured(ClientType::Seedance2Pro))
@@ -4,7 +4,6 @@ use crate::client::router_fal_client::RouterFalClient;
use crate::client::router_fal_webhook_optional_client::RouterFalWebhookOptionalClient;
use crate::client::router_gmicloud_client::RouterGmiCloudClient;
use crate::client::router_grok_api_client::RouterGrokApiClient;
use crate::client::router_muapi_client::RouterMuapiClient;
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
pub struct MultiRouterClientBuilder {
@@ -13,7 +12,6 @@ pub struct MultiRouterClientBuilder {
fal_webhook_optional_client: Option<RouterFalWebhookOptionalClient>,
gmicloud_client: Option<RouterGmiCloudClient>,
grok_api_client: Option<RouterGrokApiClient>,
muapi_client: Option<RouterMuapiClient>,
seedance2pro_client: Option<RouterSeedance2ProClient>,
}
@@ -25,7 +23,6 @@ impl MultiRouterClientBuilder {
fal_webhook_optional_client: None,
gmicloud_client: None,
grok_api_client: None,
muapi_client: None,
seedance2pro_client: None,
}
}
@@ -55,11 +52,6 @@ impl MultiRouterClientBuilder {
self
}
pub fn set_muapi_client(mut self, client: RouterMuapiClient) -> Self {
self.muapi_client = Some(client);
self
}
pub fn set_seedance2pro_client(mut self, client: RouterSeedance2ProClient) -> Self {
self.seedance2pro_client = Some(client);
self
@@ -72,7 +64,6 @@ impl MultiRouterClientBuilder {
fal_webhook_optional_client: self.fal_webhook_optional_client,
gmicloud_client: self.gmicloud_client,
grok_api_client: self.grok_api_client,
muapi_client: self.muapi_client,
seedance2pro_client: self.seedance2pro_client,
}
}
@@ -4,7 +4,6 @@ use crate::client::router_fal_client::RouterFalClient;
use crate::client::router_fal_webhook_optional_client::RouterFalWebhookOptionalClient;
use crate::client::router_gmicloud_client::RouterGmiCloudClient;
use crate::client::router_grok_api_client::RouterGrokApiClient;
use crate::client::router_muapi_client::RouterMuapiClient;
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
use crate::errors::client_error::{ClientError, ClientType};
@@ -15,7 +14,6 @@ pub enum RouterClient {
FalWebhookOptional(RouterFalWebhookOptionalClient),
GmiCloud(RouterGmiCloudClient),
GrokApi(RouterGrokApiClient),
Muapi(RouterMuapiClient),
Seedance2Pro(RouterSeedance2ProClient),
}
@@ -60,14 +58,6 @@ impl RouterClient {
}
}
pub fn get_muapi_client_ref(&self) -> Result<&RouterMuapiClient, ClientError> {
match self {
RouterClient::Muapi(client) => Ok(client),
RouterClient::Multi(multi) => multi.get_muapi_client_ref(),
_ => Err(ClientError::ClientNotConfigured(ClientType::Muapi)),
}
}
pub fn get_seedance2pro_client_ref(&self) -> Result<&RouterSeedance2ProClient, ClientError> {
match self {
RouterClient::Seedance2Pro(client) => Ok(client),
@@ -1,11 +0,0 @@
use muapi_client::creds::muapi_session::MuapiSession;
pub struct RouterMuapiClient {
pub(crate) session: MuapiSession,
}
impl RouterMuapiClient {
pub fn new(session: MuapiSession) -> Self {
RouterMuapiClient { session }
}
}
@@ -10,7 +10,6 @@ pub enum ClientType {
FalWebhookOptional,
GmiCloud,
GrokApi,
Muapi,
Seedance2Pro,
}
@@ -22,7 +21,6 @@ impl Display for ClientType {
Self::FalWebhookOptional => write!(f, "FalWebhookOptional"),
Self::GmiCloud => write!(f, "GmiCloud"),
Self::GrokApi => write!(f, "GrokApi"),
Self::Muapi => write!(f, "Muapi"),
Self::Seedance2Pro => write!(f, "Seedance2Pro"),
}
}
@@ -49,9 +47,6 @@ pub enum ClientError {
/// Fal only accepts image URLs for image inputs, not media tokens.
FalOnlySupportsUrls,
/// Muapi only accepts URLs for image inputs, not media tokens.
MuapiOnlySupportsUrls,
/// Seedance2Pro only accepts URLs for media inputs, not media tokens.
Seedance2ProOnlySupportsUrls,
@@ -91,9 +86,6 @@ impl Display for ClientError {
Self::FalOnlySupportsUrls => {
write!(f, "Fal only supports image URLs for image inputs, not media tokens")
}
Self::MuapiOnlySupportsUrls => {
write!(f, "Muapi only supports URLs for image inputs; resolve media tokens to URLs before calling this provider")
}
Self::Seedance2ProOnlySupportsUrls => {
write!(f, "Seedance2Pro only supports URLs for media inputs; resolve media tokens to URLs before calling this provider")
}
@@ -4,7 +4,6 @@ use artcraft_client::error::storyteller_error::StorytellerError;
use fal_client::error::fal_error_plus::FalErrorPlus;
use gmicloud_client::error::gmicloud_error::GmiCloudError;
use grok_api_client::error::grok_error::GrokError;
use muapi_client::error::muapi_error::MuapiError;
use seedance2pro_client::error::seedance2pro_error::Seedance2ProError;
#[derive(Debug)]
@@ -13,7 +12,6 @@ pub enum ProviderError {
Fal(FalErrorPlus),
GmiCloud(GmiCloudError),
Grok(GrokError),
Muapi(MuapiError),
Seedance2Pro(Seedance2ProError),
}
@@ -26,7 +24,6 @@ impl Display for ProviderError {
Self::Fal(e) => write!(f, "Fal provider error: {}", e),
Self::GmiCloud(e) => write!(f, "GmiCloud provider error: {}", e),
Self::Grok(e) => write!(f, "Grok provider error: {}", e),
Self::Muapi(e) => write!(f, "Muapi provider error: {}", e),
Self::Seedance2Pro(e) => write!(f, "Seedance2Pro provider error: {}", e),
}
}
@@ -56,12 +53,6 @@ impl From<GrokError> for ProviderError {
}
}
impl From<MuapiError> for ProviderError {
fn from(error: MuapiError) -> Self {
Self::Muapi(error)
}
}
impl From<Seedance2ProError> for ProviderError {
fn from(error: Seedance2ProError) -> Self {
Self::Seedance2Pro(error)
@@ -1,99 +0,0 @@
use artcraft_api_defs::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::Kling3p0ProMultiFunctionVideoGenDuration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_kling_3p0_pro_text_to_video_webhook::{
EnqueueKling3p0ProTextToVideoRequest,
EnqueueKling3p0ProTextToVideoDuration,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_pro::PlanArtcraftKling3p0Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling3p0_pro(
plan: &PlanArtcraftKling3p0Pro,
) -> VideoGenerationCostEstimate {
let duration = plan.duration
.map(|d| match d {
Kling3p0ProMultiFunctionVideoGenDuration::ThreeSeconds => EnqueueKling3p0ProTextToVideoDuration::ThreeSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::FourSeconds => EnqueueKling3p0ProTextToVideoDuration::FourSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::FiveSeconds => EnqueueKling3p0ProTextToVideoDuration::FiveSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::SixSeconds => EnqueueKling3p0ProTextToVideoDuration::SixSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::SevenSeconds => EnqueueKling3p0ProTextToVideoDuration::SevenSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::EightSeconds => EnqueueKling3p0ProTextToVideoDuration::EightSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::NineSeconds => EnqueueKling3p0ProTextToVideoDuration::NineSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::TenSeconds => EnqueueKling3p0ProTextToVideoDuration::TenSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::ElevenSeconds => EnqueueKling3p0ProTextToVideoDuration::ElevenSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::TwelveSeconds => EnqueueKling3p0ProTextToVideoDuration::TwelveSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::ThirteenSeconds => EnqueueKling3p0ProTextToVideoDuration::ThirteenSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::FourteenSeconds => EnqueueKling3p0ProTextToVideoDuration::FourteenSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::FifteenSeconds => EnqueueKling3p0ProTextToVideoDuration::FifteenSeconds,
});
// Use text-to-video cost calculator (same pricing for both modes)
let req = EnqueueKling3p0ProTextToVideoRequest {
prompt: String::new(),
generate_audio: plan.generate_audio,
negative_prompt: None,
duration,
aspect_ratio: None,
shot_type: None,
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
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,
failures_are_refunded: None,
}
}
#[cfg(test)]
mod tests {
use crate::api::common_video_model::CommonVideoModel;
use crate::api::provider::Provider;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
fn estimate_usd_cents(duration_seconds: u16) -> u64 {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Kling3p0Pro,
provider: Provider::Artcraft,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution: None,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: None,
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
#[test]
fn test_estimate_cost_usd_cents() {
// Kling 3.0 Pro: $0.336/sec (audio on, the default when None)
// Formula: (336 * duration_secs + 9) / 10 → ceiling division
// 5s: (336 * 5 + 9) / 10 = 1689 / 10 = 168
assert_eq!(estimate_usd_cents(5), 168);
// 10s: (336 * 10 + 9) / 10 = 3369 / 10 = 336
assert_eq!(estimate_usd_cents(10), 336);
// 15s: (336 * 15 + 9) / 10 = 5049 / 10 = 504
assert_eq!(estimate_usd_cents(15), 504);
}
}
@@ -1,99 +0,0 @@
use artcraft_api_defs::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::Kling3p0StandardMultiFunctionVideoGenDuration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_kling_3p0_standard_text_to_video_webhook::{
EnqueueKling3p0StandardTextToVideoRequest,
EnqueueKling3p0StandardTextToVideoDuration,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_standard::PlanArtcraftKling3p0Standard;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling3p0_standard(
plan: &PlanArtcraftKling3p0Standard,
) -> VideoGenerationCostEstimate {
let duration = plan.duration
.map(|d| match d {
Kling3p0StandardMultiFunctionVideoGenDuration::ThreeSeconds => EnqueueKling3p0StandardTextToVideoDuration::ThreeSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::FourSeconds => EnqueueKling3p0StandardTextToVideoDuration::FourSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::FiveSeconds => EnqueueKling3p0StandardTextToVideoDuration::FiveSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::SixSeconds => EnqueueKling3p0StandardTextToVideoDuration::SixSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::SevenSeconds => EnqueueKling3p0StandardTextToVideoDuration::SevenSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::EightSeconds => EnqueueKling3p0StandardTextToVideoDuration::EightSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::NineSeconds => EnqueueKling3p0StandardTextToVideoDuration::NineSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::TenSeconds => EnqueueKling3p0StandardTextToVideoDuration::TenSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::ElevenSeconds => EnqueueKling3p0StandardTextToVideoDuration::ElevenSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::TwelveSeconds => EnqueueKling3p0StandardTextToVideoDuration::TwelveSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::ThirteenSeconds => EnqueueKling3p0StandardTextToVideoDuration::ThirteenSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::FourteenSeconds => EnqueueKling3p0StandardTextToVideoDuration::FourteenSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::FifteenSeconds => EnqueueKling3p0StandardTextToVideoDuration::FifteenSeconds,
});
// Use text-to-video cost calculator (same pricing for both modes)
let req = EnqueueKling3p0StandardTextToVideoRequest {
prompt: String::new(),
generate_audio: plan.generate_audio,
negative_prompt: None,
duration,
aspect_ratio: None,
shot_type: None,
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
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,
failures_are_refunded: None,
}
}
#[cfg(test)]
mod tests {
use crate::api::common_video_model::CommonVideoModel;
use crate::api::provider::Provider;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
fn estimate_usd_cents(duration_seconds: u16) -> u64 {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Kling3p0Standard,
provider: Provider::Artcraft,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution: None,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: None,
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
#[test]
fn test_estimate_cost_usd_cents() {
// Kling 3.0 Standard: $0.252/sec (audio on, the default when None)
// Formula: (252 * duration_secs + 9) / 10 → ceiling division
// 5s: (252 * 5 + 9) / 10 = 1269 / 10 = 126
assert_eq!(estimate_usd_cents(5), 126);
// 10s: (252 * 10 + 9) / 10 = 2529 / 10 = 252
assert_eq!(estimate_usd_cents(10), 252);
// 15s: (252 * 15 + 9) / 10 = 3789 / 10 = 378
assert_eq!(estimate_usd_cents(15), 378);
}
}
@@ -1,19 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_1_6_pro::PlanArtcraftKling16Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling_1_6_pro(
plan: &PlanArtcraftKling16Pro,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v1p6_pro: 5s = $0.475 (rounded up to 48¢), 10s = $0.95 (95¢).
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,
failures_are_refunded: None,
}
}
@@ -1,19 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_1_master::PlanArtcraftKling21Master;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling_2_1_master(
plan: &PlanArtcraftKling21Master,
) -> 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,
failures_are_refunded: None,
}
}
@@ -1,19 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_1_pro::PlanArtcraftKling21Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling_2_1_pro(
plan: &PlanArtcraftKling21Pro,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v2p1_pro: 5s = $0.45, 10s = $0.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,
failures_are_refunded: None,
}
}
@@ -1,19 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_5_turbo_pro::PlanArtcraftKling2p5TurboPro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling_2_5_turbo_pro(
plan: &PlanArtcraftKling2p5TurboPro,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client kling_v2p5_turbo_pro: 5s = $0.35, 10s = $0.70.
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,
failures_are_refunded: None,
}
}
@@ -1,27 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_6_pro::PlanArtcraftKling2p6Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling_2_6_pro(
plan: &PlanArtcraftKling2p6Pro,
) -> 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,
failures_are_refunded: None,
}
}
@@ -1,102 +0,0 @@
use artcraft_api_defs::generate::video::multi_function::seedance_1p5_pro_multi_function_video_gen::Seedance1p5ProMultiFunctionVideoGenDuration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_seedance_1p5_pro_text_to_video_webhook::{
EnqueueSeedance1p5ProTextToVideoRequest,
EnqueueSeedance1p5ProTextToVideoDuration,
EnqueueSeedance1p5ProTextToVideoResolution,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance1p5_pro::PlanArtcraftSeedance1p5Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_seedance1p5_pro(
plan: &PlanArtcraftSeedance1p5Pro,
) -> VideoGenerationCostEstimate {
let duration = plan.duration
.map(|d| match d {
Seedance1p5ProMultiFunctionVideoGenDuration::FourSeconds => EnqueueSeedance1p5ProTextToVideoDuration::FourSeconds,
Seedance1p5ProMultiFunctionVideoGenDuration::FiveSeconds => EnqueueSeedance1p5ProTextToVideoDuration::FiveSeconds,
Seedance1p5ProMultiFunctionVideoGenDuration::SixSeconds => EnqueueSeedance1p5ProTextToVideoDuration::SixSeconds,
Seedance1p5ProMultiFunctionVideoGenDuration::SevenSeconds => EnqueueSeedance1p5ProTextToVideoDuration::SevenSeconds,
Seedance1p5ProMultiFunctionVideoGenDuration::EightSeconds => EnqueueSeedance1p5ProTextToVideoDuration::EightSeconds,
Seedance1p5ProMultiFunctionVideoGenDuration::NineSeconds => EnqueueSeedance1p5ProTextToVideoDuration::NineSeconds,
Seedance1p5ProMultiFunctionVideoGenDuration::TenSeconds => EnqueueSeedance1p5ProTextToVideoDuration::TenSeconds,
Seedance1p5ProMultiFunctionVideoGenDuration::ElevenSeconds => EnqueueSeedance1p5ProTextToVideoDuration::ElevenSeconds,
Seedance1p5ProMultiFunctionVideoGenDuration::TwelveSeconds => EnqueueSeedance1p5ProTextToVideoDuration::TwelveSeconds,
});
let resolution = plan.resolution
.map(|r| match r {
artcraft_api_defs::generate::video::multi_function::seedance_1p5_pro_multi_function_video_gen::Seedance1p5ProMultiFunctionVideoGenResolution::FourEightyP => EnqueueSeedance1p5ProTextToVideoResolution::FourEightyP,
artcraft_api_defs::generate::video::multi_function::seedance_1p5_pro_multi_function_video_gen::Seedance1p5ProMultiFunctionVideoGenResolution::SevenTwentyP => EnqueueSeedance1p5ProTextToVideoResolution::SevenTwentyP,
artcraft_api_defs::generate::video::multi_function::seedance_1p5_pro_multi_function_video_gen::Seedance1p5ProMultiFunctionVideoGenResolution::TenEightyP => EnqueueSeedance1p5ProTextToVideoResolution::TenEightyP,
});
// Use text-to-video cost calculator (same pricing for both modes)
let req = EnqueueSeedance1p5ProTextToVideoRequest {
prompt: String::new(),
duration,
resolution,
aspect_ratio: None,
generate_audio: plan.generate_audio,
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
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,
failures_are_refunded: None,
}
}
#[cfg(test)]
mod tests {
use crate::api::common_video_model::CommonVideoModel;
use crate::api::provider::Provider;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
fn estimate_usd_cents(duration_seconds: u16) -> u64 {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Seedance1p5Pro,
provider: Provider::Artcraft,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution: None,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: None,
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
#[test]
fn test_estimate_cost_usd_cents() {
// Default resolution is 720p (when None)
// 720p 5s = 26 cents (constant from Fal)
assert_eq!(estimate_usd_cents(5), 26);
// Other durations use token-based calculation
assert_eq!(estimate_usd_cents(4), 26);
assert_eq!(estimate_usd_cents(10), 65);
assert_eq!(estimate_usd_cents(12), 78);
}
}
@@ -1,204 +0,0 @@
use artcraft_api_defs::generate::video::multi_function::seedance_2p0_multi_function_video_gen::Seedance2p0BatchCount;
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviGenerateVideoRequest, KinoviModelType, KinoviOutputResolution};
use crate::api::common_resolution::CommonResolution;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::PlanArtcraftSeedance2p0;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub (crate) fn estimate_video_cost_artcraft_seedance2p0(
plan: &PlanArtcraftSeedance2p0,
) -> VideoGenerationCostEstimate {
let duration_seconds = plan.duration_seconds.unwrap_or(5).clamp(4, 15);
let batch_count = match plan.batch_count {
Seedance2p0BatchCount::One => KinoviBatchCount::One,
Seedance2p0BatchCount::Two => KinoviBatchCount::Two,
Seedance2p0BatchCount::Four => KinoviBatchCount::Four,
};
let output_resolution = plan.resolution.map(map_common_resolution_to_kinovi);
let request = KinoviGenerateVideoRequest {
model_type: KinoviModelType::Seedance2Pro,
prompt: String::new(),
aspect_ratio: KinoviAspectRatio::Square1x1,
duration_seconds,
batch_count,
output_resolution,
// TODO: This is a cost factor
reference_video_urls: None,
// NB: These do not contribute to costs in the Seedance2 integration
start_frame_url: None,
end_frame_url: None,
reference_image_urls: None,
reference_audio_urls: None,
character_ids: None,
use_face_blur_hack: None,
};
let cost_in_usd_cents = request.estimate_cost_in_usd_cents();
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,
failures_are_refunded: None,
}
}
fn map_common_resolution_to_kinovi(resolution: CommonResolution) -> KinoviOutputResolution {
match resolution {
CommonResolution::FourEightyP => KinoviOutputResolution::FourEightyP,
CommonResolution::SevenTwentyP => KinoviOutputResolution::SevenTwentyP,
CommonResolution::TenEightyP => KinoviOutputResolution::TenEightyP,
// For resolutions that don't map directly, default to 720p
_ => KinoviOutputResolution::SevenTwentyP,
}
}
#[cfg(test)]
mod tests {
use crate::api::common_resolution::CommonResolution;
use crate::api::common_video_model::CommonVideoModel;
use crate::api::provider::Provider;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
fn estimate_usd_cents(
duration_seconds: u16,
video_batch_count: u16,
resolution: Option<CommonResolution>,
) -> u64 {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Seedance2p0,
provider: Provider::Artcraft,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: Some(video_batch_count),
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
// -- 720p (default / legacy pricing: 250 credits/$1) --
#[test]
fn pro_720p_batch_1() {
assert_eq!(estimate_usd_cents(4, 1, Some(CommonResolution::SevenTwentyP)), 64);
assert_eq!(estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP)), 80);
assert_eq!(estimate_usd_cents(6, 1, Some(CommonResolution::SevenTwentyP)), 96);
assert_eq!(estimate_usd_cents(7, 1, Some(CommonResolution::SevenTwentyP)), 112);
assert_eq!(estimate_usd_cents(15, 1, Some(CommonResolution::SevenTwentyP)), 240);
}
#[test]
fn pro_720p_batch_2() {
assert_eq!(estimate_usd_cents(4, 2, Some(CommonResolution::SevenTwentyP)), 128);
assert_eq!(estimate_usd_cents(5, 2, Some(CommonResolution::SevenTwentyP)), 160);
assert_eq!(estimate_usd_cents(15, 2, Some(CommonResolution::SevenTwentyP)), 480);
}
#[test]
fn pro_720p_batch_4() {
assert_eq!(estimate_usd_cents(4, 4, Some(CommonResolution::SevenTwentyP)), 256);
assert_eq!(estimate_usd_cents(5, 4, Some(CommonResolution::SevenTwentyP)), 320);
assert_eq!(estimate_usd_cents(15, 4, Some(CommonResolution::SevenTwentyP)), 960);
}
#[test]
fn pro_none_resolution_same_as_720p() {
assert_eq!(
estimate_usd_cents(5, 1, None),
estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP)),
);
}
// -- 480p (new pricing: 193 credits/$1) --
#[test]
fn pro_480p_batch_1() {
assert_eq!(estimate_usd_cents(4, 1, Some(CommonResolution::FourEightyP)), 31);
assert_eq!(estimate_usd_cents(5, 1, Some(CommonResolution::FourEightyP)), 39);
assert_eq!(estimate_usd_cents(10, 1, Some(CommonResolution::FourEightyP)), 78);
assert_eq!(estimate_usd_cents(15, 1, Some(CommonResolution::FourEightyP)), 117);
}
#[test]
fn pro_480p_batch_2() {
assert_eq!(estimate_usd_cents(5, 2, Some(CommonResolution::FourEightyP)), 78);
}
#[test]
fn pro_480p_batch_4() {
assert_eq!(estimate_usd_cents(5, 4, Some(CommonResolution::FourEightyP)), 155);
}
// -- 1080p (new pricing: 193 credits/$1) --
#[test]
fn pro_1080p_batch_1() {
assert_eq!(estimate_usd_cents(4, 1, Some(CommonResolution::TenEightyP)), 187);
assert_eq!(estimate_usd_cents(5, 1, Some(CommonResolution::TenEightyP)), 233);
assert_eq!(estimate_usd_cents(10, 1, Some(CommonResolution::TenEightyP)), 466);
assert_eq!(estimate_usd_cents(15, 1, Some(CommonResolution::TenEightyP)), 699);
}
#[test]
fn pro_1080p_batch_2() {
assert_eq!(estimate_usd_cents(5, 2, Some(CommonResolution::TenEightyP)), 466);
}
#[test]
fn pro_1080p_batch_4() {
assert_eq!(estimate_usd_cents(5, 4, Some(CommonResolution::TenEightyP)), 933);
}
// -- Relative pricing --
#[test]
fn pro_480p_cheaper_than_720p_cheaper_than_1080p() {
let c480 = estimate_usd_cents(5, 1, Some(CommonResolution::FourEightyP));
let c720 = estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP));
let c1080 = estimate_usd_cents(5, 1, Some(CommonResolution::TenEightyP));
assert!(c480 < c720, "480p ({}) should be cheaper than 720p ({})", c480, c720);
assert!(c720 < c1080, "720p ({}) should be cheaper than 1080p ({})", c720, c1080);
}
#[test]
fn cost_scales_with_duration() {
let c4 = estimate_usd_cents(4, 1, Some(CommonResolution::SevenTwentyP));
let c10 = estimate_usd_cents(10, 1, Some(CommonResolution::SevenTwentyP));
let c15 = estimate_usd_cents(15, 1, Some(CommonResolution::SevenTwentyP));
assert!(c4 < c10);
assert!(c10 < c15);
}
#[test]
fn cost_scales_with_batch() {
let b1 = estimate_usd_cents(5, 1, Some(CommonResolution::TenEightyP));
let b2 = estimate_usd_cents(5, 2, Some(CommonResolution::TenEightyP));
let b4 = estimate_usd_cents(5, 4, Some(CommonResolution::TenEightyP));
assert!(b1 < b2);
assert!(b2 < b4);
}
}
@@ -1,295 +0,0 @@
use artcraft_api_defs::generate::video::multi_function::seedance_2p0_multi_function_video_gen::Seedance2p0BatchCount;
use seedance2pro_client::requests::generate_video::generate_video::{KinoviGenerateVideoRequest, KinoviBatchCount, KinoviModelType, KinoviOutputResolution, KinoviAspectRatio};
use crate::api::common_resolution::CommonResolution;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::PlanArtcraftSeedance2p0;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_seedance2p0_fast(
plan: &PlanArtcraftSeedance2p0,
) -> VideoGenerationCostEstimate {
let duration_seconds = plan.duration_seconds.unwrap_or(5).clamp(4, 15);
let batch_count = match plan.batch_count {
Seedance2p0BatchCount::One => KinoviBatchCount::One,
Seedance2p0BatchCount::Two => KinoviBatchCount::Two,
Seedance2p0BatchCount::Four => KinoviBatchCount::Four,
};
let output_resolution = plan.resolution.map(map_common_resolution_to_kinovi);
let request = KinoviGenerateVideoRequest {
model_type: KinoviModelType::Seedance2Fast,
prompt: String::new(),
aspect_ratio: KinoviAspectRatio::Square1x1,
duration_seconds,
batch_count,
output_resolution,
// TODO: This is a cost factor
reference_video_urls: None,
// NB: These do not contribute to costs in the Seedance2 integration
start_frame_url: None,
end_frame_url: None,
reference_image_urls: None,
reference_audio_urls: None,
character_ids: None,
use_face_blur_hack: None,
};
let cost_in_usd_cents = request.estimate_cost_in_usd_cents();
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,
failures_are_refunded: None,
}
}
fn map_common_resolution_to_kinovi(resolution: CommonResolution) -> KinoviOutputResolution {
match resolution {
CommonResolution::FourEightyP => KinoviOutputResolution::FourEightyP,
CommonResolution::SevenTwentyP => KinoviOutputResolution::SevenTwentyP,
CommonResolution::TenEightyP => KinoviOutputResolution::TenEightyP,
// For resolutions that don't map directly, default to 720p
_ => KinoviOutputResolution::SevenTwentyP,
}
}
#[cfg(test)]
mod tests {
use crate::api::common_resolution::CommonResolution;
use crate::api::common_video_model::CommonVideoModel;
use crate::api::provider::Provider;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
fn estimate_usd_cents(
duration_seconds: u16,
video_batch_count: u16,
resolution: Option<CommonResolution>,
) -> u64 {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Seedance2p0Fast,
provider: Provider::Artcraft,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: Some(video_batch_count),
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
fn estimate_usd_cents_downgrade(
duration_seconds: u16,
video_batch_count: u16,
resolution: Option<CommonResolution>,
) -> u64 {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Seedance2p0Fast,
provider: Provider::Artcraft,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: Some(video_batch_count),
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
fn estimate_pro_usd_cents(
duration_seconds: u16,
video_batch_count: u16,
resolution: Option<CommonResolution>,
) -> u64 {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Seedance2p0,
provider: Provider::Artcraft,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: Some(video_batch_count),
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
// -- 720p (default / legacy pricing: 220 credits/$1) --
#[test]
fn fast_720p_batch_1() {
assert_eq!(estimate_usd_cents(4, 1, Some(CommonResolution::SevenTwentyP)), 51);
assert_eq!(estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP)), 64);
assert_eq!(estimate_usd_cents(10, 1, Some(CommonResolution::SevenTwentyP)), 127);
assert_eq!(estimate_usd_cents(15, 1, Some(CommonResolution::SevenTwentyP)), 191);
}
#[test]
fn fast_720p_batch_2() {
assert_eq!(estimate_usd_cents(5, 2, Some(CommonResolution::SevenTwentyP)), 127);
}
#[test]
fn fast_720p_batch_4() {
assert_eq!(estimate_usd_cents(5, 4, Some(CommonResolution::SevenTwentyP)), 255);
}
#[test]
fn fast_none_resolution_same_as_720p() {
assert_eq!(
estimate_usd_cents(5, 1, None),
estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP)),
);
}
// -- 480p (new pricing: 193 credits/$1) --
#[test]
fn fast_480p_batch_1() {
assert_eq!(estimate_usd_cents(4, 1, Some(CommonResolution::FourEightyP)), 21);
assert_eq!(estimate_usd_cents(5, 1, Some(CommonResolution::FourEightyP)), 26);
assert_eq!(estimate_usd_cents(10, 1, Some(CommonResolution::FourEightyP)), 52);
assert_eq!(estimate_usd_cents(15, 1, Some(CommonResolution::FourEightyP)), 78);
}
#[test]
fn fast_480p_batch_2() {
assert_eq!(estimate_usd_cents(5, 2, Some(CommonResolution::FourEightyP)), 52);
}
#[test]
fn fast_480p_batch_4() {
assert_eq!(estimate_usd_cents(5, 4, Some(CommonResolution::FourEightyP)), 104);
}
// -- 1080p is not supported by Fast; ErrorOut rejects it --
#[test]
fn fast_1080p_error_out_rejects() {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Seedance2p0Fast,
provider: Provider::Artcraft,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution: Some(CommonResolution::TenEightyP),
aspect_ratio: None,
duration_seconds: Some(5),
video_batch_count: Some(1),
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
};
assert!(request.build().is_err());
}
// -- 1080p rounds down to 720p with PayLessDowngrade --
#[test]
fn fast_1080p_downgraded_same_as_720p() {
for duration in [4, 5, 10, 15] {
assert_eq!(
estimate_usd_cents_downgrade(duration, 1, Some(CommonResolution::TenEightyP)),
estimate_usd_cents(duration, 1, Some(CommonResolution::SevenTwentyP)),
);
}
}
// -- Relative pricing --
#[test]
fn fast_480p_cheaper_than_720p() {
let c480 = estimate_usd_cents(5, 1, Some(CommonResolution::FourEightyP));
let c720 = estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP));
assert!(c480 < c720, "480p ({}) should be cheaper than 720p ({})", c480, c720);
}
#[test]
fn fast_is_cheaper_than_pro() {
for res in [
Some(CommonResolution::FourEightyP),
Some(CommonResolution::SevenTwentyP),
] {
for duration in [4, 5, 10, 15] {
let fast = estimate_usd_cents(duration, 1, res);
let pro = estimate_pro_usd_cents(duration, 1, res);
assert!(
fast < pro,
"Fast ({}) should be cheaper than Pro ({}) at {}s {:?}",
fast, pro, duration, res,
);
}
}
}
#[test]
fn cost_scales_with_duration() {
let c4 = estimate_usd_cents(4, 1, Some(CommonResolution::SevenTwentyP));
let c10 = estimate_usd_cents(10, 1, Some(CommonResolution::SevenTwentyP));
let c15 = estimate_usd_cents(15, 1, Some(CommonResolution::SevenTwentyP));
assert!(c4 < c10);
assert!(c10 < c15);
}
#[test]
fn cost_scales_with_batch() {
let b1 = estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP));
let b2 = estimate_usd_cents(5, 2, Some(CommonResolution::SevenTwentyP));
let b4 = estimate_usd_cents(5, 4, Some(CommonResolution::SevenTwentyP));
assert!(b1 < b2);
assert!(b2 < b4);
}
}
@@ -1,47 +0,0 @@
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteResolution;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::{
Seedance1LiteDuration, Seedance1LiteRequest, Seedance1LiteResolution,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance_1_0_lite::PlanArtcraftSeedance10Lite;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_seedance_1_0_lite(
plan: &PlanArtcraftSeedance10Lite,
) -> VideoGenerationCostEstimate {
// Legacy handler defaults: 5 seconds, 720p. Delegate to the Fal client's
// cost calculator to guarantee parity with what we actually charge.
let duration = if plan.duration_seconds_for_cost() == 10 {
Seedance1LiteDuration::TenSeconds
} else {
Seedance1LiteDuration::FiveSeconds
};
let resolution = match plan.resolution_for_cost() {
GenerateSeedance10LiteResolution::FourEightyP => Seedance1LiteResolution::FourEightyP,
GenerateSeedance10LiteResolution::SevenTwentyP => Seedance1LiteResolution::SevenTwentyP,
};
let req = Seedance1LiteRequest {
image_url: String::new(),
end_frame_image_url: None,
prompt: String::new(),
duration,
resolution,
aspect_ratio: None,
camera_fixed: false,
seed: None,
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
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,
failures_are_refunded: None,
}
}
@@ -1,19 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_sora_2::PlanArtcraftSora2;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_sora_2(
plan: &PlanArtcraftSora2,
) -> VideoGenerationCostEstimate {
// Sora 2: $0.10/second. Default duration (Fal client): 4 seconds.
let cost_in_usd_cents = plan.duration_seconds_for_cost() * 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,
failures_are_refunded: None,
}
}
@@ -1,22 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_sora_2_pro::PlanArtcraftSora2Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_sora_2_pro(
plan: &PlanArtcraftSora2Pro,
) -> VideoGenerationCostEstimate {
// Sora 2 Pro: $0.30/second @ 720p, $0.50/second @ 1080p.
// Default duration 4s. Default resolution: 1080p for text-to-video, 720p (Auto) for image-to-video.
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.is_ten_eighty_p_for_cost() { 50 } else { 30 };
let cost_in_usd_cents = per_second_cents * seconds;
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,
failures_are_refunded: None,
}
}
@@ -1,21 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_2::PlanArtcraftVeo2;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_veo_2(
plan: &PlanArtcraftVeo2,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client veo_2 cost calculator: 5s = $2.50, +$0.50/s above 5s.
let seconds = plan.duration_seconds_for_cost();
let extra = seconds.saturating_sub(5);
let cost_in_usd_cents = 250 + extra * 50;
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,
failures_are_refunded: None,
}
}
@@ -1,22 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3::PlanArtcraftVeo3;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_veo_3(
plan: &PlanArtcraftVeo3,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client veo_3 cost calculator (720p/1080p):
// $0.20/sec audio off, $0.40/sec audio on. Default 8s, audio off (legacy handler).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio_for_cost() { 40 } else { 20 };
let cost_in_usd_cents = per_second_cents * seconds;
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,
failures_are_refunded: None,
}
}
@@ -1,22 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3_fast::PlanArtcraftVeo3Fast;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_veo_3_fast(
plan: &PlanArtcraftVeo3Fast,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client veo_3_fast cost calculator (720p/1080p):
// $0.10/sec audio off, $0.15/sec audio on. Default 8s, audio off (legacy handler).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio_for_cost() { 15 } else { 10 };
let cost_in_usd_cents = per_second_cents * seconds;
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,
failures_are_refunded: None,
}
}
@@ -1,22 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3p1::PlanArtcraftVeo3p1;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_veo_3p1(
plan: &PlanArtcraftVeo3p1,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client veo_3p1 cost calculator (720p/1080p):
// $0.20/sec audio off, $0.40/sec audio on. Default 6s, audio on (legacy handler).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio_for_cost() { 40 } else { 20 };
let cost_in_usd_cents = per_second_cents * seconds;
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,
failures_are_refunded: None,
}
}
@@ -1,22 +0,0 @@
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3p1_fast::PlanArtcraftVeo3p1Fast;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_veo_3p1_fast(
plan: &PlanArtcraftVeo3p1Fast,
) -> VideoGenerationCostEstimate {
// Mirrors fal_client veo_3p1_fast cost calculator (720p/1080p):
// $0.10/sec audio off, $0.15/sec audio on. Default 6s, audio on (legacy handler).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio_for_cost() { 15 } else { 10 };
let cost_in_usd_cents = per_second_cents * seconds;
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,
failures_are_refunded: None,
}
}
@@ -1,18 +0,0 @@
pub mod estimate_video_cost_artcraft_kling_1_6_pro;
pub mod estimate_video_cost_artcraft_kling_2_1_master;
pub mod estimate_video_cost_artcraft_kling_2_1_pro;
pub mod estimate_video_cost_artcraft_kling_2_5_turbo_pro;
pub mod estimate_video_cost_artcraft_kling_2_6_pro;
pub mod estimate_video_cost_artcraft_kling3p0_pro;
pub mod estimate_video_cost_artcraft_kling3p0_standard;
pub mod estimate_video_cost_artcraft_seedance_1_0_lite;
pub mod estimate_video_cost_artcraft_seedance1p5_pro;
pub mod estimate_video_cost_artcraft_seedance2p0;
pub mod estimate_video_cost_artcraft_seedance2p0_fast;
pub mod estimate_video_cost_artcraft_sora_2;
pub mod estimate_video_cost_artcraft_sora_2_pro;
pub mod estimate_video_cost_artcraft_veo_2;
pub mod estimate_video_cost_artcraft_veo_3;
pub mod estimate_video_cost_artcraft_veo_3_fast;
pub mod estimate_video_cost_artcraft_veo_3p1;
pub mod estimate_video_cost_artcraft_veo_3p1_fast;
@@ -1,19 +0,0 @@
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,
failures_are_refunded: None,
}
}
@@ -1,19 +0,0 @@
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,
failures_are_refunded: None,
}
}
@@ -1,19 +0,0 @@
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,
failures_are_refunded: None,
}
}
@@ -1,19 +0,0 @@
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,
failures_are_refunded: None,
}
}
@@ -1,27 +0,0 @@
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,
failures_are_refunded: None,
}
}
@@ -1,24 +0,0 @@
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,
failures_are_refunded: None,
}
}
@@ -1,23 +0,0 @@
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,
failures_are_refunded: None,
}
}
@@ -1,33 +0,0 @@
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::Seedance1LiteRequest;
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_seedance_1_0_lite::PlanFalSeedance10Lite;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_seedance_1_0_lite(
plan: &PlanFalSeedance10Lite,
) -> VideoGenerationCostEstimate {
// Delegate to the Fal client's cost calculator to guarantee parity with billing.
let req = Seedance1LiteRequest {
image_url: plan.image_url.clone(),
end_frame_image_url: plan.end_image_url.clone(),
prompt: plan.prompt.clone(),
duration: plan.duration,
resolution: plan.resolution,
aspect_ratio: plan.aspect_ratio,
camera_fixed: false,
seed: None,
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
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,
failures_are_refunded: None,
}
}
@@ -1,59 +0,0 @@
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_seedance_1p5_pro_text_to_video_webhook::{
EnqueueSeedance1p5ProTextToVideoRequest, EnqueueSeedance1p5ProTextToVideoDuration,
EnqueueSeedance1p5ProTextToVideoResolution,
};
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_seedance_1p5_pro::{
FalSeedance1p5ProDuration, FalSeedance1p5ProResolution, PlanFalSeedance1p5Pro,
};
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_seedance_1p5_pro(
plan: &PlanFalSeedance1p5Pro,
) -> VideoGenerationCostEstimate {
// Seedance 1.5 Pro t2v and i2v have identical pricing; delegate to the t2v
// calculator to guarantee parity with billing.
let req = EnqueueSeedance1p5ProTextToVideoRequest {
prompt: String::new(),
resolution: plan.resolution.map(to_t2v_resolution),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: None,
generate_audio: plan.generate_audio,
};
let cost_in_usd_cents = req.calculate_cost_in_cents();
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,
failures_are_refunded: None,
}
}
fn to_t2v_resolution(r: FalSeedance1p5ProResolution) -> EnqueueSeedance1p5ProTextToVideoResolution {
match r {
FalSeedance1p5ProResolution::FourEightyP => EnqueueSeedance1p5ProTextToVideoResolution::FourEightyP,
FalSeedance1p5ProResolution::SevenTwentyP => EnqueueSeedance1p5ProTextToVideoResolution::SevenTwentyP,
FalSeedance1p5ProResolution::TenEightyP => EnqueueSeedance1p5ProTextToVideoResolution::TenEightyP,
}
}
fn to_t2v_duration(d: FalSeedance1p5ProDuration) -> EnqueueSeedance1p5ProTextToVideoDuration {
use EnqueueSeedance1p5ProTextToVideoDuration as T;
match d {
FalSeedance1p5ProDuration::Four => T::FourSeconds,
FalSeedance1p5ProDuration::Five => T::FiveSeconds,
FalSeedance1p5ProDuration::Six => T::SixSeconds,
FalSeedance1p5ProDuration::Seven => T::SevenSeconds,
FalSeedance1p5ProDuration::Eight => T::EightSeconds,
FalSeedance1p5ProDuration::Nine => T::NineSeconds,
FalSeedance1p5ProDuration::Ten => T::TenSeconds,
FalSeedance1p5ProDuration::Eleven => T::ElevenSeconds,
FalSeedance1p5ProDuration::Twelve => T::TwelveSeconds,
}
}
@@ -1,19 +0,0 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_sora_2::PlanFalSora2;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_sora_2(
plan: &PlanFalSora2,
) -> VideoGenerationCostEstimate {
// Sora 2: $0.10/second. Default duration (Fal client): 4s.
let cost_in_usd_cents = plan.duration_seconds_for_cost() * 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,
failures_are_refunded: None,
}
}
@@ -1,21 +0,0 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_sora_2_pro::PlanFalSora2Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_sora_2_pro(
plan: &PlanFalSora2Pro,
) -> VideoGenerationCostEstimate {
// Sora 2 Pro: $0.30/second @ 720p, $0.50/second @ 1080p.
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.is_ten_eighty_p_for_cost() { 50 } else { 30 };
let cost_in_usd_cents = per_second_cents * seconds;
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,
failures_are_refunded: None,
}
}
@@ -1,21 +0,0 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_2::PlanFalVeo2;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_veo_2(
plan: &PlanFalVeo2,
) -> VideoGenerationCostEstimate {
// 5s = $2.50, +$0.50 per additional second.
let seconds = plan.duration_seconds_for_cost();
let extra = seconds.saturating_sub(5);
let cost_in_usd_cents = 250 + extra * 50;
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,
failures_are_refunded: None,
}
}
@@ -1,21 +0,0 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3::PlanFalVeo3;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_veo_3(
plan: &PlanFalVeo3,
) -> VideoGenerationCostEstimate {
// $0.20/sec audio off, $0.40/sec audio on (720p/1080p).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio { 40 } else { 20 };
let cost_in_usd_cents = per_second_cents * seconds;
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,
failures_are_refunded: None,
}
}
@@ -1,21 +0,0 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3_fast::PlanFalVeo3Fast;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_veo_3_fast(
plan: &PlanFalVeo3Fast,
) -> VideoGenerationCostEstimate {
// $0.10/sec audio off, $0.15/sec audio on (720p/1080p).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio { 15 } else { 10 };
let cost_in_usd_cents = per_second_cents * seconds;
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,
failures_are_refunded: None,
}
}
@@ -1,21 +0,0 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3p1::PlanFalVeo3p1;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_veo_3p1(
plan: &PlanFalVeo3p1,
) -> VideoGenerationCostEstimate {
// $0.20/sec audio off, $0.40/sec audio on (720p/1080p).
let seconds = plan.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.generate_audio_for_cost() { 40 } else { 20 };
let cost_in_usd_cents = per_second_cents * seconds;
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,
failures_are_refunded: None,
}
}
@@ -1,21 +0,0 @@
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3p1_fast::PlanFalVeo3p1Fast;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_fal_veo_3p1_fast(
plan: &PlanFalVeo3p1Fast,
) -> VideoGenerationCostEstimate {
// $0.10/sec audio off, $0.15/sec audio on (720p/1080p).
let seconds = plan.inner.duration_seconds_for_cost();
let per_second_cents: u64 = if plan.inner.generate_audio_for_cost() { 15 } else { 10 };
let cost_in_usd_cents = per_second_cents * seconds;
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,
failures_are_refunded: None,
}
}
@@ -1,16 +0,0 @@
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_seedance_1_0_lite;
pub(crate) mod estimate_video_cost_fal_seedance_1p5_pro;
pub(crate) mod estimate_video_cost_fal_sora_2;
pub(crate) mod estimate_video_cost_fal_sora_2_pro;
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;
pub(crate) mod estimate_video_cost_fal_veo_3p1;
pub(crate) mod estimate_video_cost_fal_veo_3p1_fast;
@@ -1,4 +0,0 @@
pub (crate) mod artcraft;
pub (crate) mod fal;
pub (crate) mod muapi;
pub (crate) mod seedance2pro;
@@ -1,60 +0,0 @@
use crate::generate::generate_video::plan::muapi::plan_generate_video_muapi_seedance2p0::{
MuapiSeedance2p0Mode, PlanMuapiSeedance2p0,
};
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
use muapi_client::creds::muapi_api_key::MuapiApiKey;
use muapi_client::creds::muapi_session::MuapiSession;
use muapi_client::requests::seedance_2p0_image_to_video::seedance_2p0_image_to_video::Seedance2p0ImageToVideoArgs;
use muapi_client::requests::seedance_2p0_text_to_video::seedance_2p0_text_to_video::Seedance2p0TextToVideoArgs;
pub(crate) fn estimate_video_cost_muapi_seedance2p0(
plan: &PlanMuapiSeedance2p0,
) -> VideoGenerationCostEstimate {
// A dummy session is sufficient — cost estimation does not make any network calls.
let dummy_session = MuapiSession::new(MuapiApiKey::new(String::new()));
let cost_in_usd_cents;
let cost_in_credits;
match &plan.mode {
MuapiSeedance2p0Mode::TextToVideo { aspect_ratio, duration, quality } => {
let args = Seedance2p0TextToVideoArgs {
session: &dummy_session,
prompt: String::new(),
aspect_ratio: *aspect_ratio,
duration: *duration,
quality: *quality,
};
cost_in_usd_cents = args.estimate_cost_in_usd_cents();
// NB: This is off by a factor of 100 because they use a fractional system.
cost_in_credits = (args.estimate_credits() * 100.0f64).round() as u64;
}
MuapiSeedance2p0Mode::ImageToVideo { aspect_ratio, duration, quality, .. } => {
let args = Seedance2p0ImageToVideoArgs {
session: &dummy_session,
prompt: String::new(),
image_urls: vec![],
aspect_ratio: *aspect_ratio,
duration: *duration,
quality: *quality,
};
cost_in_usd_cents = args.estimate_cost_in_usd_cents();
// NB: This is off by a factor of 100 because they use a fractional system.
cost_in_credits = (args.estimate_credits() * 100.0f64).round() as u64;
}
}
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_credits),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
failures_are_refunded: None,
}
}
@@ -1 +0,0 @@
pub mod estimate_video_cost_muapi_seedance2p0;
@@ -1,162 +0,0 @@
use crate::generate::generate_video::plan::seedance2pro::plan_generate_video_seedance2pro_seedance2p0::PlanSeedance2proSeedance2p0;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_seedance2pro_seedance2p0(
plan: &PlanSeedance2proSeedance2p0,
) -> VideoGenerationCostEstimate {
let cost_in_credits = plan.request.estimate_credits();
let cost_in_usd_cents = plan.request.estimate_cost_in_usd_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_credits as u64),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
failures_are_refunded: None,
}
}
#[cfg(test)]
mod tests {
use crate::api::common_resolution::CommonResolution;
use crate::api::common_video_model::CommonVideoModel;
use crate::api::provider::Provider;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
fn estimate_usd_cents(
duration_seconds: u16,
video_batch_count: u16,
resolution: Option<CommonResolution>,
) -> u64 {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Seedance2p0,
provider: Provider::Seedance2Pro,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: Some(video_batch_count),
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
// -- 720p (default / legacy pricing: 250 credits/$1) --
#[test]
fn pro_720p_batch_1() {
assert_eq!(estimate_usd_cents(4, 1, Some(CommonResolution::SevenTwentyP)), 64);
assert_eq!(estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP)), 80);
assert_eq!(estimate_usd_cents(6, 1, Some(CommonResolution::SevenTwentyP)), 96);
assert_eq!(estimate_usd_cents(7, 1, Some(CommonResolution::SevenTwentyP)), 112);
assert_eq!(estimate_usd_cents(10, 1, Some(CommonResolution::SevenTwentyP)), 160);
assert_eq!(estimate_usd_cents(15, 1, Some(CommonResolution::SevenTwentyP)), 240);
}
#[test]
fn pro_720p_batch_2() {
assert_eq!(estimate_usd_cents(4, 2, Some(CommonResolution::SevenTwentyP)), 128);
assert_eq!(estimate_usd_cents(5, 2, Some(CommonResolution::SevenTwentyP)), 160);
assert_eq!(estimate_usd_cents(15, 2, Some(CommonResolution::SevenTwentyP)), 480);
}
#[test]
fn pro_720p_batch_4() {
assert_eq!(estimate_usd_cents(4, 4, Some(CommonResolution::SevenTwentyP)), 256);
assert_eq!(estimate_usd_cents(5, 4, Some(CommonResolution::SevenTwentyP)), 320);
assert_eq!(estimate_usd_cents(15, 4, Some(CommonResolution::SevenTwentyP)), 960);
}
#[test]
fn pro_none_resolution_same_as_720p() {
assert_eq!(
estimate_usd_cents(5, 1, None),
estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP)),
);
}
// -- 480p (new pricing: 193 credits/$1) --
#[test]
fn pro_480p_batch_1() {
assert_eq!(estimate_usd_cents(4, 1, Some(CommonResolution::FourEightyP)), 31);
assert_eq!(estimate_usd_cents(5, 1, Some(CommonResolution::FourEightyP)), 39);
assert_eq!(estimate_usd_cents(10, 1, Some(CommonResolution::FourEightyP)), 78);
assert_eq!(estimate_usd_cents(15, 1, Some(CommonResolution::FourEightyP)), 117);
}
#[test]
fn pro_480p_batch_2() {
assert_eq!(estimate_usd_cents(5, 2, Some(CommonResolution::FourEightyP)), 78);
}
#[test]
fn pro_480p_batch_4() {
assert_eq!(estimate_usd_cents(5, 4, Some(CommonResolution::FourEightyP)), 155);
}
// -- 1080p (new pricing: 193 credits/$1) --
#[test]
fn pro_1080p_batch_1() {
assert_eq!(estimate_usd_cents(4, 1, Some(CommonResolution::TenEightyP)), 187);
assert_eq!(estimate_usd_cents(5, 1, Some(CommonResolution::TenEightyP)), 233);
assert_eq!(estimate_usd_cents(10, 1, Some(CommonResolution::TenEightyP)), 466);
assert_eq!(estimate_usd_cents(15, 1, Some(CommonResolution::TenEightyP)), 699);
}
#[test]
fn pro_1080p_batch_2() {
assert_eq!(estimate_usd_cents(5, 2, Some(CommonResolution::TenEightyP)), 466);
}
#[test]
fn pro_1080p_batch_4() {
assert_eq!(estimate_usd_cents(5, 4, Some(CommonResolution::TenEightyP)), 933);
}
// -- Relative pricing --
#[test]
fn pro_480p_cheaper_than_720p_cheaper_than_1080p() {
let c480 = estimate_usd_cents(5, 1, Some(CommonResolution::FourEightyP));
let c720 = estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP));
let c1080 = estimate_usd_cents(5, 1, Some(CommonResolution::TenEightyP));
assert!(c480 < c720, "480p ({}) should be cheaper than 720p ({})", c480, c720);
assert!(c720 < c1080, "720p ({}) should be cheaper than 1080p ({})", c720, c1080);
}
#[test]
fn cost_scales_with_duration() {
let c4 = estimate_usd_cents(4, 1, Some(CommonResolution::SevenTwentyP));
let c10 = estimate_usd_cents(10, 1, Some(CommonResolution::SevenTwentyP));
let c15 = estimate_usd_cents(15, 1, Some(CommonResolution::SevenTwentyP));
assert!(c4 < c10);
assert!(c10 < c15);
}
#[test]
fn cost_scales_with_batch() {
let b1 = estimate_usd_cents(5, 1, Some(CommonResolution::TenEightyP));
let b2 = estimate_usd_cents(5, 2, Some(CommonResolution::TenEightyP));
let b4 = estimate_usd_cents(5, 4, Some(CommonResolution::TenEightyP));
assert!(b1 < b2);
assert!(b2 < b4);
}
}
@@ -1,212 +0,0 @@
use crate::generate::generate_video::plan::seedance2pro::plan_generate_video_seedance2pro_seedance2p0_fast::PlanSeedance2proSeedance2p0Fast;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_seedance2pro_seedance2p0_fast(
plan: &PlanSeedance2proSeedance2p0Fast,
) -> VideoGenerationCostEstimate {
let cost_in_credits = plan.request.estimate_credits();
let cost_in_usd_cents = plan.request.estimate_cost_in_usd_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_credits as u64),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
failures_are_refunded: None,
}
}
#[cfg(test)]
mod tests {
use crate::api::common_resolution::CommonResolution;
use crate::api::common_video_model::CommonVideoModel;
use crate::api::provider::Provider;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
fn estimate_usd_cents(
duration_seconds: u16,
video_batch_count: u16,
resolution: Option<CommonResolution>,
) -> u64 {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Seedance2p0Fast,
provider: Provider::Seedance2Pro,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: Some(video_batch_count),
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
fn estimate_pro_usd_cents(
duration_seconds: u16,
video_batch_count: u16,
resolution: Option<CommonResolution>,
) -> u64 {
let request = GenerateVideoRequestBuilder {
model: CommonVideoModel::Seedance2p0,
provider: Provider::Seedance2Pro,
prompt: None,
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: Some(video_batch_count),
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
// -- 720p (default / legacy pricing: 220 credits/$1) --
#[test]
fn fast_720p_batch_1() {
assert_eq!(estimate_usd_cents(4, 1, Some(CommonResolution::SevenTwentyP)), 51);
assert_eq!(estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP)), 64);
assert_eq!(estimate_usd_cents(10, 1, Some(CommonResolution::SevenTwentyP)), 127);
assert_eq!(estimate_usd_cents(15, 1, Some(CommonResolution::SevenTwentyP)), 191);
}
#[test]
fn fast_720p_batch_2() {
assert_eq!(estimate_usd_cents(5, 2, Some(CommonResolution::SevenTwentyP)), 127);
}
#[test]
fn fast_720p_batch_4() {
assert_eq!(estimate_usd_cents(5, 4, Some(CommonResolution::SevenTwentyP)), 255);
}
#[test]
fn fast_none_resolution_same_as_720p() {
assert_eq!(
estimate_usd_cents(5, 1, None),
estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP)),
);
}
// -- 480p (new pricing: 193 credits/$1) --
#[test]
fn fast_480p_batch_1() {
assert_eq!(estimate_usd_cents(4, 1, Some(CommonResolution::FourEightyP)), 21);
assert_eq!(estimate_usd_cents(5, 1, Some(CommonResolution::FourEightyP)), 26);
assert_eq!(estimate_usd_cents(10, 1, Some(CommonResolution::FourEightyP)), 52);
assert_eq!(estimate_usd_cents(15, 1, Some(CommonResolution::FourEightyP)), 78);
}
#[test]
fn fast_480p_batch_2() {
assert_eq!(estimate_usd_cents(5, 2, Some(CommonResolution::FourEightyP)), 52);
}
#[test]
fn fast_480p_batch_4() {
assert_eq!(estimate_usd_cents(5, 4, Some(CommonResolution::FourEightyP)), 104);
}
// -- 1080p maps to 720p for Fast (Kinovi doesn't support 1080p fast) --
#[test]
fn fast_1080p_same_as_720p() {
for duration in [4, 5, 10, 15] {
assert_eq!(
estimate_usd_cents(duration, 1, Some(CommonResolution::TenEightyP)),
estimate_usd_cents(duration, 1, Some(CommonResolution::SevenTwentyP)),
);
}
}
#[test]
fn fast_1080p_batch_2() {
assert_eq!(
estimate_usd_cents(5, 2, Some(CommonResolution::TenEightyP)),
estimate_usd_cents(5, 2, Some(CommonResolution::SevenTwentyP)),
);
}
#[test]
fn fast_1080p_batch_4() {
assert_eq!(
estimate_usd_cents(5, 4, Some(CommonResolution::TenEightyP)),
estimate_usd_cents(5, 4, Some(CommonResolution::SevenTwentyP)),
);
}
// -- Relative pricing --
#[test]
fn fast_480p_cheaper_than_720p() {
let c480 = estimate_usd_cents(5, 1, Some(CommonResolution::FourEightyP));
let c720 = estimate_usd_cents(5, 1, Some(CommonResolution::SevenTwentyP));
assert!(c480 < c720, "480p ({}) should be cheaper than 720p ({})", c480, c720);
}
#[test]
fn fast_is_cheaper_than_pro() {
for res in [
Some(CommonResolution::FourEightyP),
Some(CommonResolution::SevenTwentyP),
Some(CommonResolution::TenEightyP),
] {
for duration in [4, 5, 10, 15] {
let fast = estimate_usd_cents(duration, 1, res);
let pro = estimate_pro_usd_cents(duration, 1, res);
assert!(
fast < pro,
"Fast ({}) should be cheaper than Pro ({}) at {}s {:?}",
fast, pro, duration, res,
);
}
}
}
#[test]
fn cost_scales_with_duration() {
let c4 = estimate_usd_cents(4, 1, Some(CommonResolution::SevenTwentyP));
let c10 = estimate_usd_cents(10, 1, Some(CommonResolution::SevenTwentyP));
let c15 = estimate_usd_cents(15, 1, Some(CommonResolution::SevenTwentyP));
assert!(c4 < c10);
assert!(c10 < c15);
}
#[test]
fn cost_scales_with_batch() {
let b1 = estimate_usd_cents(5, 1, Some(CommonResolution::TenEightyP));
let b2 = estimate_usd_cents(5, 2, Some(CommonResolution::TenEightyP));
let b4 = estimate_usd_cents(5, 4, Some(CommonResolution::TenEightyP));
assert!(b1 < b2);
assert!(b2 < b4);
}
}
@@ -1,2 +0,0 @@
pub mod estimate_video_cost_seedance2pro_seedance2p0;
pub mod estimate_video_cost_seedance2pro_seedance2p0_fast;
@@ -1,39 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_pro::PlanArtcraftKling3p0Pro;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use artcraft_api_defs::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::Kling3p0ProMultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::kling_3p0_pro_multi_function_video_gen;
pub async fn execute_artcraft_kling3p0_pro(
plan: &PlanArtcraftKling3p0Pro,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling3p0ProMultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
image_media_token: plan.start_frame.clone(),
end_image_media_token: plan.end_frame.clone(),
duration: plan.duration,
generate_audio: plan.generate_audio,
aspect_ratio: plan.aspect_ratio,
};
let response = kling_3p0_pro_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,39 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_standard::PlanArtcraftKling3p0Standard;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use artcraft_api_defs::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::Kling3p0StandardMultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::kling_3p0_standard_multi_function_video_gen;
pub async fn execute_artcraft_kling3p0_standard(
plan: &PlanArtcraftKling3p0Standard,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling3p0StandardMultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
image_media_token: plan.start_frame.clone(),
end_image_media_token: plan.end_frame.clone(),
duration: plan.duration,
generate_audio: plan.generate_audio,
aspect_ratio: plan.aspect_ratio,
};
let response = kling_3p0_standard_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,37 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_1_6_pro::PlanArtcraftKling16Pro;
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::GenerateKling16ProImageToVideoRequest;
use artcraft_client::endpoints::generate::video::generate_kling_16_pro_image_to_video::generate_kling_16_pro_image_to_video;
pub async fn execute_artcraft_kling_1_6_pro(
plan: &PlanArtcraftKling16Pro,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = GenerateKling16ProImageToVideoRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
media_file_token: Some(plan.start_frame.clone()),
end_frame_image_media_token: plan.end_frame.clone(),
prompt: plan.prompt.clone(),
aspect_ratio: plan.aspect_ratio,
duration: plan.duration,
};
let response = generate_kling_16_pro_image_to_video(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,36 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_1_master::PlanArtcraftKling21Master;
use artcraft_api_defs::generate::video::generate_kling_2_1_master_image_to_video::GenerateKling21MasterImageToVideoRequest;
use artcraft_client::endpoints::generate::video::generate_kling_21_master_image_to_video::generate_kling_21_master_image_to_video;
pub async fn execute_artcraft_kling_2_1_master(
plan: &PlanArtcraftKling21Master,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = GenerateKling21MasterImageToVideoRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
media_file_token: Some(plan.start_frame.clone()),
prompt: plan.prompt.clone(),
aspect_ratio: plan.aspect_ratio,
duration: plan.duration,
};
let response = generate_kling_21_master_image_to_video(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,37 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_1_pro::PlanArtcraftKling21Pro;
use artcraft_api_defs::generate::video::generate_kling_2_1_pro_image_to_video::GenerateKling21ProImageToVideoRequest;
use artcraft_client::endpoints::generate::video::generate_kling_21_pro_image_to_video::generate_kling_21_pro_image_to_video;
pub async fn execute_artcraft_kling_2_1_pro(
plan: &PlanArtcraftKling21Pro,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = GenerateKling21ProImageToVideoRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
media_file_token: Some(plan.start_frame.clone()),
end_frame_image_media_token: plan.end_frame.clone(),
prompt: plan.prompt.clone(),
aspect_ratio: plan.aspect_ratio,
duration: plan.duration,
};
let response = generate_kling_21_pro_image_to_video(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,38 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_5_turbo_pro::PlanArtcraftKling2p5TurboPro;
use artcraft_api_defs::generate::video::multi_function::kling_2_5_turbo_multi_function_video_gen::Kling2p5TurboProMultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::kling_2p5_turbo_pro_multi_function_video_gen::kling_2p5_turbo_pro_multi_function_video_gen;
pub async fn execute_artcraft_kling_2_5_turbo_pro(
plan: &PlanArtcraftKling2p5TurboPro,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling2p5TurboProMultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
start_frame_image_media_token: plan.start_frame.clone(),
end_frame_image_media_token: plan.end_frame.clone(),
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let response = kling_2p5_turbo_pro_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,38 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_6_pro::PlanArtcraftKling2p6Pro;
use artcraft_api_defs::generate::video::multi_function::kling_2_6_multi_function_video_gen::Kling2p6ProMultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::kling_2p6_pro_multi_function_video_gen::kling_2p6_pro_multi_function_video_gen;
pub async fn execute_artcraft_kling_2_6_pro(
plan: &PlanArtcraftKling2p6Pro,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling2p6ProMultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
start_frame_image_media_token: plan.start_frame.clone(),
duration: plan.duration,
generate_audio: plan.generate_audio,
aspect_ratio: plan.aspect_ratio,
};
let response = kling_2p6_pro_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,39 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance1p5_pro::PlanArtcraftSeedance1p5Pro;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use artcraft_api_defs::generate::video::multi_function::seedance_1p5_pro_multi_function_video_gen::Seedance1p5ProMultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::seedance_1p5_pro_multi_function_video_gen::seedance_1p5_pro_multi_function_video_gen;
pub async fn execute_artcraft_seedance1p5_pro(
plan: &PlanArtcraftSeedance1p5Pro,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Seedance1p5ProMultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
start_frame_image_media_token: plan.start_frame.clone(),
end_frame_image_media_token: plan.end_frame.clone(),
aspect_ratio: plan.aspect_ratio,
duration: plan.duration,
resolution: plan.resolution,
generate_audio: plan.generate_audio,
};
let response = seedance_1p5_pro_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,107 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::PlanArtcraftSeedance2p0;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::api::common_resolution::CommonResolution as CommonResolutionRouter;
use artcraft_api_defs::generate::video::multi_function::seedance_2p0_multi_function_video_gen::{
Seedance2p0MultiFunctionVideoGenRequest, Seedance2p0OutputResolution,
};
use artcraft_client::endpoints::generate::video::multi_function::seedance_2p0_multi_function_video_gen::seedance_2p0_multi_function_video_gen;
pub async fn execute_artcraft_seedance2p0(
plan: &PlanArtcraftSeedance2p0,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let output_resolution = plan.resolution.map(|r| match r {
CommonResolutionRouter::FourEightyP => Seedance2p0OutputResolution::FourEightyP,
CommonResolutionRouter::SevenTwentyP => Seedance2p0OutputResolution::SevenTwentyP,
CommonResolutionRouter::TenEightyP => Seedance2p0OutputResolution::TenEightyP,
_ => Seedance2p0OutputResolution::SevenTwentyP,
});
let request = Seedance2p0MultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
start_frame_media_token: plan.start_frame.clone(),
end_frame_media_token: plan.end_frame.clone(),
reference_image_media_tokens: plan.reference_images.clone(),
reference_video_media_tokens: plan.reference_videos.clone(),
reference_audio_media_tokens: plan.reference_audio.clone(),
reference_character_tokens: plan.reference_characters.clone(),
aspect_ratio: plan.aspect_ratio,
output_resolution,
duration_seconds: plan.duration_seconds,
batch_count: Some(plan.batch_count),
};
let response = seedance_2p0_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: response.all_inference_job_tokens,
}))
}
#[cfg(test)]
mod tests {
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
use crate::test_helpers::{base_video_request, get_artcraft_client};
#[tokio::test]
#[ignore] // manually run — fires a real API request and incurs cost
async fn test_text_to_video_seedance_2p0() {
let client = get_artcraft_client();
let request = crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
video_batch_count: Some(1),
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
..base_video_request()
};
let plan = request.build().unwrap();
let result = plan.generate_video(&client).await;
println!("Result: {:?}", result);
let response = result.expect("generate_video request failed");
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
println!("Job token: {:?}", payload.inference_job_token);
println!("All job tokens: {:?}", payload.all_inference_job_tokens);
assert_eq!(1, 2); // NB: Intentional failure to inspect the response above.
}
#[tokio::test]
#[ignore] // manually run — fires a real API request and incurs cost
async fn test_text_to_video_seedance_2p0_batch_two() {
let client = get_artcraft_client();
let request = crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::Square),
video_batch_count: Some(2),
prompt: Some("a dog surfing a wave, cinematic".to_string()),
..base_video_request()
};
let plan = request.build().unwrap();
let result = plan.generate_video(&client).await;
println!("Result: {:?}", result);
let response = result.expect("generate_video request failed");
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
println!("Job tokens ({} total):", payload.all_inference_job_tokens.len());
for token in &payload.all_inference_job_tokens {
println!(" {:?}", token);
}
assert_eq!(1, 2); // NB: Intentional failure to inspect the response above.
}
}
@@ -1,236 +0,0 @@
use crate::api::common_resolution::CommonResolution as CommonResolutionRouter;
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::PlanArtcraftSeedance2p0;
use artcraft_api_defs::generate::video::multi_function::seedance_2p0_multi_function_video_gen::{Seedance2p0AspectRatio, Seedance2p0BatchCount};
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest;
use artcraft_client::endpoints::omni_gen::generate::video::omni_gen_video::omni_gen_video_generate;
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
use enums::common::generation::common_resolution::CommonResolution;
use enums::common::generation::common_video_model::CommonVideoModel;
/// Execute Seedance 2.0 Fast via the Artcraft omni-gen video endpoint.
///
/// Unlike Seedance 2.0 Pro (which uses the legacy dedicated endpoint),
/// Seedance 2.0 Fast routes through the omni-gen unified video endpoint.
pub async fn execute_artcraft_seedance2p0_fast(
plan: &PlanArtcraftSeedance2p0,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let aspect_ratio = plan.aspect_ratio.map(|ar| match ar {
Seedance2p0AspectRatio::Landscape16x9 => CommonAspectRatio::WideSixteenByNine,
Seedance2p0AspectRatio::Portrait9x16 => CommonAspectRatio::TallNineBySixteen,
Seedance2p0AspectRatio::Square1x1 => CommonAspectRatio::Square,
Seedance2p0AspectRatio::Standard4x3 => CommonAspectRatio::WideFourByThree,
Seedance2p0AspectRatio::Portrait3x4 => CommonAspectRatio::TallThreeByFour,
});
let resolution = plan.resolution.map(|r| match r {
CommonResolutionRouter::FourEightyP => CommonResolution::FourEightyP,
CommonResolutionRouter::SevenTwentyP => CommonResolution::SevenTwentyP,
CommonResolutionRouter::TenEightyP => CommonResolution::TenEightyP,
CommonResolutionRouter::HalfK => CommonResolution::HalfK,
CommonResolutionRouter::OneK => CommonResolution::OneK,
CommonResolutionRouter::TwoK => CommonResolution::TwoK,
CommonResolutionRouter::ThreeK => CommonResolution::ThreeK,
CommonResolutionRouter::FourK => CommonResolution::FourK,
});
let request = OmniGenVideoCostAndGenerateRequest {
idempotency_token: Some(plan.idempotency_token.clone()),
model: Some(CommonVideoModel::Seedance2p0Fast),
prompt: plan.prompt.clone(),
start_frame_image_media_token: plan.start_frame.clone(),
end_frame_image_media_token: plan.end_frame.clone(),
reference_image_media_tokens: plan.reference_images.clone(),
reference_video_media_tokens: plan.reference_videos.clone(),
reference_audio_media_tokens: plan.reference_audio.clone(),
reference_character_tokens: plan.reference_characters.clone(),
resolution,
aspect_ratio,
duration_seconds: plan.duration_seconds.map(|d| d as u16),
video_batch_count: Some(match plan.batch_count {
Seedance2p0BatchCount::One => 1,
Seedance2p0BatchCount::Two => 2,
Seedance2p0BatchCount::Four => 4,
}),
quality: None,
generate_audio: None,
negative_prompt: None,
};
let response = omni_gen_video_generate(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
#[cfg(test)]
mod tests {
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::common_resolution::CommonResolution;
use crate::api::common_video_model::CommonVideoModel;
use crate::api::image_ref::ImageRef;
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
use crate::test_helpers::{base_video_request, get_artcraft_client};
use tokens::tokens::media_files::MediaFileToken;
fn fast_request() -> GenerateVideoRequestBuilder {
GenerateVideoRequestBuilder {
model: CommonVideoModel::Seedance2p0Fast,
prompt: Some("a corgi walking through a cyberpunk city at night".to_string()),
..base_video_request()
}
}
#[tokio::test]
#[ignore] // manually run — fires a real API request and incurs cost
async fn test_text_to_video_fast_720p_wide() {
let client = get_artcraft_client();
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
resolution: Some(CommonResolution::SevenTwentyP),
video_batch_count: Some(1),
..fast_request()
};
let plan = request.build().unwrap();
let result = plan.generate_video(&client).await;
println!("Result: {:?}", result);
let response = result.expect("generate_video request failed");
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
println!("Job token: {:?}", payload.inference_job_token);
assert_eq!(1, 2); // NB: Intentional failure to inspect the response above.
}
#[tokio::test]
#[ignore] // manually run — fires a real API request and incurs cost
async fn test_text_to_video_fast_480p_square() {
let client = get_artcraft_client();
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::Square),
resolution: Some(CommonResolution::FourEightyP),
video_batch_count: Some(1),
..fast_request()
};
let plan = request.build().unwrap();
let result = plan.generate_video(&client).await;
println!("Result: {:?}", result);
let response = result.expect("generate_video request failed");
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
println!("Job token: {:?}", payload.inference_job_token);
assert_eq!(1, 2);
}
#[tokio::test]
#[ignore] // manually run — fires a real API request and incurs cost
async fn test_image_to_video_fast_with_start_keyframe() {
let client = get_artcraft_client();
let request = GenerateVideoRequestBuilder {
start_frame: Some(ImageRef::MediaFileToken(
MediaFileToken::new("mf_test_keyframe_placeholder".to_string()),
)),
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
resolution: Some(CommonResolution::SevenTwentyP),
video_batch_count: Some(1),
..fast_request()
};
let plan = request.build().unwrap();
let result = plan.generate_video(&client).await;
println!("Result: {:?}", result);
let response = result.expect("generate_video request failed");
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
println!("Job token: {:?}", payload.inference_job_token);
assert_eq!(1, 2);
}
#[tokio::test]
#[ignore] // manually run — fires a real API request and incurs cost
async fn test_text_to_video_fast_batch_2() {
let client = get_artcraft_client();
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen),
resolution: Some(CommonResolution::SevenTwentyP),
video_batch_count: Some(2),
..fast_request()
};
let plan = request.build().unwrap();
let result = plan.generate_video(&client).await;
println!("Result: {:?}", result);
let response = result.expect("generate_video request failed");
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
println!("Job tokens ({} total):", payload.all_inference_job_tokens.len());
for token in &payload.all_inference_job_tokens {
println!(" {:?}", token);
}
assert_eq!(1, 2);
}
#[tokio::test]
#[ignore] // manually run — fires a real API request and incurs cost
async fn test_text_to_video_fast_with_reference_images() {
let client = get_artcraft_client();
let request = GenerateVideoRequestBuilder {
reference_images: Some(crate::api::image_list_ref::ImageListRef::MediaFileTokens(vec![
MediaFileToken::new("mf_test_ref_image_placeholder".to_string()),
])),
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
resolution: Some(CommonResolution::SevenTwentyP),
video_batch_count: Some(1),
..fast_request()
};
let plan = request.build().unwrap();
let result = plan.generate_video(&client).await;
println!("Result: {:?}", result);
let response = result.expect("generate_video request failed");
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
println!("Job token: {:?}", payload.inference_job_token);
assert_eq!(1, 2);
}
#[test]
fn plan_includes_resolution() {
let request = GenerateVideoRequestBuilder {
resolution: Some(CommonResolution::FourEightyP),
..fast_request()
};
let plan = request.build().unwrap();
let VideoGenerationPlan::ArtcraftSeedance2p0Fast(p) = plan else { panic!("wrong variant") };
assert!(matches!(p.resolution, Some(CommonResolution::FourEightyP)));
}
#[test]
fn plan_includes_characters() {
use crate::api::character_list_ref::CharacterListRef;
use tokens::tokens::characters::CharacterToken;
let request = GenerateVideoRequestBuilder {
reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![
CharacterToken::new("char_test123".to_string()),
])),
..fast_request()
};
let plan = request.build().unwrap();
let VideoGenerationPlan::ArtcraftSeedance2p0Fast(p) = plan else { panic!("wrong variant") };
assert!(p.reference_characters.is_some());
assert_eq!(p.reference_characters.unwrap().len(), 1);
}
}
@@ -1,38 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance_1_0_lite::PlanArtcraftSeedance10Lite;
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteImageToVideoRequest;
use artcraft_client::endpoints::generate::video::generate_seedance_1_0_lite_image_to_video::generate_seedance_1_0_lite_image_to_video;
pub async fn execute_artcraft_seedance_1_0_lite(
plan: &PlanArtcraftSeedance10Lite,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = GenerateSeedance10LiteImageToVideoRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
media_file_token: Some(plan.start_frame.clone()),
end_frame_image_media_token: plan.end_frame.clone(),
prompt: plan.prompt.clone(),
resolution: plan.resolution,
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let response = generate_seedance_1_0_lite_image_to_video(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,37 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_sora_2::PlanArtcraftSora2;
use artcraft_api_defs::generate::video::multi_function::sora_2_multi_function_video_gen::Sora2MultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::sora_2_multi_function_video_gen::sora_2_multi_function_video_gen;
pub async fn execute_artcraft_sora_2(
plan: &PlanArtcraftSora2,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Sora2MultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
image_media_token: plan.start_frame.clone(),
resolution: plan.resolution,
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let response = sora_2_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,37 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_sora_2_pro::PlanArtcraftSora2Pro;
use artcraft_api_defs::generate::video::multi_function::sora_2_pro_multi_function_video_gen::Sora2ProMultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::sora_2_pro_multi_function_video_gen::sora_2_pro_multi_function_video_gen;
pub async fn execute_artcraft_sora_2_pro(
plan: &PlanArtcraftSora2Pro,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Sora2ProMultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
image_media_token: plan.start_frame.clone(),
resolution: plan.resolution,
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let response = sora_2_pro_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,77 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_2::PlanArtcraftVeo2;
use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::GenerateVeo2ImageToVideoRequest;
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest;
use artcraft_client::endpoints::generate::video::generate_veo_2_image_to_video::generate_veo_2_image_to_video;
use artcraft_client::endpoints::omni_gen::generate::video::omni_gen_video::omni_gen_video_generate;
use enums::common::generation::common_video_model::CommonVideoModel;
pub async fn execute_artcraft_veo_2(
plan: &PlanArtcraftVeo2,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let inference_job_token = match plan.start_frame.clone() {
Some(media_file_token) => {
// Image-to-video: use the legacy dedicated endpoint.
let request = GenerateVeo2ImageToVideoRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
media_file_token: Some(media_file_token.to_owned()),
prompt: plan.prompt.clone(),
aspect_ratio: plan.aspect_ratio.clone(),
duration: plan.duration.clone(),
};
let response = generate_veo_2_image_to_video(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
response.inference_job_token
}
None => {
// Text-to-video: use the omni-gen unified endpoint.
let request = OmniGenVideoCostAndGenerateRequest {
idempotency_token: Some(plan.idempotency_token.clone()),
model: Some(CommonVideoModel::Veo2),
prompt: plan.prompt.clone(),
negative_prompt: None,
start_frame_image_media_token: None,
end_frame_image_media_token: None,
reference_image_media_tokens: None,
reference_video_media_tokens: None,
reference_audio_media_tokens: None,
reference_character_tokens: None,
resolution: None,
aspect_ratio: None,
quality: None,
duration_seconds: None,
video_batch_count: None,
generate_audio: None,
};
let response = omni_gen_video_generate(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
response.inference_job_token
}
};
let all_tokens = vec![inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,79 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3::PlanArtcraftVeo3;
use artcraft_api_defs::generate::video::generate_veo_3_image_to_video::GenerateVeo3ImageToVideoRequest;
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest;
use artcraft_client::endpoints::generate::video::generate_veo_3_image_to_video::generate_veo_3_image_to_video;
use artcraft_client::endpoints::omni_gen::generate::video::omni_gen_video::omni_gen_video_generate;
use enums::common::generation::common_video_model::CommonVideoModel;
pub async fn execute_artcraft_veo_3(
plan: &PlanArtcraftVeo3,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let inference_job_token = match plan.start_frame.clone() {
Some(media_file_token) => {
// Image-to-video: use the legacy dedicated endpoint.
let request = GenerateVeo3ImageToVideoRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
media_file_token: Some(media_file_token.to_owned()),
prompt: plan.prompt.clone(),
aspect_ratio: plan.aspect_ratio,
resolution: plan.resolution,
duration: plan.duration,
generate_audio: plan.generate_audio,
};
let response = generate_veo_3_image_to_video(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
response.inference_job_token
}
None => {
// Text-to-video: use the omni-gen unified endpoint.
let request = OmniGenVideoCostAndGenerateRequest {
idempotency_token: Some(plan.idempotency_token.clone()),
model: Some(CommonVideoModel::Veo3),
prompt: plan.prompt.clone(),
negative_prompt: None,
start_frame_image_media_token: None,
end_frame_image_media_token: None,
reference_image_media_tokens: None,
reference_video_media_tokens: None,
reference_audio_media_tokens: None,
reference_character_tokens: None,
resolution: None,
aspect_ratio: None,
quality: None,
duration_seconds: None,
video_batch_count: None,
generate_audio: plan.generate_audio,
};
let response = omni_gen_video_generate(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
response.inference_job_token
}
};
let all_tokens = vec![inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,38 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3_fast::PlanArtcraftVeo3Fast;
use artcraft_api_defs::generate::video::generate_veo_3_fast_image_to_video::GenerateVeo3FastImageToVideoRequest;
use artcraft_client::endpoints::generate::video::generate_veo_3_fast_image_to_video::generate_veo_3_fast_image_to_video;
pub async fn execute_artcraft_veo_3_fast(
plan: &PlanArtcraftVeo3Fast,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = GenerateVeo3FastImageToVideoRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
media_file_token: plan.start_frame.clone(),
prompt: plan.prompt.clone(),
aspect_ratio: plan.aspect_ratio,
resolution: plan.resolution,
duration: plan.duration,
generate_audio: plan.generate_audio,
};
let response = generate_veo_3_fast_image_to_video(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,43 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3p1::PlanArtcraftVeo3p1;
use artcraft_api_defs::generate::video::multi_function::veo_3p1_multi_function_video_gen::Veo3p1MultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::veo_3p1_multi_function_video_gen::veo_3p1_multi_function_video_gen;
pub async fn execute_artcraft_veo_3p1(
plan: &PlanArtcraftVeo3p1,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Veo3p1MultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
start_frame_image_media_token: plan.start_frame.clone(),
end_frame_image_media_token: plan.end_frame.clone(),
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
resolution: plan.resolution,
generate_audio: plan.generate_audio,
enhance_prompt: None,
seed: None,
auto_fix: None,
};
let response = veo_3p1_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,43 +0,0 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3p1_fast::PlanArtcraftVeo3p1Fast;
use artcraft_api_defs::generate::video::multi_function::veo_3p1_fast_multi_function_video_gen::Veo3p1FastMultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::veo_3p1_fast_multi_function_video_gen::veo_3p1_fast_multi_function_video_gen;
pub async fn execute_artcraft_veo_3p1_fast(
plan: &PlanArtcraftVeo3p1Fast,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Veo3p1FastMultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
start_frame_image_media_token: plan.start_frame.clone(),
end_frame_image_media_token: plan.end_frame.clone(),
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
resolution: plan.resolution,
generate_audio: plan.generate_audio,
enhance_prompt: None,
seed: None,
auto_fix: None,
};
let response = veo_3p1_fast_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,18 +0,0 @@
pub mod generate_video_artcraft_kling_1_6_pro;
pub mod generate_video_artcraft_kling_2_1_master;
pub mod generate_video_artcraft_kling_2_1_pro;
pub mod generate_video_artcraft_kling_2_5_turbo_pro;
pub mod generate_video_artcraft_kling_2_6_pro;
pub mod generate_video_artcraft_kling3p0_pro;
pub mod generate_video_artcraft_kling3p0_standard;
pub mod generate_video_artcraft_seedance_1_0_lite;
pub mod generate_video_artcraft_seedance1p5_pro;
pub mod generate_video_artcraft_seedance2p0;
pub mod generate_video_artcraft_seedance2p0_fast;
pub mod generate_video_artcraft_sora_2;
pub mod generate_video_artcraft_sora_2_pro;
pub mod generate_video_artcraft_veo_2;
pub mod generate_video_artcraft_veo_3;
pub mod generate_video_artcraft_veo_3_fast;
pub mod generate_video_artcraft_veo_3p1;
pub mod generate_video_artcraft_veo_3p1_fast;
@@ -1,44 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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, Kling1p6ProRequest,
};
pub async fn execute_fal_kling_1_6_pro(
plan: &PlanFalKling16Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling1p6ProRequest {
image_url: plan.image_url.clone(),
end_frame_image_url: plan.end_image_url.clone(),
prompt: plan.prompt.clone(),
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Kling1p6ProArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,43 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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, Kling2p1MasterRequest,
};
pub async fn execute_fal_kling_2_1_master(
plan: &PlanFalKling21Master,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling2p1MasterRequest {
image_url: plan.image_url.clone(),
prompt: plan.prompt.clone(),
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Kling2p1MasterArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,44 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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, Kling2p1ProRequest,
};
pub async fn execute_fal_kling_2_1_pro(
plan: &PlanFalKling21Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling2p1ProRequest {
image_url: plan.image_url.clone(),
end_frame_image_url: plan.end_image_url.clone(),
prompt: plan.prompt.clone(),
duration: plan.duration,
aspect_ratio: plan.aspect_ratio,
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Kling2p1ProArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,92 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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,
EnqueueKlingV2p5TurboProImageToVideoRequest, 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,
EnqueueKlingV2p5TurboProTextToVideoRequest, EnqueueKlingV2p5TurboProTextToVideoAspectRatio,
EnqueueKlingV2p5TurboProTextToVideoDurationSeconds,
};
pub async fn execute_fal_kling_2_5_turbo_pro(
plan: &PlanFalKling2p5TurboPro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalKling2p5TurboProMode::TextToVideo => {
let request = EnqueueKlingV2p5TurboProTextToVideoRequest {
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),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKlingV2p5TurboProTextToVideoArgs {
request,
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, outbound)
}
FalKling2p5TurboProMode::ImageToVideo { image_url, end_image_url } => {
let request = EnqueueKlingV2p5TurboProImageToVideoRequest {
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),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKlingV2p5TurboProImageToVideoArgs {
request,
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, outbound)
}
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
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,
}
}
@@ -1,92 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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,
EnqueueKlingV2p6ProImageToVideoRequest, 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,
EnqueueKlingV2p6ProTextToVideoRequest, EnqueueKlingV2p6ProTextToVideoAspectRatio,
EnqueueKlingV2p6ProTextToVideoDurationSeconds,
};
pub async fn execute_fal_kling_2_6_pro(
plan: &PlanFalKling2p6Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalKling2p6ProMode::TextToVideo => {
let request = EnqueueKlingV2p6ProTextToVideoRequest {
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),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKlingV2p6ProTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_v2p6_pro_text_to_video_webhook(args).await, outbound)
}
FalKling2p6ProMode::ImageToVideo { image_url } => {
let request = EnqueueKlingV2p6ProImageToVideoRequest {
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),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKlingV2p6ProImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_v2p6_pro_image_to_video_webhook(args).await, outbound)
}
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
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,
}
}
@@ -1,129 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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,
EnqueueKling3p0ProImageToVideoRequest, 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,
EnqueueKling3p0ProTextToVideoRequest, EnqueueKling3p0ProTextToVideoAspectRatio,
EnqueueKling3p0ProTextToVideoDuration,
};
pub async fn execute_fal_kling_3p0_pro(
plan: &PlanFalKling3p0Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalKling3p0Mode::TextToVideo => {
let request = EnqueueKling3p0ProTextToVideoRequest {
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,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKling3p0ProTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_3p0_pro_text_to_video_webhook(args).await, outbound)
}
FalKling3p0Mode::ImageToVideo { image_url, end_image_url } => {
let request = EnqueueKling3p0ProImageToVideoRequest {
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,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKling3p0ProImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_3p0_pro_image_to_video_webhook(args).await, outbound)
}
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
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,
}
}
@@ -1,130 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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,
EnqueueKling3p0StandardImageToVideoRequest, 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,
EnqueueKling3p0StandardTextToVideoRequest, EnqueueKling3p0StandardTextToVideoAspectRatio,
EnqueueKling3p0StandardTextToVideoDuration,
};
pub async fn execute_fal_kling_3p0_standard(
plan: &PlanFalKling3p0Standard,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalKling3p0Mode::TextToVideo => {
let request = EnqueueKling3p0StandardTextToVideoRequest {
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,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKling3p0StandardTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_3p0_standard_text_to_video_webhook(args).await, outbound)
}
FalKling3p0Mode::ImageToVideo { image_url, end_image_url } => {
let request = EnqueueKling3p0StandardImageToVideoRequest {
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,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueKling3p0StandardImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_kling_3p0_standard_image_to_video_webhook(args).await, outbound)
}
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
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,
}
}
@@ -1,47 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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_seedance_1_0_lite::PlanFalSeedance10Lite;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::{
enqueue_seedance_1_lite_image_to_video_webhook, Seedance1LiteArgs, Seedance1LiteRequest,
};
pub async fn execute_fal_seedance_1_0_lite(
plan: &PlanFalSeedance10Lite,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Seedance1LiteRequest {
image_url: plan.image_url.clone(),
end_frame_image_url: plan.end_image_url.clone(),
prompt: plan.prompt.clone(),
duration: plan.duration,
resolution: plan.resolution,
aspect_ratio: plan.aspect_ratio,
camera_fixed: false,
seed: None,
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Seedance1LiteArgs {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
let webhook_response = enqueue_seedance_1_lite_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,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,146 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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_seedance_1p5_pro::{
FalSeedance1p5ProAspectRatio, FalSeedance1p5ProDuration, FalSeedance1p5ProMode,
FalSeedance1p5ProResolution, PlanFalSeedance1p5Pro,
};
use fal_client::requests::webhook::video::image::enqueue_seedance_1p5_pro_image_to_video_webhook::{
enqueue_seedance_1p5_pro_image_to_video_webhook, EnqueueSeedance1p5ProImageToVideoArgs,
EnqueueSeedance1p5ProImageToVideoAspectRatio, EnqueueSeedance1p5ProImageToVideoDuration,
EnqueueSeedance1p5ProImageToVideoRequest, EnqueueSeedance1p5ProImageToVideoResolution,
};
use fal_client::requests::webhook::video::text::enqueue_seedance_1p5_pro_text_to_video_webhook::{
enqueue_seedance_1p5_pro_text_to_video_webhook, EnqueueSeedance1p5ProTextToVideoArgs,
EnqueueSeedance1p5ProTextToVideoRequest, EnqueueSeedance1p5ProTextToVideoAspectRatio,
EnqueueSeedance1p5ProTextToVideoDuration, EnqueueSeedance1p5ProTextToVideoResolution,
};
pub async fn execute_fal_seedance_1p5_pro(
plan: &PlanFalSeedance1p5Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalSeedance1p5ProMode::TextToVideo => {
let request = EnqueueSeedance1p5ProTextToVideoRequest {
prompt: plan.prompt.clone(),
resolution: plan.resolution.map(to_t2v_resolution),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSeedance1p5ProTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_seedance_1p5_pro_text_to_video_webhook(args).await, outbound)
}
FalSeedance1p5ProMode::ImageToVideo { image_url, end_image_url } => {
let request = EnqueueSeedance1p5ProImageToVideoRequest {
prompt: plan.prompt.clone(),
image_url: image_url.clone(),
end_image_url: end_image_url.clone(),
resolution: plan.resolution.map(to_i2v_resolution),
duration: plan.duration.map(to_i2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSeedance1p5ProImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_seedance_1p5_pro_image_to_video_webhook(args).await, outbound)
}
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
fn to_t2v_resolution(r: FalSeedance1p5ProResolution) -> EnqueueSeedance1p5ProTextToVideoResolution {
match r {
FalSeedance1p5ProResolution::FourEightyP => EnqueueSeedance1p5ProTextToVideoResolution::FourEightyP,
FalSeedance1p5ProResolution::SevenTwentyP => EnqueueSeedance1p5ProTextToVideoResolution::SevenTwentyP,
FalSeedance1p5ProResolution::TenEightyP => EnqueueSeedance1p5ProTextToVideoResolution::TenEightyP,
}
}
fn to_t2v_duration(d: FalSeedance1p5ProDuration) -> EnqueueSeedance1p5ProTextToVideoDuration {
use EnqueueSeedance1p5ProTextToVideoDuration as T;
match d {
FalSeedance1p5ProDuration::Four => T::FourSeconds,
FalSeedance1p5ProDuration::Five => T::FiveSeconds,
FalSeedance1p5ProDuration::Six => T::SixSeconds,
FalSeedance1p5ProDuration::Seven => T::SevenSeconds,
FalSeedance1p5ProDuration::Eight => T::EightSeconds,
FalSeedance1p5ProDuration::Nine => T::NineSeconds,
FalSeedance1p5ProDuration::Ten => T::TenSeconds,
FalSeedance1p5ProDuration::Eleven => T::ElevenSeconds,
FalSeedance1p5ProDuration::Twelve => T::TwelveSeconds,
}
}
fn to_t2v_aspect_ratio(a: FalSeedance1p5ProAspectRatio) -> EnqueueSeedance1p5ProTextToVideoAspectRatio {
use EnqueueSeedance1p5ProTextToVideoAspectRatio as T;
match a {
FalSeedance1p5ProAspectRatio::Auto => T::Auto,
FalSeedance1p5ProAspectRatio::TwentyOneByNine => T::TwentyOneByNine,
FalSeedance1p5ProAspectRatio::SixteenByNine => T::SixteenByNine,
FalSeedance1p5ProAspectRatio::FourByThree => T::FourByThree,
FalSeedance1p5ProAspectRatio::Square => T::Square,
FalSeedance1p5ProAspectRatio::ThreeByFour => T::ThreeByFour,
FalSeedance1p5ProAspectRatio::NineBySixteen => T::NineBySixteen,
}
}
fn to_i2v_resolution(r: FalSeedance1p5ProResolution) -> EnqueueSeedance1p5ProImageToVideoResolution {
match r {
FalSeedance1p5ProResolution::FourEightyP => EnqueueSeedance1p5ProImageToVideoResolution::FourEightyP,
FalSeedance1p5ProResolution::SevenTwentyP => EnqueueSeedance1p5ProImageToVideoResolution::SevenTwentyP,
FalSeedance1p5ProResolution::TenEightyP => EnqueueSeedance1p5ProImageToVideoResolution::TenEightyP,
}
}
fn to_i2v_duration(d: FalSeedance1p5ProDuration) -> EnqueueSeedance1p5ProImageToVideoDuration {
use EnqueueSeedance1p5ProImageToVideoDuration as T;
match d {
FalSeedance1p5ProDuration::Four => T::FourSeconds,
FalSeedance1p5ProDuration::Five => T::FiveSeconds,
FalSeedance1p5ProDuration::Six => T::SixSeconds,
FalSeedance1p5ProDuration::Seven => T::SevenSeconds,
FalSeedance1p5ProDuration::Eight => T::EightSeconds,
FalSeedance1p5ProDuration::Nine => T::NineSeconds,
FalSeedance1p5ProDuration::Ten => T::TenSeconds,
FalSeedance1p5ProDuration::Eleven => T::ElevenSeconds,
FalSeedance1p5ProDuration::Twelve => T::TwelveSeconds,
}
}
fn to_i2v_aspect_ratio(a: FalSeedance1p5ProAspectRatio) -> EnqueueSeedance1p5ProImageToVideoAspectRatio {
use EnqueueSeedance1p5ProImageToVideoAspectRatio as T;
match a {
FalSeedance1p5ProAspectRatio::Auto => T::Auto,
FalSeedance1p5ProAspectRatio::TwentyOneByNine => T::TwentyOneByNine,
FalSeedance1p5ProAspectRatio::SixteenByNine => T::SixteenByNine,
FalSeedance1p5ProAspectRatio::FourByThree => T::FourByThree,
FalSeedance1p5ProAspectRatio::Square => T::Square,
FalSeedance1p5ProAspectRatio::ThreeByFour => T::ThreeByFour,
FalSeedance1p5ProAspectRatio::NineBySixteen => T::NineBySixteen,
}
}
@@ -1,117 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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_sora_2::{
FalSora2AspectRatio, FalSora2Duration, FalSora2Mode, FalSora2Resolution, PlanFalSora2,
};
use fal_client::requests::webhook::video::image::enqueue_sora_2_image_to_video_webhook::{
enqueue_sora_2_image_to_video_webhook, EnqueueSora2ImageToVideoArgs,
EnqueueSora2ImageToVideoAspectRatio, EnqueueSora2ImageToVideoDurationSeconds,
EnqueueSora2ImageToVideoRequest, EnqueueSora2ImageToVideoResolution,
};
use fal_client::requests::webhook::video::text::enqueue_sora_2_text_to_video_webhook::{
enqueue_sora_2_text_to_video_webhook, EnqueueSora2TextToVideoArgs,
EnqueueSora2TextToVideoRequest, EnqueueSora2TextToVideoAspectRatio,
EnqueueSora2TextToVideoDurationSeconds, EnqueueSora2TextToVideoResolution,
};
pub async fn execute_fal_sora_2(
plan: &PlanFalSora2,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalSora2Mode::TextToVideo => {
// Text-to-video does not support Auto aspect ratio or Auto resolution.
let request = EnqueueSora2TextToVideoRequest {
prompt: plan.prompt.clone(),
resolution: plan.resolution.and_then(to_t2v_resolution),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.and_then(to_t2v_aspect_ratio),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSora2TextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_sora_2_text_to_video_webhook(args).await, outbound)
}
FalSora2Mode::ImageToVideo { image_url } => {
let request = EnqueueSora2ImageToVideoRequest {
prompt: plan.prompt.clone(),
image_url: image_url.clone(),
duration: plan.duration.map(to_i2v_duration),
resolution: plan.resolution.map(to_i2v_resolution),
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSora2ImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_sora_2_image_to_video_webhook(args).await, outbound)
}
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
fn to_t2v_duration(d: FalSora2Duration) -> EnqueueSora2TextToVideoDurationSeconds {
match d {
FalSora2Duration::Four => EnqueueSora2TextToVideoDurationSeconds::Four,
FalSora2Duration::Eight => EnqueueSora2TextToVideoDurationSeconds::Eight,
FalSora2Duration::Twelve => EnqueueSora2TextToVideoDurationSeconds::Twelve,
}
}
fn to_t2v_resolution(r: FalSora2Resolution) -> Option<EnqueueSora2TextToVideoResolution> {
match r {
FalSora2Resolution::SevenTwentyP => Some(EnqueueSora2TextToVideoResolution::SevenTwentyP),
FalSora2Resolution::Auto => None,
}
}
fn to_t2v_aspect_ratio(a: FalSora2AspectRatio) -> Option<EnqueueSora2TextToVideoAspectRatio> {
match a {
FalSora2AspectRatio::SixteenByNine => Some(EnqueueSora2TextToVideoAspectRatio::SixteenByNine),
FalSora2AspectRatio::NineBySixteen => Some(EnqueueSora2TextToVideoAspectRatio::NineBySixteen),
FalSora2AspectRatio::Auto => None,
}
}
fn to_i2v_duration(d: FalSora2Duration) -> EnqueueSora2ImageToVideoDurationSeconds {
match d {
FalSora2Duration::Four => EnqueueSora2ImageToVideoDurationSeconds::Four,
FalSora2Duration::Eight => EnqueueSora2ImageToVideoDurationSeconds::Eight,
FalSora2Duration::Twelve => EnqueueSora2ImageToVideoDurationSeconds::Twelve,
}
}
fn to_i2v_resolution(r: FalSora2Resolution) -> EnqueueSora2ImageToVideoResolution {
match r {
FalSora2Resolution::SevenTwentyP => EnqueueSora2ImageToVideoResolution::SevenTwentyP,
FalSora2Resolution::Auto => EnqueueSora2ImageToVideoResolution::Auto,
}
}
fn to_i2v_aspect_ratio(a: FalSora2AspectRatio) -> EnqueueSora2ImageToVideoAspectRatio {
match a {
FalSora2AspectRatio::SixteenByNine => EnqueueSora2ImageToVideoAspectRatio::SixteenByNine,
FalSora2AspectRatio::NineBySixteen => EnqueueSora2ImageToVideoAspectRatio::NineBySixteen,
FalSora2AspectRatio::Auto => EnqueueSora2ImageToVideoAspectRatio::Auto,
}
}
@@ -1,120 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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_sora_2_pro::{
FalSora2ProAspectRatio, FalSora2ProDuration, FalSora2ProMode, FalSora2ProResolution,
PlanFalSora2Pro,
};
use fal_client::requests::webhook::video::image::enqueue_sora_2_pro_image_to_video_webhook::{
enqueue_sora_2_pro_image_to_video_webhook, EnqueueSora2ProImageToVideoArgs,
EnqueueSora2ProImageToVideoAspectRatio, EnqueueSora2ProImageToVideoDurationSeconds,
EnqueueSora2ProImageToVideoRequest, EnqueueSora2ProImageToVideoResolution,
};
use fal_client::requests::webhook::video::text::enqueue_sora_2_pro_text_to_video_webhook::{
enqueue_sora_2_pro_text_to_video_webhook, EnqueueSora2ProTextToVideoArgs,
EnqueueSora2ProTextToVideoRequest, EnqueueSora2ProTextToVideoAspectRatio,
EnqueueSora2ProTextToVideoDurationSeconds, EnqueueSora2ProTextToVideoResolution,
};
pub async fn execute_fal_sora_2_pro(
plan: &PlanFalSora2Pro,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalSora2ProMode::TextToVideo => {
// Text-to-video does not support Auto aspect ratio or Auto resolution.
let request = EnqueueSora2ProTextToVideoRequest {
prompt: plan.prompt.clone(),
resolution: plan.resolution.and_then(to_t2v_resolution),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.and_then(to_t2v_aspect_ratio),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSora2ProTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_sora_2_pro_text_to_video_webhook(args).await, outbound)
}
FalSora2ProMode::ImageToVideo { image_url } => {
let request = EnqueueSora2ProImageToVideoRequest {
prompt: plan.prompt.clone(),
image_url: image_url.clone(),
duration: plan.duration.map(to_i2v_duration),
resolution: plan.resolution.map(to_i2v_resolution),
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueSora2ProImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_sora_2_pro_image_to_video_webhook(args).await, outbound)
}
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
fn to_t2v_duration(d: FalSora2ProDuration) -> EnqueueSora2ProTextToVideoDurationSeconds {
match d {
FalSora2ProDuration::Four => EnqueueSora2ProTextToVideoDurationSeconds::Four,
FalSora2ProDuration::Eight => EnqueueSora2ProTextToVideoDurationSeconds::Eight,
FalSora2ProDuration::Twelve => EnqueueSora2ProTextToVideoDurationSeconds::Twelve,
}
}
fn to_t2v_resolution(r: FalSora2ProResolution) -> Option<EnqueueSora2ProTextToVideoResolution> {
match r {
FalSora2ProResolution::SevenTwentyP => Some(EnqueueSora2ProTextToVideoResolution::SevenTwentyP),
FalSora2ProResolution::TenEightyP => Some(EnqueueSora2ProTextToVideoResolution::TenEightyP),
FalSora2ProResolution::Auto => None,
}
}
fn to_t2v_aspect_ratio(a: FalSora2ProAspectRatio) -> Option<EnqueueSora2ProTextToVideoAspectRatio> {
match a {
FalSora2ProAspectRatio::SixteenByNine => Some(EnqueueSora2ProTextToVideoAspectRatio::SixteenByNine),
FalSora2ProAspectRatio::NineBySixteen => Some(EnqueueSora2ProTextToVideoAspectRatio::NineBySixteen),
FalSora2ProAspectRatio::Auto => None,
}
}
fn to_i2v_duration(d: FalSora2ProDuration) -> EnqueueSora2ProImageToVideoDurationSeconds {
match d {
FalSora2ProDuration::Four => EnqueueSora2ProImageToVideoDurationSeconds::Four,
FalSora2ProDuration::Eight => EnqueueSora2ProImageToVideoDurationSeconds::Eight,
FalSora2ProDuration::Twelve => EnqueueSora2ProImageToVideoDurationSeconds::Twelve,
}
}
fn to_i2v_resolution(r: FalSora2ProResolution) -> EnqueueSora2ProImageToVideoResolution {
match r {
FalSora2ProResolution::Auto => EnqueueSora2ProImageToVideoResolution::Auto,
FalSora2ProResolution::SevenTwentyP => EnqueueSora2ProImageToVideoResolution::SevenTwentyP,
FalSora2ProResolution::TenEightyP => EnqueueSora2ProImageToVideoResolution::TenEightyP,
}
}
fn to_i2v_aspect_ratio(a: FalSora2ProAspectRatio) -> EnqueueSora2ProImageToVideoAspectRatio {
match a {
FalSora2ProAspectRatio::Auto => EnqueueSora2ProImageToVideoAspectRatio::Auto,
FalSora2ProAspectRatio::SixteenByNine => EnqueueSora2ProImageToVideoAspectRatio::SixteenByNine,
FalSora2ProAspectRatio::NineBySixteen => EnqueueSora2ProImageToVideoAspectRatio::NineBySixteen,
}
}
@@ -1,69 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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_veo_2::{
FalVeo2Mode, PlanFalVeo2,
};
use fal_client::requests::webhook::video::image::enqueue_veo_2_image_to_video_webhook::{
enqueue_veo_2_image_to_video_webhook, Veo2Args, Veo2AspectRatio, Veo2Request,
};
use fal_client::requests::webhook::video::text::enqueue_veo_2_text_to_video_webhook::{
enqueue_veo_2_text_to_video_webhook, Veo2TextToVideoArgs, Veo2TextToVideoRequest,
};
pub async fn execute_fal_veo_2(
plan: &PlanFalVeo2,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalVeo2Mode::TextToVideo => {
let request = Veo2TextToVideoRequest {
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
duration: plan.duration,
aspect_ratio: plan.aspect_ratio.unwrap_or(Veo2AspectRatio::Auto),
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Veo2TextToVideoArgs {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
let resp = enqueue_veo_2_text_to_video_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
}
FalVeo2Mode::ImageToVideo { image_url } => {
// Image-to-video does not support aspect_ratio — the output
// inherits the source image's aspect ratio.
let request = Veo2Request {
image_url: image_url.to_string(),
prompt: plan.prompt.clone(),
duration: plan.duration,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Veo2Args {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
let resp = enqueue_veo_2_image_to_video_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
}
};
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,125 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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_veo_3::{
FalVeo3Duration, FalVeo3I2vAspectRatio, FalVeo3Mode, FalVeo3Resolution,
FalVeo3T2vAspectRatio, PlanFalVeo3,
};
use fal_client::requests::webhook::video::image::enqueue_veo_3_image_to_video_webhook::{
enqueue_veo_3_image_to_video_webhook, Veo3Args, Veo3I2vAspectRatio, Veo3I2vDuration,
Veo3I2vResolution, Veo3Request,
};
use fal_client::requests::webhook::video::text::enqueue_veo_3_text_to_video_webhook::{
enqueue_veo_3_text_to_video_webhook, Veo3T2vAspectRatio, Veo3T2vDuration, Veo3T2vResolution,
Veo3TextToVideoArgs, Veo3TextToVideoRequest,
};
pub async fn execute_fal_veo_3(
plan: &PlanFalVeo3,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalVeo3Mode::TextToVideo => {
let request = Veo3TextToVideoRequest {
prompt: plan.prompt.clone(),
negative_prompt: plan.negative_prompt.clone(),
duration: to_t2v_duration(plan.duration),
aspect_ratio: plan.t2v_aspect_ratio.map(to_t2v_aspect_ratio).unwrap_or(Veo3T2vAspectRatio::Default),
resolution: to_t2v_resolution(plan.resolution),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Veo3TextToVideoArgs {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
let resp = enqueue_veo_3_text_to_video_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
}
FalVeo3Mode::ImageToVideo { image_url } => {
let request = Veo3Request {
image_url: image_url.to_string(),
prompt: plan.prompt.clone(),
duration: to_i2v_duration(plan.duration),
aspect_ratio: plan.i2v_aspect_ratio.map(to_i2v_aspect_ratio).unwrap_or(Veo3I2vAspectRatio::Auto),
resolution: to_i2v_resolution(plan.resolution),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Veo3Args {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
let resp = enqueue_veo_3_image_to_video_webhook(args)
.await
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?;
(resp, outbound)
}
};
Ok(GenerateVideoResponse::Fal(FalVideoResponsePayload {
request_id: webhook_response.request_id,
gateway_request_id: webhook_response.gateway_request_id,
maybe_outbound_request: Some(outbound_request),
}))
}
fn to_i2v_duration(d: FalVeo3Duration) -> Veo3I2vDuration {
match d {
FalVeo3Duration::Default => Veo3I2vDuration::Default,
FalVeo3Duration::FourSeconds => Veo3I2vDuration::FourSeconds,
FalVeo3Duration::SixSeconds => Veo3I2vDuration::SixSeconds,
FalVeo3Duration::EightSeconds => Veo3I2vDuration::EightSeconds,
}
}
fn to_t2v_duration(d: FalVeo3Duration) -> Veo3T2vDuration {
match d {
FalVeo3Duration::Default => Veo3T2vDuration::Default,
FalVeo3Duration::FourSeconds => Veo3T2vDuration::FourSeconds,
FalVeo3Duration::SixSeconds => Veo3T2vDuration::SixSeconds,
FalVeo3Duration::EightSeconds => Veo3T2vDuration::EightSeconds,
}
}
fn to_i2v_resolution(r: FalVeo3Resolution) -> Veo3I2vResolution {
match r {
FalVeo3Resolution::Default => Veo3I2vResolution::Default,
FalVeo3Resolution::SevenTwentyP => Veo3I2vResolution::SevenTwentyP,
FalVeo3Resolution::TenEightyP => Veo3I2vResolution::TenEightyP,
}
}
fn to_t2v_resolution(r: FalVeo3Resolution) -> Veo3T2vResolution {
match r {
FalVeo3Resolution::Default => Veo3T2vResolution::Default,
FalVeo3Resolution::SevenTwentyP => Veo3T2vResolution::SevenTwentyP,
FalVeo3Resolution::TenEightyP => Veo3T2vResolution::TenEightyP,
}
}
fn to_t2v_aspect_ratio(ar: FalVeo3T2vAspectRatio) -> Veo3T2vAspectRatio {
match ar {
FalVeo3T2vAspectRatio::Default => Veo3T2vAspectRatio::Default,
FalVeo3T2vAspectRatio::WideSixteenNine => Veo3T2vAspectRatio::WideSixteenNine,
FalVeo3T2vAspectRatio::TallNineSixteen => Veo3T2vAspectRatio::TallNineSixteen,
}
}
fn to_i2v_aspect_ratio(ar: FalVeo3I2vAspectRatio) -> Veo3I2vAspectRatio {
match ar {
FalVeo3I2vAspectRatio::Auto => Veo3I2vAspectRatio::Auto,
FalVeo3I2vAspectRatio::WideSixteenNine => Veo3I2vAspectRatio::WideSixteenNine,
FalVeo3I2vAspectRatio::TallNineSixteen => Veo3I2vAspectRatio::TallNineSixteen,
}
}
@@ -1,45 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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_veo_3_fast::PlanFalVeo3Fast;
use fal_client::requests::webhook::video::image::enqueue_veo_3_fast_image_to_video_webhook::{
enqueue_veo_3_fast_image_to_video_webhook, Veo3FastArgs, Veo3FastRequest,
};
pub async fn execute_fal_veo_3_fast(
plan: &PlanFalVeo3Fast,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Veo3FastRequest {
prompt: plan.prompt.clone(),
image_url: plan.start_frame_url.clone(),
aspect_ratio: plan.aspect_ratio,
duration: plan.duration,
resolution: plan.resolution,
generate_audio: plan.generate_audio,
};
let outbound_request: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = Veo3FastArgs {
request,
api_key: &fal_client.api_key,
webhook_url: fal_client.webhook_url.as_str(),
};
let webhook_response = enqueue_veo_3_fast_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,
maybe_outbound_request: Some(outbound_request),
}))
}
@@ -1,171 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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_veo_3p1::{
FalVeo3p1AspectRatio, FalVeo3p1Duration, FalVeo3p1Mode, FalVeo3p1Resolution, PlanFalVeo3p1,
};
use fal_client::requests::webhook::video::image::enqueue_veo_3p1_first_last_frame_image_to_video_webhook::{
enqueue_veo_3p1_first_last_frame_image_to_video_webhook,
EnqueueVeo3p1FirstLastFrameImageToVideoArgs,
EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio,
EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds,
EnqueueVeo3p1FirstLastFrameImageToVideoRequest,
EnqueueVeo3p1FirstLastFrameImageToVideoResolution,
};
use fal_client::requests::webhook::video::image::enqueue_veo_3p1_image_to_video_webhook::{
enqueue_veo_3p1_image_to_video_webhook, EnqueueVeo3p1ImageToVideoArgs,
EnqueueVeo3p1ImageToVideoAspectRatio, EnqueueVeo3p1ImageToVideoDurationSeconds,
EnqueueVeo3p1ImageToVideoRequest, EnqueueVeo3p1ImageToVideoResolution,
};
use fal_client::requests::webhook::video::text::enqueue_veo_3p1_text_to_video_webhook::{
enqueue_veo_3p1_text_to_video_webhook, EnqueueVeo3p1TextToVideoArgs,
EnqueueVeo3p1TextToVideoRequest, EnqueueVeo3p1TextToVideoAspectRatio,
EnqueueVeo3p1TextToVideoDurationSeconds, EnqueueVeo3p1TextToVideoResolution,
};
pub async fn execute_fal_veo_3p1(
plan: &PlanFalVeo3p1,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let (webhook_response, outbound_request) = match &plan.mode {
FalVeo3p1Mode::TextToVideo => {
let request = EnqueueVeo3p1TextToVideoRequest {
prompt: plan.prompt.clone(),
duration: plan.duration.map(to_t2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_t2v_aspect_ratio),
resolution: plan.resolution.map(to_t2v_resolution),
generate_audio: plan.generate_audio,
enhance_prompt: None,
negative_prompt: plan.negative_prompt.clone(),
seed: None,
auto_fix: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1TextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_text_to_video_webhook(args).await, outbound)
}
FalVeo3p1Mode::ImageToVideo { start_frame_url } => {
let request = EnqueueVeo3p1ImageToVideoRequest {
prompt: plan.prompt.clone(),
image_url: start_frame_url.clone(),
duration: plan.duration.map(to_i2v_duration),
aspect_ratio: plan.aspect_ratio.map(to_i2v_aspect_ratio),
resolution: plan.resolution.map(to_i2v_resolution),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1ImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_image_to_video_webhook(args).await, outbound)
}
FalVeo3p1Mode::FirstLastFrame { first_frame_url, last_frame_url } => {
let request = EnqueueVeo3p1FirstLastFrameImageToVideoRequest {
prompt: plan.prompt.clone(),
first_frame_url: first_frame_url.clone(),
last_frame_url: last_frame_url.clone(),
duration: plan.duration.map(to_flf_duration),
aspect_ratio: plan.aspect_ratio.map(to_flf_aspect_ratio),
resolution: plan.resolution.map(to_flf_resolution),
generate_audio: plan.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1FirstLastFrameImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_first_last_frame_image_to_video_webhook(args).await, outbound)
}
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
fn to_t2v_duration(d: FalVeo3p1Duration) -> EnqueueVeo3p1TextToVideoDurationSeconds {
match d {
FalVeo3p1Duration::Four => EnqueueVeo3p1TextToVideoDurationSeconds::Four,
FalVeo3p1Duration::Six => EnqueueVeo3p1TextToVideoDurationSeconds::Six,
FalVeo3p1Duration::Eight => EnqueueVeo3p1TextToVideoDurationSeconds::Eight,
}
}
fn to_t2v_aspect_ratio(a: FalVeo3p1AspectRatio) -> EnqueueVeo3p1TextToVideoAspectRatio {
match a {
FalVeo3p1AspectRatio::Auto => EnqueueVeo3p1TextToVideoAspectRatio::Auto,
FalVeo3p1AspectRatio::SixteenByNine => EnqueueVeo3p1TextToVideoAspectRatio::SixteenByNine,
FalVeo3p1AspectRatio::NineBySixteen => EnqueueVeo3p1TextToVideoAspectRatio::NineBySixteen,
}
}
fn to_t2v_resolution(r: FalVeo3p1Resolution) -> EnqueueVeo3p1TextToVideoResolution {
match r {
FalVeo3p1Resolution::SevenTwentyP => EnqueueVeo3p1TextToVideoResolution::SevenTwentyP,
FalVeo3p1Resolution::TenEightyP => EnqueueVeo3p1TextToVideoResolution::TenEightyP,
}
}
fn to_i2v_duration(d: FalVeo3p1Duration) -> EnqueueVeo3p1ImageToVideoDurationSeconds {
match d {
FalVeo3p1Duration::Four => EnqueueVeo3p1ImageToVideoDurationSeconds::Four,
FalVeo3p1Duration::Six => EnqueueVeo3p1ImageToVideoDurationSeconds::Six,
FalVeo3p1Duration::Eight => EnqueueVeo3p1ImageToVideoDurationSeconds::Eight,
}
}
fn to_i2v_aspect_ratio(a: FalVeo3p1AspectRatio) -> EnqueueVeo3p1ImageToVideoAspectRatio {
match a {
FalVeo3p1AspectRatio::Auto => EnqueueVeo3p1ImageToVideoAspectRatio::Auto,
FalVeo3p1AspectRatio::SixteenByNine => EnqueueVeo3p1ImageToVideoAspectRatio::SixteenByNine,
FalVeo3p1AspectRatio::NineBySixteen => EnqueueVeo3p1ImageToVideoAspectRatio::NineBySixteen,
}
}
fn to_i2v_resolution(r: FalVeo3p1Resolution) -> EnqueueVeo3p1ImageToVideoResolution {
match r {
FalVeo3p1Resolution::SevenTwentyP => EnqueueVeo3p1ImageToVideoResolution::SevenTwentyP,
FalVeo3p1Resolution::TenEightyP => EnqueueVeo3p1ImageToVideoResolution::TenEightyP,
}
}
fn to_flf_duration(d: FalVeo3p1Duration) -> EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds {
match d {
FalVeo3p1Duration::Four => EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Four,
FalVeo3p1Duration::Six => EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Six,
FalVeo3p1Duration::Eight => EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Eight,
}
}
fn to_flf_aspect_ratio(a: FalVeo3p1AspectRatio) -> EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio {
match a {
FalVeo3p1AspectRatio::Auto => EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::Auto,
FalVeo3p1AspectRatio::SixteenByNine => EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::SixteenByNine,
FalVeo3p1AspectRatio::NineBySixteen => EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::NineBySixteen,
}
}
fn to_flf_resolution(r: FalVeo3p1Resolution) -> EnqueueVeo3p1FirstLastFrameImageToVideoResolution {
match r {
FalVeo3p1Resolution::SevenTwentyP => EnqueueVeo3p1FirstLastFrameImageToVideoResolution::SevenTwentyP,
FalVeo3p1Resolution::TenEightyP => EnqueueVeo3p1FirstLastFrameImageToVideoResolution::TenEightyP,
}
}
@@ -1,174 +0,0 @@
use std::fmt::Debug;
use std::sync::Arc;
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_veo_3p1::{
FalVeo3p1AspectRatio, FalVeo3p1Duration, FalVeo3p1Mode, FalVeo3p1Resolution,
};
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3p1_fast::PlanFalVeo3p1Fast;
use fal_client::requests::webhook::video::image::enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook::{
enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook,
EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs,
EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio,
EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds,
EnqueueVeo3p1FastFirstLastFrameImageToVideoRequest,
EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution,
};
use fal_client::requests::webhook::video::image::enqueue_veo_3p1_fast_image_to_video_webhook::{
enqueue_veo_3p1_fast_image_to_video_webhook, EnqueueVeo3p1FastImageToVideoArgs,
EnqueueVeo3p1FastImageToVideoAspectRatio, EnqueueVeo3p1FastImageToVideoDurationSeconds,
EnqueueVeo3p1FastImageToVideoRequest, EnqueueVeo3p1FastImageToVideoResolution,
};
use fal_client::requests::webhook::video::text::enqueue_veo_3p1_fast_text_to_video_webhook::{
enqueue_veo_3p1_fast_text_to_video_webhook, EnqueueVeo3p1FastTextToVideoArgs,
EnqueueVeo3p1FastTextToVideoRequest, EnqueueVeo3p1FastTextToVideoAspectRatio,
EnqueueVeo3p1FastTextToVideoDurationSeconds, EnqueueVeo3p1FastTextToVideoResolution,
};
pub async fn execute_fal_veo_3p1_fast(
plan: &PlanFalVeo3p1Fast,
fal_client: &RouterFalClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let inner = &plan.inner;
let (webhook_response, outbound_request) = match &inner.mode {
FalVeo3p1Mode::TextToVideo => {
let request = EnqueueVeo3p1FastTextToVideoRequest {
prompt: inner.prompt.clone(),
duration: inner.duration.map(to_t2v_duration),
aspect_ratio: inner.aspect_ratio.map(to_t2v_aspect_ratio),
resolution: inner.resolution.map(to_t2v_resolution),
generate_audio: inner.generate_audio,
enhance_prompt: None,
negative_prompt: inner.negative_prompt.clone(),
seed: None,
auto_fix: None,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1FastTextToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_fast_text_to_video_webhook(args).await, outbound)
}
FalVeo3p1Mode::ImageToVideo { start_frame_url } => {
let request = EnqueueVeo3p1FastImageToVideoRequest {
prompt: inner.prompt.clone(),
image_url: start_frame_url.clone(),
duration: inner.duration.map(to_i2v_duration),
aspect_ratio: inner.aspect_ratio.map(to_i2v_aspect_ratio),
resolution: inner.resolution.map(to_i2v_resolution),
generate_audio: inner.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1FastImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_fast_image_to_video_webhook(args).await, outbound)
}
FalVeo3p1Mode::FirstLastFrame { first_frame_url, last_frame_url } => {
let request = EnqueueVeo3p1FastFirstLastFrameImageToVideoRequest {
prompt: inner.prompt.clone(),
first_frame_url: first_frame_url.clone(),
last_frame_url: last_frame_url.clone(),
duration: inner.duration.map(to_flf_duration),
aspect_ratio: inner.aspect_ratio.map(to_flf_aspect_ratio),
resolution: inner.resolution.map(to_flf_resolution),
generate_audio: inner.generate_audio,
};
let outbound: Arc<dyn Debug + Send + Sync> = Arc::new(request.clone());
let args = EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs {
request,
webhook_url: fal_client.webhook_url.as_str(),
api_key: &fal_client.api_key,
};
(enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook(args).await, outbound)
}
};
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,
maybe_outbound_request: Some(outbound_request),
}))
}
fn to_t2v_duration(d: FalVeo3p1Duration) -> EnqueueVeo3p1FastTextToVideoDurationSeconds {
match d {
FalVeo3p1Duration::Four => EnqueueVeo3p1FastTextToVideoDurationSeconds::Four,
FalVeo3p1Duration::Six => EnqueueVeo3p1FastTextToVideoDurationSeconds::Six,
FalVeo3p1Duration::Eight => EnqueueVeo3p1FastTextToVideoDurationSeconds::Eight,
}
}
fn to_t2v_aspect_ratio(a: FalVeo3p1AspectRatio) -> EnqueueVeo3p1FastTextToVideoAspectRatio {
match a {
FalVeo3p1AspectRatio::Auto => EnqueueVeo3p1FastTextToVideoAspectRatio::Auto,
FalVeo3p1AspectRatio::SixteenByNine => EnqueueVeo3p1FastTextToVideoAspectRatio::SixteenByNine,
FalVeo3p1AspectRatio::NineBySixteen => EnqueueVeo3p1FastTextToVideoAspectRatio::NineBySixteen,
}
}
fn to_t2v_resolution(r: FalVeo3p1Resolution) -> EnqueueVeo3p1FastTextToVideoResolution {
match r {
FalVeo3p1Resolution::SevenTwentyP => EnqueueVeo3p1FastTextToVideoResolution::SevenTwentyP,
FalVeo3p1Resolution::TenEightyP => EnqueueVeo3p1FastTextToVideoResolution::TenEightyP,
}
}
fn to_i2v_duration(d: FalVeo3p1Duration) -> EnqueueVeo3p1FastImageToVideoDurationSeconds {
match d {
FalVeo3p1Duration::Four => EnqueueVeo3p1FastImageToVideoDurationSeconds::Four,
FalVeo3p1Duration::Six => EnqueueVeo3p1FastImageToVideoDurationSeconds::Six,
FalVeo3p1Duration::Eight => EnqueueVeo3p1FastImageToVideoDurationSeconds::Eight,
}
}
fn to_i2v_aspect_ratio(a: FalVeo3p1AspectRatio) -> EnqueueVeo3p1FastImageToVideoAspectRatio {
match a {
FalVeo3p1AspectRatio::Auto => EnqueueVeo3p1FastImageToVideoAspectRatio::Auto,
FalVeo3p1AspectRatio::SixteenByNine => EnqueueVeo3p1FastImageToVideoAspectRatio::SixteenByNine,
FalVeo3p1AspectRatio::NineBySixteen => EnqueueVeo3p1FastImageToVideoAspectRatio::NineBySixteen,
}
}
fn to_i2v_resolution(r: FalVeo3p1Resolution) -> EnqueueVeo3p1FastImageToVideoResolution {
match r {
FalVeo3p1Resolution::SevenTwentyP => EnqueueVeo3p1FastImageToVideoResolution::SevenTwentyP,
FalVeo3p1Resolution::TenEightyP => EnqueueVeo3p1FastImageToVideoResolution::TenEightyP,
}
}
fn to_flf_duration(d: FalVeo3p1Duration) -> EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds {
match d {
FalVeo3p1Duration::Four => EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Four,
FalVeo3p1Duration::Six => EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Six,
FalVeo3p1Duration::Eight => EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Eight,
}
}
fn to_flf_aspect_ratio(a: FalVeo3p1AspectRatio) -> EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio {
match a {
FalVeo3p1AspectRatio::Auto => EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::Auto,
FalVeo3p1AspectRatio::SixteenByNine => EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::SixteenByNine,
FalVeo3p1AspectRatio::NineBySixteen => EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::NineBySixteen,
}
}
fn to_flf_resolution(r: FalVeo3p1Resolution) -> EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution {
match r {
FalVeo3p1Resolution::SevenTwentyP => EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution::SevenTwentyP,
FalVeo3p1Resolution::TenEightyP => EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution::TenEightyP,
}
}
@@ -1,16 +0,0 @@
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_seedance_1_0_lite;
pub mod generate_video_fal_seedance_1p5_pro;
pub mod generate_video_fal_sora_2;
pub mod generate_video_fal_sora_2_pro;
pub mod generate_video_fal_veo_2;
pub mod generate_video_fal_veo_3;
pub mod generate_video_fal_veo_3_fast;
pub mod generate_video_fal_veo_3p1;
pub mod generate_video_fal_veo_3p1_fast;
@@ -1,4 +0,0 @@
pub mod artcraft;
pub mod fal;
pub mod muapi;
pub mod seedance2pro;
@@ -1,56 +0,0 @@
use crate::client::router_muapi_client::RouterMuapiClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
GenerateVideoResponse, MuapiVideoResponsePayload,
};
use crate::generate::generate_video::plan::muapi::plan_generate_video_muapi_seedance2p0::{
MuapiSeedance2p0Mode, PlanMuapiSeedance2p0,
};
use muapi_client::requests::seedance_2p0_image_to_video::seedance_2p0_image_to_video::{
Seedance2p0ImageToVideoArgs, seedance_2p0_image_to_video,
};
use muapi_client::requests::seedance_2p0_text_to_video::seedance_2p0_text_to_video::{
Seedance2p0TextToVideoArgs, seedance_2p0_text_to_video,
};
pub async fn execute_muapi_seedance2p0(
plan: &PlanMuapiSeedance2p0,
muapi_client: &RouterMuapiClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let session = &muapi_client.session;
let request_id = match &plan.mode {
MuapiSeedance2p0Mode::TextToVideo { aspect_ratio, duration, quality } => {
let args = Seedance2p0TextToVideoArgs {
session,
prompt: plan.prompt.clone(),
aspect_ratio: *aspect_ratio,
duration: *duration,
quality: *quality,
};
let response = seedance_2p0_text_to_video(args)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Muapi(err)))?;
response.request_id
}
MuapiSeedance2p0Mode::ImageToVideo { image_urls, aspect_ratio, duration, quality } => {
let args = Seedance2p0ImageToVideoArgs {
session,
prompt: plan.prompt.clone(),
image_urls: image_urls.clone(),
aspect_ratio: *aspect_ratio,
duration: *duration,
quality: *quality,
};
let response = seedance_2p0_image_to_video(args)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Muapi(err)))?;
response.request_id
}
};
Ok(GenerateVideoResponse::Muapi(MuapiVideoResponsePayload {
request_id,
}))
}
@@ -1 +0,0 @@
pub mod execute_muapi_seedance2p0;
@@ -1,115 +0,0 @@
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::generate_video_response::{
GenerateVideoResponse, Seedance2proVideoResponsePayload,
};
use crate::generate::generate_video::plan::seedance2pro::plan_generate_video_seedance2pro_seedance2p0::PlanSeedance2proSeedance2p0;
use crate::utils::download_file::download_file;
use seedance2pro_client::requests::generate_video::generate_video::{
generate_video, GenerateVideoArgs,
};
use seedance2pro_client::requests::prepare_file_upload::prepare_file_upload::{
prepare_file_upload, PrepareFileUploadArgs,
};
use seedance2pro_client::requests::upload_file::upload_file::{
upload_file, UploadFileArgs,
};
use url_utils::extension::extract_extension_from_url::{
extract_extension_from_url_str, ExtractExtensions,
};
pub async fn execute_seedance2pro_seedance2p0(
plan: &PlanSeedance2proSeedance2p0,
seedance2pro_client: &RouterSeedance2ProClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let session = &seedance2pro_client.session;
// Upload media files to seedance2pro CDN
let start_frame_url = upload_optional_url(session, plan.request.start_frame_url.as_deref()).await?;
let end_frame_url = upload_optional_url(session, plan.request.end_frame_url.as_deref()).await?;
let reference_image_urls = upload_optional_url_list(session, plan.request.reference_image_urls.as_deref()).await?;
let reference_video_urls = upload_optional_url_list(session, plan.request.reference_video_urls.as_deref()).await?;
let reference_audio_urls = upload_optional_url_list(session, plan.request.reference_audio_urls.as_deref()).await?;
let mut request = plan.request.clone();
request.start_frame_url = start_frame_url;
request.end_frame_url = end_frame_url;
request.reference_image_urls = reference_image_urls;
request.reference_video_urls = reference_video_urls;
request.reference_audio_urls = reference_audio_urls;
let args = GenerateVideoArgs {
session,
host_override: None,
request,
};
let response = generate_video(args)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
Ok(GenerateVideoResponse::Seedance2Pro(Seedance2proVideoResponsePayload {
order_id: response.order_id,
task_id: response.task_id,
maybe_order_ids: response.order_ids,
maybe_task_ids: response.task_ids,
}))
}
/// Downloads a file from a source URL and re-uploads it to seedance2pro CDN.
pub(crate) async fn upload_to_seedance2pro(
session: &seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession,
source_url: &str,
) -> Result<String, ArtcraftRouterError> {
let extension = extract_extension_from_url_str(source_url, &ExtractExtensions::All)
.map(|ext| ext.without_period().to_string())
.unwrap_or_else(|| "jpg".to_string());
let file_bytes = download_file(source_url).await?;
let prepare_response = prepare_file_upload(PrepareFileUploadArgs {
session,
extension,
host_override: None,
})
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
let upload_response = upload_file(UploadFileArgs {
upload_url: prepare_response.upload_url,
file_bytes,
host_override: None,
})
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
Ok(upload_response.public_url)
}
pub(crate) async fn upload_optional_url(
session: &seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession,
url: Option<&str>,
) -> Result<Option<String>, ArtcraftRouterError> {
match url {
None => Ok(None),
Some(u) => Ok(Some(upload_to_seedance2pro(session, u).await?)),
}
}
pub(crate) async fn upload_optional_url_list(
session: &seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession,
urls: Option<&[String]>,
) -> Result<Option<Vec<String>>, ArtcraftRouterError> {
match urls {
None => Ok(None),
Some(url_list) if url_list.is_empty() => Ok(None),
Some(url_list) => {
let mut uploaded = Vec::with_capacity(url_list.len());
for url in url_list {
uploaded.push(upload_to_seedance2pro(session, url).await?);
}
Ok(Some(uploaded))
}
}
}
@@ -1,55 +0,0 @@
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::execute::seedance2pro::execute_seedance2pro_seedance2p0::{
upload_optional_url, upload_optional_url_list,
};
use crate::generate::generate_video::generate_video_response::{
GenerateVideoResponse, Seedance2proVideoResponsePayload,
};
use crate::generate::generate_video::plan::seedance2pro::plan_generate_video_seedance2pro_seedance2p0_fast::PlanSeedance2proSeedance2p0Fast;
use seedance2pro_client::requests::generate_video::generate_video::{
generate_video, GenerateVideoArgs,
};
/// Execute Seedance 2.0 Fast via the Seedance2Pro/Kinovi provider.
///
/// Identical to the Seedance 2.0 Pro executor except it uses
/// `ModelType::Seedance2Fast` instead of `ModelType::Seedance2Pro`.
pub async fn execute_seedance2pro_seedance2p0_fast(
plan: &PlanSeedance2proSeedance2p0Fast,
seedance2pro_client: &RouterSeedance2ProClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let session = &seedance2pro_client.session;
// Upload media files to seedance2pro CDN
let start_frame_url = upload_optional_url(session, plan.request.start_frame_url.as_deref()).await?;
let end_frame_url = upload_optional_url(session, plan.request.end_frame_url.as_deref()).await?;
let reference_image_urls = upload_optional_url_list(session, plan.request.reference_image_urls.as_deref()).await?;
let reference_video_urls = upload_optional_url_list(session, plan.request.reference_video_urls.as_deref()).await?;
let reference_audio_urls = upload_optional_url_list(session, plan.request.reference_audio_urls.as_deref()).await?;
let mut request = plan.request.clone();
request.start_frame_url = start_frame_url;
request.end_frame_url = end_frame_url;
request.reference_image_urls = reference_image_urls;
request.reference_video_urls = reference_video_urls;
request.reference_audio_urls = reference_audio_urls;
let args = GenerateVideoArgs {
session,
host_override: None,
request,
};
let response = generate_video(args)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
Ok(GenerateVideoResponse::Seedance2Pro(Seedance2proVideoResponsePayload {
order_id: response.order_id,
task_id: response.task_id,
maybe_order_ids: response.order_ids,
maybe_task_ids: response.task_ids,
}))
}
@@ -1,2 +0,0 @@
pub mod execute_seedance2pro_seedance2p0;
pub mod execute_seedance2pro_seedance2p0_fast;
@@ -9,44 +9,6 @@ use crate::api::provider::Provider;
use crate::api::video_list_ref::VideoListRef;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_pro::plan_generate_video_artcraft_kling3p0_pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_standard::plan_generate_video_artcraft_kling3p0_standard;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_1_6_pro::plan_generate_video_artcraft_kling_1_6_pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_1_master::plan_generate_video_artcraft_kling_2_1_master;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_1_pro::plan_generate_video_artcraft_kling_2_1_pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_5_turbo_pro::plan_generate_video_artcraft_kling_2_5_turbo_pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling_2_6_pro::plan_generate_video_artcraft_kling_2_6_pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance1p5_pro::plan_generate_video_artcraft_seedance1p5_pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::plan_generate_video_artcraft_seedance2p0;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0_fast::plan_generate_video_artcraft_seedance2p0_fast;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance_1_0_lite::plan_generate_video_artcraft_seedance_1_0_lite;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_sora_2::plan_generate_video_artcraft_sora_2;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_sora_2_pro::plan_generate_video_artcraft_sora_2_pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_2::plan_generate_video_artcraft_veo_2;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_veo_3::plan_generate_video_artcraft_veo_3;
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_seedance_1_0_lite::plan_generate_video_fal_seedance_1_0_lite;
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_seedance_1p5_pro::plan_generate_video_fal_seedance_1p5_pro;
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_sora_2::plan_generate_video_fal_sora_2;
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_sora_2_pro::plan_generate_video_fal_sora_2_pro;
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;
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3p1::plan_generate_video_fal_veo_3p1;
use crate::generate::generate_video::plan::fal::plan_generate_video_fal_veo_3p1_fast::plan_generate_video_fal_veo_3p1_fast;
use crate::generate::generate_video::plan::muapi::plan_generate_video_muapi_seedance2p0::plan_generate_video_muapi_seedance2p0;
use crate::generate::generate_video::plan::seedance2pro::plan_generate_video_seedance2pro_seedance2p0::plan_generate_video_seedance2pro_seedance2p0;
use crate::generate::generate_video::plan::seedance2pro::plan_generate_video_seedance2pro_seedance2p0_fast::plan_generate_video_seedance2pro_seedance2p0_fast;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
use crate::generate::generate_video_v2::providers::artcraft::grok_imagine_video::build::build_artcraft_grok_imagine_video;
use crate::generate::generate_video_v2::providers::artcraft::happy_horse_1p0::build::build_artcraft_happy_horse_1p0;
use crate::generate::generate_video_v2::providers::artcraft::kling_1_6_pro::build::build_artcraft_kling_1_6_pro;
@@ -58,6 +20,8 @@ use crate::generate::generate_video_v2::providers::artcraft::kling_3p0_pro::buil
use crate::generate::generate_video_v2::providers::artcraft::kling_3p0_standard::build::build_artcraft_kling_3p0_standard;
use crate::generate::generate_video_v2::providers::artcraft::preview_model::build::build_artcraft_preview_model;
use crate::generate::generate_video_v2::providers::artcraft::preview_model_fast::build::build_artcraft_preview_model_fast;
use crate::generate::generate_video_v2::providers::artcraft::seedance_1p0_lite::build::build_artcraft_seedance_1p0_lite;
use crate::generate::generate_video_v2::providers::artcraft::seedance_1p5_pro::build::build_artcraft_seedance_1p5_pro;
use crate::generate::generate_video_v2::providers::artcraft::seedance_2p0::build::build_artcraft_seedance_2p0;
use crate::generate::generate_video_v2::providers::artcraft::seedance_2p0_fast::build::build_artcraft_seedance_2p0_fast;
use crate::generate::generate_video_v2::providers::artcraft::seedance_2p0_u::build::build_artcraft_seedance_2p0_u;
@@ -95,9 +59,8 @@ use crate::generate::generate_video_v2::providers::fal::veo_3p1_fast::build::bui
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0_fast::build::build_kinovi_seedance_2p0_fast;
use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
/// Plan to either (1) generate a video or (2) determine how much it costs to generate that video.
/// This works across multiple providers by shaping a generic "GenerateVideoRequest" into a provider-specific plan.
/// That plan can then be used to return a cost estimate for that given provider or return a struct that can be used to send a real generation request.
/// Provider-agnostic video generation request. Distilled by `build2()` into a
/// `VideoGenerationDraftOrRequest` for the selected (provider, model) pair.
#[derive(Clone, Debug)]
pub struct GenerateVideoRequestBuilder {
/// Which model to use.
@@ -182,64 +145,6 @@ impl Default for GenerateVideoRequestBuilder {
impl GenerateVideoRequestBuilder {
pub fn use_new_builder(&self) -> bool {
match (self.provider, self.model) {
// Artcraft
(Provider::Artcraft, CommonVideoModel::GrokImagineVideo) => true,
(Provider::Artcraft, CommonVideoModel::HappyHorse1p0) => true,
(Provider::Artcraft, CommonVideoModel::Kling16Pro) => true,
(Provider::Artcraft, CommonVideoModel::Kling21Master) => true,
(Provider::Artcraft, CommonVideoModel::Kling21Pro) => true,
(Provider::Artcraft, CommonVideoModel::Kling2p5TurboPro) => true,
(Provider::Artcraft, CommonVideoModel::Kling2p6Pro) => true,
(Provider::Artcraft, CommonVideoModel::Kling3p0Pro) => true,
(Provider::Artcraft, CommonVideoModel::Kling3p0Standard) => true,
(Provider::Artcraft, CommonVideoModel::Seedance2p0) => true,
(Provider::Artcraft, CommonVideoModel::Seedance2p0Fast) => true,
(Provider::Artcraft, CommonVideoModel::Seedance2p0Ultra) => true,
(Provider::Artcraft, CommonVideoModel::Seedance2p0UltraFast) => true,
(Provider::Artcraft, CommonVideoModel::Seedance2p0BytePlus) => true,
(Provider::Artcraft, CommonVideoModel::Seedance2p0BytePlusFast) => true,
(Provider::Artcraft, CommonVideoModel::PreviewModel) => true,
(Provider::Artcraft, CommonVideoModel::PreviewModelFast) => true,
(Provider::Artcraft, CommonVideoModel::Sora2) => true,
(Provider::Artcraft, CommonVideoModel::Sora2Pro) => true,
(Provider::Artcraft, CommonVideoModel::Veo2) => true,
(Provider::Artcraft, CommonVideoModel::Veo3) => true,
(Provider::Artcraft, CommonVideoModel::Veo3Fast) => true,
(Provider::Artcraft, CommonVideoModel::Veo3p1) => true,
(Provider::Artcraft, CommonVideoModel::Veo3p1Fast) => true,
// Fal
(Provider::Fal, CommonVideoModel::Kling16Pro) => true,
(Provider::Fal, CommonVideoModel::Kling21Master) => true,
(Provider::Fal, CommonVideoModel::Kling21Pro) => true,
(Provider::Fal, CommonVideoModel::Kling2p5TurboPro) => true,
(Provider::Fal, CommonVideoModel::Kling2p6Pro) => true,
(Provider::Fal, CommonVideoModel::Kling3p0Pro) => true,
(Provider::Fal, CommonVideoModel::Kling3p0Standard) => true,
(Provider::Fal, CommonVideoModel::Seedance10Lite) => true,
(Provider::Fal, CommonVideoModel::Seedance1p5Pro) => true,
(Provider::Fal, CommonVideoModel::Sora2) => true,
(Provider::Fal, CommonVideoModel::Sora2Pro) => true,
(Provider::Fal, CommonVideoModel::Veo2) => true,
(Provider::Fal, CommonVideoModel::Veo3) => true,
(Provider::Fal, CommonVideoModel::Veo3Fast) => true,
(Provider::Fal, CommonVideoModel::Veo3p1) => true,
(Provider::Fal, CommonVideoModel::Veo3p1Fast) => true,
// GmiCloud
(Provider::GmiCloud, CommonVideoModel::Seedance2p0Ultra) => true,
(Provider::GmiCloud, CommonVideoModel::Seedance2p0UltraFast) => true,
// Grok
(Provider::GrokApi, CommonVideoModel::GrokImagineVideo) => true,
// Kinovi
(Provider::Seedance2Pro, CommonVideoModel::HappyHorse1p0) => true,
(Provider::Seedance2Pro, CommonVideoModel::Seedance2p0) => true,
(Provider::Seedance2Pro, CommonVideoModel::Seedance2p0Fast) => true,
_ => false,
}
}
// New builder (transitional state)
pub fn build2(self) -> Result<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
match (self.provider, self.model) {
// Artcraft
@@ -260,6 +165,8 @@ impl GenerateVideoRequestBuilder {
(Provider::Artcraft, CommonVideoModel::Seedance2p0BytePlusFast) => build_artcraft_seedance_2p0_bp_fast(self),
(Provider::Artcraft, CommonVideoModel::PreviewModel) => build_artcraft_preview_model(self),
(Provider::Artcraft, CommonVideoModel::PreviewModelFast) => build_artcraft_preview_model_fast(self),
(Provider::Artcraft, CommonVideoModel::Seedance10Lite) => build_artcraft_seedance_1p0_lite(self),
(Provider::Artcraft, CommonVideoModel::Seedance1p5Pro) => build_artcraft_seedance_1p5_pro(self),
(Provider::Artcraft, CommonVideoModel::Sora2) => build_artcraft_sora_2(self),
(Provider::Artcraft, CommonVideoModel::Sora2Pro) => build_artcraft_sora_2_pro(self),
(Provider::Artcraft, CommonVideoModel::Veo2) => build_artcraft_veo_2(self),
@@ -297,88 +204,9 @@ impl GenerateVideoRequestBuilder {
}
}
/// Read the video generation request, construct a plan, then yield a means to execute it.
pub fn build(&self) -> Result<VideoGenerationPlan, ArtcraftRouterError> {
match self.provider {
Provider::Artcraft => self.build_artcraft(),
Provider::Fal => self.build_fal(),
Provider::GmiCloud => self.unsupported_provider(), // GmiCloud uses build2() only
Provider::GrokApi => self.unsupported_provider(), // Grok uses build2() only
Provider::Muapi => self.build_muapi(),
Provider::Seedance2Pro => self.build_seedance2pro(),
}
}
fn build_artcraft(&self) -> Result<VideoGenerationPlan, ArtcraftRouterError> {
match self.model {
CommonVideoModel::Kling16Pro => plan_generate_video_artcraft_kling_1_6_pro(self),
CommonVideoModel::Kling21Master => plan_generate_video_artcraft_kling_2_1_master(self),
CommonVideoModel::Kling21Pro => plan_generate_video_artcraft_kling_2_1_pro(self),
CommonVideoModel::Kling2p5TurboPro => plan_generate_video_artcraft_kling_2_5_turbo_pro(self),
CommonVideoModel::Kling2p6Pro => plan_generate_video_artcraft_kling_2_6_pro(self),
CommonVideoModel::Kling3p0Pro => plan_generate_video_artcraft_kling3p0_pro(self),
CommonVideoModel::Kling3p0Standard => plan_generate_video_artcraft_kling3p0_standard(self),
CommonVideoModel::Seedance10Lite => plan_generate_video_artcraft_seedance_1_0_lite(self),
CommonVideoModel::Seedance1p5Pro => plan_generate_video_artcraft_seedance1p5_pro(self),
CommonVideoModel::Seedance2p0 => plan_generate_video_artcraft_seedance2p0(self),
CommonVideoModel::Seedance2p0Fast => plan_generate_video_artcraft_seedance2p0_fast(self),
CommonVideoModel::Sora2 => plan_generate_video_artcraft_sora_2(self),
CommonVideoModel::Sora2Pro => plan_generate_video_artcraft_sora_2_pro(self),
CommonVideoModel::Veo2 => plan_generate_video_artcraft_veo_2(self),
CommonVideoModel::Veo3 => plan_generate_video_artcraft_veo_3(self),
CommonVideoModel::Veo3Fast => plan_generate_video_artcraft_veo_3_fast(self),
CommonVideoModel::Veo3p1 => plan_generate_video_artcraft_veo_3p1(self),
CommonVideoModel::Veo3p1Fast => plan_generate_video_artcraft_veo_3p1_fast(self),
_ => Err(ArtcraftRouterError::UnsupportedModel(format!("{:?}", self.model))),
}
}
fn build_fal(&self) -> Result<VideoGenerationPlan, ArtcraftRouterError> {
match self.model {
CommonVideoModel::Veo2 => plan_generate_video_fal_veo_2(self),
CommonVideoModel::Veo3 => plan_generate_video_fal_veo_3(self),
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),
CommonVideoModel::Seedance10Lite => plan_generate_video_fal_seedance_1_0_lite(self),
CommonVideoModel::Seedance1p5Pro => plan_generate_video_fal_seedance_1p5_pro(self),
CommonVideoModel::Sora2 => plan_generate_video_fal_sora_2(self),
CommonVideoModel::Sora2Pro => plan_generate_video_fal_sora_2_pro(self),
_ => Err(ArtcraftRouterError::UnsupportedModel(format!("{:?}", self.model))),
}
}
fn build_muapi(&self) -> Result<VideoGenerationPlan, ArtcraftRouterError> {
match self.model {
CommonVideoModel::Seedance2p0 => plan_generate_video_muapi_seedance2p0(self),
_ => Err(ArtcraftRouterError::UnsupportedModel(format!("{:?}", self.model))),
}
}
fn build_seedance2pro(&self) -> Result<VideoGenerationPlan, ArtcraftRouterError> {
match self.model {
CommonVideoModel::Seedance2p0 => plan_generate_video_seedance2pro_seedance2p0(self),
CommonVideoModel::Seedance2p0Fast => plan_generate_video_seedance2pro_seedance2p0_fast(self),
_ => Err(ArtcraftRouterError::UnsupportedModel(format!("{:?}", self.model))),
}
}
fn unsupported_provider(&self) -> Result<VideoGenerationPlan, ArtcraftRouterError> {
Err(ArtcraftRouterError::UnsupportedModel(
format!("Video generation for model `{:?}` is not supported for provider {:?}", self.model, self.provider)
))
}
fn unsupported_provider_and_model(&self) -> Result<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
Err(ArtcraftRouterError::UnsupportedProviderAndModelForNewApi(
format!("Video generation for model `{:?}` is not supported for provider {:?} using the new API", self.model, self.provider)
format!("Video generation for model `{:?}` is not supported for provider {:?}", self.model, self.provider)
))
}
@@ -1,7 +1,6 @@
use std::fmt::Debug;
use std::sync::Arc;
use muapi_client::api_types::request_id::RequestId as MuapiRequestId;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
#[derive(Clone, Debug)]
@@ -10,11 +9,6 @@ pub struct ArtcraftVideoResponsePayload {
pub all_inference_job_tokens: Vec<InferenceJobToken>,
}
#[derive(Clone, Debug)]
pub struct MuapiVideoResponsePayload {
pub request_id: MuapiRequestId,
}
#[derive(Clone, Debug)]
pub struct Seedance2proVideoResponsePayload {
pub order_id: String,
@@ -56,7 +50,6 @@ pub enum GenerateVideoResponse {
Artcraft(ArtcraftVideoResponsePayload),
GmiCloud(GmiCloudVideoResponsePayload),
Grok(GrokVideoResponsePayload),
Muapi(MuapiVideoResponsePayload),
Seedance2Pro(Seedance2proVideoResponsePayload),
Fal(FalVideoResponsePayload),
}
@@ -69,13 +62,6 @@ impl GenerateVideoResponse {
}
}
pub fn get_muapi_payload(&self) -> Option<MuapiVideoResponsePayload> {
match self {
Self::Muapi(p) => Some(p.clone()),
_ => None,
}
}
pub fn get_seedance2pro_payload(&self) -> Option<Seedance2proVideoResponsePayload> {
match self {
Self::Seedance2Pro(p) => Some(p.clone()),
@@ -1,7 +1,3 @@
pub mod cost;
pub mod execute;
pub mod generate_video_request_builder;
pub mod generate_video_response;
pub mod plan;
pub mod video_generation_cost_estimate;
pub mod video_generation_plan;
@@ -1,18 +0,0 @@
pub mod plan_generate_video_artcraft_kling_1_6_pro;
pub mod plan_generate_video_artcraft_kling_2_1_master;
pub mod plan_generate_video_artcraft_kling_2_1_pro;
pub mod plan_generate_video_artcraft_kling_2_5_turbo_pro;
pub mod plan_generate_video_artcraft_kling_2_6_pro;
pub mod plan_generate_video_artcraft_kling3p0_pro;
pub mod plan_generate_video_artcraft_kling3p0_standard;
pub mod plan_generate_video_artcraft_seedance_1_0_lite;
pub mod plan_generate_video_artcraft_seedance1p5_pro;
pub mod plan_generate_video_artcraft_seedance2p0;
pub mod plan_generate_video_artcraft_seedance2p0_fast;
pub mod plan_generate_video_artcraft_sora_2;
pub mod plan_generate_video_artcraft_sora_2_pro;
pub mod plan_generate_video_artcraft_veo_2;
pub mod plan_generate_video_artcraft_veo_3;
pub mod plan_generate_video_artcraft_veo_3_fast;
pub mod plan_generate_video_artcraft_veo_3p1;
pub mod plan_generate_video_artcraft_veo_3p1_fast;
@@ -1,276 +0,0 @@
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_builder::GenerateVideoRequestBuilder;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
use artcraft_api_defs::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::{
Kling3p0ProMultiFunctionVideoGenAspectRatio,
Kling3p0ProMultiFunctionVideoGenDuration,
};
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftKling3p0Pro {
pub prompt: Option<String>,
pub negative_prompt: Option<String>,
pub start_frame: Option<MediaFileToken>,
pub end_frame: Option<MediaFileToken>,
pub aspect_ratio: Option<Kling3p0ProMultiFunctionVideoGenAspectRatio>,
pub duration: Option<Kling3p0ProMultiFunctionVideoGenDuration>,
pub generate_audio: Option<bool>,
pub idempotency_token: String,
}
pub fn plan_generate_video_artcraft_kling3p0_pro(
request: &GenerateVideoRequestBuilder,
) -> Result<VideoGenerationPlan, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let start_frame = resolve_image_ref(request.start_frame.clone())?;
let end_frame = resolve_image_ref(request.end_frame.clone())?;
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
let duration = plan_duration(request.duration_seconds, strategy)?;
Ok(VideoGenerationPlan::ArtcraftKling3p0Pro(PlanArtcraftKling3p0Pro {
prompt: request.prompt.clone(),
negative_prompt: request.negative_prompt.clone(),
start_frame,
end_frame,
aspect_ratio,
duration,
generate_audio: request.generate_audio,
idempotency_token: request.get_or_generate_idempotency_token(),
}))
}
fn resolve_image_ref(
image_ref: Option<ImageRef>,
) -> Result<Option<MediaFileToken>, ArtcraftRouterError> {
match image_ref {
None => Ok(None),
Some(ImageRef::MediaFileToken(t)) => Ok(Some(t)),
Some(ImageRef::Url(_)) => {
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
}
}
}
// Kling 3.0 Pro supports: Square (1:1), SixteenByNine (16:9), NineBySixteen (9:16)
fn plan_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Kling3p0ProMultiFunctionVideoGenAspectRatio>, ArtcraftRouterError> {
match aspect_ratio {
None => Ok(None),
// Direct mappings
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => {
Ok(Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::SixteenByNine))
}
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => {
Ok(Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::NineBySixteen))
}
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => {
Ok(Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::Square))
}
// Auto defaults to 16:9
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => {
Ok(Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::SixteenByNine))
}
// Mismatches
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
RequestMismatchMitigationStrategy::PayMoreUpgrade
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
Ok(Some(nearest_aspect_ratio(unsupported)))
}
},
}
}
fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> Kling3p0ProMultiFunctionVideoGenAspectRatio {
match aspect_ratio {
// Wide-ish → 16:9
CommonAspectRatio::WideFourByThree
| CommonAspectRatio::WideFiveByFour
| CommonAspectRatio::WideThreeByTwo
| CommonAspectRatio::WideTwentyOneByNine => Kling3p0ProMultiFunctionVideoGenAspectRatio::SixteenByNine,
// Tall-ish → 9:16
CommonAspectRatio::TallThreeByFour
| CommonAspectRatio::TallFourByFive
| CommonAspectRatio::TallTwoByThree
| CommonAspectRatio::TallNineByTwentyOne => Kling3p0ProMultiFunctionVideoGenAspectRatio::NineBySixteen,
// Fallback
_ => Kling3p0ProMultiFunctionVideoGenAspectRatio::Square,
}
}
// Kling 3.0 Pro supports durations of 3–15 seconds.
fn plan_duration(
duration_seconds: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Kling3p0ProMultiFunctionVideoGenDuration>, ArtcraftRouterError> {
const MIN: u16 = 3;
const MAX: u16 = 15;
match duration_seconds {
None => Ok(None),
Some(d) if d >= MIN && d <= MAX => Ok(Some(seconds_to_duration(d))),
Some(d) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "duration_seconds",
value: format!("{}", d),
}))
}
_ => Ok(Some(seconds_to_duration(d.clamp(MIN, MAX)))),
},
}
}
fn seconds_to_duration(seconds: u16) -> Kling3p0ProMultiFunctionVideoGenDuration {
match seconds {
3 => Kling3p0ProMultiFunctionVideoGenDuration::ThreeSeconds,
4 => Kling3p0ProMultiFunctionVideoGenDuration::FourSeconds,
5 => Kling3p0ProMultiFunctionVideoGenDuration::FiveSeconds,
6 => Kling3p0ProMultiFunctionVideoGenDuration::SixSeconds,
7 => Kling3p0ProMultiFunctionVideoGenDuration::SevenSeconds,
8 => Kling3p0ProMultiFunctionVideoGenDuration::EightSeconds,
9 => Kling3p0ProMultiFunctionVideoGenDuration::NineSeconds,
10 => Kling3p0ProMultiFunctionVideoGenDuration::TenSeconds,
11 => Kling3p0ProMultiFunctionVideoGenDuration::ElevenSeconds,
12 => Kling3p0ProMultiFunctionVideoGenDuration::TwelveSeconds,
13 => Kling3p0ProMultiFunctionVideoGenDuration::ThirteenSeconds,
14 => Kling3p0ProMultiFunctionVideoGenDuration::FourteenSeconds,
_ => Kling3p0ProMultiFunctionVideoGenDuration::FifteenSeconds,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::common_video_model::CommonVideoModel;
use crate::api::image_ref::ImageRef;
use crate::api::provider::Provider;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
fn base_request() -> GenerateVideoRequestBuilder {
GenerateVideoRequestBuilder {
model: CommonVideoModel::Kling3p0Pro,
provider: Provider::Artcraft,
prompt: Some("a cat in space".to_string()),
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution: None,
aspect_ratio: None,
duration_seconds: None,
video_batch_count: None,
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
}
}
#[test]
fn aspect_ratio_direct_16x9() {
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Pro(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::SixteenByNine)));
}
#[test]
fn aspect_ratio_direct_9x16() {
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Pro(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::NineBySixteen)));
}
#[test]
fn aspect_ratio_direct_square() {
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::Square),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Pro(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::Square)));
}
#[test]
fn aspect_ratio_error_out_on_unsupported() {
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::WideThreeByTwo),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
));
}
#[test]
fn duration_valid_range() {
for d in 3..=15 {
let request = GenerateVideoRequestBuilder {
duration_seconds: Some(d),
..base_request()
};
let result = request.build();
assert!(result.is_ok(), "duration {} should be valid", d);
}
}
#[test]
fn duration_out_of_range_error_out() {
let request = GenerateVideoRequestBuilder {
duration_seconds: Some(16),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
));
}
#[test]
fn url_image_ref_returns_error() {
let request = GenerateVideoRequestBuilder {
start_frame: Some(ImageRef::Url("https://example.com/image.jpg".to_string())),
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
));
}
}
@@ -1,276 +0,0 @@
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_builder::GenerateVideoRequestBuilder;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
use artcraft_api_defs::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::{
Kling3p0StandardMultiFunctionVideoGenAspectRatio,
Kling3p0StandardMultiFunctionVideoGenDuration,
};
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftKling3p0Standard {
pub prompt: Option<String>,
pub negative_prompt: Option<String>,
pub start_frame: Option<MediaFileToken>,
pub end_frame: Option<MediaFileToken>,
pub aspect_ratio: Option<Kling3p0StandardMultiFunctionVideoGenAspectRatio>,
pub duration: Option<Kling3p0StandardMultiFunctionVideoGenDuration>,
pub generate_audio: Option<bool>,
pub idempotency_token: String,
}
pub fn plan_generate_video_artcraft_kling3p0_standard(
request: &GenerateVideoRequestBuilder,
) -> Result<VideoGenerationPlan, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let start_frame = resolve_image_ref(request.start_frame.clone())?;
let end_frame = resolve_image_ref(request.end_frame.clone())?;
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
let duration = plan_duration(request.duration_seconds, strategy)?;
Ok(VideoGenerationPlan::ArtcraftKling3p0Standard(PlanArtcraftKling3p0Standard {
prompt: request.prompt.clone(),
negative_prompt: request.negative_prompt.clone(),
start_frame,
end_frame,
aspect_ratio,
duration,
generate_audio: request.generate_audio,
idempotency_token: request.get_or_generate_idempotency_token(),
}))
}
fn resolve_image_ref(
image_ref: Option<ImageRef>,
) -> Result<Option<MediaFileToken>, ArtcraftRouterError> {
match image_ref {
None => Ok(None),
Some(ImageRef::MediaFileToken(t)) => Ok(Some(t)),
Some(ImageRef::Url(_)) => {
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
}
}
}
// Kling 3.0 Standard supports: Square (1:1), SixteenByNine (16:9), NineBySixteen (9:16)
fn plan_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Kling3p0StandardMultiFunctionVideoGenAspectRatio>, ArtcraftRouterError> {
match aspect_ratio {
None => Ok(None),
// Direct mappings
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => {
Ok(Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::SixteenByNine))
}
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => {
Ok(Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::NineBySixteen))
}
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => {
Ok(Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::Square))
}
// Auto defaults to 16:9
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => {
Ok(Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::SixteenByNine))
}
// Mismatches
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
RequestMismatchMitigationStrategy::PayMoreUpgrade
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
Ok(Some(nearest_aspect_ratio(unsupported)))
}
},
}
}
fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> Kling3p0StandardMultiFunctionVideoGenAspectRatio {
match aspect_ratio {
// Wide-ish → 16:9
CommonAspectRatio::WideFourByThree
| CommonAspectRatio::WideFiveByFour
| CommonAspectRatio::WideThreeByTwo
| CommonAspectRatio::WideTwentyOneByNine => Kling3p0StandardMultiFunctionVideoGenAspectRatio::SixteenByNine,
// Tall-ish → 9:16
CommonAspectRatio::TallThreeByFour
| CommonAspectRatio::TallFourByFive
| CommonAspectRatio::TallTwoByThree
| CommonAspectRatio::TallNineByTwentyOne => Kling3p0StandardMultiFunctionVideoGenAspectRatio::NineBySixteen,
// Fallback
_ => Kling3p0StandardMultiFunctionVideoGenAspectRatio::Square,
}
}
// Kling 3.0 Standard supports durations of 3–15 seconds.
fn plan_duration(
duration_seconds: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Kling3p0StandardMultiFunctionVideoGenDuration>, ArtcraftRouterError> {
const MIN: u16 = 3;
const MAX: u16 = 15;
match duration_seconds {
None => Ok(None),
Some(d) if d >= MIN && d <= MAX => Ok(Some(seconds_to_duration(d))),
Some(d) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "duration_seconds",
value: format!("{}", d),
}))
}
_ => Ok(Some(seconds_to_duration(d.clamp(MIN, MAX)))),
},
}
}
fn seconds_to_duration(seconds: u16) -> Kling3p0StandardMultiFunctionVideoGenDuration {
match seconds {
3 => Kling3p0StandardMultiFunctionVideoGenDuration::ThreeSeconds,
4 => Kling3p0StandardMultiFunctionVideoGenDuration::FourSeconds,
5 => Kling3p0StandardMultiFunctionVideoGenDuration::FiveSeconds,
6 => Kling3p0StandardMultiFunctionVideoGenDuration::SixSeconds,
7 => Kling3p0StandardMultiFunctionVideoGenDuration::SevenSeconds,
8 => Kling3p0StandardMultiFunctionVideoGenDuration::EightSeconds,
9 => Kling3p0StandardMultiFunctionVideoGenDuration::NineSeconds,
10 => Kling3p0StandardMultiFunctionVideoGenDuration::TenSeconds,
11 => Kling3p0StandardMultiFunctionVideoGenDuration::ElevenSeconds,
12 => Kling3p0StandardMultiFunctionVideoGenDuration::TwelveSeconds,
13 => Kling3p0StandardMultiFunctionVideoGenDuration::ThirteenSeconds,
14 => Kling3p0StandardMultiFunctionVideoGenDuration::FourteenSeconds,
_ => Kling3p0StandardMultiFunctionVideoGenDuration::FifteenSeconds,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::common_video_model::CommonVideoModel;
use crate::api::image_ref::ImageRef;
use crate::api::provider::Provider;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
fn base_request() -> GenerateVideoRequestBuilder {
GenerateVideoRequestBuilder {
model: CommonVideoModel::Kling3p0Standard,
provider: Provider::Artcraft,
prompt: Some("a cat in space".to_string()),
negative_prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
reference_videos: None,
reference_audio: None,
reference_character_tokens: None,
resolution: None,
aspect_ratio: None,
duration_seconds: None,
video_batch_count: None,
generate_audio: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
}
}
#[test]
fn aspect_ratio_direct_16x9() {
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Standard(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::SixteenByNine)));
}
#[test]
fn aspect_ratio_direct_9x16() {
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Standard(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::NineBySixteen)));
}
#[test]
fn aspect_ratio_direct_square() {
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::Square),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Standard(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::Square)));
}
#[test]
fn aspect_ratio_error_out_on_unsupported() {
let request = GenerateVideoRequestBuilder {
aspect_ratio: Some(CommonAspectRatio::WideThreeByTwo),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
));
}
#[test]
fn duration_valid_range() {
for d in 3..=15 {
let request = GenerateVideoRequestBuilder {
duration_seconds: Some(d),
..base_request()
};
let result = request.build();
assert!(result.is_ok(), "duration {} should be valid", d);
}
}
#[test]
fn duration_out_of_range_error_out() {
let request = GenerateVideoRequestBuilder {
duration_seconds: Some(16),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
));
}
#[test]
fn url_image_ref_returns_error() {
let request = GenerateVideoRequestBuilder {
start_frame: Some(ImageRef::Url("https://example.com/image.jpg".to_string())),
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
));
}
}
Loaded 100 of 188 files, more files were not shown because too many files have changed in this diff. Show more