diff --git a/crates/api_clients/artcraft/artcraft_client/src/datatypes/common_enums/generation/api_client_video_model.rs b/crates/api_clients/artcraft/artcraft_client/src/datatypes/common_enums/generation/api_client_video_model.rs index 18fed733aa..25ee442203 100644 --- a/crates/api_clients/artcraft/artcraft_client/src/datatypes/common_enums/generation/api_client_video_model.rs +++ b/crates/api_clients/artcraft/artcraft_client/src/datatypes/common_enums/generation/api_client_video_model.rs @@ -25,8 +25,6 @@ pub enum ApiClientVideoModel { Seedance2p0Fast, Seedance2p0BytePlus, Seedance2p0BytePlusFast, - Seedance2p0Ultra, - Seedance2p0UltraFast, Seedance2p0BytePlusUltra, Seedance2p0BytePlusUltraFast, Seedance2p0Mini, @@ -65,8 +63,6 @@ impl From for ApiClientVideoModel { "seedance_2p0_fast" => Self::Seedance2p0Fast, "seedance_2p0_bp" => Self::Seedance2p0BytePlus, "seedance_2p0_bp_fast" => Self::Seedance2p0BytePlusFast, - "seedance_2p0_u" => Self::Seedance2p0Ultra, - "seedance_2p0_u_fast" => Self::Seedance2p0UltraFast, "seedance_2p0_bpu" => Self::Seedance2p0BytePlusUltra, "seedance_2p0_bpu_fast" => Self::Seedance2p0BytePlusUltraFast, "seedance_2p0_mini" => Self::Seedance2p0Mini, @@ -106,8 +102,6 @@ impl From for String { ApiClientVideoModel::Seedance2p0Fast => "seedance_2p0_fast".to_string(), ApiClientVideoModel::Seedance2p0BytePlus => "seedance_2p0_bp".to_string(), ApiClientVideoModel::Seedance2p0BytePlusFast => "seedance_2p0_bp_fast".to_string(), - ApiClientVideoModel::Seedance2p0Ultra => "seedance_2p0_u".to_string(), - ApiClientVideoModel::Seedance2p0UltraFast => "seedance_2p0_u_fast".to_string(), ApiClientVideoModel::Seedance2p0BytePlusUltra => "seedance_2p0_bpu".to_string(), ApiClientVideoModel::Seedance2p0BytePlusUltraFast => "seedance_2p0_bpu_fast".to_string(), ApiClientVideoModel::Seedance2p0Mini => "seedance_2p0_mini".to_string(), @@ -165,8 +159,6 @@ mod tests { (ApiClientVideoModel::Seedance2p0Fast, "seedance_2p0_fast"), (ApiClientVideoModel::Seedance2p0BytePlus, "seedance_2p0_bp"), (ApiClientVideoModel::Seedance2p0BytePlusFast, "seedance_2p0_bp_fast"), - (ApiClientVideoModel::Seedance2p0Ultra, "seedance_2p0_u"), - (ApiClientVideoModel::Seedance2p0UltraFast, "seedance_2p0_u_fast"), (ApiClientVideoModel::Seedance2p0BytePlusUltra, "seedance_2p0_bpu"), (ApiClientVideoModel::Seedance2p0BytePlusUltraFast, "seedance_2p0_bpu_fast"), (ApiClientVideoModel::Seedance2p0Mini, "seedance_2p0_mini"), diff --git a/crates/api_clients/artcraft/artcraft_router/src/api/router_video_model.rs b/crates/api_clients/artcraft/artcraft_router/src/api/router_video_model.rs index c5259f86d1..816e7d0789 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/api/router_video_model.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/api/router_video_model.rs @@ -68,11 +68,7 @@ pub enum RouterVideoModel { #[serde(rename = "seedance_2p0_bp_fast")] Seedance2p0BytePlusFast, - #[serde(rename = "seedance_2p0_u")] - Seedance2p0Ultra, - #[serde(rename = "seedance_2p0_u_fast")] - Seedance2p0UltraFast, #[serde(rename = "seedance_2p0_bpu")] Seedance2p0BytePlusUltra, diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs index 1a2d2c840f..c5615f153b 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs @@ -26,8 +26,6 @@ use crate::generate::generate_video::providers::artcraft::seedance_1p0_lite::bui use crate::generate::generate_video::providers::artcraft::seedance_1p5_pro::build::build_artcraft_seedance_1p5_pro; use crate::generate::generate_video::providers::artcraft::seedance_2p0::build::build_artcraft_seedance_2p0; use crate::generate::generate_video::providers::artcraft::seedance_2p0_fast::build::build_artcraft_seedance_2p0_fast; -use crate::generate::generate_video::providers::artcraft::seedance_2p0_u::build::build_artcraft_seedance_2p0_u; -use crate::generate::generate_video::providers::artcraft::seedance_2p0_u_fast::build::build_artcraft_seedance_2p0_u_fast; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp::build::build_artcraft_seedance_2p0_bp; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_fast::build::build_artcraft_seedance_2p0_bp_fast; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu::build::build_artcraft_seedance_2p0_bpu; @@ -50,8 +48,6 @@ use crate::generate::generate_video::providers::artcraft::vidu_q3::build::build_ use crate::generate::generate_video::providers::artcraft::vidu_q3_turbo::build::build_artcraft_vidu_q3_turbo; use crate::generate::generate_video::providers::kinovi::happy_horse_1p0::build::build_kinovi_happy_horse_1p0; use crate::generate::generate_video::providers::kinovi::seedance_2p0::build::build_kinovi_seedance_2p0; -use crate::generate::generate_video::providers::gmicloud::seedance_2p0_g::build::build_gmicloud_seedance_2p0_u; -use crate::generate::generate_video::providers::gmicloud::seedance_2p0_fast_g::build::build_gmicloud_seedance_2p0_u_fast; use crate::generate::generate_video::providers::grok_api::grok_imagine_video::build::build_grok_api_grok_imagine_video; use crate::generate::generate_video::providers::grok_api::grok_imagine_video_1p5::build::build_grok_api_grok_imagine_video_1p5; use crate::generate::generate_video::providers::fal::kling_1_6_pro::build::build_fal_kling_1_6_pro; @@ -201,8 +197,6 @@ impl GenerateVideoRequestBuilder { (RouterProvider::Artcraft, RouterVideoModel::MinimaxH3) => build_artcraft_minimax_h3(self), (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0) => build_artcraft_seedance_2p0(self), (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0Fast) => build_artcraft_seedance_2p0_fast(self), - (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0Ultra) => build_artcraft_seedance_2p0_u(self), - (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0UltraFast) => build_artcraft_seedance_2p0_u_fast(self), (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlus) => build_artcraft_seedance_2p0_bp(self), (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusFast) => build_artcraft_seedance_2p0_bp_fast(self), (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusUltra) => build_artcraft_seedance_2p0_bpu(self), @@ -250,9 +244,6 @@ impl GenerateVideoRequestBuilder { (RouterProvider::Fal, RouterVideoModel::Veo3p1Lite) => build_fal_veo_3p1_lite(self), (RouterProvider::Fal, RouterVideoModel::ViduQ3) => build_fal_vidu_q3(self), (RouterProvider::Fal, RouterVideoModel::ViduQ3Turbo) => build_fal_vidu_q3_turbo(self), - // GmiCloud - (RouterProvider::GmiCloud, RouterVideoModel::Seedance2p0Ultra) => build_gmicloud_seedance_2p0_u(self), - (RouterProvider::GmiCloud, RouterVideoModel::Seedance2p0UltraFast) => build_gmicloud_seedance_2p0_u_fast(self), // Grok (RouterProvider::GrokApi, RouterVideoModel::GrokImagineVideo) => build_grok_api_grok_imagine_video(self), (RouterProvider::GrokApi, RouterVideoModel::GrokImagineVideo1p5) => build_grok_api_grok_imagine_video_1p5(self), diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs index 0044f19004..87d97560c2 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs @@ -23,8 +23,6 @@ pub mod seedance_1p0_lite; pub mod seedance_1p5_pro; pub mod seedance_2p0; pub mod seedance_2p0_fast; -pub mod seedance_2p0_u_fast; -pub mod seedance_2p0_u; pub mod seedance_2p0_bp; pub mod seedance_2p0_bp_fast; pub mod seedance_2p0_bpu; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/build.rs deleted file mode 100644 index 61866d76a8..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/build.rs +++ /dev/null @@ -1,189 +0,0 @@ -use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; - -use crate::errors::artcraft_router_error::ArtcraftRouterError; -use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; -use crate::generate::generate_video::providers::artcraft::build_common::{ - build_artcraft_omni_video_request, SupportedResolutions, UltraWideSupport, -}; -use crate::generate::generate_video::providers::artcraft::seedance_2p0_u::request::ArtcraftSeedance2p0UltraRequestState; -use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; -use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; - -pub fn build_artcraft_seedance_2p0_u(builder: GenerateVideoRequestBuilder) -> Result { - let request = build_artcraft_omni_video_request( - builder, - CommonVideoModelEnum::Seedance2p0Ultra, - SupportedResolutions::FullWith4k, - UltraWideSupport::Supported, - )?; - let state = ArtcraftSeedance2p0UltraRequestState { request }; - Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0Ultra(state))) -} - -#[cfg(test)] -mod tests { - use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum; - use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; - use enums::common::generation::common_bitrate::CommonBitrate as CommonBitrateEnum; - use crate::api::router_bitrate::RouterBitrate; - use tokens::tokens::characters::CharacterToken; - use tokens::tokens::media_files::MediaFileToken; - - use crate::api::character_list_ref::CharacterListRef; - use crate::api::router_resolution::RouterResolution; - use crate::api::image_list_ref::ImageListRef; - use crate::api::image_ref::ImageRef; - use crate::api::router_provider::RouterProvider; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; - use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; - - use super::*; - - // ── Field conversions ── - - mod field_conversions { - use super::*; - - #[test] - fn model_is_seedance_2p0() { - let req = unwrap_request(make_builder(|_| {})); - assert!(matches!(req.request.model, Some(CommonVideoModelEnum::Seedance2p0Ultra))); - } - - #[test] - fn prompt_passed_through() { - let req = unwrap_request(make_builder(|b| { b.prompt = Some("test".to_string()); })); - assert_eq!(req.request.prompt, Some("test".to_string())); - } - - #[test] - fn duration_passed_through() { - let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(10); })); - assert_eq!(req.request.duration_seconds, Some(10)); - } - - #[test] - fn duration_clamped_to_max() { - let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(99); })); - assert_eq!(req.request.duration_seconds, Some(15)); - } - - #[test] - fn batch_count_passed_through() { - let req = unwrap_request(make_builder(|b| { b.video_batch_count = Some(4); })); - assert_eq!(req.request.video_batch_count, Some(4)); - } - - #[test] - fn bitrate_passed_through() { - let req = unwrap_request(make_builder(|b| { b.bitrate = Some(RouterBitrate::High); })); - assert_eq!(req.request.bitrate, Some(CommonBitrateEnum::High)); - } - } - - // ── Resolution ── - - mod resolution_tests { - use super::*; - - #[test] - fn res_480p() { - let req = unwrap_request(make_builder(|b| { b.resolution = Some(RouterResolution::FourEightyP); })); - assert_eq!(req.request.resolution, Some(CommonResolutionEnum::FourEightyP)); - } - - #[test] - fn res_720p() { - let req = unwrap_request(make_builder(|b| { b.resolution = Some(RouterResolution::SevenTwentyP); })); - assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP)); - } - - #[test] - fn res_1080p() { - let req = unwrap_request(make_builder(|b| { b.resolution = Some(RouterResolution::TenEightyP); })); - assert_eq!(req.request.resolution, Some(CommonResolutionEnum::TenEightyP)); - } - - #[test] - fn res_4k() { - let req = unwrap_request(make_builder(|b| { b.resolution = Some(RouterResolution::FourK); })); - assert_eq!(req.request.resolution, Some(CommonResolutionEnum::FourK)); - } - - #[test] - fn none_stays_none() { - let req = unwrap_request(make_builder(|_| {})); - assert!(req.request.resolution.is_none()); - } - } - - // ── Media tokens ── - - mod media_token_tests { - use super::*; - - #[test] - fn start_frame_token_passed_through() { - let token = MediaFileToken::new("mf_start".to_string()); - let req = unwrap_request(make_builder(|b| { - b.start_frame = Some(ImageRef::MediaFileToken(token.clone())); - })); - assert_eq!(req.request.start_frame_image_media_token, Some(token)); - } - - #[test] - fn url_start_frame_rejected() { - let result = build_artcraft_seedance_2p0_u(GenerateVideoRequestBuilder { - start_frame: Some(ImageRef::Url("https://example.com".to_string())), - ..base_builder() - }); - assert!(result.is_err()); - } - - #[test] - fn reference_image_tokens_passed_through() { - let tokens = vec![MediaFileToken::new("mf_a".to_string()), MediaFileToken::new("mf_b".to_string())]; - let req = unwrap_request(make_builder(|b| { - b.reference_images = Some(ImageListRef::MediaFileTokens(tokens.clone())); - })); - assert_eq!(req.request.reference_image_media_tokens, Some(tokens)); - } - - #[test] - fn character_tokens_passed_through() { - let tokens = vec![CharacterToken::new("char_a".to_string()), CharacterToken::new("char_b".to_string())]; - let req = unwrap_request(make_builder(|b| { - b.reference_character_tokens = Some(CharacterListRef::CharacterTokens(tokens.clone())); - })); - assert_eq!(req.request.reference_character_tokens, Some(tokens)); - } - } - - // ── Helpers ── - - fn base_builder() -> GenerateVideoRequestBuilder { - GenerateVideoRequestBuilder { - provider: RouterProvider::Artcraft, - duration_seconds: Some(5), - video_batch_count: Some(1), - ..Default::default() - } - } - - fn make_builder(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder { - let mut builder = base_builder(); - f(&mut builder); - builder - } - - fn unwrap_request(builder: GenerateVideoRequestBuilder) -> ArtcraftSeedance2p0UltraRequestState { - let result = build_artcraft_seedance_2p0_u(builder).expect("build should succeed"); - match result { - VideoGenerationDraftOrRequest::Request( - VideoGenerationRequest::ArtcraftSeedance2p0Ultra(state) - ) => state, - _ => panic!("expected ArtcraftSeedance2p0 request"), - } - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/cost.rs deleted file mode 100644 index 6e83274dfe..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/cost.rs +++ /dev/null @@ -1,237 +0,0 @@ -use enums::common::generation::common_resolution::CommonResolution; - -use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_four_k_usd_cents; -use crate::generate::generate_video::providers::artcraft::seedance_2p0_u::request::ArtcraftSeedance2p0UltraRequestState; -use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; - -// ── Pricing constants ── - -const CENTS_PER_SECOND_480P: f64 = 5.4404; -const CENTS_PER_SECOND_720P: f64 = 11.2; -const CENTS_PER_SECOND_1080P: f64 = 32.6424; - -pub struct ArtcraftSeedance2p0UltraCostState { - pub resolution: CommonResolution, - pub duration_seconds: u16, - pub batch_count: u16, - pub has_video_reference: bool, -} - -impl ArtcraftSeedance2p0UltraCostState { - pub fn from_request(request: &ArtcraftSeedance2p0UltraRequestState) -> Self { - let resolution = request.request.resolution - .unwrap_or(CommonResolution::SevenTwentyP); - let duration_seconds = request.request.duration_seconds.unwrap_or(5); - let batch_count = request.request.video_batch_count.unwrap_or(1); - let has_video_reference = request.request.reference_video_media_tokens - .as_ref() - .is_some_and(|tokens| !tokens.is_empty()); - - Self { resolution, duration_seconds, batch_count, has_video_reference } - } - - pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { - if self.resolution == CommonResolution::FourK { - let usd_cents = seedance_2p0_four_k_usd_cents( - self.duration_seconds, - self.batch_count, - self.has_video_reference, - ); - return VideoGenerationCostEstimate { - cost_in_credits: Some(usd_cents), - cost_in_usd_cents: Some(usd_cents), - is_free: false, - is_unlimited: false, - is_rate_limited: false, - has_watermark: false, - failures_are_refunded: None, - }; - } - - let cents_per_second = match self.resolution { - CommonResolution::FourEightyP => CENTS_PER_SECOND_480P, - CommonResolution::TenEightyP => CENTS_PER_SECOND_1080P, - _ => CENTS_PER_SECOND_720P, - }; - - let cents_per_video = (cents_per_second * self.duration_seconds as f64).ceil() as u64; - let usd_cents = cents_per_video * self.batch_count as u64; - - VideoGenerationCostEstimate { - cost_in_credits: Some(usd_cents), - cost_in_usd_cents: Some(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::router_provider::RouterProvider; - use crate::api::router_resolution::RouterResolution; - use crate::api::router_video_model::RouterVideoModel; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - - mod pricing_720p { - use super::*; - - #[test] - fn batch_1() { - // 11.2 * 5 = 56¢ - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 1), 56); - // 11.2 * 10 = 112¢ - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 10, 1), 112); - // 11.2 * 15 = 168¢ - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 15, 1), 168); - } - - #[test] - fn batch_2() { - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 2), 112); - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 10, 2), 224); - } - - #[test] - fn batch_4() { - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 4), 224); - } - - #[test] - fn none_defaults_to_720p() { - assert_eq!(cost_cents(None, 10, 1), cost_cents(Some(RouterResolution::SevenTwentyP), 10, 1)); - } - } - - mod pricing_480p { - use super::*; - - #[test] - fn batch_1() { - // 5.4404 * 5 = 27.202 → ceil = 28¢ - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 1), 28); - // 5.4404 * 10 = 54.404 → ceil = 55¢ - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 10, 1), 55); - // 5.4404 * 15 = 81.606 → ceil = 82¢ - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 15, 1), 82); - } - - #[test] - fn batch_4() { - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 10, 4), 220); - } - } - - mod pricing_1080p { - use super::*; - - #[test] - fn batch_1() { - // 32.6424 * 5 = 163.212 → ceil = 164¢ - assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 1), 164); - // 32.6424 * 10 = 326.424 → ceil = 327¢ - assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 10, 1), 327); - // 32.6424 * 15 = 489.636 → ceil = 490¢ - assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 15, 1), 490); - } - - #[test] - fn batch_4() { - assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 10, 4), 1308); - } - } - - mod four_k_pricing { - use enums::common::generation::common_resolution::CommonResolution; - - fn artcraft_4k_cents(duration_seconds: u16, batch_count: u16, has_video_reference: bool) -> u64 { - super::super::ArtcraftSeedance2p0UltraCostState { - resolution: CommonResolution::FourK, - duration_seconds, - batch_count, - has_video_reference, - } - .estimate_cost() - .cost_in_usd_cents - .unwrap() - } - - #[test] - fn explicit_4k_without_video_reference() { - assert_eq!(artcraft_4k_cents(4, 1, false), 347); - assert_eq!(artcraft_4k_cents(5, 1, false), 433); - assert_eq!(artcraft_4k_cents(10, 1, false), 866); - assert_eq!(artcraft_4k_cents(15, 1, false), 1299); - } - - #[test] - fn explicit_4k_with_video_reference() { - assert_eq!(artcraft_4k_cents(4, 1, true), 416); - assert_eq!(artcraft_4k_cents(5, 1, true), 519); - assert_eq!(artcraft_4k_cents(10, 1, true), 1038); - assert_eq!(artcraft_4k_cents(15, 1, true), 1557); - } - } - - mod relative_pricing { - use super::*; - - #[test] - fn cost_480p_cheaper_than_720p_cheaper_than_1080p() { - let c480 = cost_cents(Some(RouterResolution::FourEightyP), 10, 1); - let c720 = cost_cents(Some(RouterResolution::SevenTwentyP), 10, 1); - let c1080 = cost_cents(Some(RouterResolution::TenEightyP), 10, 1); - assert!(c480 < c720); - assert!(c720 < c1080); - } - - #[test] - fn cost_scales_with_duration() { - let c5 = cost_cents(None, 5, 1); - let c10 = cost_cents(None, 10, 1); - let c15 = cost_cents(None, 15, 1); - assert!(c5 < c10); - assert!(c10 < c15); - } - - #[test] - fn cost_scales_with_batch() { - let b1 = cost_cents(None, 10, 1); - let b2 = cost_cents(None, 10, 2); - let b4 = cost_cents(None, 10, 4); - assert!(b1 < b2); - assert!(b2 < b4); - } - - #[test] - fn credits_equal_usd_cents() { - let cost = build_cost(None, 10, 1); - assert_eq!(cost.cost_in_credits, cost.cost_in_usd_cents); - } - } - - // -- Helpers -- - - fn build_cost( - resolution: Option, - duration_seconds: u16, - video_batch_count: u16, - ) -> crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate { - let builder = GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0Ultra, - provider: RouterProvider::Artcraft, - resolution, - duration_seconds: Some(duration_seconds), - video_batch_count: Some(video_batch_count), - ..Default::default() - }; - builder.build2().expect("build2").estimate_cost().expect("estimate_cost") - } - - fn cost_cents(resolution: Option, duration_seconds: u16, batch: u16) -> u64 { - build_cost(resolution, duration_seconds, batch).cost_in_usd_cents.unwrap() - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/mod.rs deleted file mode 100644 index 98150bd014..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod build; -pub mod cost; -pub mod request; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/request.rs deleted file mode 100644 index c6a7106d06..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u/request.rs +++ /dev/null @@ -1,237 +0,0 @@ -use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; - -use crate::client::router_artcraft_client::RouterArtcraftClient; -use crate::errors::artcraft_router_error::ArtcraftRouterError; -use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; -use crate::generate::generate_video::providers::artcraft::request_common::send_artcraft_omni_video_request; - -#[derive(Clone, Debug)] -pub struct ArtcraftSeedance2p0UltraRequestState { - pub request: OmniGenVideoCostAndGenerateRequest, -} - -impl ArtcraftSeedance2p0UltraRequestState { - pub async fn send(&self, client: &RouterArtcraftClient) -> Result { - send_artcraft_omni_video_request(&self.request, client).await - } -} - -#[cfg(test)] -mod tests { - use tokens::tokens::characters::CharacterToken; - use tokens::tokens::media_files::MediaFileToken; - - use crate::api::character_list_ref::CharacterListRef; - use crate::api::router_aspect_ratio::RouterAspectRatio; - use crate::api::router_resolution::RouterResolution; - use crate::api::image_list_ref::ImageListRef; - use crate::api::image_ref::ImageRef; - use crate::api::router_provider::RouterProvider; - use crate::client::router_artcraft_client::RouterArtcraftClient; - use crate::client::router_client::RouterClient; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; - use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; - use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; - use artcraft_client::utils::api_host::ApiHost; - - use test_data::web::character_tokens::{JIM, KNIGHT}; - use test_data::web::image_media_tokens::{ - FOREST_BACKDROP_PRODUCTION_MEDIA_TOKEN, - JUNO_AT_LAKE_PRODUCTION_MEDIA_TOKEN, - WHITE_HOUSE_SUNSET_PRODUCTION_MEDIA_TOKEN, - }; - - // -- Aspect ratio tests -- - - mod aspect_ratio_tests { - use super::*; - - #[tokio::test] - #[ignore] - async fn landscape() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A corgi running through a field of wildflowers at sunset.".to_string()), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - ..artcraft_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] - async fn portrait() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A cat sitting on a windowsill watching rain.".to_string()), - aspect_ratio: Some(RouterAspectRatio::TallNineBySixteen), - ..artcraft_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] - async fn square() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A hummingbird hovering near a flower.".to_string()), - aspect_ratio: Some(RouterAspectRatio::Square), - ..artcraft_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - } - - // -- Resolution tests -- - - mod resolution_tests { - use super::*; - - #[tokio::test] - #[ignore] - async fn res_480p() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A shiba inu playing in autumn leaves.".to_string()), - resolution: Some(RouterResolution::FourEightyP), - ..artcraft_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] - async fn res_720p() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A golden retriever catching a frisbee on the beach.".to_string()), - resolution: Some(RouterResolution::SevenTwentyP), - ..artcraft_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] - async fn res_1080p() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A fox walking through a snowy forest.".to_string()), - resolution: Some(RouterResolution::TenEightyP), - ..artcraft_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - } - - // -- Modality tests -- - - mod modality_tests { - use super::*; - use test_data::web::image_media_tokens::TALL_MOCHI_WITH_GLASSES_PRODUCTION_MEDIA_TOKEN; - - #[tokio::test] - #[ignore] - async fn text_to_video() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A whale breaching in the open ocean at dawn, cinematic.".to_string()), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - ..artcraft_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] - async fn keyframe_start_and_end_frame() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("The dog walks from the lake to the forest.".to_string()), - start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new(JUNO_AT_LAKE_PRODUCTION_MEDIA_TOKEN.to_string()))), - end_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new(FOREST_BACKDROP_PRODUCTION_MEDIA_TOKEN.to_string()))), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - ..artcraft_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] - async fn image_references() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("The dog in @2 runs through the scenery in @1 towards the building in @3.".to_string()), - reference_images: Some(ImageListRef::MediaFileTokens(vec![ - MediaFileToken::new(FOREST_BACKDROP_PRODUCTION_MEDIA_TOKEN.to_string()), - MediaFileToken::new(TALL_MOCHI_WITH_GLASSES_PRODUCTION_MEDIA_TOKEN.to_string()), - MediaFileToken::new(WHITE_HOUSE_SUNSET_PRODUCTION_MEDIA_TOKEN.to_string()), - ])), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - ..artcraft_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] - async fn character_references() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("@Jim and @Knight are sparring in a medieval arena.".to_string()), - reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![ - CharacterToken::new(JIM.token.to_string()), - CharacterToken::new(KNIGHT.token.to_string()), - ])), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - ..artcraft_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - } - - // -- Helpers -- - - fn artcraft_builder() -> GenerateVideoRequestBuilder { - GenerateVideoRequestBuilder { - provider: RouterProvider::Artcraft, - duration_seconds: Some(4), - video_batch_count: Some(1), - ..Default::default() - } - } - - fn get_artcraft_client() -> RouterClient { - let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/artcraft_cookies.txt") - .expect("Failed to read artcraft cookies"); - let cookies = cookies.trim().to_string(); - let credentials = StorytellerCredentialSet::parse_multi_cookie_header(&cookies) - .expect("Failed to parse cookies") - .expect("No credentials found"); - RouterClient::Artcraft(RouterArtcraftClient::new(ApiHost::Storyteller, credentials)) - } - - async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse { - let client = get_artcraft_client(); - - let draft_or_request = builder.build2().expect("build2 should succeed"); - let request = match draft_or_request { - VideoGenerationDraftOrRequest::Request(r) => r, - _ => panic!("expected Request variant (Artcraft skips draft)"), - }; - - let response = request.send_request(&client).await.expect("send_request should succeed"); - - match &response { - GenerateVideoResponse::Artcraft(p) => { - println!("inference_job_token={:?}", p.inference_job_token); - println!("all_inference_job_tokens={:?}", p.all_inference_job_tokens); - } - other => println!("response: {:?}", other), - } - - response - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/build.rs deleted file mode 100644 index b8f85eb71c..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/build.rs +++ /dev/null @@ -1,253 +0,0 @@ -use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; - -use crate::errors::artcraft_router_error::ArtcraftRouterError; -use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; -use crate::generate::generate_video::providers::artcraft::build_common::{ - build_artcraft_omni_video_request, SupportedResolutions, UltraWideSupport, -}; -use crate::generate::generate_video::providers::artcraft::seedance_2p0_u_fast::request::ArtcraftSeedance2p0UltraFastRequestState; -use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; -use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; - -pub fn build_artcraft_seedance_2p0_u_fast(builder: GenerateVideoRequestBuilder) -> Result { - let request = build_artcraft_omni_video_request( - builder, - CommonVideoModelEnum::Seedance2p0UltraFast, - SupportedResolutions::Fast, - UltraWideSupport::Supported, - )?; - let state = ArtcraftSeedance2p0UltraFastRequestState { request }; - Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0UltraFast(state))) -} - -#[cfg(test)] -mod tests { - use enums::common::generation::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioEnum; - use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum; - use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; - use enums::common::generation::common_bitrate::CommonBitrate as CommonBitrateEnum; - use crate::api::router_bitrate::RouterBitrate; - use tokens::tokens::characters::CharacterToken; - use tokens::tokens::media_files::MediaFileToken; - - use crate::api::character_list_ref::CharacterListRef; - use crate::api::router_aspect_ratio::RouterAspectRatio; - use crate::api::router_resolution::RouterResolution; - use crate::api::router_video_model::RouterVideoModel; - use crate::api::image_list_ref::ImageListRef; - use crate::api::image_ref::ImageRef; - use crate::api::router_provider::RouterProvider; - use crate::api::video_list_ref::VideoListRef; - use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; - use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; - - use super::*; - - // ── Field conversions ── - - mod field_conversions { - use super::*; - - #[test] - fn prompt_is_passed_through() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { b.prompt = Some("test".to_string()); })); - assert_eq!(req.request.prompt, Some("test".to_string())); - } - - #[test] - fn prompt_none_stays_none() { - let req = unwrap_request(artcraft_fast_builder_with(|_| {})); - assert!(req.request.prompt.is_none()); - } - - #[test] - fn model_is_seedance_2p0_fast() { - let req = unwrap_request(artcraft_fast_builder_with(|_| {})); - assert!(matches!(req.request.model, Some(CommonVideoModelEnum::Seedance2p0UltraFast))); - } - - #[test] - fn duration_passed_through() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { b.duration_seconds = Some(10); })); - assert_eq!(req.request.duration_seconds, Some(10)); - } - - #[test] - fn duration_clamped_to_max() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { b.duration_seconds = Some(99); })); - assert_eq!(req.request.duration_seconds, Some(15)); - } - - #[test] - fn batch_count_passed_through() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { b.video_batch_count = Some(4); })); - assert_eq!(req.request.video_batch_count, Some(4)); - } - - #[test] - fn bitrate_passed_through() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { b.bitrate = Some(RouterBitrate::High); })); - assert_eq!(req.request.bitrate, Some(CommonBitrateEnum::High)); - } - } - - // ── Aspect ratio ── - - mod aspect_ratio_tests { - use super::*; - - #[test] - fn wide() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { - b.aspect_ratio = Some(RouterAspectRatio::WideSixteenByNine); - })); - assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideSixteenByNine)); - } - - #[test] - fn tall() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { - b.aspect_ratio = Some(RouterAspectRatio::TallNineBySixteen); - })); - assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::TallNineBySixteen)); - } - - #[test] - fn square() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { - b.aspect_ratio = Some(RouterAspectRatio::Square); - })); - assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::Square)); - } - - #[test] - fn none_stays_none() { - let req = unwrap_request(artcraft_fast_builder_with(|_| {})); - assert!(req.request.aspect_ratio.is_none()); - } - } - - // ── Resolution ── - - mod resolution_tests { - use super::*; - - #[test] - fn res_480p() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { - b.resolution = Some(RouterResolution::FourEightyP); - })); - assert_eq!(req.request.resolution, Some(CommonResolutionEnum::FourEightyP)); - } - - #[test] - fn res_720p() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { - b.resolution = Some(RouterResolution::SevenTwentyP); - })); - assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP)); - } - - #[test] - fn res_1080p_downgrades_to_720p() { - let req = unwrap_request(artcraft_fast_builder_with(|b| { - b.resolution = Some(RouterResolution::TenEightyP); - })); - assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP)); - } - - #[test] - fn res_1080p_error_out() { - let result = build_artcraft_seedance_2p0_u_fast(GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0UltraFast, - provider: RouterProvider::Artcraft, - resolution: Some(RouterResolution::TenEightyP), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, - ..Default::default() - }); - assert!(result.is_err()); - } - - #[test] - fn none_stays_none() { - let req = unwrap_request(artcraft_fast_builder_with(|_| {})); - assert!(req.request.resolution.is_none()); - } - } - - // ── Media tokens ── - - mod media_token_tests { - use super::*; - - #[test] - fn start_frame_token_passed_through() { - let token = MediaFileToken::new("mf_start".to_string()); - let req = unwrap_request(artcraft_fast_builder_with(|b| { - b.start_frame = Some(ImageRef::MediaFileToken(token.clone())); - })); - assert_eq!(req.request.start_frame_image_media_token, Some(token)); - } - - #[test] - fn url_start_frame_rejected() { - let result = build_artcraft_seedance_2p0_u_fast(GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0UltraFast, - provider: RouterProvider::Artcraft, - start_frame: Some(ImageRef::Url("https://example.com".to_string())), - ..Default::default() - }); - assert!(result.is_err()); - } - - #[test] - fn reference_image_tokens_passed_through() { - let tokens = vec![ - MediaFileToken::new("mf_a".to_string()), - MediaFileToken::new("mf_b".to_string()), - ]; - let req = unwrap_request(artcraft_fast_builder_with(|b| { - b.reference_images = Some(ImageListRef::MediaFileTokens(tokens.clone())); - })); - assert_eq!(req.request.reference_image_media_tokens, Some(tokens)); - } - - #[test] - fn character_tokens_passed_through() { - let tokens = vec![ - CharacterToken::new("char_a".to_string()), - CharacterToken::new("char_b".to_string()), - ]; - let req = unwrap_request(artcraft_fast_builder_with(|b| { - b.reference_character_tokens = Some(CharacterListRef::CharacterTokens(tokens.clone())); - })); - assert_eq!(req.request.reference_character_tokens, Some(tokens)); - } - } - - // ── Helpers ── - - fn artcraft_fast_builder_with(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder { - let mut builder = GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0UltraFast, - provider: RouterProvider::Artcraft, - duration_seconds: Some(5), - video_batch_count: Some(1), - ..Default::default() - }; - f(&mut builder); - builder - } - - fn unwrap_request(builder: GenerateVideoRequestBuilder) -> ArtcraftSeedance2p0UltraFastRequestState { - let result = build_artcraft_seedance_2p0_u_fast(builder).expect("build should succeed"); - match result { - VideoGenerationDraftOrRequest::Request( - VideoGenerationRequest::ArtcraftSeedance2p0UltraFast(state) - ) => state, - _ => panic!("expected ArtcraftSeedance2p0Fast request"), - } - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/cost.rs deleted file mode 100644 index 4352468c9c..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/cost.rs +++ /dev/null @@ -1,168 +0,0 @@ -use enums::common::generation::common_resolution::CommonResolution; - -use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; -use crate::generate::generate_video::providers::artcraft::seedance_2p0_u_fast::request::ArtcraftSeedance2p0UltraFastRequestState; - -// ── Pricing constants ── - -const CENTS_PER_SECOND_480P: f64 = 3.6267; -const CENTS_PER_SECOND_720P: f64 = 8.9089; - -pub struct ArtcraftSeedance2p0UltraFastCostState { - pub resolution: CommonResolution, - pub duration_seconds: u16, - pub batch_count: u16, -} - -impl ArtcraftSeedance2p0UltraFastCostState { - pub fn from_request(request: &ArtcraftSeedance2p0UltraFastRequestState) -> Self { - let resolution = request.request.resolution - .unwrap_or(CommonResolution::SevenTwentyP); - let duration_seconds = request.request.duration_seconds.unwrap_or(5); - let batch_count = request.request.video_batch_count.unwrap_or(1); - - Self { resolution, duration_seconds, batch_count } - } - - pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { - let cents_per_second = match self.resolution { - CommonResolution::FourEightyP => CENTS_PER_SECOND_480P, - // Fast model doesn't support 1080p; all non-480p resolves to 720p pricing - _ => CENTS_PER_SECOND_720P, - }; - - let cents_per_video = (cents_per_second * self.duration_seconds as f64).ceil() as u64; - let usd_cents = cents_per_video * self.batch_count as u64; - - VideoGenerationCostEstimate { - cost_in_credits: Some(usd_cents), - cost_in_usd_cents: Some(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::router_resolution::RouterResolution; - use crate::api::router_video_model::RouterVideoModel; - use crate::api::router_provider::RouterProvider; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - - mod pricing_720p { - use super::*; - - #[test] - fn batch_1() { - // 8.9089 * 5 = 44.5445 → ceil = 45¢ - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 1), 45); - // 8.9089 * 10 = 89.089 → ceil = 90¢ - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 10, 1), 90); - // 8.9089 * 15 = 133.6335 → ceil = 134¢ - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 15, 1), 134); - } - - #[test] - fn batch_2() { - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 10, 2), 180); - } - - #[test] - fn batch_4() { - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 10, 4), 360); - } - - #[test] - fn none_defaults_to_720p() { - assert_eq!(cost_cents(None, 10, 1), cost_cents(Some(RouterResolution::SevenTwentyP), 10, 1)); - } - } - - mod pricing_480p { - use super::*; - - #[test] - fn batch_1() { - // 3.6267 * 5 = 18.1335 → ceil = 19¢ - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 1), 19); - // 3.6267 * 10 = 36.267 → ceil = 37¢ - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 10, 1), 37); - // 3.6267 * 15 = 54.4005 → ceil = 55¢ - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 15, 1), 55); - } - - #[test] - fn batch_4() { - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 10, 4), 148); - } - } - - mod relative_pricing { - use super::*; - - #[test] - fn cost_480p_cheaper_than_720p() { - let c480 = cost_cents(Some(RouterResolution::FourEightyP), 10, 1); - let c720 = cost_cents(Some(RouterResolution::SevenTwentyP), 10, 1); - assert!(c480 < c720, "480p ({c480}) should be < 720p ({c720})"); - } - - #[test] - fn cost_scales_with_duration() { - let c5 = cost_cents(None, 5, 1); - let c10 = cost_cents(None, 10, 1); - let c15 = cost_cents(None, 15, 1); - assert!(c5 < c10); - assert!(c10 < c15); - } - - #[test] - fn fast_g_cheaper_than_standard_g() { - let fast = cost_cents(Some(RouterResolution::SevenTwentyP), 10, 1); - let standard = { - let builder = GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0Ultra, - provider: RouterProvider::Artcraft, - resolution: Some(RouterResolution::SevenTwentyP), - duration_seconds: Some(10), - video_batch_count: Some(1), - ..Default::default() - }; - builder.build2().unwrap().estimate_cost().unwrap().cost_in_usd_cents.unwrap() - }; - assert!(fast < standard, "Fast ({fast}¢) should be < Standard ({standard}¢)"); - } - - #[test] - fn credits_equal_usd_cents() { - let cost = build_cost(None, 10, 1); - assert_eq!(cost.cost_in_credits, cost.cost_in_usd_cents); - } - } - - // -- Helpers -- - - fn build_cost( - resolution: Option, - duration_seconds: u16, - video_batch_count: u16, - ) -> crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate { - let builder = GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0UltraFast, - provider: RouterProvider::Artcraft, - resolution, - duration_seconds: Some(duration_seconds), - video_batch_count: Some(video_batch_count), - ..Default::default() - }; - builder.build2().expect("build2").estimate_cost().expect("estimate_cost") - } - - fn cost_cents(resolution: Option, duration_seconds: u16, batch: u16) -> u64 { - build_cost(resolution, duration_seconds, batch).cost_in_usd_cents.unwrap() - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/mod.rs deleted file mode 100644 index 98150bd014..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod build; -pub mod cost; -pub mod request; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/request.rs deleted file mode 100644 index f34c8a1b6a..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_u_fast/request.rs +++ /dev/null @@ -1,227 +0,0 @@ -use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; - -use crate::client::router_artcraft_client::RouterArtcraftClient; -use crate::errors::artcraft_router_error::ArtcraftRouterError; -use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; -use crate::generate::generate_video::providers::artcraft::request_common::send_artcraft_omni_video_request; - -#[derive(Clone, Debug)] -pub struct ArtcraftSeedance2p0UltraFastRequestState { - pub request: OmniGenVideoCostAndGenerateRequest, -} - -impl ArtcraftSeedance2p0UltraFastRequestState { - pub async fn send(&self, client: &RouterArtcraftClient) -> Result { - send_artcraft_omni_video_request(&self.request, client).await - } -} - -#[cfg(test)] -mod tests { - use tokens::tokens::characters::CharacterToken; - use tokens::tokens::media_files::MediaFileToken; - - use crate::api::character_list_ref::CharacterListRef; - use crate::api::router_aspect_ratio::RouterAspectRatio; - use crate::api::router_resolution::RouterResolution; - use crate::api::router_video_model::RouterVideoModel; - use crate::api::image_list_ref::ImageListRef; - use crate::api::image_ref::ImageRef; - use crate::api::router_provider::RouterProvider; - use crate::client::router_artcraft_client::RouterArtcraftClient; - use crate::client::router_client::RouterClient; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; - use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; - use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; - use artcraft_client::utils::api_host::ApiHost; - - use test_data::web::character_tokens::{JIM, KNIGHT}; - use test_data::web::image_media_tokens::{ - FOREST_BACKDROP_PRODUCTION_MEDIA_TOKEN, - JUNO_AT_LAKE_PRODUCTION_MEDIA_TOKEN, - WHITE_HOUSE_SUNSET_PRODUCTION_MEDIA_TOKEN, - }; - - // -- Aspect ratio tests -- - - mod aspect_ratio_tests { - use super::*; - - #[tokio::test] - #[ignore] // manually run — fires a real API request and incurs cost - async fn landscape() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A corgi running through a field of wildflowers at sunset.".to_string()), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - ..artcraft_fast_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] // manually run — fires a real API request and incurs cost - async fn portrait() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A cat sitting on a windowsill watching rain.".to_string()), - aspect_ratio: Some(RouterAspectRatio::TallNineBySixteen), - ..artcraft_fast_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] // manually run — fires a real API request and incurs cost - async fn square() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A hummingbird hovering near a flower.".to_string()), - aspect_ratio: Some(RouterAspectRatio::Square), - ..artcraft_fast_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - } - - // -- Resolution tests -- - - mod resolution_tests { - use super::*; - - #[tokio::test] - #[ignore] // manually run — fires a real API request and incurs cost - async fn res_480p() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A shiba inu playing in autumn leaves.".to_string()), - resolution: Some(RouterResolution::FourEightyP), - ..artcraft_fast_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] // manually run — fires a real API request and incurs cost - async fn res_720p() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A golden retriever catching a frisbee on the beach.".to_string()), - resolution: Some(RouterResolution::SevenTwentyP), - ..artcraft_fast_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - } - - // -- Modality tests -- - - mod modality_tests { - use super::*; - use test_data::web::image_media_tokens::TALL_MOCHI_WITH_GLASSES_PRODUCTION_MEDIA_TOKEN; - - #[tokio::test] - #[ignore] // manually run — fires a real API request and incurs cost - async fn text_to_video() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A sky whale breaching in the open sky, pink clouds at dawn, cinematic.".to_string()), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - ..artcraft_fast_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] // manually run — fires a real API request and incurs cost - async fn keyframe_start_and_end_frame() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("The dog walks from the lake to the forest.".to_string()), - start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new(JUNO_AT_LAKE_PRODUCTION_MEDIA_TOKEN.to_string()))), - end_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new(FOREST_BACKDROP_PRODUCTION_MEDIA_TOKEN.to_string()))), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - ..artcraft_fast_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] // manually run — fires a real API request and incurs cost - async fn image_references() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("The dog in @2 runs through the scenery in @1 towards the building in @3.".to_string()), - reference_images: Some(ImageListRef::MediaFileTokens(vec![ - MediaFileToken::new(FOREST_BACKDROP_PRODUCTION_MEDIA_TOKEN.to_string()), - MediaFileToken::new(TALL_MOCHI_WITH_GLASSES_PRODUCTION_MEDIA_TOKEN.to_string()), - MediaFileToken::new(WHITE_HOUSE_SUNSET_PRODUCTION_MEDIA_TOKEN.to_string()), - ])), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - ..artcraft_fast_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - - #[tokio::test] - #[ignore] // manually run — fires a real API request and incurs cost - async fn character_references() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("@Jim and @Knight are sparring in a medieval arena.".to_string()), - reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![ - CharacterToken::new(JIM.token.to_string()), - CharacterToken::new(KNIGHT.token.to_string()), - ])), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - ..artcraft_fast_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); - assert_eq!(1, 2, "Inspect output above"); - } - } - - // -- Helpers -- - - fn artcraft_fast_builder() -> GenerateVideoRequestBuilder { - GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0UltraFast, - provider: RouterProvider::Artcraft, - duration_seconds: Some(4), - video_batch_count: Some(1), - ..Default::default() - } - } - - fn get_artcraft_client() -> RouterClient { - let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/artcraft_cookies.txt") - .expect("Failed to read artcraft cookies"); - let cookies = cookies.trim().to_string(); - let credentials = StorytellerCredentialSet::parse_multi_cookie_header(&cookies) - .expect("Failed to parse cookies") - .expect("No credentials found"); - RouterClient::Artcraft(RouterArtcraftClient::new(ApiHost::Storyteller, credentials)) - } - - async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse { - let client = get_artcraft_client(); - - let draft_or_request = builder.build2().expect("build2 should succeed"); - let request = match draft_or_request { - VideoGenerationDraftOrRequest::Request(r) => r, - _ => panic!("expected Request variant (Artcraft skips draft)"), - }; - - let response = request.send_request(&client).await.expect("send_request should succeed"); - - match &response { - GenerateVideoResponse::Artcraft(p) => { - println!("inference_job_token={:?}", p.inference_job_token); - println!("all_inference_job_tokens={:?}", p.all_inference_job_tokens); - } - other => println!("response: {:?}", other), - } - - response - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/mod.rs deleted file mode 100644 index f76c64a320..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod seedance_2p0_g; -pub mod seedance_2p0_fast_g; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/build.rs deleted file mode 100644 index 8afff771ba..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/build.rs +++ /dev/null @@ -1,323 +0,0 @@ -use gmicloud_client::requests::api::video::seedance_2_0_fast_260128::api::{ - Seedance20FastRatio, Seedance20FastRequest, Seedance20FastResolution, -}; - -use crate::api::audio_list_ref::AudioListRef; -use crate::api::router_aspect_ratio::RouterAspectRatio; -use crate::api::router_resolution::RouterResolution; -use crate::api::image_list_ref::ImageListRef; -use crate::api::image_ref::ImageRef; -use crate::api::video_list_ref::VideoListRef; -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::providers::gmicloud::seedance_2p0_fast_g::request::GmiCloudSeedance2p0UltraFastRequestState; -use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; -use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; - -pub fn build_gmicloud_seedance_2p0_u_fast( - mut builder: GenerateVideoRequestBuilder, -) -> Result { - let strategy = builder.request_mismatch_mitigation_strategy; - - let ratio = plan_ratio(builder.aspect_ratio.take(), strategy)?; - let resolution = plan_resolution(builder.resolution.take(), strategy)?; - let duration = builder.duration_seconds.take().map(|d| (d as u8).clamp(4, 15)); - let prompt = builder.prompt.take().unwrap_or_default(); - - let first_frame = resolve_url(builder.start_frame.take())?; - let last_frame = resolve_url(builder.end_frame.take())?; - let reference_images = resolve_url_list_from_images(builder.reference_images.take())?; - let reference_videos = resolve_url_list_from_videos(builder.reference_videos.take())?; - let reference_audios = resolve_url_list_from_audios(builder.reference_audio.take())?; - - let request = Seedance20FastRequest { - prompt, - duration, - resolution, - ratio, - seed: None, - watermark: Some(false), - generate_audio: Some(true), - web_search: None, - first_frame, - last_frame, - reference_images, - reference_videos, - reference_audios, - reference_asset_ids: None, - }; - - let state = GmiCloudSeedance2p0UltraFastRequestState { request }; - Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::GmiCloudSeedance2p0UltraFast(state))) -} - -fn plan_ratio( - aspect_ratio: Option, - _strategy: RequestMismatchMitigationStrategy, -) -> Result, ArtcraftRouterError> { - match aspect_ratio { - None | Some(RouterAspectRatio::Auto) | Some(RouterAspectRatio::Auto2k) - | Some(RouterAspectRatio::Auto3k) | Some(RouterAspectRatio::Auto4k) => Ok(None), - Some(RouterAspectRatio::WideSixteenByNine) | Some(RouterAspectRatio::Wide) => Ok(Some(Seedance20FastRatio::Landscape16x9)), - Some(RouterAspectRatio::TallNineBySixteen) | Some(RouterAspectRatio::Tall) => Ok(Some(Seedance20FastRatio::Portrait9x16)), - Some(RouterAspectRatio::Square) | Some(RouterAspectRatio::SquareHd) => Ok(Some(Seedance20FastRatio::Square)), - Some(RouterAspectRatio::WideFourByThree) => Ok(Some(Seedance20FastRatio::Standard4x3)), - Some(RouterAspectRatio::TallThreeByFour) => Ok(Some(Seedance20FastRatio::Portrait3x4)), - Some(RouterAspectRatio::WideTwentyOneByNine) | Some(RouterAspectRatio::TallNineByTwentyOne) => { - Ok(Some(Seedance20FastRatio::UltraWide21x9)) - } - Some(RouterAspectRatio::WideThreeByTwo) | Some(RouterAspectRatio::WideFiveByFour) - | Some(RouterAspectRatio::TallFourByFive) | Some(RouterAspectRatio::TallTwoByThree) => { - Ok(Some(Seedance20FastRatio::Adaptive)) - } - } -} - -fn plan_resolution( - resolution: Option, - _strategy: RequestMismatchMitigationStrategy, -) -> Result, ArtcraftRouterError> { - match resolution { - None => Ok(None), - Some(RouterResolution::FourEightyP) => Ok(Some(Seedance20FastResolution::FourEightyP)), - Some(RouterResolution::SevenTwentyP) => Ok(Some(Seedance20FastResolution::SevenTwentyP)), - // Fast model doesn't support 1080p — fall back to 720p - Some(RouterResolution::TenEightyP) => Ok(Some(Seedance20FastResolution::SevenTwentyP)), - Some(RouterResolution::HalfK) | Some(RouterResolution::OneK) => Ok(Some(Seedance20FastResolution::FourEightyP)), - Some(RouterResolution::TwoK) | Some(RouterResolution::ThreeK) | Some(RouterResolution::FourK) => { - Ok(Some(Seedance20FastResolution::SevenTwentyP)) - } - } -} - -fn resolve_url(image_ref: Option) -> Result, ArtcraftRouterError> { - match image_ref { - None => Ok(None), - Some(ImageRef::Url(url)) => Ok(Some(url)), - Some(ImageRef::MediaFileToken(_)) => { - Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { - field: "start_frame/end_frame", - value: "GmiCloud only supports image URLs, not media file tokens".to_string(), - })) - } - } -} - -fn resolve_url_list_from_images( - list_ref: Option, -) -> Result>, ArtcraftRouterError> { - match list_ref { - None => Ok(None), - Some(ImageListRef::Urls(urls)) => Ok(Some(urls)), - Some(ImageListRef::MediaFileTokens(_)) => { - Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { - field: "reference_images", - value: "GmiCloud only supports image URLs, not media file tokens".to_string(), - })) - } - } -} - -fn resolve_url_list_from_videos( - list_ref: Option, -) -> Result>, ArtcraftRouterError> { - match list_ref { - None => Ok(None), - Some(VideoListRef::Urls(urls)) => Ok(Some(urls)), - Some(VideoListRef::MediaFileTokens(_)) => { - Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { - field: "reference_videos", - value: "GmiCloud only supports video URLs, not media file tokens".to_string(), - })) - } - } -} - -#[cfg(test)] -mod tests { - use gmicloud_client::requests::api::video::seedance_2_0_fast_260128::api::{ - Seedance20FastRatio, Seedance20FastResolution, - }; - use tokens::tokens::media_files::MediaFileToken; - - use crate::api::router_aspect_ratio::RouterAspectRatio; - use crate::api::router_resolution::RouterResolution; - use crate::api::router_video_model::RouterVideoModel; - use crate::api::image_list_ref::ImageListRef; - use crate::api::image_ref::ImageRef; - use crate::api::router_provider::RouterProvider; - use crate::api::video_list_ref::VideoListRef; - use crate::api::audio_list_ref::AudioListRef; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; - use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; - - use super::*; - - mod field_conversions { - use super::*; - - #[test] - fn prompt_passed_through() { - let req = unwrap_request(make_builder(|b| { b.prompt = Some("fast test".to_string()); })); - assert_eq!(req.request.prompt, "fast test"); - } - - #[test] - fn duration_passed_through() { - let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(8); })); - assert_eq!(req.request.duration, Some(8)); - } - - #[test] - fn duration_clamped_to_min() { - let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(2); })); - assert_eq!(req.request.duration, Some(4)); - } - - #[test] - fn duration_clamped_to_max() { - let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(30); })); - assert_eq!(req.request.duration, Some(15)); - } - } - - mod resolution_tests { - use super::*; - - #[test] - fn res_480p() { - let req = unwrap_request(make_builder(|b| { b.resolution = Some(RouterResolution::FourEightyP); })); - assert_eq!(req.request.resolution, Some(Seedance20FastResolution::FourEightyP)); - } - - #[test] - fn res_720p() { - let req = unwrap_request(make_builder(|b| { b.resolution = Some(RouterResolution::SevenTwentyP); })); - assert_eq!(req.request.resolution, Some(Seedance20FastResolution::SevenTwentyP)); - } - - #[test] - fn res_1080p_falls_back_to_720p() { - let req = unwrap_request(make_builder(|b| { b.resolution = Some(RouterResolution::TenEightyP); })); - assert_eq!(req.request.resolution, Some(Seedance20FastResolution::SevenTwentyP)); - } - - #[test] - fn none_stays_none() { - let req = unwrap_request(make_builder(|_| {})); - assert!(req.request.resolution.is_none()); - } - } - - mod aspect_ratio_tests { - use super::*; - - #[test] - fn landscape_16x9() { - let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideSixteenByNine); })); - assert_eq!(req.request.ratio, Some(Seedance20FastRatio::Landscape16x9)); - } - - #[test] - fn square() { - let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(RouterAspectRatio::Square); })); - assert_eq!(req.request.ratio, Some(Seedance20FastRatio::Square)); - } - - #[test] - fn auto_maps_to_none() { - let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(RouterAspectRatio::Auto); })); - assert_eq!(req.request.ratio, None); - } - } - - mod media_url_tests { - use super::*; - - #[test] - fn start_frame_url_passed_through() { - let req = unwrap_request(make_builder(|b| { - b.start_frame = Some(ImageRef::Url("https://example.com/img.png".to_string())); - })); - assert_eq!(req.request.first_frame, Some("https://example.com/img.png".to_string())); - } - - #[test] - fn media_file_token_rejected() { - let result = build_gmicloud_seedance_2p0_u_fast(GenerateVideoRequestBuilder { - start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new("mf_x".to_string()))), - ..base_builder() - }); - assert!(result.is_err()); - } - - #[test] - fn reference_image_urls_passed_through() { - let urls = vec!["https://example.com/ref.png".to_string()]; - let req = unwrap_request(make_builder(|b| { - b.reference_images = Some(ImageListRef::Urls(urls.clone())); - })); - assert_eq!(req.request.reference_images, Some(urls)); - } - - #[test] - fn reference_video_urls_passed_through() { - let urls = vec!["https://example.com/ref.mp4".to_string()]; - let req = unwrap_request(make_builder(|b| { - b.reference_videos = Some(VideoListRef::Urls(urls.clone())); - })); - assert_eq!(req.request.reference_videos, Some(urls)); - } - - #[test] - fn reference_audio_urls_passed_through() { - let urls = vec!["https://example.com/ref.wav".to_string()]; - let req = unwrap_request(make_builder(|b| { - b.reference_audio = Some(AudioListRef::Urls(urls.clone())); - })); - assert_eq!(req.request.reference_audios, Some(urls)); - } - } - - fn base_builder() -> GenerateVideoRequestBuilder { - GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0UltraFast, - provider: RouterProvider::GmiCloud, - duration_seconds: Some(5), - video_batch_count: Some(1), - ..Default::default() - } - } - - fn make_builder(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder { - let mut builder = base_builder(); - f(&mut builder); - builder - } - - fn unwrap_request(builder: GenerateVideoRequestBuilder) -> GmiCloudSeedance2p0UltraFastRequestState { - let result = build_gmicloud_seedance_2p0_u_fast(builder).expect("build should succeed"); - match result { - VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::GmiCloudSeedance2p0UltraFast(state)) => state, - _ => panic!("expected GmiCloudSeedance2p0FastG request"), - } - } -} - -fn resolve_url_list_from_audios( - list_ref: Option, -) -> Result>, ArtcraftRouterError> { - match list_ref { - None => Ok(None), - Some(AudioListRef::Urls(urls)) => Ok(Some(urls)), - Some(AudioListRef::MediaFileTokens(_)) => { - Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { - field: "reference_audios", - value: "GmiCloud only supports audio URLs, not media file tokens".to_string(), - })) - } - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/cost.rs deleted file mode 100644 index 9562d37c44..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/cost.rs +++ /dev/null @@ -1,125 +0,0 @@ -use gmicloud_client::traits::gmicloud_request_cost_calculator_trait::GmiCloudRequestCostCalculator; - -use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; -use crate::generate::generate_video::providers::gmicloud::seedance_2p0_fast_g::request::GmiCloudSeedance2p0UltraFastRequestState; - -pub struct GmiCloudSeedance2p0UltraFastCostState { - request: GmiCloudSeedance2p0UltraFastRequestState, -} - -impl GmiCloudSeedance2p0UltraFastCostState { - pub fn from_request(request: &GmiCloudSeedance2p0UltraFastRequestState) -> Self { - Self { request: request.clone() } - } - - pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { - let cost_in_usd_cents = self.request.request.calculate_cost_in_cents(); - - VideoGenerationCostEstimate { - cost_in_credits: None, - 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::router_resolution::RouterResolution; - use crate::api::router_video_model::RouterVideoModel; - use crate::api::router_provider::RouterProvider; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - - mod pricing_720p { - use super::*; - - #[test] - fn default_5s() { - // 720p: 3.1 ¢/s * 5 = 15.5 → ceil = 16¢ - assert_eq!(cost_cents(None, 5), 16); - } - - #[test] - fn default_10s() { - // 3.1 * 10 = 31¢ - assert_eq!(cost_cents(None, 10), 31); - } - - #[test] - fn default_15s() { - // 3.1 * 15 = 46.5 → ceil = 47¢ - assert_eq!(cost_cents(None, 15), 47); - } - } - - mod pricing_480p { - use super::*; - - #[test] - fn p480_5s() { - // 1.0 * 5 = 5¢ - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5), 5); - } - - #[test] - fn p480_10s() { - // 1.0 * 10 = 10¢ - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 10), 10); - } - - #[test] - fn p480_15s() { - // 1.0 * 15 = 15¢ - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 15), 15); - } - } - - mod relative_pricing { - use super::*; - - #[test] - fn higher_resolution_costs_more() { - let c480 = cost_cents(Some(RouterResolution::FourEightyP), 10); - let c720 = cost_cents(Some(RouterResolution::SevenTwentyP), 10); - assert!(c480 < c720, "480p ({c480}) should be < 720p ({c720})"); - } - - #[test] - fn fast_cheaper_than_standard() { - let fast = cost_cents(Some(RouterResolution::SevenTwentyP), 10); - let standard = { - let builder = GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0Ultra, - provider: RouterProvider::GmiCloud, - resolution: Some(RouterResolution::SevenTwentyP), - duration_seconds: Some(10), - video_batch_count: Some(1), - ..Default::default() - }; - builder.build2().unwrap().estimate_cost().unwrap().cost_in_usd_cents.unwrap() - }; - assert!(fast < standard, "Fast ({fast}¢) should be < Standard ({standard}¢)"); - } - } - - fn cost_cents(resolution: Option, duration_seconds: u16) -> u64 { - let builder = GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0UltraFast, - provider: RouterProvider::GmiCloud, - resolution, - duration_seconds: Some(duration_seconds), - video_batch_count: Some(1), - ..Default::default() - }; - builder.build2() - .expect("build2 should succeed") - .estimate_cost() - .expect("estimate_cost should succeed") - .cost_in_usd_cents - .unwrap() - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/mod.rs deleted file mode 100644 index 98150bd014..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod build; -pub mod cost; -pub mod request; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/request.rs deleted file mode 100644 index 557e95776f..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_fast_g/request.rs +++ /dev/null @@ -1,100 +0,0 @@ -use gmicloud_client::requests::api::video::seedance_2_0_fast_260128::api::Seedance20FastRequest; - -use crate::client::router_gmicloud_client::RouterGmiCloudClient; -use crate::errors::artcraft_router_error::ArtcraftRouterError; -use crate::errors::provider_error::ProviderError; -use crate::generate::generate_video::generate_video_response::{ - GenerateVideoResponse, GmiCloudVideoResponsePayload, -}; - -#[derive(Clone, Debug)] -pub struct GmiCloudSeedance2p0UltraFastRequestState { - pub request: Seedance20FastRequest, -} - -impl GmiCloudSeedance2p0UltraFastRequestState { - pub async fn send(&self, client: &RouterGmiCloudClient) -> Result { - let response = self.request.send_request(&client.api_key) - .await - .map_err(|err| ArtcraftRouterError::Provider(ProviderError::GmiCloud(err)))?; - - Ok(GenerateVideoResponse::GmiCloud(GmiCloudVideoResponsePayload { - request_id: response.request_id, - })) - } -} - -#[cfg(test)] -mod tests { - use crate::api::router_aspect_ratio::RouterAspectRatio; - use crate::api::router_resolution::RouterResolution; - use crate::api::router_video_model::RouterVideoModel; - use crate::api::image_ref::ImageRef; - use crate::api::router_provider::RouterProvider; - use crate::client::router_client::RouterClient; - use crate::client::router_gmicloud_client::RouterGmiCloudClient; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; - use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; - use gmicloud_client::creds::gmicloud_api_key::GmiCloudApiKey; - use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL; - - #[tokio::test] - #[ignore] // requires real API key, incurs costs - async fn test_text_to_video_720p() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A shiba inu playing in autumn leaves.".to_string()), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - resolution: Some(RouterResolution::SevenTwentyP), - duration_seconds: Some(5), - ..gmicloud_fast_builder() - }).await; - println!("response: {:?}", response); - assert!(matches!(response, GenerateVideoResponse::GmiCloud(_))); - } - - #[tokio::test] - #[ignore] // requires real API key, incurs costs - async fn test_image_to_video_480p() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("The dog starts running and splashing in the lake.".to_string()), - start_frame: Some(ImageRef::Url(JUNO_AT_LAKE_IMAGE_URL.to_string())), - resolution: Some(RouterResolution::FourEightyP), - duration_seconds: Some(5), - ..gmicloud_fast_builder() - }).await; - println!("response: {:?}", response); - assert!(matches!(response, GenerateVideoResponse::GmiCloud(_))); - } - - fn gmicloud_fast_builder() -> GenerateVideoRequestBuilder { - GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0UltraFast, - provider: RouterProvider::GmiCloud, - video_batch_count: Some(1), - ..Default::default() - } - } - - fn get_gmicloud_client() -> RouterClient { - let secret = std::fs::read_to_string("/Users/bt/Artcraft/credentials/gmicloud_api_key.txt") - .expect("Failed to read GmiCloud API key"); - let api_key = GmiCloudApiKey::from_str(&secret); - RouterClient::GmiCloud(RouterGmiCloudClient::new(api_key)) - } - - async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse { - let client = get_gmicloud_client(); - let draft_or_request = builder.build2().expect("build2 should succeed"); - let request = match draft_or_request { - VideoGenerationDraftOrRequest::Request(r) => r, - _ => panic!("expected Request variant (GmiCloud skips draft)"), - }; - let response = request.send_request(&client).await.expect("send_request should succeed"); - match &response { - GenerateVideoResponse::GmiCloud(p) => println!("request_id={}", p.request_id), - other => println!("response: {:?}", other), - } - response - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/build.rs deleted file mode 100644 index 063e913e30..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/build.rs +++ /dev/null @@ -1,355 +0,0 @@ -use gmicloud_client::requests::api::video::seedance_2_0_260128::api::{ - Seedance20Ratio, Seedance20Request, Seedance20Resolution, -}; - -use crate::api::audio_list_ref::AudioListRef; -use crate::api::router_aspect_ratio::RouterAspectRatio; -use crate::api::router_resolution::RouterResolution; -use crate::api::image_list_ref::ImageListRef; -use crate::api::image_ref::ImageRef; -use crate::api::video_list_ref::VideoListRef; -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::providers::gmicloud::seedance_2p0_g::request::GmiCloudSeedance2p0UltraRequestState; -use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; -use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; - -pub fn build_gmicloud_seedance_2p0_u( - mut builder: GenerateVideoRequestBuilder, -) -> Result { - let strategy = builder.request_mismatch_mitigation_strategy; - - let ratio = plan_ratio(builder.aspect_ratio.take(), strategy)?; - let resolution = plan_resolution(builder.resolution.take(), strategy)?; - let duration = builder.duration_seconds.take().map(|d| (d as u8).clamp(4, 15)); - let prompt = builder.prompt.take().unwrap_or_default(); - - let first_frame = resolve_url(builder.start_frame.take())?; - let last_frame = resolve_url(builder.end_frame.take())?; - let reference_images = resolve_url_list_from_images(builder.reference_images.take())?; - let reference_videos = resolve_url_list_from_videos(builder.reference_videos.take())?; - let reference_audios = resolve_url_list_from_audios(builder.reference_audio.take())?; - - let request = Seedance20Request { - prompt, - duration, - resolution, - ratio, - seed: None, - watermark: Some(false), - generate_audio: Some(true), - web_search: None, - first_frame, - last_frame, - reference_images, - reference_videos, - reference_audios, - reference_asset_ids: None, - }; - - let state = GmiCloudSeedance2p0UltraRequestState { request }; - Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::GmiCloudSeedance2p0Ultra(state))) -} - -fn plan_ratio( - aspect_ratio: Option, - _strategy: RequestMismatchMitigationStrategy, -) -> Result, ArtcraftRouterError> { - match aspect_ratio { - None | Some(RouterAspectRatio::Auto) | Some(RouterAspectRatio::Auto2k) - | Some(RouterAspectRatio::Auto3k) | Some(RouterAspectRatio::Auto4k) => Ok(None), - Some(RouterAspectRatio::WideSixteenByNine) | Some(RouterAspectRatio::Wide) => Ok(Some(Seedance20Ratio::Landscape16x9)), - Some(RouterAspectRatio::TallNineBySixteen) | Some(RouterAspectRatio::Tall) => Ok(Some(Seedance20Ratio::Portrait9x16)), - Some(RouterAspectRatio::Square) | Some(RouterAspectRatio::SquareHd) => Ok(Some(Seedance20Ratio::Square)), - Some(RouterAspectRatio::WideFourByThree) => Ok(Some(Seedance20Ratio::Standard4x3)), - Some(RouterAspectRatio::TallThreeByFour) => Ok(Some(Seedance20Ratio::Portrait3x4)), - Some(RouterAspectRatio::WideTwentyOneByNine) | Some(RouterAspectRatio::TallNineByTwentyOne) => { - Ok(Some(Seedance20Ratio::UltraWide21x9)) - } - Some(RouterAspectRatio::WideThreeByTwo) | Some(RouterAspectRatio::WideFiveByFour) - | Some(RouterAspectRatio::TallFourByFive) | Some(RouterAspectRatio::TallTwoByThree) => { - Ok(Some(Seedance20Ratio::Adaptive)) - } - } -} - -fn plan_resolution( - resolution: Option, - _strategy: RequestMismatchMitigationStrategy, -) -> Result, ArtcraftRouterError> { - match resolution { - None => Ok(None), - Some(RouterResolution::FourEightyP) => Ok(Some(Seedance20Resolution::FourEightyP)), - Some(RouterResolution::SevenTwentyP) => Ok(Some(Seedance20Resolution::SevenTwentyP)), - Some(RouterResolution::TenEightyP) => Ok(Some(Seedance20Resolution::TenEightyP)), - Some(RouterResolution::HalfK) | Some(RouterResolution::OneK) => Ok(Some(Seedance20Resolution::FourEightyP)), - Some(RouterResolution::TwoK) | Some(RouterResolution::ThreeK) | Some(RouterResolution::FourK) => { - Ok(Some(Seedance20Resolution::TenEightyP)) - } - } -} - -fn resolve_url(image_ref: Option) -> Result, ArtcraftRouterError> { - match image_ref { - None => Ok(None), - Some(ImageRef::Url(url)) => Ok(Some(url)), - Some(ImageRef::MediaFileToken(_)) => { - Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { - field: "start_frame/end_frame", - value: "GmiCloud only supports image URLs, not media file tokens".to_string(), - })) - } - } -} - -fn resolve_url_list_from_images( - list_ref: Option, -) -> Result>, ArtcraftRouterError> { - match list_ref { - None => Ok(None), - Some(ImageListRef::Urls(urls)) => Ok(Some(urls)), - Some(ImageListRef::MediaFileTokens(_)) => { - Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { - field: "reference_images", - value: "GmiCloud only supports image URLs, not media file tokens".to_string(), - })) - } - } -} - -fn resolve_url_list_from_videos( - list_ref: Option, -) -> Result>, ArtcraftRouterError> { - match list_ref { - None => Ok(None), - Some(VideoListRef::Urls(urls)) => Ok(Some(urls)), - Some(VideoListRef::MediaFileTokens(_)) => { - Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { - field: "reference_videos", - value: "GmiCloud only supports video URLs, not media file tokens".to_string(), - })) - } - } -} - -#[cfg(test)] -mod tests { - use gmicloud_client::requests::api::video::seedance_2_0_260128::api::{ - Seedance20Ratio, Seedance20Resolution, - }; - use tokens::tokens::media_files::MediaFileToken; - - use crate::api::router_aspect_ratio::RouterAspectRatio; - use crate::api::router_resolution::RouterResolution; - use crate::api::router_video_model::RouterVideoModel; - use crate::api::image_list_ref::ImageListRef; - use crate::api::image_ref::ImageRef; - use crate::api::router_provider::RouterProvider; - use crate::api::video_list_ref::VideoListRef; - use crate::api::audio_list_ref::AudioListRef; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; - use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; - - use super::*; - - mod field_conversions { - use super::*; - - #[test] - fn prompt_passed_through() { - let req = unwrap_request(make_builder(|b| { b.prompt = Some("test prompt".to_string()); })); - assert_eq!(req.request.prompt, "test prompt"); - } - - #[test] - fn duration_passed_through() { - let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(10); })); - assert_eq!(req.request.duration, Some(10)); - } - - #[test] - fn duration_clamped_to_min() { - let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(1); })); - assert_eq!(req.request.duration, Some(4)); - } - - #[test] - fn duration_clamped_to_max() { - let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(99); })); - assert_eq!(req.request.duration, Some(15)); - } - - #[test] - fn duration_none_stays_none() { - let req = unwrap_request(make_builder(|b| { b.duration_seconds = None; })); - assert_eq!(req.request.duration, None); - } - } - - mod aspect_ratio_tests { - use super::*; - - #[test] - fn landscape_16x9() { - let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideSixteenByNine); })); - assert_eq!(req.request.ratio, Some(Seedance20Ratio::Landscape16x9)); - } - - #[test] - fn portrait_9x16() { - let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(RouterAspectRatio::TallNineBySixteen); })); - assert_eq!(req.request.ratio, Some(Seedance20Ratio::Portrait9x16)); - } - - #[test] - fn square() { - let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(RouterAspectRatio::Square); })); - assert_eq!(req.request.ratio, Some(Seedance20Ratio::Square)); - } - - #[test] - fn four_by_three() { - let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideFourByThree); })); - assert_eq!(req.request.ratio, Some(Seedance20Ratio::Standard4x3)); - } - - #[test] - fn unsupported_maps_to_adaptive() { - let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideThreeByTwo); })); - assert_eq!(req.request.ratio, Some(Seedance20Ratio::Adaptive)); - } - - #[test] - fn auto_maps_to_none() { - let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(RouterAspectRatio::Auto); })); - assert_eq!(req.request.ratio, None); - } - } - - mod resolution_tests { - use super::*; - - #[test] - fn res_480p() { - let req = unwrap_request(make_builder(|b| { b.resolution = Some(RouterResolution::FourEightyP); })); - assert_eq!(req.request.resolution, Some(Seedance20Resolution::FourEightyP)); - } - - #[test] - fn res_720p() { - let req = unwrap_request(make_builder(|b| { b.resolution = Some(RouterResolution::SevenTwentyP); })); - assert_eq!(req.request.resolution, Some(Seedance20Resolution::SevenTwentyP)); - } - - #[test] - fn res_1080p() { - let req = unwrap_request(make_builder(|b| { b.resolution = Some(RouterResolution::TenEightyP); })); - assert_eq!(req.request.resolution, Some(Seedance20Resolution::TenEightyP)); - } - - #[test] - fn none_stays_none() { - let req = unwrap_request(make_builder(|_| {})); - assert!(req.request.resolution.is_none()); - } - } - - mod media_url_tests { - use super::*; - - #[test] - fn start_frame_url_passed_through() { - let req = unwrap_request(make_builder(|b| { - b.start_frame = Some(ImageRef::Url("https://example.com/start.png".to_string())); - })); - assert_eq!(req.request.first_frame, Some("https://example.com/start.png".to_string())); - } - - #[test] - fn media_file_token_rejected_for_start_frame() { - let result = build_gmicloud_seedance_2p0_u(GenerateVideoRequestBuilder { - start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new("mf_test".to_string()))), - ..base_builder() - }); - assert!(result.is_err()); - } - - #[test] - fn reference_image_urls_passed_through() { - let urls = vec!["https://example.com/a.png".to_string(), "https://example.com/b.png".to_string()]; - let req = unwrap_request(make_builder(|b| { - b.reference_images = Some(ImageListRef::Urls(urls.clone())); - })); - assert_eq!(req.request.reference_images, Some(urls)); - } - - #[test] - fn reference_image_tokens_rejected() { - let result = build_gmicloud_seedance_2p0_u(GenerateVideoRequestBuilder { - reference_images: Some(ImageListRef::MediaFileTokens(vec![MediaFileToken::new("mf_a".to_string())])), - ..base_builder() - }); - assert!(result.is_err()); - } - - #[test] - fn reference_video_urls_passed_through() { - let urls = vec!["https://example.com/v.mp4".to_string()]; - let req = unwrap_request(make_builder(|b| { - b.reference_videos = Some(VideoListRef::Urls(urls.clone())); - })); - assert_eq!(req.request.reference_videos, Some(urls)); - } - - #[test] - fn reference_audio_urls_passed_through() { - let urls = vec!["https://example.com/a.wav".to_string()]; - let req = unwrap_request(make_builder(|b| { - b.reference_audio = Some(AudioListRef::Urls(urls.clone())); - })); - assert_eq!(req.request.reference_audios, Some(urls)); - } - } - - fn base_builder() -> GenerateVideoRequestBuilder { - GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0Ultra, - provider: RouterProvider::GmiCloud, - duration_seconds: Some(5), - video_batch_count: Some(1), - ..Default::default() - } - } - - fn make_builder(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder { - let mut builder = base_builder(); - f(&mut builder); - builder - } - - fn unwrap_request(builder: GenerateVideoRequestBuilder) -> GmiCloudSeedance2p0UltraRequestState { - let result = build_gmicloud_seedance_2p0_u(builder).expect("build should succeed"); - match result { - VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::GmiCloudSeedance2p0Ultra(state)) => state, - _ => panic!("expected GmiCloudSeedance2p0G request"), - } - } -} - -fn resolve_url_list_from_audios( - list_ref: Option, -) -> Result>, ArtcraftRouterError> { - match list_ref { - None => Ok(None), - Some(AudioListRef::Urls(urls)) => Ok(Some(urls)), - Some(AudioListRef::MediaFileTokens(_)) => { - Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { - field: "reference_audios", - value: "GmiCloud only supports audio URLs, not media file tokens".to_string(), - })) - } - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/cost.rs deleted file mode 100644 index d31e1c329f..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/cost.rs +++ /dev/null @@ -1,133 +0,0 @@ -use gmicloud_client::traits::gmicloud_request_cost_calculator_trait::GmiCloudRequestCostCalculator; - -use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; -use crate::generate::generate_video::providers::gmicloud::seedance_2p0_g::request::GmiCloudSeedance2p0UltraRequestState; - -pub struct GmiCloudSeedance2p0UltraCostState { - request: GmiCloudSeedance2p0UltraRequestState, -} - -impl GmiCloudSeedance2p0UltraCostState { - pub fn from_request(request: &GmiCloudSeedance2p0UltraRequestState) -> Self { - Self { request: request.clone() } - } - - pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { - let cost_in_usd_cents = self.request.request.calculate_cost_in_cents(); - - VideoGenerationCostEstimate { - cost_in_credits: None, - 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::router_resolution::RouterResolution; - use crate::api::router_video_model::RouterVideoModel; - use crate::api::router_provider::RouterProvider; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - - mod pricing_720p { - use super::*; - - #[test] - fn default_5s() { - // 720p: 5.2 ¢/s * 5 = 26¢ - assert_eq!(cost_cents(None, 5), 26); - } - - #[test] - fn default_10s() { - assert_eq!(cost_cents(None, 10), 52); - } - - #[test] - fn explicit_720p_5s() { - assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5), 26); - } - } - - mod pricing_480p { - use super::*; - - #[test] - fn p480_5s() { - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5), 12); - } - - #[test] - fn p480_10s() { - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 10), 24); - } - - #[test] - fn p480_15s() { - assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 15), 36); - } - } - - mod pricing_1080p { - use super::*; - - #[test] - fn p1080_5s() { - assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5), 58); - } - - #[test] - fn p1080_10s() { - assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 10), 116); - } - - #[test] - fn p1080_15s() { - assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 15), 174); - } - } - - mod relative_pricing { - use super::*; - - #[test] - fn higher_resolution_costs_more() { - let c480 = cost_cents(Some(RouterResolution::FourEightyP), 10); - let c720 = cost_cents(Some(RouterResolution::SevenTwentyP), 10); - let c1080 = cost_cents(Some(RouterResolution::TenEightyP), 10); - assert!(c480 < c720, "480p ({c480}) should be < 720p ({c720})"); - assert!(c720 < c1080, "720p ({c720}) should be < 1080p ({c1080})"); - } - - #[test] - fn longer_duration_costs_more() { - let c5 = cost_cents(None, 5); - let c10 = cost_cents(None, 10); - let c15 = cost_cents(None, 15); - assert!(c5 < c10); - assert!(c10 < c15); - } - } - - fn cost_cents(resolution: Option, duration_seconds: u16) -> u64 { - let builder = GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0Ultra, - provider: RouterProvider::GmiCloud, - resolution, - duration_seconds: Some(duration_seconds), - video_batch_count: Some(1), - ..Default::default() - }; - builder.build2() - .expect("build2 should succeed") - .estimate_cost() - .expect("estimate_cost should succeed") - .cost_in_usd_cents - .unwrap() - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/mod.rs deleted file mode 100644 index 98150bd014..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod build; -pub mod cost; -pub mod request; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/request.rs deleted file mode 100644 index f7224d7cb9..0000000000 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/gmicloud/seedance_2p0_g/request.rs +++ /dev/null @@ -1,98 +0,0 @@ -use gmicloud_client::requests::api::video::seedance_2_0_260128::api::Seedance20Request; - -use crate::client::router_gmicloud_client::RouterGmiCloudClient; -use crate::errors::artcraft_router_error::ArtcraftRouterError; -use crate::errors::provider_error::ProviderError; -use crate::generate::generate_video::generate_video_response::{ - GenerateVideoResponse, GmiCloudVideoResponsePayload, -}; - -#[derive(Clone, Debug)] -pub struct GmiCloudSeedance2p0UltraRequestState { - pub request: Seedance20Request, -} - -impl GmiCloudSeedance2p0UltraRequestState { - pub async fn send(&self, client: &RouterGmiCloudClient) -> Result { - let response = self.request.send_request(&client.api_key) - .await - .map_err(|err| ArtcraftRouterError::Provider(ProviderError::GmiCloud(err)))?; - - Ok(GenerateVideoResponse::GmiCloud(GmiCloudVideoResponsePayload { - request_id: response.request_id, - })) - } -} - -#[cfg(test)] -mod tests { - use crate::api::router_aspect_ratio::RouterAspectRatio; - use crate::api::router_resolution::RouterResolution; - use crate::api::router_video_model::RouterVideoModel; - use crate::api::image_ref::ImageRef; - use crate::api::router_provider::RouterProvider; - use crate::client::router_client::RouterClient; - use crate::client::router_gmicloud_client::RouterGmiCloudClient; - use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; - use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; - use gmicloud_client::creds::gmicloud_api_key::GmiCloudApiKey; - use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL; - - #[tokio::test] - #[ignore] // requires real API key, incurs costs - async fn test_text_to_video_720p() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("A corgi running through a field of wildflowers at sunset.".to_string()), - aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), - resolution: Some(RouterResolution::SevenTwentyP), - duration_seconds: Some(5), - ..gmicloud_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::GmiCloud(_))); - } - - #[tokio::test] - #[ignore] // requires real API key, incurs costs - async fn test_image_to_video_480p() { - let response = run_pipeline(GenerateVideoRequestBuilder { - prompt: Some("The dog starts running and splashing in the lake.".to_string()), - start_frame: Some(ImageRef::Url(JUNO_AT_LAKE_IMAGE_URL.to_string())), - resolution: Some(RouterResolution::FourEightyP), - duration_seconds: Some(5), - ..gmicloud_builder() - }).await; - assert!(matches!(response, GenerateVideoResponse::GmiCloud(_))); - } - - fn gmicloud_builder() -> GenerateVideoRequestBuilder { - GenerateVideoRequestBuilder { - model: RouterVideoModel::Seedance2p0Ultra, - provider: RouterProvider::GmiCloud, - video_batch_count: Some(1), - ..Default::default() - } - } - - fn get_gmicloud_client() -> RouterClient { - let secret = std::fs::read_to_string("/Users/bt/Artcraft/credentials/gmicloud_api_key.txt") - .expect("Failed to read GmiCloud API key"); - let api_key = GmiCloudApiKey::from_str(&secret); - RouterClient::GmiCloud(RouterGmiCloudClient::new(api_key)) - } - - async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse { - let client = get_gmicloud_client(); - let draft_or_request = builder.build2().expect("build2 should succeed"); - let request = match draft_or_request { - VideoGenerationDraftOrRequest::Request(r) => r, - _ => panic!("expected Request variant (GmiCloud skips draft)"), - }; - let response = request.send_request(&client).await.expect("send_request should succeed"); - match &response { - GenerateVideoResponse::GmiCloud(p) => println!("request_id={}", p.request_id), - other => println!("response: {:?}", other), - } - response - } -} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/mod.rs index d06c6c2d02..b77b7daae5 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/mod.rs @@ -1,5 +1,4 @@ pub mod artcraft; pub mod fal; -pub mod gmicloud; pub mod grok_api; pub mod kinovi; \ No newline at end of file diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_request.rs index 9443d5ebf6..eb8b02dfa7 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_request.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_request.rs @@ -41,10 +41,6 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0::cost::Ar use crate::generate::generate_video::providers::artcraft::seedance_2p0::request::ArtcraftSeedance2p0RequestState; use crate::generate::generate_video::providers::artcraft::seedance_2p0_fast::cost::ArtcraftSeedance2p0FastCostState; use crate::generate::generate_video::providers::artcraft::seedance_2p0_fast::request::ArtcraftSeedance2p0FastRequestState; -use crate::generate::generate_video::providers::artcraft::seedance_2p0_u::cost::ArtcraftSeedance2p0UltraCostState; -use crate::generate::generate_video::providers::artcraft::seedance_2p0_u::request::ArtcraftSeedance2p0UltraRequestState; -use crate::generate::generate_video::providers::artcraft::seedance_2p0_u_fast::cost::ArtcraftSeedance2p0UltraFastCostState; -use crate::generate::generate_video::providers::artcraft::seedance_2p0_u_fast::request::ArtcraftSeedance2p0UltraFastRequestState; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp::cost::ArtcraftSeedance2p0BytePlusCostState; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp::request::ArtcraftSeedance2p0BytePlusRequestState; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_fast::cost::ArtcraftSeedance2p0BytePlusFastCostState; @@ -129,10 +125,6 @@ use crate::generate::generate_video::providers::fal::vidu_q3::cost::FalViduQ3Cos use crate::generate::generate_video::providers::fal::vidu_q3::request::FalViduQ3RequestState; use crate::generate::generate_video::providers::fal::vidu_q3_turbo::cost::FalViduQ3TurboCostState; use crate::generate::generate_video::providers::fal::vidu_q3_turbo::request::FalViduQ3TurboRequestState; -use crate::generate::generate_video::providers::gmicloud::seedance_2p0_g::cost::GmiCloudSeedance2p0UltraCostState; -use crate::generate::generate_video::providers::gmicloud::seedance_2p0_g::request::GmiCloudSeedance2p0UltraRequestState; -use crate::generate::generate_video::providers::gmicloud::seedance_2p0_fast_g::cost::GmiCloudSeedance2p0UltraFastCostState; -use crate::generate::generate_video::providers::gmicloud::seedance_2p0_fast_g::request::GmiCloudSeedance2p0UltraFastRequestState; use crate::generate::generate_video::providers::grok_api::grok_imagine_video::cost::GrokApiGrokImagineVideoCostState; use crate::generate::generate_video::providers::grok_api::grok_imagine_video::request::GrokApiGrokImagineVideoRequestState; use crate::generate::generate_video::providers::grok_api::grok_imagine_video_1p5::cost::GrokApiGrokImagineVideo1p5CostState; @@ -171,8 +163,6 @@ pub enum VideoGenerationRequest { ArtcraftSeedance1p5Pro(ArtcraftSeedance1p5ProRequestState), ArtcraftSeedance2p0(ArtcraftSeedance2p0RequestState), ArtcraftSeedance2p0Fast(ArtcraftSeedance2p0FastRequestState), - ArtcraftSeedance2p0Ultra(ArtcraftSeedance2p0UltraRequestState), - ArtcraftSeedance2p0UltraFast(ArtcraftSeedance2p0UltraFastRequestState), ArtcraftSeedance2p0BytePlus(ArtcraftSeedance2p0BytePlusRequestState), ArtcraftSeedance2p0BytePlusFast(ArtcraftSeedance2p0BytePlusFastRequestState), ArtcraftSeedance2p0BytePlusUltra(ArtcraftSeedance2p0BytePlusUltraRequestState), @@ -215,8 +205,6 @@ pub enum VideoGenerationRequest { FalVeo3p1Lite(FalVeo3p1LiteRequestState), FalViduQ3(FalViduQ3RequestState), FalViduQ3Turbo(FalViduQ3TurboRequestState), - GmiCloudSeedance2p0Ultra(GmiCloudSeedance2p0UltraRequestState), - GmiCloudSeedance2p0UltraFast(GmiCloudSeedance2p0UltraFastRequestState), GrokApiGrokImagineVideo(GrokApiGrokImagineVideoRequestState), GrokApiGrokImagineVideo1p5(GrokApiGrokImagineVideo1p5RequestState), KinoviHappyHorse1p0(KinoviHappyHorse1p0RequestState), @@ -250,8 +238,6 @@ impl VideoGenerationRequest { Self::ArtcraftSeedance1p5Pro(_) => RouterProvider::Artcraft, Self::ArtcraftSeedance2p0(_) => RouterProvider::Artcraft, Self::ArtcraftSeedance2p0Fast(_) => RouterProvider::Artcraft, - Self::ArtcraftSeedance2p0Ultra(_) => RouterProvider::Artcraft, - Self::ArtcraftSeedance2p0UltraFast(_) => RouterProvider::Artcraft, Self::ArtcraftSeedance2p0BytePlus(_) => RouterProvider::Artcraft, Self::ArtcraftSeedance2p0BytePlusFast(_) => RouterProvider::Artcraft, Self::ArtcraftSeedance2p0BytePlusUltra(_) => RouterProvider::Artcraft, @@ -294,8 +280,6 @@ impl VideoGenerationRequest { Self::FalVeo3p1Lite(_) => RouterProvider::Fal, Self::FalViduQ3(_) => RouterProvider::Fal, Self::FalViduQ3Turbo(_) => RouterProvider::Fal, - Self::GmiCloudSeedance2p0Ultra(_) => RouterProvider::GmiCloud, - Self::GmiCloudSeedance2p0UltraFast(_) => RouterProvider::GmiCloud, Self::GrokApiGrokImagineVideo(_) => RouterProvider::GrokApi, Self::GrokApiGrokImagineVideo1p5(_) => RouterProvider::GrokApi, Self::KinoviHappyHorse1p0(_) => RouterProvider::KinoviWeb, @@ -329,8 +313,6 @@ impl VideoGenerationRequest { VideoGenerationRequest::ArtcraftSeedance1p5Pro(request) => Ok(ArtcraftSeedance1p5ProCostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSeedance2p0(request) => Ok(ArtcraftSeedance2p0CostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSeedance2p0Fast(request) => Ok(ArtcraftSeedance2p0FastCostState::from_request(request).estimate_cost()), - VideoGenerationRequest::ArtcraftSeedance2p0Ultra(request) => Ok(ArtcraftSeedance2p0UltraCostState::from_request(request).estimate_cost()), - VideoGenerationRequest::ArtcraftSeedance2p0UltraFast(request) => Ok(ArtcraftSeedance2p0UltraFastCostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSeedance2p0BytePlus(request) => Ok(ArtcraftSeedance2p0BytePlusCostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSeedance2p0BytePlusFast(request) => Ok(ArtcraftSeedance2p0BytePlusFastCostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltra(request) => Ok(ArtcraftSeedance2p0BytePlusUltraCostState::from_request(request).estimate_cost()), @@ -373,8 +355,6 @@ impl VideoGenerationRequest { VideoGenerationRequest::FalVeo3p1Lite(request) => Ok(FalVeo3p1LiteCostState::from_request(request).estimate_cost()), VideoGenerationRequest::FalViduQ3(request) => Ok(FalViduQ3CostState::from_request(request).estimate_cost()), VideoGenerationRequest::FalViduQ3Turbo(request) => Ok(FalViduQ3TurboCostState::from_request(request).estimate_cost()), - VideoGenerationRequest::GmiCloudSeedance2p0Ultra(request) => Ok(GmiCloudSeedance2p0UltraCostState::from_request(request).estimate_cost()), - VideoGenerationRequest::GmiCloudSeedance2p0UltraFast(request) => Ok(GmiCloudSeedance2p0UltraFastCostState::from_request(request).estimate_cost()), VideoGenerationRequest::GrokApiGrokImagineVideo(request) => Ok(GrokApiGrokImagineVideoCostState::from_request(request).estimate_cost()), VideoGenerationRequest::GrokApiGrokImagineVideo1p5(request) => GrokApiGrokImagineVideo1p5CostState::from_request(request).estimate_cost(), VideoGenerationRequest::KinoviHappyHorse1p0(request) => Ok(KinoviHappyHorse1p0CostState::from_request(request).estimate_cost()), @@ -466,14 +446,6 @@ impl VideoGenerationRequest { let client_ref = client.get_artcraft_client_ref()?; request.send(client_ref).await }, - VideoGenerationRequest::ArtcraftSeedance2p0Ultra(request) => { - let client_ref = client.get_artcraft_client_ref()?; - request.send(client_ref).await - }, - VideoGenerationRequest::ArtcraftSeedance2p0UltraFast(request) => { - let client_ref = client.get_artcraft_client_ref()?; - request.send(client_ref).await - }, VideoGenerationRequest::ArtcraftSeedance2p0BytePlus(request) => { let client_ref = client.get_artcraft_client_ref()?; request.send(client_ref).await @@ -642,14 +614,6 @@ impl VideoGenerationRequest { let client_ref = client.get_fal_client_ref()?; request.send(client_ref).await }, - VideoGenerationRequest::GmiCloudSeedance2p0Ultra(request) => { - let client_ref = client.get_gmicloud_client_ref()?; - request.send(client_ref).await - }, - VideoGenerationRequest::GmiCloudSeedance2p0UltraFast(request) => { - let client_ref = client.get_gmicloud_client_ref()?; - request.send(client_ref).await - }, VideoGenerationRequest::GrokApiGrokImagineVideo(request) => { let client_ref = client.get_grok_api_client_ref()?; request.send(client_ref).await diff --git a/crates/desktop/artcraft/src/core/commands/generate/common/router_video_request_to_artcraft_prompt.rs b/crates/desktop/artcraft/src/core/commands/generate/common/router_video_request_to_artcraft_prompt.rs index c76284e2fe..9420763e78 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/common/router_video_request_to_artcraft_prompt.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/common/router_video_request_to_artcraft_prompt.rs @@ -78,8 +78,6 @@ fn video_model_to_common_model_type(model: RouterVideoModel) -> Option Some(CommonModelType::ViduQ3Turbo), RouterVideoModel::Seedance2p0BytePlus => Some(CommonModelType::Seedance2p0BytePlus), RouterVideoModel::Seedance2p0BytePlusFast => Some(CommonModelType::Seedance2p0BytePlusFast), - RouterVideoModel::Seedance2p0Ultra => Some(CommonModelType::Seedance2p0Ultra), - RouterVideoModel::Seedance2p0UltraFast => Some(CommonModelType::Seedance2p0UltraFast), RouterVideoModel::Seedance2p0BytePlusUltra => Some(CommonModelType::Seedance2p0BytePlusUltra), RouterVideoModel::Seedance2p0BytePlusUltraFast => Some(CommonModelType::Seedance2p0BytePlusUltraFast), RouterVideoModel::Seedance2p0Mini => Some(CommonModelType::Seedance2p0Mini), @@ -91,6 +89,7 @@ fn video_model_to_common_model_type(model: RouterVideoModel) -> Option Some(CommonModelType::GrokImagineVideo1p5), RouterVideoModel::Seedance2p5Preview => Some(CommonModelType::Seedance2p5Preview), RouterVideoModel::Seedance2p5 => Some(CommonModelType::Seedance2p5), + RouterVideoModel::Seedance2p5Ultra => Some(CommonModelType::Seedance2p5Ultra), } } @@ -164,6 +163,7 @@ mod tests { duration_seconds: None, video_batch_count: None, generate_audio: None, + total_reference_video_input_seconds: None, request_mismatch_mitigation_strategy: artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy::ErrorOut, idempotency_token: None, diff --git a/crates/desktop/artcraft/src/core/commands/generate/models/video/response_types.rs b/crates/desktop/artcraft/src/core/commands/generate/models/video/response_types.rs index dc1b4902e7..82ac4f17da 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/models/video/response_types.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/models/video/response_types.rs @@ -177,8 +177,6 @@ pub enum ListVideoModelsVideoModel { Seedance2p0Fast, Seedance2p0BytePlus, Seedance2p0BytePlusFast, - Seedance2p0Ultra, - Seedance2p0UltraFast, Seedance2p0BytePlusUltra, Seedance2p0BytePlusUltraFast, Seedance2p0Mini, @@ -216,8 +214,6 @@ impl From for ListVideoModelsVideoModel { ApiClientVideoModel::Seedance2p0Fast => Self::Seedance2p0Fast, ApiClientVideoModel::Seedance2p0BytePlus => Self::Seedance2p0BytePlus, ApiClientVideoModel::Seedance2p0BytePlusFast => Self::Seedance2p0BytePlusFast, - ApiClientVideoModel::Seedance2p0Ultra => Self::Seedance2p0Ultra, - ApiClientVideoModel::Seedance2p0UltraFast => Self::Seedance2p0UltraFast, ApiClientVideoModel::Seedance2p0BytePlusUltra => Self::Seedance2p0BytePlusUltra, ApiClientVideoModel::Seedance2p0BytePlusUltraFast => Self::Seedance2p0BytePlusUltraFast, ApiClientVideoModel::Seedance2p0Mini => Self::Seedance2p0Mini, @@ -257,8 +253,6 @@ impl From for String { ListVideoModelsVideoModel::Seedance2p0Fast => "seedance_2p0_fast".to_string(), ListVideoModelsVideoModel::Seedance2p0BytePlus => "seedance_2p0_bp".to_string(), ListVideoModelsVideoModel::Seedance2p0BytePlusFast => "seedance_2p0_bp_fast".to_string(), - ListVideoModelsVideoModel::Seedance2p0Ultra => "seedance_2p0_u".to_string(), - ListVideoModelsVideoModel::Seedance2p0UltraFast => "seedance_2p0_u_fast".to_string(), ListVideoModelsVideoModel::Seedance2p0BytePlusUltra => "seedance_2p0_bpu".to_string(), ListVideoModelsVideoModel::Seedance2p0BytePlusUltraFast => "seedance_2p0_bpu_fast".to_string(), ListVideoModelsVideoModel::Seedance2p0Mini => "seedance_2p0_mini".to_string(), diff --git a/crates/service/web/storyteller_web/CLAUDE.md b/crates/service/web/storyteller_web/CLAUDE.md index 93bfbb00d5..00d6a27a2e 100644 --- a/crates/service/web/storyteller_web/CLAUDE.md +++ b/crates/service/web/storyteller_web/CLAUDE.md @@ -75,23 +75,23 @@ curl -s -c /tmp/cookies.txt -X POST http://localhost:12345/v1/login \ -H "Content-Type: application/json" \ -d '{"username_or_email":"newuser4","password":"newuser4"}' -# 2. Generate video (GmiCloud Seedance 2.0 Fast Global): +# 2. Generate video (Seedance 2.0 Fast): # Requires a UUID-format idempotency_token. curl -s -b /tmp/cookies.txt -X POST http://localhost:12345/v1/omni_gen/generate/video \ -H "Content-Type: application/json" \ -d '{ - "model": "seedance_2p0_fast_g", + "model": "seedance_2p0_fast", "prompt": "a corgi running through a field", "duration_seconds": 5, "aspect_ratio": "wide_sixteen_by_nine", "idempotency_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }' -# 3. Generate video (GmiCloud Seedance 2.0 Global, 720p): +# 3. Generate video (Seedance 2.0, 720p): curl -s -b /tmp/cookies.txt -X POST http://localhost:12345/v1/omni_gen/generate/video \ -H "Content-Type: application/json" \ -d '{ - "model": "seedance_2p0_g", + "model": "seedance_2p0", "prompt": "a corgi running through a field", "duration_seconds": 5, "resolution": "seven_twenty_p", diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/video/estimate_video_cost_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/video/estimate_video_cost_handler.rs index b5bcc21791..5f7dab1176 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/video/estimate_video_cost_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/video/estimate_video_cost_handler.rs @@ -147,8 +147,11 @@ fn map_video_model(model: CommonVideoModel) -> Result RouterVideoModel::Seedance1p5Pro, CommonVideoModel::Seedance2p0 => RouterVideoModel::Seedance2p0, CommonVideoModel::Seedance2p0Fast => RouterVideoModel::Seedance2p0Fast, - CommonVideoModel::Seedance2p0Ultra => RouterVideoModel::Seedance2p0Ultra, - CommonVideoModel::Seedance2p0UltraFast => RouterVideoModel::Seedance2p0UltraFast, + // Deprecated: these never launched and have no execution route. + CommonVideoModel::Seedance2p0Ultra + | CommonVideoModel::Seedance2p0UltraFast => { + return Err(HandlerError::InvalidInput(format!("Model is not supported: {:?}", model))); + } CommonVideoModel::Seedance2p0BytePlus=> RouterVideoModel::Seedance2p0BytePlus, CommonVideoModel::Seedance2p0BytePlusFast=> RouterVideoModel::Seedance2p0BytePlusFast, CommonVideoModel::Seedance2p0BytePlusUltra => RouterVideoModel::Seedance2p0BytePlusUltra, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_seedance_2p0_limits.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_seedance_2p0_limits.rs index ca5e87aec6..e1b0f418f9 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_seedance_2p0_limits.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/shared_utils/video/validate_seedance_2p0_limits.rs @@ -164,8 +164,6 @@ mod tests { #[test] fn ignores_over_limit_prompt_for_excluded_seedance_variants() { let excluded_models = [ - CommonVideoModel::Seedance2p0Ultra, - CommonVideoModel::Seedance2p0UltraFast, CommonVideoModel::Seedance2p0BytePlusUltra, CommonVideoModel::Seedance2p0BytePlusUltraFast, CommonVideoModel::Seedance2p0BytePlusUltraMini, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/pipeline_v2/run_pipeline_v2.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/pipeline_v2/run_pipeline_v2.rs index ac38cc1625..9072757119 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/pipeline_v2/run_pipeline_v2.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/pipeline_v2/run_pipeline_v2.rs @@ -83,8 +83,6 @@ pub async fn run_pipeline_v2(args: RunPipelineV2Args<'_>) -> Result RouterProvider::KinoviWeb, RouterVideoModel::Seedance2p5 => RouterProvider::KinoviWeb, RouterVideoModel::Seedance2p5Ultra => RouterProvider::KinoviWeb, - //RouterVideoModel::Seedance2p0Ultra => RouterProvider::GmiCloud, - //RouterVideoModel::Seedance2p0UltraFast => RouterProvider::GmiCloud, RouterVideoModel::GrokImagineVideo => RouterProvider::GrokApi, RouterVideoModel::GrokImagineVideo1p5 => RouterProvider::GrokApi, _ => RouterProvider::Fal, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/tests/seedance_2p0/seedance_2p0_fast_tests.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/tests/seedance_2p0/seedance_2p0_fast_tests.rs index 9146994e1c..ca8b844fab 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/tests/seedance_2p0/seedance_2p0_fast_tests.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/tests/seedance_2p0/seedance_2p0_fast_tests.rs @@ -1035,14 +1035,14 @@ mod seedance_2p0_bpu_fast { } } -// ── Seedance 2.0 Ultra Fast (GmiCloud, decommissioned) ── +// ── Seedance 2.0 Ultra Fast (deprecated, unroutable) ── mod seedance_2p0_u_fast { use super::*; - /// Seedance2p0UltraFast has no active execution route (its GmiCloud routing - /// is disabled in the pipeline). The request must fail cleanly BEFORE - /// billing. If the route is ever re-enabled, this pin fails and pricing - /// tests must be written for it. + /// Seedance2p0UltraFast is deprecated and has no execution route (its + /// GmiCloud routing was removed). The request must fail cleanly BEFORE + /// billing. If the model is ever revived, this pin fails and pricing tests + /// must be written for it. #[tokio::test] #[cfg_attr(feature = "skip_database_tests", ignore)] async fn seedance_2p0_ultra_fast_is_unroutable_and_charges_nothing() { @@ -1081,25 +1081,27 @@ mod premium { } } - /// Seedance2p0UltraFast is NOT priced higher than Fast: its legacy (GmiCloud) - /// rate card quotes BELOW the Fast card (720p 5s: 45 vs 64), it has no active - /// execution route, and production has zero jobs for it, ever. Pinned via the - /// cost endpoint (it cannot generate); if this fails, someone revived or - /// repriced the Ultra tier and real pricing tests are required. + /// Seedance2p0UltraFast is deprecated: its router model, rate card, and + /// GmiCloud routing were removed, so the cost endpoint must REJECT it while + /// still quoting Fast normally (720p 5s: 64). Production has zero jobs for + /// it, ever. If the rejection stops, someone revived the Ultra tier and + /// real pricing tests are required. #[tokio::test] #[cfg_attr(feature = "skip_database_tests", ignore)] - async fn seedance_2p0_ultra_fast_quotes_below_fast_on_its_legacy_rate_card() { + async fn seedance_2p0_ultra_fast_is_deprecated_and_cannot_be_quoted() { use actix_web::web::Json; - let fast_quote = quote_credits(CommonVideoModel::Seedance2p0Fast).await; - let ultra_fast_quote = quote_credits(CommonVideoModel::Seedance2p0UltraFast).await; + // 720p 5s: Fast 12.727 ¢/s → 64. + assert_eq!(quote_credits(CommonVideoModel::Seedance2p0Fast).await.expect("Fast quotes"), 64); - // 720p 5s: Fast 12.727 ¢/s → 64; UltraFast (legacy) 8.9089 ¢/s → 45. - assert_eq!(fast_quote, 64); - assert_eq!(ultra_fast_quote, 45); - assert_eq!(fast_quote - ultra_fast_quote, 19); + assert!( + quote_credits(CommonVideoModel::Seedance2p0UltraFast).await.is_err(), + "deprecated UltraFast must not quote", + ); - async fn quote_credits(model: CommonVideoModel) -> u64 { + async fn quote_credits( + model: CommonVideoModel, + ) -> Result { let mut request = crate::http_server::endpoints::omni_gen::generate::video::tests::support::base_generate_request(model); request.resolution = Some(CommonResolution::SevenTwentyP); request.duration_seconds = Some(5); @@ -1108,14 +1110,11 @@ mod premium { .uri("/v1/omni_gen/cost/video") .to_http_request(); // No ServerState: quotes come from the router alone (no DB, no network). - crate::http_server::endpoints::omni_gen::cost::video::omni_gen_video_cost_handler::omni_gen_video_cost_handler( + let response = crate::http_server::endpoints::omni_gen::cost::video::omni_gen_video_cost_handler::omni_gen_video_cost_handler( http_request, Json(request), None, ) - .await - .expect("cost quote should succeed") - .into_inner() - .cost_in_credits - .expect("quote should carry credits") + .await?; + Ok(response.into_inner().cost_in_credits.expect("quote should carry credits")) } } } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/tests/seedance_2p0/seedance_2p0_tests.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/tests/seedance_2p0/seedance_2p0_tests.rs index f1e9bc3384..eadac1b0f6 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/tests/seedance_2p0/seedance_2p0_tests.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/tests/seedance_2p0/seedance_2p0_tests.rs @@ -1058,12 +1058,12 @@ mod seedance_2p0_bpu { } } -// ── Seedance 2.0 Ultra (GmiCloud, decommissioned) ── +// ── Seedance 2.0 Ultra (deprecated, unroutable) ── mod seedance_2p0_u { use super::*; - /// Seedance2p0Ultra has no active execution route (its GmiCloud routing is - /// disabled in the pipeline). The request must fail cleanly BEFORE billing. + /// Seedance2p0Ultra is deprecated and has no execution route (its + /// GmiCloud routing was removed). The request must fail cleanly BEFORE billing. /// If the route is ever re-enabled, this pin fails and pricing tests must be /// written for it. #[tokio::test] diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/shared_utils/video/validate_seedance_2p0_limits.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/shared_utils/video/validate_seedance_2p0_limits.rs index 6fdcbca54d..d2da0c308f 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/shared_utils/video/validate_seedance_2p0_limits.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/shared_utils/video/validate_seedance_2p0_limits.rs @@ -150,8 +150,6 @@ mod tests { #[test] fn ignores_over_limit_prompt_for_excluded_seedance_variants() { let excluded_models = [ - CommonVideoModel::Seedance2p0Ultra, - CommonVideoModel::Seedance2p0UltraFast, CommonVideoModel::Seedance2p0BytePlusUltra, CommonVideoModel::Seedance2p0BytePlusUltraFast, CommonVideoModel::Seedance2p0BytePlusUltraMini, diff --git a/frontend/apps/video-info-website/src/lib/options.ts b/frontend/apps/video-info-website/src/lib/options.ts index 18806512d2..084623ad4a 100644 --- a/frontend/apps/video-info-website/src/lib/options.ts +++ b/frontend/apps/video-info-website/src/lib/options.ts @@ -23,8 +23,6 @@ export const MODEL_TYPE_GROUPS: SelectGroup[] = [ { value: 'seedance_2p0_fast', label: 'Seedance 2.0 Fast' }, { value: 'seedance_2p0_bp', label: 'Seedance 2.0 (BytePlus)' }, { value: 'seedance_2p0_bp_fast', label: 'Seedance 2.0 Fast (BytePlus)' }, - { value: 'seedance_2p0_u', label: 'Seedance 2.0 Ultra' }, - { value: 'seedance_2p0_u_fast', label: 'Seedance 2.0 Ultra Fast' }, { value: 'seedance_2p0_bpu', label: 'Seedance 2.0 Ultra (BytePlus)' }, { value: 'seedance_2p0_bpu_fast', label: 'Seedance 2.0 Ultra Fast (BytePlus)' }, ], diff --git a/frontend/libs/model-list/src/lib/classes/metadata/ModelMapping.ts b/frontend/libs/model-list/src/lib/classes/metadata/ModelMapping.ts index e2895f24fe..204a3154b0 100644 --- a/frontend/libs/model-list/src/lib/classes/metadata/ModelMapping.ts +++ b/frontend/libs/model-list/src/lib/classes/metadata/ModelMapping.ts @@ -90,8 +90,6 @@ export const getModelDisplayName = ( seedance_2p0_bp: "Seedance 2.0 Plus", seedance_2p0_bp_fast: "Seedance 2.0 Plus Fast", seedance_2p0_bp_mini: "Seedance 2.0 Plus Mini", - seedance_2p0_u: "Seedance 2.0 Ultra", - seedance_2p0_u_fast: "Seedance 2.0 Ultra Fast", seedance_2p0_bpu: "Seedance 2.0 Plus Ultra", seedance_2p0_bpu_fast: "Seedance 2.0 Plus Ultra Fast", seedance_2p0_bpu_mini: "Seedance 2.0 Plus Ultra Mini",