From 22d0631280bbfd1b5301cee7358f4a21dbdd82bc Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 23 Feb 2026 16:10:11 +0700 Subject: [PATCH] planning --- .../artcraft_router/src/api/image_list_ref.rs | 1 + .../lib/artcraft_router/src/api/image_ref.rs | 1 + .../request_mismatch_mitigation_strategy.rs | 1 + .../src/errors/client_error.rs | 7 + .../src/generate/generate_video/mod.rs | 4 +- .../generate_video/plan/artcraft/mod.rs | 1 + ...lan_generate_video_artcraft_seedance2p0.rs | 177 ++++++++++++++++++ .../generate_video/plan_video_generation.rs | 19 ++ .../generate_video/video_generation_plan.rs | 5 + 9 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 crates/lib/artcraft_router/src/generate/generate_video/plan/artcraft/plan_generate_video_artcraft_seedance2p0.rs create mode 100644 crates/lib/artcraft_router/src/generate/generate_video/plan_video_generation.rs create mode 100644 crates/lib/artcraft_router/src/generate/generate_video/video_generation_plan.rs diff --git a/crates/lib/artcraft_router/src/api/image_list_ref.rs b/crates/lib/artcraft_router/src/api/image_list_ref.rs index 6b057ed906..b029b50f2f 100644 --- a/crates/lib/artcraft_router/src/api/image_list_ref.rs +++ b/crates/lib/artcraft_router/src/api/image_list_ref.rs @@ -1,5 +1,6 @@ use tokens::tokens::media_files::MediaFileToken; +#[derive(Copy, Clone)] pub enum ImageListRef<'a> { MediaFileTokens(&'a Vec), Urls(&'a Vec), diff --git a/crates/lib/artcraft_router/src/api/image_ref.rs b/crates/lib/artcraft_router/src/api/image_ref.rs index 617a27bdc1..ca3d0a7e73 100644 --- a/crates/lib/artcraft_router/src/api/image_ref.rs +++ b/crates/lib/artcraft_router/src/api/image_ref.rs @@ -1,5 +1,6 @@ use tokens::tokens::media_files::MediaFileToken; +#[derive(Copy, Clone)] pub enum ImageRef<'a> { MediaFileToken(&'a MediaFileToken), Url(&'a str), diff --git a/crates/lib/artcraft_router/src/client/request_mismatch_mitigation_strategy.rs b/crates/lib/artcraft_router/src/client/request_mismatch_mitigation_strategy.rs index 63a2ed5013..db1e77e611 100644 --- a/crates/lib/artcraft_router/src/client/request_mismatch_mitigation_strategy.rs +++ b/crates/lib/artcraft_router/src/client/request_mismatch_mitigation_strategy.rs @@ -1,4 +1,5 @@ +#[derive(Copy, Clone)] pub enum RequestMismatchMitigationStrategy { /// Upgrade the request to pay more. PayMoreUpgrade, diff --git a/crates/lib/artcraft_router/src/errors/client_error.rs b/crates/lib/artcraft_router/src/errors/client_error.rs index 61fba9acf7..572e02557b 100644 --- a/crates/lib/artcraft_router/src/errors/client_error.rs +++ b/crates/lib/artcraft_router/src/errors/client_error.rs @@ -18,6 +18,10 @@ impl Display for ClientType { pub enum ClientError { /// The requested client is not configured on the RouterClient. ClientNotConfigured(ClientType), + + /// The model does not support the given option value. + /// `field` is the request field name, `value` is what was requested. + ModelDoesNotSupportOption { field: &'static str, value: String }, } impl Error for ClientError {} @@ -28,6 +32,9 @@ impl Display for ClientError { Self::ClientNotConfigured(client_type) => { write!(f, "{} client is not configured on the RouterClient", client_type) } + Self::ModelDoesNotSupportOption { field, value } => { + write!(f, "Model does not support '{}' for field '{}'", value, field) + } } } } diff --git a/crates/lib/artcraft_router/src/generate/generate_video/mod.rs b/crates/lib/artcraft_router/src/generate/generate_video/mod.rs index 9625d1e5e3..ee42519c45 100644 --- a/crates/lib/artcraft_router/src/generate/generate_video/mod.rs +++ b/crates/lib/artcraft_router/src/generate/generate_video/mod.rs @@ -1,4 +1,6 @@ +pub mod execute; pub mod generate_video; pub mod generate_video_request; pub mod plan; -pub mod execute; \ No newline at end of file +pub mod plan_video_generation; +pub mod video_generation_plan; \ No newline at end of file diff --git a/crates/lib/artcraft_router/src/generate/generate_video/plan/artcraft/mod.rs b/crates/lib/artcraft_router/src/generate/generate_video/plan/artcraft/mod.rs index e69de29bb2..f8dad5eb7c 100644 --- a/crates/lib/artcraft_router/src/generate/generate_video/plan/artcraft/mod.rs +++ b/crates/lib/artcraft_router/src/generate/generate_video/plan/artcraft/mod.rs @@ -0,0 +1 @@ +pub mod plan_generate_video_artcraft_seedance2p0; diff --git a/crates/lib/artcraft_router/src/generate/generate_video/plan/artcraft/plan_generate_video_artcraft_seedance2p0.rs b/crates/lib/artcraft_router/src/generate/generate_video/plan/artcraft/plan_generate_video_artcraft_seedance2p0.rs new file mode 100644 index 0000000000..266b301a41 --- /dev/null +++ b/crates/lib/artcraft_router/src/generate/generate_video/plan/artcraft/plan_generate_video_artcraft_seedance2p0.rs @@ -0,0 +1,177 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::api::image_list_ref::ImageListRef; +use crate::api::image_ref::ImageRef; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_video::generate_video_request::GenerateVideoRequest; +use artcraft_api_defs::generate::video::multi_function::seedance_2p0_multi_function_video_gen::{ + Seedance2p0AspectRatio, Seedance2p0BatchCount, +}; + +pub struct PlanArtcraftSeedance2p0<'a> { + pub prompt: Option<&'a str>, + pub start_frame: Option>, + pub end_frame: Option>, + pub reference_images: Option>, + pub aspect_ratio: Option, + pub duration_seconds: Option, + pub batch_count: Seedance2p0BatchCount, + pub idempotency_token: String, +} + +// Aspect ratio values (width / height) for ordering: +// Portrait9x16 = 0.5625, Portrait3x4 = 0.75, Square1x1 = 1.0, Standard4x3 = 1.33, Landscape16x9 = 1.78 + +fn plan_aspect_ratio( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + match aspect_ratio { + // No preference or auto — let the model decide + None + | Some(CommonAspectRatio::Auto) + | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto4k) => Ok(None), + + // Direct mappings + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => { + Ok(Some(Seedance2p0AspectRatio::Landscape16x9)) + } + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => { + Ok(Some(Seedance2p0AspectRatio::Portrait9x16)) + } + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => { + Ok(Some(Seedance2p0AspectRatio::Square1x1)) + } + Some(CommonAspectRatio::WideFourByThree) => Ok(Some(Seedance2p0AspectRatio::Standard4x3)), + Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(Seedance2p0AspectRatio::Portrait3x4)), + + // Mismatches — apply strategy + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade => { + Ok(Some(aspect_ratio_upgrade(unsupported))) + } + RequestMismatchMitigationStrategy::PayLessDowngrade => { + Ok(Some(aspect_ratio_downgrade(unsupported))) + } + }, + } +} + +/// Round an unsupported aspect ratio up toward a higher AR (wider / less portrait). +fn aspect_ratio_upgrade(aspect_ratio: CommonAspectRatio) -> Seedance2p0AspectRatio { + match aspect_ratio { + // Wide mismatches: round up to the next wider supported ratio + CommonAspectRatio::WideFiveByFour => Seedance2p0AspectRatio::Standard4x3, // 1.25 → 1.33 + CommonAspectRatio::WideThreeByTwo => Seedance2p0AspectRatio::Landscape16x9, // 1.50 → 1.78 + CommonAspectRatio::WideTwentyOneByNine => Seedance2p0AspectRatio::Landscape16x9, // 2.33 → 1.78 (max) + // Tall mismatches: round up toward Square (higher AR = less portrait) + CommonAspectRatio::TallFourByFive => Seedance2p0AspectRatio::Square1x1, // 0.80 → 1.00 + CommonAspectRatio::TallTwoByThree => Seedance2p0AspectRatio::Portrait3x4, // 0.67 → 0.75 + CommonAspectRatio::TallNineByTwentyOne => Seedance2p0AspectRatio::Portrait9x16, // 0.43 → 0.56 (max portrait) + _ => Seedance2p0AspectRatio::Square1x1, + } +} + +/// Round an unsupported aspect ratio down toward a lower AR (narrower / more portrait). +fn aspect_ratio_downgrade(aspect_ratio: CommonAspectRatio) -> Seedance2p0AspectRatio { + match aspect_ratio { + // Wide mismatches: round down to the next narrower supported ratio + CommonAspectRatio::WideFiveByFour => Seedance2p0AspectRatio::Square1x1, // 1.25 → 1.00 + CommonAspectRatio::WideThreeByTwo => Seedance2p0AspectRatio::Standard4x3, // 1.50 → 1.33 + CommonAspectRatio::WideTwentyOneByNine => Seedance2p0AspectRatio::Landscape16x9, // 2.33 → 1.78 (only option) + // Tall mismatches: round down toward most portrait (lower AR) + CommonAspectRatio::TallFourByFive => Seedance2p0AspectRatio::Portrait3x4, // 0.80 → 0.75 + CommonAspectRatio::TallTwoByThree => Seedance2p0AspectRatio::Portrait9x16, // 0.67 → 0.56 + CommonAspectRatio::TallNineByTwentyOne => Seedance2p0AspectRatio::Portrait9x16, // 0.43 → 0.56 (only option) + _ => Seedance2p0AspectRatio::Square1x1, + } +} + +fn plan_batch_count( + video_batch_count: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + let count = video_batch_count.unwrap_or(1); + match count { + 1 => Ok(Seedance2p0BatchCount::One), + 2 => Ok(Seedance2p0BatchCount::Two), + 4 => Ok(Seedance2p0BatchCount::Four), + _ => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "video_batch_count", + value: format!("{}", count), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade => { + // Round up to the next supported count; clamp at 4 + if count == 0 { + Ok(Seedance2p0BatchCount::One) + } else if count < 4 { + Ok(Seedance2p0BatchCount::Four) + } else { + Ok(Seedance2p0BatchCount::Four) + } + } + RequestMismatchMitigationStrategy::PayLessDowngrade => { + // Round down to the previous supported count; clamp at 4 for overflows + if count == 0 { + Ok(Seedance2p0BatchCount::One) + } else if count < 4 { + Ok(Seedance2p0BatchCount::Two) + } else { + Ok(Seedance2p0BatchCount::Four) + } + } + }, + } +} + +fn plan_duration( + duration_seconds: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + const MIN: u16 = 4; + const MAX: u16 = 15; + match duration_seconds { + None => Ok(None), + Some(d) if d >= MIN && d <= MAX => Ok(Some(d as u8)), + Some(d) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "duration_seconds", + value: format!("{}", d), + })) + } + _ => Ok(Some(d.clamp(MIN, MAX) as u8)), + }, + } +} + +pub fn plan_generate_video_artcraft_seedance2p0<'a>( + request: &'a GenerateVideoRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?; + let batch_count = plan_batch_count(request.video_batch_count, strategy)?; + let duration_seconds = plan_duration(request.duration_seconds, strategy)?; + + Ok(PlanArtcraftSeedance2p0 { + prompt: request.prompt, + start_frame: request.start_frame, + end_frame: request.end_frame, + reference_images: request.reference_images, + aspect_ratio, + duration_seconds, + batch_count, + idempotency_token: request.get_or_generate_idempotency_token(), + }) +} diff --git a/crates/lib/artcraft_router/src/generate/generate_video/plan_video_generation.rs b/crates/lib/artcraft_router/src/generate/generate_video/plan_video_generation.rs new file mode 100644 index 0000000000..dbe7456ac6 --- /dev/null +++ b/crates/lib/artcraft_router/src/generate/generate_video/plan_video_generation.rs @@ -0,0 +1,19 @@ +use crate::api::common_video_model::CommonVideoModel; +use crate::api::provider::Provider; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::generate::generate_video::generate_video_request::GenerateVideoRequest; +use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::plan_generate_video_artcraft_seedance2p0; +use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan; + +pub fn plan_video_generation<'a>( + request: &'a GenerateVideoRequest<'a>, +) -> Result, ArtcraftRouterError> { + match request.provider { + Provider::Artcraft => match request.model { + CommonVideoModel::Seedance2p0 => { + plan_generate_video_artcraft_seedance2p0(request).map(VideoGenerationPlan::ArtcraftSeedance2p0) + } + _ => Err(ArtcraftRouterError::UnsupportedModel(format!("{:?}", request.model))), + }, + } +} diff --git a/crates/lib/artcraft_router/src/generate/generate_video/video_generation_plan.rs b/crates/lib/artcraft_router/src/generate/generate_video/video_generation_plan.rs new file mode 100644 index 0000000000..de4549b73f --- /dev/null +++ b/crates/lib/artcraft_router/src/generate/generate_video/video_generation_plan.rs @@ -0,0 +1,5 @@ +use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::PlanArtcraftSeedance2p0; + +pub enum VideoGenerationPlan<'a> { + ArtcraftSeedance2p0(PlanArtcraftSeedance2p0<'a>), +}