From dfe71177ce90175cefe68ecc22b041e015aff480 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 24 Jun 2026 17:17:20 -0400 Subject: [PATCH] pricing --- .../providers/artcraft/build_common.rs | 64 ---------------- .../generate_video/providers/artcraft/mod.rs | 1 + .../providers/artcraft/seedance_2p0/cost.rs | 4 +- .../artcraft/seedance_2p0_bp/cost.rs | 4 +- .../artcraft/seedance_2p0_bpu/cost.rs | 4 +- .../providers/artcraft/seedance_2p0_u/cost.rs | 7 +- .../providers/artcraft/seedance_common.rs | 75 +++++++++++++++++++ 7 files changed, 84 insertions(+), 75 deletions(-) create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_common.rs diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/build_common.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/build_common.rs index 0d25e6d7d2..b3a0c740f2 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/build_common.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/build_common.rs @@ -289,70 +289,6 @@ pub fn plan_duration( } } -// ── Seedance 2.0 4K pricing (5% margin over Kinovi) ── -// -// 4K is priced uniformly across the non-Fast Seedance 2.0 ArtCraft providers, -// off the Kinovi Seedance 2.0 4K cost (the `seedance2pro_client` source of -// truth) plus a 5% margin. Kinovi's 4K cost already includes the per-second -// video-reference surcharge, so both with- and without-reference are handled. - -/// Margin applied to Kinovi's Seedance 2.0 4K cost to set ArtCraft's user price. -const SEEDANCE_2P0_FOUR_K_MARGIN_MULTIPLIER: f64 = 1.05; - -/// Kinovi's cost (USD cents) for Seedance 2.0 at 4K — the source of truth. -/// `has_video_reference` toggles Kinovi's per-second video-reference surcharge. -pub fn kinovi_seedance_2p0_four_k_usd_cents( - duration_seconds: u16, - batch_count: u16, - has_video_reference: bool, -) -> u64 { - use seedance2pro_client::generate::video::generate_seedance_2p0::{ - GenerateSeedance2p0Request, KinoviSeedance2p0BatchCount, KinoviSeedance2p0OutputResolution, - }; - - let batch_count = match batch_count { - 2 => Some(KinoviSeedance2p0BatchCount::Two), - 4 => Some(KinoviSeedance2p0BatchCount::Four), - _ => Some(KinoviSeedance2p0BatchCount::One), - }; - let reference_video_urls = if has_video_reference { - // Presence drives the surcharge; the URL contents don't matter for pricing. - Some(vec!["pricing-placeholder".to_string()]) - } else { - None - }; - - GenerateSeedance2p0Request { - output_resolution: Some(KinoviSeedance2p0OutputResolution::FourK), - duration_seconds: duration_seconds as u8, - batch_count, - reference_video_urls, - prompt: String::new(), - aspect_ratio: None, - start_frame_url: None, - end_frame_url: None, - reference_image_urls: None, - reference_audio_urls: None, - character_ids: None, - use_face_blur_hack: None, - bitrate: None, - } - .calculate_costs() - .total_cost - .usd_cents_rounded_up -} - -/// ArtCraft's user-facing price (USD cents) for Seedance 2.0 at 4K: a 5% margin -/// over Kinovi's cost, rounded up so the margin never drops below 5%. -pub fn seedance_2p0_four_k_usd_cents( - duration_seconds: u16, - batch_count: u16, - has_video_reference: bool, -) -> u64 { - let kinovi_cents = kinovi_seedance_2p0_four_k_usd_cents(duration_seconds, batch_count, has_video_reference); - (kinovi_cents as f64 * SEEDANCE_2P0_FOUR_K_MARGIN_MULTIPLIER).ceil() as u64 -} - #[cfg(test)] mod tests { use super::*; 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 ed10944726..13aaf8b336 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 @@ -1,4 +1,5 @@ pub(crate) mod build_common; +pub(crate) mod seedance_common; #[cfg(test)] mod parity_tests; pub mod grok_imagine_video; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0/cost.rs index e400e0aa6d..c4f2d5d284 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0/cost.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0/cost.rs @@ -1,6 +1,6 @@ use enums::common::generation::common_resolution::CommonResolution; -use crate::generate::generate_video::providers::artcraft::build_common::seedance_2p0_four_k_usd_cents; +use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_four_k_usd_cents; use crate::generate::generate_video::providers::artcraft::seedance_2p0::request::ArtcraftSeedance2p0RequestState; use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; @@ -197,7 +197,7 @@ mod tests { 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), 1297); + assert_eq!(artcraft_4k_cents(15, 1, false), 1299); } #[test] diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp/cost.rs index 085f9a5ba6..73276fb4d6 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp/cost.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp/cost.rs @@ -1,6 +1,6 @@ use enums::common::generation::common_resolution::CommonResolution; -use crate::generate::generate_video::providers::artcraft::build_common::seedance_2p0_four_k_usd_cents; +use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_four_k_usd_cents; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp::request::ArtcraftSeedance2p0BytePlusRequestState; use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; @@ -167,7 +167,7 @@ mod tests { 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), 1297); + assert_eq!(artcraft_4k_cents(15, 1, false), 1299); } #[test] diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu/cost.rs index 373a44942d..a1a000d78a 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu/cost.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu/cost.rs @@ -1,6 +1,6 @@ use enums::common::generation::common_resolution::CommonResolution; -use crate::generate::generate_video::providers::artcraft::build_common::seedance_2p0_four_k_usd_cents; +use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_four_k_usd_cents; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu::request::ArtcraftSeedance2p0BytePlusUltraRequestState; use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; @@ -167,7 +167,7 @@ mod tests { 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), 1297); + assert_eq!(artcraft_4k_cents(15, 1, false), 1299); } #[test] 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 index b8ced24c68..6e83274dfe 100644 --- 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 @@ -1,6 +1,6 @@ use enums::common::generation::common_resolution::CommonResolution; -use crate::generate::generate_video::providers::artcraft::build_common::seedance_2p0_four_k_usd_cents; +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; @@ -31,9 +31,6 @@ impl ArtcraftSeedance2p0UltraCostState { } pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { - // 4K is priced at a 5% margin over Kinovi's Seedance 2.0 4K cost (which - // already includes the per-second video-reference surcharge). Other - // resolutions use this model's own per-second rates. if self.resolution == CommonResolution::FourK { let usd_cents = seedance_2p0_four_k_usd_cents( self.duration_seconds, @@ -167,7 +164,7 @@ mod tests { 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), 1297); + assert_eq!(artcraft_4k_cents(15, 1, false), 1299); } #[test] diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_common.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_common.rs new file mode 100644 index 0000000000..396ae2ded0 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_common.rs @@ -0,0 +1,75 @@ +//! Shared pricing for the Seedance 2.0 family of ArtCraft providers. +//! +//! ArtCraft credits equal USD cents (100 credits = $1.00). + +// 4K is priced uniformly across the non-Fast Seedance 2.0 models. Rates are held +// in hundredths of a USD cent per second so the math is exact integer arithmetic +// (no floating point), then rounded up to whole cents. + +/// 4K output price, in hundredths of a USD cent per second (86.60 ¢/s). +const FOUR_K_CENTI_CENTS_PER_SECOND: u64 = 8660; + +/// Extra hundredths of a USD cent per second when a reference video is attached +/// at 4K (17.20 ¢/s surcharge). +const FOUR_K_VIDEO_REFERENCE_SURCHARGE_CENTI_CENTS_PER_SECOND: u64 = 1720; + +/// ArtCraft's user-facing price (USD cents) for Seedance 2.0 at 4K. +/// +/// A reference video adds a per-second surcharge (4K with and without a video +/// reference are priced differently). The cost scales with duration and batch +/// count, rounded up to whole cents. +pub fn seedance_2p0_four_k_usd_cents( + duration_seconds: u16, + batch_count: u16, + has_video_reference: bool, +) -> u64 { + let mut centi_cents_per_second = FOUR_K_CENTI_CENTS_PER_SECOND; + if has_video_reference { + centi_cents_per_second += FOUR_K_VIDEO_REFERENCE_SURCHARGE_CENTI_CENTS_PER_SECOND; + } + + let total_centi_cents = + centi_cents_per_second * duration_seconds as u64 * batch_count as u64; + + // Round up to whole cents. + total_centi_cents.div_ceil(100) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn four_k_without_video_reference() { + assert_eq!(seedance_2p0_four_k_usd_cents(4, 1, false), 347); + assert_eq!(seedance_2p0_four_k_usd_cents(5, 1, false), 433); + assert_eq!(seedance_2p0_four_k_usd_cents(10, 1, false), 866); + assert_eq!(seedance_2p0_four_k_usd_cents(15, 1, false), 1299); + } + + #[test] + fn four_k_with_video_reference() { + assert_eq!(seedance_2p0_four_k_usd_cents(4, 1, true), 416); + assert_eq!(seedance_2p0_four_k_usd_cents(5, 1, true), 519); + assert_eq!(seedance_2p0_four_k_usd_cents(10, 1, true), 1038); + assert_eq!(seedance_2p0_four_k_usd_cents(15, 1, true), 1557); + } + + #[test] + fn video_reference_always_costs_more() { + for &duration in &[4u16, 5, 10, 15] { + assert!( + seedance_2p0_four_k_usd_cents(duration, 1, true) + > seedance_2p0_four_k_usd_cents(duration, 1, false), + "video reference should cost more at {duration}s", + ); + } + } + + #[test] + fn batch_count_multiplies() { + assert_eq!(seedance_2p0_four_k_usd_cents(5, 2, false), 866); + assert_eq!(seedance_2p0_four_k_usd_cents(5, 4, false), 1732); + assert_eq!(seedance_2p0_four_k_usd_cents(5, 2, true), 1038); + } +}