From 2833c2a05b377142e7641d99e66a5bd3e7529972 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 23 Feb 2026 16:23:01 +0700 Subject: [PATCH] cleanup --- .../src/errors/client_error.rs | 12 ++ ...lan_generate_video_artcraft_seedance2p0.rs | 147 +++++++++--------- 2 files changed, 88 insertions(+), 71 deletions(-) diff --git a/crates/lib/artcraft_router/src/errors/client_error.rs b/crates/lib/artcraft_router/src/errors/client_error.rs index 572e02557b..3f4c3a021c 100644 --- a/crates/lib/artcraft_router/src/errors/client_error.rs +++ b/crates/lib/artcraft_router/src/errors/client_error.rs @@ -22,6 +22,12 @@ pub enum ClientError { /// 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 }, + + /// The caller requested zero generations, which is never valid. + UserRequestedZeroGenerations, + + /// ArtCraft only accepts media tokens for image inputs, not raw URLs. + ArtcraftOnlySupportsMediaTokens, } impl Error for ClientError {} @@ -35,6 +41,12 @@ impl Display for ClientError { Self::ModelDoesNotSupportOption { field, value } => { write!(f, "Model does not support '{}' for field '{}'", value, field) } + Self::UserRequestedZeroGenerations => { + write!(f, "Cannot request zero generations") + } + Self::ArtcraftOnlySupportsMediaTokens => { + write!(f, "ArtCraft only supports media tokens for image inputs; upload the image first to obtain a media token") + } } } } 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 index 266b301a41..29bce7a37b 100644 --- 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 @@ -8,21 +8,73 @@ use crate::generate::generate_video::generate_video_request::GenerateVideoReques use artcraft_api_defs::generate::video::multi_function::seedance_2p0_multi_function_video_gen::{ Seedance2p0AspectRatio, Seedance2p0BatchCount, }; +use tokens::tokens::media_files::MediaFileToken; pub struct PlanArtcraftSeedance2p0<'a> { pub prompt: Option<&'a str>, - pub start_frame: Option>, - pub end_frame: Option>, - pub reference_images: Option>, + pub start_frame: Option<&'a MediaFileToken>, + pub end_frame: Option<&'a MediaFileToken>, + pub reference_images: Option<&'a Vec>, 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 +pub fn plan_generate_video_artcraft_seedance2p0<'a>( + request: &'a GenerateVideoRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + let start_frame = resolve_image_ref(request.start_frame)?; + let end_frame = resolve_image_ref(request.end_frame)?; + let reference_images = resolve_image_list_ref(request.reference_images)?; + + 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, + end_frame, + reference_images, + aspect_ratio, + duration_seconds, + batch_count, + idempotency_token: request.get_or_generate_idempotency_token(), + }) +} + +fn resolve_image_ref<'a>( + image_ref: Option>, +) -> Result, ArtcraftRouterError> { + match image_ref { + None => Ok(None), + Some(ImageRef::MediaFileToken(t)) => Ok(Some(t)), + Some(ImageRef::Url(_)) => { + Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens)) + } + } +} + +fn resolve_image_list_ref<'a>( + image_list_ref: Option>, +) -> Result>, ArtcraftRouterError> { + match image_list_ref { + None => Ok(None), + Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)), + Some(ImageListRef::Urls(_)) => { + Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens)) + } + } +} + +// Supported aspect ratios and their AR values (width / height): +// Portrait9x16 = 0.5625, Portrait3x4 = 0.75, Square1x1 = 1.0, Standard4x3 = 1.33, Landscape16x9 = 1.78 +// +// All supported ratios cost the same, so PayMoreUpgrade and PayLessDowngrade both +// select the nearest match rather than rounding in a specific direction. fn plan_aspect_ratio( aspect_ratio: Option, strategy: RequestMismatchMitigationStrategy, @@ -55,52 +107,36 @@ fn plan_aspect_ratio( value: format!("{:?}", unsupported), })) } - RequestMismatchMitigationStrategy::PayMoreUpgrade => { - Ok(Some(aspect_ratio_upgrade(unsupported))) - } - RequestMismatchMitigationStrategy::PayLessDowngrade => { - Ok(Some(aspect_ratio_downgrade(unsupported))) + RequestMismatchMitigationStrategy::PayMoreUpgrade + | RequestMismatchMitigationStrategy::PayLessDowngrade => { + Ok(Some(nearest_aspect_ratio(unsupported))) } }, } } -/// Round an unsupported aspect ratio up toward a higher AR (wider / less portrait). -fn aspect_ratio_upgrade(aspect_ratio: CommonAspectRatio) -> Seedance2p0AspectRatio { +/// Pick the nearest supported aspect ratio by AR value (width / height). +/// All Seedance2p0 aspect ratios cost the same, so this is used for both upgrade and downgrade. +fn nearest_aspect_ratio(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) + CommonAspectRatio::WideFiveByFour => Seedance2p0AspectRatio::Standard4x3, // 1.25, nearest 1.33 + CommonAspectRatio::WideThreeByTwo => Seedance2p0AspectRatio::Standard4x3, // 1.50, nearest 1.33 + CommonAspectRatio::WideTwentyOneByNine => Seedance2p0AspectRatio::Landscape16x9, // 2.33, nearest 1.78 + CommonAspectRatio::TallFourByFive => Seedance2p0AspectRatio::Portrait3x4, // 0.80, nearest 0.75 + CommonAspectRatio::TallTwoByThree => Seedance2p0AspectRatio::Portrait3x4, // 0.67, nearest 0.75 + CommonAspectRatio::TallNineByTwentyOne => Seedance2p0AspectRatio::Portrait9x16, // 0.43, nearest 0.56 _ => Seedance2p0AspectRatio::Square1x1, } } +// Seedance2p0 supports batch counts of 1, 2, and 4 only. fn plan_batch_count( video_batch_count: Option, strategy: RequestMismatchMitigationStrategy, ) -> Result { let count = video_batch_count.unwrap_or(1); match count { + 0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)), 1 => Ok(Seedance2p0BatchCount::One), 2 => Ok(Seedance2p0BatchCount::Two), 4 => Ok(Seedance2p0BatchCount::Four), @@ -112,29 +148,18 @@ fn plan_batch_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) - } + // Round up: 3 → Four; anything > 4 clamps to Four + Ok(if count < 4 { Seedance2p0BatchCount::Four } else { 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) - } + // Round down: 3 → Two; anything > 4 clamps to Four (max available) + Ok(if count < 4 { Seedance2p0BatchCount::Two } else { Seedance2p0BatchCount::Four }) } }, } } +// Seedance2p0 supports duration of 4–15 seconds. fn plan_duration( duration_seconds: Option, strategy: RequestMismatchMitigationStrategy, @@ -155,23 +180,3 @@ fn plan_duration( }, } } - -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(), - }) -}