mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-25 11:19:29 -04:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfc36215cb | ||
|
|
da6a6c8441 | ||
|
|
5cacd89ab8 | ||
|
|
85e4c15598 | ||
|
|
a10b49b4c6 | ||
|
|
bc198875ef | ||
|
|
178e6de3c6 | ||
|
|
b65372beb7 | ||
|
|
5f04b6dbee | ||
|
|
9a57fa0c3a | ||
|
|
c96d979585 | ||
|
|
343c2a0e47 | ||
|
|
48d2092edb | ||
|
|
865fcb8b56 | ||
|
|
f563b06614 | ||
|
|
0a84a3ce80 |
+1
-1
@@ -9,4 +9,4 @@
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "03919ba8286d18980ecaa0038df76f43f5388db0d3838181fa8f15f4cbb932af"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,4 +9,4 @@
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "258ac40d3fab39f110e1e82d50e73542423bfe9a21f8127e6cd83214c1726e25"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -110,4 +110,4 @@
|
||||
]
|
||||
},
|
||||
"hash": "62b3e2e058331a6819acd98c634b3bf2d10aa38d43b49ab69fb8637497beec2f"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,4 +33,4 @@
|
||||
]
|
||||
},
|
||||
"hash": "6ca0fcdf495624cf347f612586868f6dea93f52d9f942a92dcd2b3e6aba7c63c"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,4 +9,4 @@
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "96599b4854248d84188b092db84edb50dc5402bb7771fd8ea96a96ad43e732c0"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -176,4 +176,4 @@
|
||||
]
|
||||
},
|
||||
"hash": "cd04f4f7b48ae9da53b6a210111f8e3edff94dee30263e22b45d5e29dbe9245a"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -99,4 +99,4 @@
|
||||
]
|
||||
},
|
||||
"hash": "cf2794c87868e77dfaa45bdbe630ae276dbb8c2d041442ba465af0b7a6d61af6"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,4 +9,4 @@
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "d88098343686ded52a71fc45a1b96d28ea3003975d6e7d20eee2022aa31d8189"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -121,4 +121,4 @@
|
||||
]
|
||||
},
|
||||
"hash": "f1d41ad17cb01f5aef0ce2eea042ccb3e5e649365a0befaf0f46492b75523442"
|
||||
}
|
||||
}
|
||||
|
||||
-8
@@ -25,8 +25,6 @@ pub enum ApiClientVideoModel {
|
||||
Seedance2p0Fast,
|
||||
Seedance2p0BytePlus,
|
||||
Seedance2p0BytePlusFast,
|
||||
Seedance2p0Ultra,
|
||||
Seedance2p0UltraFast,
|
||||
Seedance2p0BytePlusUltra,
|
||||
Seedance2p0BytePlusUltraFast,
|
||||
Seedance2p0Mini,
|
||||
@@ -65,8 +63,6 @@ impl From<String> 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<ApiClientVideoModel> 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"),
|
||||
|
||||
@@ -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,
|
||||
|
||||
+37
-15
@@ -22,14 +22,15 @@ use crate::generate::generate_image::generate_image_request_builder::GenerateIma
|
||||
/// Per-model build functions are expected to validate model-specific
|
||||
/// constraints (e.g. unsupported aspect ratios) BEFORE calling this helper.
|
||||
/// Validation that's identical across every Artcraft image model — batch count
|
||||
/// (1..=4) and image_inputs shape — lives here.
|
||||
/// (1..=[`max_batch_count`]) and image_inputs shape — lives here.
|
||||
pub fn build_artcraft_omni_image_request(
|
||||
builder: GenerateImageRequestBuilder,
|
||||
model: CommonImageModelEnum,
|
||||
) -> Result<OmniGenImageCostAndGenerateRequest, ArtcraftRouterError> {
|
||||
let strategy = builder.request_mismatch_mitigation_strategy;
|
||||
|
||||
let image_batch_count = plan_batch_count(builder.image_batch_count, strategy)?;
|
||||
let image_batch_count =
|
||||
plan_batch_count_with_max(builder.image_batch_count, strategy, max_batch_count(model))?;
|
||||
let image_media_tokens = resolve_image_list_ref(builder.image_inputs.clone())?;
|
||||
|
||||
let aspect_ratio = builder.aspect_ratio.map(to_aspect_ratio_enum);
|
||||
@@ -52,26 +53,47 @@ pub fn build_artcraft_omni_image_request(
|
||||
})
|
||||
}
|
||||
|
||||
/// The largest batch each Artcraft image model accepts. Seedream 5.0 Pro and
|
||||
/// Ultra accept batches of up to 8; every other model caps at 4.
|
||||
pub fn max_batch_count(model: CommonImageModelEnum) -> u16 {
|
||||
match model {
|
||||
CommonImageModelEnum::Seedream5p0Pro | CommonImageModelEnum::Seedream5p0ProUltra => 8,
|
||||
_ => 4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate batch count (1..=4). Mirrors the v1 plan-level validation that
|
||||
/// every Artcraft image model performs.
|
||||
pub fn plan_batch_count(
|
||||
image_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<u16, ArtcraftRouterError> {
|
||||
plan_batch_count_with_max(image_batch_count, strategy, 4)
|
||||
}
|
||||
|
||||
/// Validate batch count (1..=`max`), clamping or erroring per the mitigation
|
||||
/// strategy when the request exceeds the model's cap.
|
||||
pub fn plan_batch_count_with_max(
|
||||
image_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
max: u16,
|
||||
) -> Result<u16, ArtcraftRouterError> {
|
||||
let count = image_batch_count.unwrap_or(1);
|
||||
match count {
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1..=4 => Ok(count),
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(4),
|
||||
},
|
||||
if count == 0 {
|
||||
return Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations));
|
||||
}
|
||||
if count <= max {
|
||||
return Ok(count);
|
||||
}
|
||||
match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(max),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-9
@@ -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),
|
||||
|
||||
+4
-5
@@ -231,7 +231,7 @@ fn plan_output_resolution(
|
||||
}
|
||||
}
|
||||
|
||||
/// Batch counts: 1, 2, 4.
|
||||
/// Batch counts: 1 through 4.
|
||||
pub fn plan_batch_count(
|
||||
video_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
@@ -239,7 +239,8 @@ pub fn plan_batch_count(
|
||||
let count = video_batch_count.unwrap_or(1);
|
||||
match count {
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1 | 2 | 4 => Ok(count),
|
||||
1..=4 => Ok(count),
|
||||
// 5 and above:
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
@@ -248,9 +249,7 @@ pub fn plan_batch_count(
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade => Ok(4),
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade => {
|
||||
Ok(if count < 4 { 2 } else { 4 })
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(4),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -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;
|
||||
|
||||
+16
@@ -79,6 +79,12 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 15, 2), 750);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 3), 375);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 15, 3), 1125);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 4), 500);
|
||||
@@ -109,6 +115,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 2), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 3), 150);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 4), 200);
|
||||
@@ -133,6 +144,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 2), 500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 3), 750);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 4), 1000);
|
||||
|
||||
+11
@@ -76,6 +76,12 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 15, 2), 600);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 3), 300);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 15, 3), 900);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 4), 400);
|
||||
@@ -106,6 +112,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 2), 90);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 3), 135);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 4), 180);
|
||||
|
||||
+91
-5
@@ -65,7 +65,6 @@ impl ArtcraftSeedance2p0CostState {
|
||||
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),
|
||||
@@ -146,6 +145,13 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 15, 2), 480);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 4, 3), 192);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 3), 240);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 15, 3), 720);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 4, 4), 256);
|
||||
@@ -177,6 +183,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 2), 78);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 3), 117);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 4), 155);
|
||||
@@ -201,6 +212,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 2), 466);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 3), 699);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 4), 933);
|
||||
@@ -226,10 +242,10 @@ mod tests {
|
||||
|
||||
#[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);
|
||||
assert_eq!(artcraft_4k_cents(4, 1, false), 358);
|
||||
assert_eq!(artcraft_4k_cents(5, 1, false), 447);
|
||||
assert_eq!(artcraft_4k_cents(10, 1, false), 893);
|
||||
assert_eq!(artcraft_4k_cents(15, 1, false), 1340);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -464,4 +480,74 @@ mod tests {
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// ── Video reference deltas ──
|
||||
//
|
||||
// References price on the with-reference rate card; the delta must be positive at every resolution and duration.
|
||||
|
||||
mod video_reference_deltas {
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
use super::super::ArtcraftSeedance2p0CostState;
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_480p() {
|
||||
assert_ref_delta(CommonResolution::FourEightyP, 5, 39, 45, 6);
|
||||
assert_ref_delta(CommonResolution::FourEightyP, 10, 78, 89, 11);
|
||||
assert_ref_delta(CommonResolution::FourEightyP, 15, 117, 133, 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_720p() {
|
||||
assert_ref_delta(CommonResolution::SevenTwentyP, 5, 80, 111, 31);
|
||||
assert_ref_delta(CommonResolution::SevenTwentyP, 10, 160, 221, 61);
|
||||
assert_ref_delta(CommonResolution::SevenTwentyP, 15, 240, 331, 91);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_1080p() {
|
||||
assert_ref_delta(CommonResolution::TenEightyP, 5, 233, 256, 23);
|
||||
assert_ref_delta(CommonResolution::TenEightyP, 10, 466, 511, 45);
|
||||
assert_ref_delta(CommonResolution::TenEightyP, 15, 699, 767, 68);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_4k() {
|
||||
assert_ref_delta(CommonResolution::FourK, 5, 447, 569, 122);
|
||||
assert_ref_delta(CommonResolution::FourK, 10, 893, 1138, 245);
|
||||
assert_ref_delta(CommonResolution::FourK, 15, 1340, 1707, 367);
|
||||
}
|
||||
|
||||
/// Price the same generation with and without a reference video; pin
|
||||
/// both prices and the delta, in USD cents AND credits (credits equal
|
||||
/// cents, and both must show references costing more).
|
||||
fn assert_ref_delta(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
expected_no_ref: u64,
|
||||
expected_with_ref: u64,
|
||||
expected_delta: u64,
|
||||
) {
|
||||
let no_ref = ArtcraftSeedance2p0CostState {
|
||||
resolution, duration_seconds, batch_count: 1, has_video_reference: false,
|
||||
}.estimate_cost();
|
||||
let with_ref = ArtcraftSeedance2p0CostState {
|
||||
resolution, duration_seconds, batch_count: 1, has_video_reference: true,
|
||||
}.estimate_cost();
|
||||
|
||||
let no_ref_cents = no_ref.cost_in_usd_cents.unwrap();
|
||||
let with_ref_cents = with_ref.cost_in_usd_cents.unwrap();
|
||||
assert_eq!(no_ref_cents, expected_no_ref);
|
||||
assert_eq!(with_ref_cents, expected_with_ref);
|
||||
assert_eq!(with_ref_cents - no_ref_cents, expected_delta);
|
||||
assert!(with_ref_cents > no_ref_cents, "references must cost more");
|
||||
|
||||
let no_ref_credits = no_ref.cost_in_credits.unwrap();
|
||||
let with_ref_credits = with_ref.cost_in_credits.unwrap();
|
||||
assert_eq!(no_ref_credits, expected_no_ref);
|
||||
assert_eq!(with_ref_credits, expected_with_ref);
|
||||
assert_eq!(with_ref_credits - no_ref_credits, expected_delta);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+146
-14
@@ -1,6 +1,5 @@
|
||||
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_bp::request::ArtcraftSeedance2p0BytePlusRequestState;
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
|
||||
@@ -12,6 +11,24 @@ const CENTS_PER_SECOND_480P: f64 = 10.0;
|
||||
const CENTS_PER_SECOND_720P: f64 = 25.0;
|
||||
const CENTS_PER_SECOND_1080P: f64 = 50.0;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
/// reference videos are attached. Held as integer hundredths so the math is
|
||||
/// exact; rounded up to whole cents once, after multiplying by
|
||||
/// duration × batch.
|
||||
/// 480p: 10.20 ¢/s
|
||||
/// 720p: 25.70 ¢/s
|
||||
/// 1080p: 57.80 ¢/s
|
||||
/// 4K: 119.00 ¢/s
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P: u64 = 1_020;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P: u64 = 2_570;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_1080P: u64 = 5_780;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_4K: u64 = 11_900;
|
||||
|
||||
/// 4K output price with no reference videos, in hundredths of a USD cent
|
||||
/// per second (95.00 ¢/s), rounded up to whole cents after multiplying by
|
||||
/// duration × batch.
|
||||
const CENTI_CENTS_PER_SECOND_4K: u64 = 9_500;
|
||||
|
||||
pub struct ArtcraftSeedance2p0BytePlusCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
@@ -32,12 +49,24 @@ impl ArtcraftSeedance2p0BytePlusCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
if self.has_video_reference {
|
||||
let usd_cents = self.with_video_reference_usd_cents();
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
if self.resolution == CommonResolution::FourK {
|
||||
let usd_cents = seedance_2p0_four_k_usd_cents(
|
||||
self.duration_seconds,
|
||||
self.batch_count,
|
||||
self.has_video_reference,
|
||||
);
|
||||
let total_centi_cents = CENTI_CENTS_PER_SECOND_4K
|
||||
* self.duration_seconds as u64
|
||||
* self.batch_count as u64;
|
||||
let usd_cents = total_centi_cents.div_ceil(100);
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
@@ -67,6 +96,22 @@ impl ArtcraftSeedance2p0BytePlusCostState {
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Price when one or more reference videos are attached.
|
||||
fn with_video_reference_usd_cents(&self) -> u64 {
|
||||
let centi_cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P,
|
||||
CommonResolution::TenEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_1080P,
|
||||
CommonResolution::FourK => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_4K,
|
||||
// Everything else (including 720p) prices at 720p.
|
||||
_ => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P,
|
||||
};
|
||||
|
||||
let seconds = self.duration_seconds as u64 * self.batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
(centi_cents_per_second * seconds).div_ceil(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -92,6 +137,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 2), 250);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 3), 375);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 4), 500);
|
||||
@@ -119,6 +169,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 2), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 3), 150);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 4), 200);
|
||||
@@ -141,6 +196,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 2), 500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 3), 750);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 4), 1000);
|
||||
@@ -164,18 +224,20 @@ mod tests {
|
||||
|
||||
#[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);
|
||||
// 95.00 ¢/s card, rounded up to whole cents.
|
||||
assert_eq!(artcraft_4k_cents(4, 1, false), 380);
|
||||
assert_eq!(artcraft_4k_cents(5, 1, false), 475);
|
||||
assert_eq!(artcraft_4k_cents(10, 1, false), 950);
|
||||
assert_eq!(artcraft_4k_cents(15, 1, false), 1425);
|
||||
}
|
||||
|
||||
#[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);
|
||||
// 119.00 ¢/s with-reference card, rounded up to whole cents.
|
||||
assert_eq!(artcraft_4k_cents(4, 1, true), 476);
|
||||
assert_eq!(artcraft_4k_cents(5, 1, true), 595);
|
||||
assert_eq!(artcraft_4k_cents(10, 1, true), 1190);
|
||||
assert_eq!(artcraft_4k_cents(15, 1, true), 1785);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,4 +290,74 @@ mod tests {
|
||||
fn cost_cents(resolution: Option<RouterResolution>, duration_seconds: u16, video_batch_count: u16) -> u64 {
|
||||
build_cost(resolution, duration_seconds, video_batch_count).cost_in_usd_cents.unwrap()
|
||||
}
|
||||
|
||||
// ── Video reference deltas ──
|
||||
//
|
||||
// References price on the with-reference rate card (10.20/25.70/57.80/113.80 ¢/s); the delta must be positive at every resolution and duration.
|
||||
|
||||
mod video_reference_deltas {
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
use super::super::ArtcraftSeedance2p0BytePlusCostState;
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_480p() {
|
||||
assert_ref_delta(CommonResolution::FourEightyP, 5, 50, 51, 1);
|
||||
assert_ref_delta(CommonResolution::FourEightyP, 10, 100, 102, 2);
|
||||
assert_ref_delta(CommonResolution::FourEightyP, 15, 150, 153, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_720p() {
|
||||
assert_ref_delta(CommonResolution::SevenTwentyP, 5, 125, 129, 4);
|
||||
assert_ref_delta(CommonResolution::SevenTwentyP, 10, 250, 257, 7);
|
||||
assert_ref_delta(CommonResolution::SevenTwentyP, 15, 375, 386, 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_1080p() {
|
||||
assert_ref_delta(CommonResolution::TenEightyP, 5, 250, 289, 39);
|
||||
assert_ref_delta(CommonResolution::TenEightyP, 10, 500, 578, 78);
|
||||
assert_ref_delta(CommonResolution::TenEightyP, 15, 750, 867, 117);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_4k() {
|
||||
assert_ref_delta(CommonResolution::FourK, 5, 475, 595, 120);
|
||||
assert_ref_delta(CommonResolution::FourK, 10, 950, 1190, 240);
|
||||
assert_ref_delta(CommonResolution::FourK, 15, 1425, 1785, 360);
|
||||
}
|
||||
|
||||
/// Price the same generation with and without a reference video; pin
|
||||
/// both prices and the delta, in USD cents AND credits (credits equal
|
||||
/// cents, and both must show references costing more).
|
||||
fn assert_ref_delta(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
expected_no_ref: u64,
|
||||
expected_with_ref: u64,
|
||||
expected_delta: u64,
|
||||
) {
|
||||
let no_ref = ArtcraftSeedance2p0BytePlusCostState {
|
||||
resolution, duration_seconds, batch_count: 1, has_video_reference: false,
|
||||
}.estimate_cost();
|
||||
let with_ref = ArtcraftSeedance2p0BytePlusCostState {
|
||||
resolution, duration_seconds, batch_count: 1, has_video_reference: true,
|
||||
}.estimate_cost();
|
||||
|
||||
let no_ref_cents = no_ref.cost_in_usd_cents.unwrap();
|
||||
let with_ref_cents = with_ref.cost_in_usd_cents.unwrap();
|
||||
assert_eq!(no_ref_cents, expected_no_ref);
|
||||
assert_eq!(with_ref_cents, expected_with_ref);
|
||||
assert_eq!(with_ref_cents - no_ref_cents, expected_delta);
|
||||
assert!(with_ref_cents > no_ref_cents, "references must cost more");
|
||||
|
||||
let no_ref_credits = no_ref.cost_in_credits.unwrap();
|
||||
let with_ref_credits = with_ref.cost_in_credits.unwrap();
|
||||
assert_eq!(no_ref_credits, expected_no_ref);
|
||||
assert_eq!(with_ref_credits, expected_with_ref);
|
||||
assert_eq!(with_ref_credits - no_ref_credits, expected_delta);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+51
-1
@@ -9,10 +9,20 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_fast::
|
||||
const CENTS_PER_SECOND_480P: f64 = 9.0;
|
||||
const CENTS_PER_SECOND_720P: f64 = 20.0;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
/// reference videos are attached. Held as integer hundredths so the math is
|
||||
/// exact; rounded up to whole cents once, after multiplying by
|
||||
/// duration × batch.
|
||||
/// 480p: 9.65 ¢/s
|
||||
/// 720p: 20.50 ¢/s
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P: u64 = 965;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P: u64 = 2_050;
|
||||
|
||||
pub struct ArtcraftSeedance2p0BytePlusFastCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub batch_count: u16,
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p0BytePlusFastCostState {
|
||||
@@ -21,10 +31,26 @@ impl ArtcraftSeedance2p0BytePlusFastCostState {
|
||||
.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 }
|
||||
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.has_video_reference {
|
||||
let usd_cents = self.with_video_reference_usd_cents();
|
||||
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,
|
||||
_ => CENTS_PER_SECOND_720P,
|
||||
@@ -42,6 +68,20 @@ impl ArtcraftSeedance2p0BytePlusFastCostState {
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Price when one or more reference videos are attached.
|
||||
fn with_video_reference_usd_cents(&self) -> u64 {
|
||||
let centi_cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P,
|
||||
// Everything else (including 720p) prices at 720p.
|
||||
_ => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P,
|
||||
};
|
||||
|
||||
let seconds = self.duration_seconds as u64 * self.batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
(centi_cents_per_second * seconds).div_ceil(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -67,6 +107,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 2), 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 3), 300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 4), 400);
|
||||
@@ -94,6 +139,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 2), 90);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 3), 135);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 4), 180);
|
||||
|
||||
+33
-33
@@ -65,18 +65,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 14);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 50);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 15);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 36);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 54);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 63);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 23);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 45);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 68);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +85,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 88);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 131);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 37);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 46);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 91);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 137);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 53);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 105);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 158);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 55);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 110);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 165);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -112,31 +112,31 @@ mod tests {
|
||||
/// apply). Once, batch 8 executed in full but billed as batch 4.
|
||||
#[test]
|
||||
fn every_batch_size_720p_5s() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 88);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 131);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 175);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 219);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 262);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 306);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 349);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 46);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 91);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 137);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 182);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 228);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 273);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 319);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 364);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_batch_size_480p_5s() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 50);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 66);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 82);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 99);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 115);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 131);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 36);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 54);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 71);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 89);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 107);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 125);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 142);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_eight_with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 419);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 438);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+146
-14
@@ -1,6 +1,5 @@
|
||||
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_bpu::request::ArtcraftSeedance2p0BytePlusUltraRequestState;
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
|
||||
@@ -12,6 +11,24 @@ const CENTS_PER_SECOND_480P: f64 = 10.0;
|
||||
const CENTS_PER_SECOND_720P: f64 = 25.0;
|
||||
const CENTS_PER_SECOND_1080P: f64 = 50.0;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
/// reference videos are attached. Held as integer hundredths so the math is
|
||||
/// exact; rounded up to whole cents once, after multiplying by
|
||||
/// duration × batch.
|
||||
/// 480p: 10.20 ¢/s
|
||||
/// 720p: 25.70 ¢/s
|
||||
/// 1080p: 57.80 ¢/s
|
||||
/// 4K: 119.00 ¢/s
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P: u64 = 1_020;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P: u64 = 2_570;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_1080P: u64 = 5_780;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_4K: u64 = 11_900;
|
||||
|
||||
/// 4K output price with no reference videos, in hundredths of a USD cent
|
||||
/// per second (95.00 ¢/s), rounded up to whole cents after multiplying by
|
||||
/// duration × batch.
|
||||
const CENTI_CENTS_PER_SECOND_4K: u64 = 9_500;
|
||||
|
||||
pub struct ArtcraftSeedance2p0BytePlusUltraCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
@@ -32,12 +49,24 @@ impl ArtcraftSeedance2p0BytePlusUltraCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
if self.has_video_reference {
|
||||
let usd_cents = self.with_video_reference_usd_cents();
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
if self.resolution == CommonResolution::FourK {
|
||||
let usd_cents = seedance_2p0_four_k_usd_cents(
|
||||
self.duration_seconds,
|
||||
self.batch_count,
|
||||
self.has_video_reference,
|
||||
);
|
||||
let total_centi_cents = CENTI_CENTS_PER_SECOND_4K
|
||||
* self.duration_seconds as u64
|
||||
* self.batch_count as u64;
|
||||
let usd_cents = total_centi_cents.div_ceil(100);
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
@@ -67,6 +96,22 @@ impl ArtcraftSeedance2p0BytePlusUltraCostState {
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Price when one or more reference videos are attached.
|
||||
fn with_video_reference_usd_cents(&self) -> u64 {
|
||||
let centi_cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P,
|
||||
CommonResolution::TenEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_1080P,
|
||||
CommonResolution::FourK => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_4K,
|
||||
// Everything else (including 720p) prices at 720p.
|
||||
_ => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P,
|
||||
};
|
||||
|
||||
let seconds = self.duration_seconds as u64 * self.batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
(centi_cents_per_second * seconds).div_ceil(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -92,6 +137,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 2), 250);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 3), 375);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 4), 500);
|
||||
@@ -119,6 +169,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 2), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 3), 150);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 4), 200);
|
||||
@@ -141,6 +196,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 2), 500);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 3), 750);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::TenEightyP), 5, 4), 1000);
|
||||
@@ -164,18 +224,20 @@ mod tests {
|
||||
|
||||
#[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);
|
||||
// 95.00 ¢/s card, rounded up to whole cents.
|
||||
assert_eq!(artcraft_4k_cents(4, 1, false), 380);
|
||||
assert_eq!(artcraft_4k_cents(5, 1, false), 475);
|
||||
assert_eq!(artcraft_4k_cents(10, 1, false), 950);
|
||||
assert_eq!(artcraft_4k_cents(15, 1, false), 1425);
|
||||
}
|
||||
|
||||
#[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);
|
||||
// 119.00 ¢/s with-reference card, rounded up to whole cents.
|
||||
assert_eq!(artcraft_4k_cents(4, 1, true), 476);
|
||||
assert_eq!(artcraft_4k_cents(5, 1, true), 595);
|
||||
assert_eq!(artcraft_4k_cents(10, 1, true), 1190);
|
||||
assert_eq!(artcraft_4k_cents(15, 1, true), 1785);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,4 +290,74 @@ mod tests {
|
||||
fn cost_cents(resolution: Option<RouterResolution>, duration_seconds: u16, video_batch_count: u16) -> u64 {
|
||||
build_cost(resolution, duration_seconds, video_batch_count).cost_in_usd_cents.unwrap()
|
||||
}
|
||||
|
||||
// ── Video reference deltas ──
|
||||
//
|
||||
// References price on the with-reference rate card (10.20/25.70/57.80/113.80 ¢/s); the delta must be positive at every resolution and duration.
|
||||
|
||||
mod video_reference_deltas {
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
use super::super::ArtcraftSeedance2p0BytePlusUltraCostState;
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_480p() {
|
||||
assert_ref_delta(CommonResolution::FourEightyP, 5, 50, 51, 1);
|
||||
assert_ref_delta(CommonResolution::FourEightyP, 10, 100, 102, 2);
|
||||
assert_ref_delta(CommonResolution::FourEightyP, 15, 150, 153, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_720p() {
|
||||
assert_ref_delta(CommonResolution::SevenTwentyP, 5, 125, 129, 4);
|
||||
assert_ref_delta(CommonResolution::SevenTwentyP, 10, 250, 257, 7);
|
||||
assert_ref_delta(CommonResolution::SevenTwentyP, 15, 375, 386, 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_1080p() {
|
||||
assert_ref_delta(CommonResolution::TenEightyP, 5, 250, 289, 39);
|
||||
assert_ref_delta(CommonResolution::TenEightyP, 10, 500, 578, 78);
|
||||
assert_ref_delta(CommonResolution::TenEightyP, 15, 750, 867, 117);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refs_cost_more_at_4k() {
|
||||
assert_ref_delta(CommonResolution::FourK, 5, 475, 595, 120);
|
||||
assert_ref_delta(CommonResolution::FourK, 10, 950, 1190, 240);
|
||||
assert_ref_delta(CommonResolution::FourK, 15, 1425, 1785, 360);
|
||||
}
|
||||
|
||||
/// Price the same generation with and without a reference video; pin
|
||||
/// both prices and the delta, in USD cents AND credits (credits equal
|
||||
/// cents, and both must show references costing more).
|
||||
fn assert_ref_delta(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
expected_no_ref: u64,
|
||||
expected_with_ref: u64,
|
||||
expected_delta: u64,
|
||||
) {
|
||||
let no_ref = ArtcraftSeedance2p0BytePlusUltraCostState {
|
||||
resolution, duration_seconds, batch_count: 1, has_video_reference: false,
|
||||
}.estimate_cost();
|
||||
let with_ref = ArtcraftSeedance2p0BytePlusUltraCostState {
|
||||
resolution, duration_seconds, batch_count: 1, has_video_reference: true,
|
||||
}.estimate_cost();
|
||||
|
||||
let no_ref_cents = no_ref.cost_in_usd_cents.unwrap();
|
||||
let with_ref_cents = with_ref.cost_in_usd_cents.unwrap();
|
||||
assert_eq!(no_ref_cents, expected_no_ref);
|
||||
assert_eq!(with_ref_cents, expected_with_ref);
|
||||
assert_eq!(with_ref_cents - no_ref_cents, expected_delta);
|
||||
assert!(with_ref_cents > no_ref_cents, "references must cost more");
|
||||
|
||||
let no_ref_credits = no_ref.cost_in_credits.unwrap();
|
||||
let with_ref_credits = with_ref.cost_in_credits.unwrap();
|
||||
assert_eq!(no_ref_credits, expected_no_ref);
|
||||
assert_eq!(with_ref_credits, expected_with_ref);
|
||||
assert_eq!(with_ref_credits - no_ref_credits, expected_delta);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+51
-1
@@ -9,10 +9,20 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_fast:
|
||||
const CENTS_PER_SECOND_480P: f64 = 9.0;
|
||||
const CENTS_PER_SECOND_720P: f64 = 20.0;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
/// reference videos are attached. Held as integer hundredths so the math is
|
||||
/// exact; rounded up to whole cents once, after multiplying by
|
||||
/// duration × batch.
|
||||
/// 480p: 9.65 ¢/s
|
||||
/// 720p: 20.50 ¢/s
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P: u64 = 965;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P: u64 = 2_050;
|
||||
|
||||
pub struct ArtcraftSeedance2p0BytePlusUltraFastCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub batch_count: u16,
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p0BytePlusUltraFastCostState {
|
||||
@@ -21,10 +31,26 @@ impl ArtcraftSeedance2p0BytePlusUltraFastCostState {
|
||||
.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 }
|
||||
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.has_video_reference {
|
||||
let usd_cents = self.with_video_reference_usd_cents();
|
||||
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,
|
||||
_ => CENTS_PER_SECOND_720P,
|
||||
@@ -42,6 +68,20 @@ impl ArtcraftSeedance2p0BytePlusUltraFastCostState {
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Price when one or more reference videos are attached.
|
||||
fn with_video_reference_usd_cents(&self) -> u64 {
|
||||
let centi_cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P,
|
||||
// Everything else (including 720p) prices at 720p.
|
||||
_ => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P,
|
||||
};
|
||||
|
||||
let seconds = self.duration_seconds as u64 * self.batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
(centi_cents_per_second * seconds).div_ceil(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -67,6 +107,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 2), 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 3), 300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 4), 400);
|
||||
@@ -94,6 +139,11 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 2), 90);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 3), 135);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 4), 180);
|
||||
|
||||
+33
-33
@@ -65,18 +65,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 14);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 50);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 15);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 36);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 54);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 63);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 23);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 45);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 68);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +85,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 88);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 131);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 37);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 46);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 91);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 137);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 53);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 105);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 158);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 55);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 110);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 165);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -112,31 +112,31 @@ mod tests {
|
||||
/// apply). Once, batch 8 executed in full but billed as batch 4.
|
||||
#[test]
|
||||
fn every_batch_size_720p_5s() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 88);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 131);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 175);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 219);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 262);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 306);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 349);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 46);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 91);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 137);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 182);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 228);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 273);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 319);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 364);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_batch_size_480p_5s() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 50);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 66);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 82);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 99);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 115);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 131);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 36);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 54);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 71);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 89);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 107);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 125);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 142);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_eight_with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 419);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 438);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+19
-7
@@ -13,7 +13,7 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0_fast::req
|
||||
///
|
||||
/// Kept as f64 because per-second rates are fractional; rounding happens
|
||||
/// once at the end after multiplying by duration * batch.
|
||||
const CENTS_PER_SECOND_480P: f64 = 5.181;
|
||||
const CENTS_PER_SECOND_480P: f64 = 6.40;
|
||||
const CENTS_PER_SECOND_720P: f64 = 12.727;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
@@ -122,6 +122,13 @@ mod tests {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 15, 2), 382);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 4, 3), 153);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 5, 3), 191);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 15, 3), 573);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::SevenTwentyP), 4, 4), 204);
|
||||
@@ -142,20 +149,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_1() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 4, 1), 21);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 1), 26);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 10, 1), 52);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 15, 1), 78);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 4, 1), 26);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 1), 32);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 10, 1), 64);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 15, 1), 96);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 2), 52);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 2), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 3), 96);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4() {
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 4), 104);
|
||||
assert_eq!(cost_cents(Some(RouterResolution::FourEightyP), 5, 4), 128);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+33
-33
@@ -65,18 +65,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 13);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 49);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 14);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 52);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 62);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 22);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 44);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 66);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +85,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 87);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 130);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 36);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 45);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 89);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 134);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 52);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 104);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 156);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 43);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 54);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 108);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 161);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -112,31 +112,31 @@ mod tests {
|
||||
/// apply). Once, batch 8 executed in full but billed as batch 4.
|
||||
#[test]
|
||||
fn every_batch_size_720p_5s() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 87);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 130);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 173);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 217);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 260);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 303);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 346);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 45);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 89);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 134);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 178);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 223);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 267);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 312);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 356);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_batch_size_480p_5s() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 49);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 65);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 82);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 98);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 114);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 130);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 35);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 52);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 69);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 87);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 104);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 121);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 138);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_eight_with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 415);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 429);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
-189
@@ -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<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
-237
@@ -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<RouterResolution>,
|
||||
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<RouterResolution>, duration_seconds: u16, batch: u16) -> u64 {
|
||||
build_cost(resolution, duration_seconds, batch).cost_in_usd_cents.unwrap()
|
||||
}
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
-237
@@ -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<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
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
|
||||
}
|
||||
}
|
||||
-253
@@ -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<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
-168
@@ -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<RouterResolution>,
|
||||
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<RouterResolution>, duration_seconds: u16, batch: u16) -> u64 {
|
||||
build_cost(resolution, duration_seconds, batch).cost_in_usd_cents.unwrap()
|
||||
}
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
-227
@@ -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<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
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
|
||||
}
|
||||
}
|
||||
+13
-10
@@ -13,7 +13,7 @@ pub struct ArtcraftSeedance2p5CostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub has_video_references: bool,
|
||||
pub total_input_seconds: u16,
|
||||
pub maybe_total_input_seconds: Option<u16>,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p5CostState {
|
||||
@@ -30,20 +30,21 @@ impl ArtcraftSeedance2p5CostState {
|
||||
resolution,
|
||||
duration_seconds,
|
||||
has_video_references,
|
||||
total_input_seconds: request.total_input_seconds.unwrap_or(0),
|
||||
maybe_total_input_seconds: request.total_input_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Input-second clamping (the 4..=30 billing range) happens inside the
|
||||
// shared pricing function.
|
||||
let total_input_seconds = self.total_input_seconds;
|
||||
// Input-second clamping (the 4..=30 billing range) and the
|
||||
// unknown-or-zero-input failsafe happen inside the shared pricing
|
||||
// function.
|
||||
let maybe_total_input_seconds = self.maybe_total_input_seconds;
|
||||
|
||||
let usd_cents = seedance_2p5_usd_cents(
|
||||
self.resolution,
|
||||
self.duration_seconds,
|
||||
self.has_video_references,
|
||||
total_input_seconds,
|
||||
maybe_total_input_seconds,
|
||||
);
|
||||
|
||||
// ArtCraft credits: 100 credits = $1.00, so credits = cents.
|
||||
@@ -115,10 +116,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_input_seconds_bill_the_four_second_minimum() {
|
||||
// No measured input still bills the 4-second total minimum:
|
||||
// 7.24279835 ¢/s × (10+4) = 101.40 → 102¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 102);
|
||||
fn missing_or_zero_input_seconds_bill_the_thirty_second_maximum() {
|
||||
// FAILSAFE: an unmeasured (or zero) input bills the 30-second MAXIMUM,
|
||||
// matching the provider client's fallback:
|
||||
// 7.24279835 ¢/s × (10+30) = 289.71 → 290¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 290);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, Some(0)), 290);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+13
-10
@@ -14,7 +14,7 @@ pub struct ArtcraftSeedance2p5UltraCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub has_video_references: bool,
|
||||
pub total_input_seconds: u16,
|
||||
pub maybe_total_input_seconds: Option<u16>,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p5UltraCostState {
|
||||
@@ -31,20 +31,21 @@ impl ArtcraftSeedance2p5UltraCostState {
|
||||
resolution,
|
||||
duration_seconds,
|
||||
has_video_references,
|
||||
total_input_seconds: request.total_input_seconds.unwrap_or(0),
|
||||
maybe_total_input_seconds: request.total_input_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Input-second clamping (the 4..=30 billing range) happens inside the
|
||||
// shared pricing function.
|
||||
let total_input_seconds = self.total_input_seconds;
|
||||
// Input-second clamping (the 4..=30 billing range) and the
|
||||
// unknown-or-zero-input failsafe happen inside the shared pricing
|
||||
// function.
|
||||
let maybe_total_input_seconds = self.maybe_total_input_seconds;
|
||||
|
||||
let usd_cents = seedance_2p5_ultra_usd_cents(
|
||||
self.resolution,
|
||||
self.duration_seconds,
|
||||
self.has_video_references,
|
||||
total_input_seconds,
|
||||
maybe_total_input_seconds,
|
||||
);
|
||||
|
||||
// ArtCraft credits: 100 credits = $1.00, so credits = cents.
|
||||
@@ -133,10 +134,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_input_seconds_bill_the_four_second_minimum() {
|
||||
// No measured input still bills the 4-second total minimum:
|
||||
// 8.55967078 ¢/s × (10+4) = 119.84 → 120¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 120);
|
||||
fn missing_or_zero_input_seconds_bill_the_thirty_second_maximum() {
|
||||
// FAILSAFE: an unmeasured (or zero) input bills the 30-second MAXIMUM,
|
||||
// matching the provider client's fallback:
|
||||
// 8.55967078 ¢/s × (10+30) = 342.39 → 343¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 343);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, Some(0)), 343);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+86
-97
@@ -11,30 +11,19 @@ use kinovi_web_client::generate::video::generate_seedance_2p5::{
|
||||
// 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;
|
||||
/// 4K output price, in hundredths of a USD cent per second (89.30 ¢/s).
|
||||
const FOUR_K_CENTI_CENTS_PER_SECOND: u64 = 8930;
|
||||
|
||||
/// 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 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.
|
||||
/// ArtCraft's price (USD cents) for Seedance 2.0 at 4K with no reference
|
||||
/// videos attached. Each model prices reference videos through its own
|
||||
/// with-reference rate card. 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;
|
||||
FOUR_K_CENTI_CENTS_PER_SECOND * duration_seconds as u64 * batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
total_centi_cents.div_ceil(100)
|
||||
@@ -51,22 +40,22 @@ pub fn seedance_2p0_four_k_usd_cents(
|
||||
// rate sets and helpers below.
|
||||
|
||||
/// Regular Mini — 480p price, USD cents per second.
|
||||
const MINI_CENTS_PER_SECOND_480P: f64 = 3.24074074;
|
||||
const MINI_CENTS_PER_SECOND_480P: f64 = 3.45;
|
||||
/// Regular Mini — 480p reference-video surcharge, USD cents per second.
|
||||
const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.86419753;
|
||||
const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.90;
|
||||
/// Regular Mini — 720p price, USD cents per second.
|
||||
const MINI_CENTS_PER_SECOND_720P: f64 = 8.64197531;
|
||||
const MINI_CENTS_PER_SECOND_720P: f64 = 8.90;
|
||||
/// Regular Mini — 720p reference-video surcharge, USD cents per second.
|
||||
const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.72839506;
|
||||
const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.80;
|
||||
|
||||
/// BytePlus / BytePlus Ultra Mini — 480p price, USD cents per second.
|
||||
const BYTEPLUS_MINI_CENTS_PER_SECOND_480P: f64 = 3.27160494;
|
||||
const BYTEPLUS_MINI_CENTS_PER_SECOND_480P: f64 = 3.55;
|
||||
/// BytePlus / BytePlus Ultra Mini — 480p reference-video surcharge, USD cents per second.
|
||||
const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.87242798;
|
||||
const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.95;
|
||||
/// BytePlus / BytePlus Ultra Mini — 720p price, USD cents per second.
|
||||
const BYTEPLUS_MINI_CENTS_PER_SECOND_720P: f64 = 8.72427984;
|
||||
const BYTEPLUS_MINI_CENTS_PER_SECOND_720P: f64 = 9.10;
|
||||
/// BytePlus / BytePlus Ultra Mini — 720p reference-video surcharge, USD cents per second.
|
||||
const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.74485597;
|
||||
const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.85;
|
||||
|
||||
/// ArtCraft's price (USD cents) for the regular Seedance 2.0 Mini.
|
||||
///
|
||||
@@ -165,7 +154,7 @@ pub fn seedance_2p5_usd_cents(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
has_video_references: bool,
|
||||
total_input_seconds: u16,
|
||||
maybe_total_input_seconds: Option<u16>,
|
||||
) -> u64 {
|
||||
let (cents_per_second, billed_seconds) = if has_video_references {
|
||||
let rate = match resolution {
|
||||
@@ -175,8 +164,18 @@ pub fn seedance_2p5_usd_cents(
|
||||
};
|
||||
// The TOTAL input duration clamps to the 4..=30 second billing range
|
||||
// (three 1s videos sum to 3 and bill 4; three 3s videos bill 9).
|
||||
let billed_input_seconds = total_input_seconds
|
||||
.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS));
|
||||
//
|
||||
// FAILSAFE: an unknown (never probed) or zero input duration bills the
|
||||
// 30-second MAXIMUM, matching the provider client's own fallback. It
|
||||
// must never default toward the minimum — billing 4 input seconds for
|
||||
// an unmeasured input while the provider assumes 30 sells input seconds
|
||||
// far below cost.
|
||||
let billed_input_seconds = match maybe_total_input_seconds {
|
||||
None | Some(0) => u16::from(MAX_BILLED_INPUT_SECONDS),
|
||||
Some(seconds) => {
|
||||
seconds.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS))
|
||||
}
|
||||
};
|
||||
(rate, u64::from(duration_seconds) + u64::from(billed_input_seconds))
|
||||
} else {
|
||||
let rate = match resolution {
|
||||
@@ -209,7 +208,7 @@ pub fn seedance_2p5_ultra_usd_cents(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
has_video_references: bool,
|
||||
total_input_seconds: u16,
|
||||
maybe_total_input_seconds: Option<u16>,
|
||||
) -> u64 {
|
||||
let (cents_per_second, billed_seconds) = if has_video_references {
|
||||
let rate = match resolution {
|
||||
@@ -219,8 +218,18 @@ pub fn seedance_2p5_ultra_usd_cents(
|
||||
};
|
||||
// The TOTAL input duration clamps to the 4..=30 second billing range
|
||||
// (three 1s videos sum to 3 and bill 4; three 3s videos bill 9).
|
||||
let billed_input_seconds = total_input_seconds
|
||||
.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS));
|
||||
//
|
||||
// FAILSAFE: an unknown (never probed) or zero input duration bills the
|
||||
// 30-second MAXIMUM, matching the provider client's own fallback. It
|
||||
// must never default toward the minimum — billing 4 input seconds for
|
||||
// an unmeasured input while the provider assumes 30 sells input seconds
|
||||
// far below cost.
|
||||
let billed_input_seconds = match maybe_total_input_seconds {
|
||||
None | Some(0) => u16::from(MAX_BILLED_INPUT_SECONDS),
|
||||
Some(seconds) => {
|
||||
seconds.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS))
|
||||
}
|
||||
};
|
||||
(rate, u64::from(duration_seconds) + u64::from(billed_input_seconds))
|
||||
} else {
|
||||
let rate = match resolution {
|
||||
@@ -263,37 +272,17 @@ 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",
|
||||
);
|
||||
}
|
||||
fn four_k_base_prices() {
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(4, 1), 358);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 1), 447);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(10, 1), 893);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(15, 1), 1340);
|
||||
}
|
||||
|
||||
#[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);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 2), 893);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 4), 1786);
|
||||
}
|
||||
|
||||
// ── Seedance 2.0 Mini ──
|
||||
@@ -310,34 +299,34 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 13);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 33);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 49);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 14);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 18);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 35);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 52);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 21);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 42);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 62);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 18);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 22);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 44);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 66);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 35);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 44);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 87);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 130);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 36);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 45);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 89);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 134);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 42);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 52);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 104);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 156);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 43);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 54);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 108);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 161);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -364,9 +353,9 @@ mod tests {
|
||||
fn batch_count_scales_the_total() {
|
||||
// Batch is baked in before the single round-up, so batched totals can be
|
||||
// a cent under N× the single price.
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 87);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 173);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 33);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 89);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 178);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 35);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,34 +373,34 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 14);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 33);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 50);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 15);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 18);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 36);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 54);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 21);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 42);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 63);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 18);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 23);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 45);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 68);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 35);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 44);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 88);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 131);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 37);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 46);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 91);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 137);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 42);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 53);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 105);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 158);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 44);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 55);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 110);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 165);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -439,9 +428,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_count_scales_the_total() {
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 88);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 175);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 33);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 91);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 182);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 36);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -11,8 +11,8 @@ impl ArtcraftVeo3CostState {
|
||||
pub fn from_request(request: &ArtcraftVeo3RequestState) -> Self {
|
||||
Self {
|
||||
duration_seconds: duration_seconds_for_cost(request.request.duration_seconds),
|
||||
// v1 legacy Veo 3 handler defaults generate_audio to false.
|
||||
generate_audio: request.request.generate_audio.unwrap_or(false),
|
||||
// Unset defaults to audio on.
|
||||
generate_audio: request.request.generate_audio.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_default_is_off() {
|
||||
// None → audio defaults to false.
|
||||
assert_eq!(cost_cents(Some(8), None), cost_cents(Some(8), Some(false)));
|
||||
fn audio_default_is_on() {
|
||||
assert_eq!(cost_cents(Some(8), None), cost_cents(Some(8), Some(true)));
|
||||
}
|
||||
}
|
||||
|
||||
+46
-14
@@ -1,22 +1,32 @@
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video::providers::artcraft::veo_3_fast::request::ArtcraftVeo3FastRequestState;
|
||||
|
||||
/// Per-second rates in hundredths of a US cent.
|
||||
const CENTI_CENTS_PER_SECOND_AUDIO_OFF: u64 = 1_150;
|
||||
const CENTI_CENTS_PER_SECOND_AUDIO_ON: u64 = 1_725;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftVeo3FastCostState {
|
||||
pub duration_seconds: u64,
|
||||
pub generate_audio: bool,
|
||||
}
|
||||
|
||||
impl ArtcraftVeo3FastCostState {
|
||||
pub fn from_request(request: &ArtcraftVeo3FastRequestState) -> Self {
|
||||
Self {
|
||||
// v1 legacy Veo 3 Fast handler defaults generate_audio to false.
|
||||
generate_audio: request.request.generate_audio.unwrap_or(false),
|
||||
duration_seconds: duration_seconds_for_cost(request.request.duration_seconds),
|
||||
// Unset defaults to audio on.
|
||||
generate_audio: request.request.generate_audio.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Flat per-video price: 88¢ audio off, 132¢ audio on.
|
||||
let cost_in_usd_cents: u64 = if self.generate_audio { 132 } else { 88 };
|
||||
let rate = if self.generate_audio {
|
||||
CENTI_CENTS_PER_SECOND_AUDIO_ON
|
||||
} else {
|
||||
CENTI_CENTS_PER_SECOND_AUDIO_OFF
|
||||
};
|
||||
let cost_in_usd_cents = (rate * self.duration_seconds).div_ceil(100);
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -30,6 +40,16 @@ impl ArtcraftVeo3FastCostState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors Veo 3's `duration_seconds_for_cost`: s≤4 → 4, s≤6 → 6, else → 8
|
||||
/// (incl. None and 7+).
|
||||
fn duration_seconds_for_cost(d: Option<u16>) -> u64 {
|
||||
match d {
|
||||
Some(s) if s <= 4 => 4,
|
||||
Some(s) if s <= 6 => 6,
|
||||
_ => 8,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
@@ -49,22 +69,34 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_off_is_88() { assert_eq!(cost_cents(Some(8), Some(false)), 88); }
|
||||
fn audio_off_4s_is_46() { assert_eq!(cost_cents(Some(4), Some(false)), 46); }
|
||||
|
||||
#[test]
|
||||
fn audio_on_is_132() { assert_eq!(cost_cents(Some(8), Some(true)), 132); }
|
||||
fn audio_off_6s_is_69() { assert_eq!(cost_cents(Some(6), Some(false)), 69); }
|
||||
|
||||
#[test]
|
||||
fn audio_default_is_off() {
|
||||
assert_eq!(cost_cents(Some(8), None), 88);
|
||||
fn audio_off_8s_is_92() { assert_eq!(cost_cents(Some(8), Some(false)), 92); }
|
||||
|
||||
#[test]
|
||||
fn audio_on_4s_is_69() { assert_eq!(cost_cents(Some(4), Some(true)), 69); }
|
||||
|
||||
#[test]
|
||||
fn audio_on_6s_is_104() {
|
||||
// 1725 × 6 = 10350 hundredth-cents → 104 cents (rounded up).
|
||||
assert_eq!(cost_cents(Some(6), Some(true)), 104);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_does_not_affect_cost() {
|
||||
// v1 always bills 8s regardless of duration_seconds.
|
||||
assert_eq!(cost_cents(Some(4), Some(false)), 88);
|
||||
assert_eq!(cost_cents(Some(6), Some(false)), 88);
|
||||
assert_eq!(cost_cents(Some(8), Some(false)), 88);
|
||||
assert_eq!(cost_cents(None, Some(false)), 88);
|
||||
fn audio_on_8s_is_138() { assert_eq!(cost_cents(Some(8), Some(true)), 138); }
|
||||
|
||||
#[test]
|
||||
fn default_duration_is_8s() {
|
||||
assert_eq!(cost_cents(None, Some(false)), cost_cents(Some(8), Some(false)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_default_is_on() {
|
||||
assert_eq!(cost_cents(Some(8), None), cost_cents(Some(8), Some(true)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-32
@@ -3,7 +3,6 @@
|
||||
//! TODO(2026-08-06): Placeholder rates copied from the existing Minimax H3
|
||||
//! pricing. Finalize once first-party GPU inference costs are known.
|
||||
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest;
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
/// Per-second rates in hundredths of a US cent.
|
||||
@@ -15,12 +14,15 @@ const DEFAULT_DURATION_SECONDS: u64 = 5;
|
||||
|
||||
/// Estimate the user-facing Minimax H3 Ultra cost in USD cents.
|
||||
/// System credits = cents (same convention as the v2 pipeline).
|
||||
pub fn estimate_minimax_h3_ultra_cost_usd_cents(request: &OmniGenVideoCostAndGenerateRequest) -> u64 {
|
||||
let duration_seconds = request.duration_seconds
|
||||
pub fn estimate_minimax_h3_ultra_cost_usd_cents(
|
||||
maybe_duration_seconds: Option<u16>,
|
||||
maybe_resolution: Option<CommonResolution>,
|
||||
) -> u64 {
|
||||
let duration_seconds = maybe_duration_seconds
|
||||
.map(u64::from)
|
||||
.unwrap_or(DEFAULT_DURATION_SECONDS);
|
||||
|
||||
let rate = if is_2k(request.resolution) {
|
||||
let rate = if is_2k(maybe_resolution) {
|
||||
HIGH_RES_RATE_HUNDREDTH_CENTS_PER_SEC
|
||||
} else {
|
||||
LOW_RES_RATE_HUNDREDTH_CENTS_PER_SEC
|
||||
@@ -52,46 +54,28 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_resolution_5s_is_150() {
|
||||
assert_eq!(cost_cents(None, None), 150);
|
||||
assert_eq!(estimate_minimax_h3_ultra_cost_usd_cents(None, None), 150);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_res_5s_is_92() {
|
||||
assert_eq!(cost_cents(Some(5), Some(CommonResolution::SevenTwentyP)), 92);
|
||||
assert_eq!(
|
||||
estimate_minimax_h3_ultra_cost_usd_cents(Some(5), Some(CommonResolution::SevenTwentyP)),
|
||||
92,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_res_10s_is_299() {
|
||||
assert_eq!(cost_cents(Some(10), Some(CommonResolution::TenEightyP)), 299);
|
||||
assert_eq!(
|
||||
estimate_minimax_h3_ultra_cost_usd_cents(Some(10), Some(CommonResolution::TenEightyP)),
|
||||
299,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn odd_duration_rounds_up_to_whole_cents() {
|
||||
// 7s high res: 2990 × 7 = 20930 hundredth-cents → 210 cents.
|
||||
assert_eq!(cost_cents(Some(7), None), 210);
|
||||
}
|
||||
|
||||
fn cost_cents(duration_seconds: Option<u16>, resolution: Option<CommonResolution>) -> u64 {
|
||||
let request = OmniGenVideoCostAndGenerateRequest {
|
||||
idempotency_token: None,
|
||||
model: None,
|
||||
prompt: None,
|
||||
negative_prompt: None,
|
||||
start_frame_image_media_token: None,
|
||||
end_frame_image_media_token: None,
|
||||
reference_image_media_tokens: None,
|
||||
reference_video_media_tokens: None,
|
||||
reference_audio_media_tokens: None,
|
||||
reference_character_tokens: None,
|
||||
resolution,
|
||||
aspect_ratio: None,
|
||||
bitrate: None,
|
||||
quality: None,
|
||||
duration_seconds,
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
estimate_only: None,
|
||||
};
|
||||
estimate_minimax_h3_ultra_cost_usd_cents(&request)
|
||||
assert_eq!(estimate_minimax_h3_ultra_cost_usd_cents(Some(7), None), 210);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
//! Models fulfilled on first-party infrastructure; enqueued directly by
|
||||
//! storyteller-web rather than routed.
|
||||
|
||||
pub mod minimax_h3_ultra_cost;
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
pub mod seedance_2p0_g;
|
||||
pub mod seedance_2p0_fast_g;
|
||||
-323
@@ -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<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
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<RouterAspectRatio>,
|
||||
_strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<Seedance20FastRatio>, 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<RouterResolution>,
|
||||
_strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<Seedance20FastResolution>, 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<ImageRef>) -> Result<Option<String>, 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<ImageListRef>,
|
||||
) -> Result<Option<Vec<String>>, 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<VideoListRef>,
|
||||
) -> Result<Option<Vec<String>>, 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<AudioListRef>,
|
||||
) -> Result<Option<Vec<String>>, 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(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
-125
@@ -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<RouterResolution>, 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()
|
||||
}
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
-100
@@ -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<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
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
|
||||
}
|
||||
}
|
||||
-355
@@ -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<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
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<RouterAspectRatio>,
|
||||
_strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<Seedance20Ratio>, 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<RouterResolution>,
|
||||
_strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<Seedance20Resolution>, 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<ImageRef>) -> Result<Option<String>, 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<ImageListRef>,
|
||||
) -> Result<Option<Vec<String>>, 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<VideoListRef>,
|
||||
) -> Result<Option<Vec<String>>, 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<AudioListRef>,
|
||||
) -> Result<Option<Vec<String>>, 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(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
-133
@@ -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<RouterResolution>, 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()
|
||||
}
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
-98
@@ -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<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
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
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -100,7 +100,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviHappyHorse1p0OutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP)),
|
||||
|
||||
Some(RouterResolution::SevenTwentyP) => Ok(Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP)),
|
||||
Some(RouterResolution::TenEightyP) => Ok(Some(KinoviHappyHorse1p0OutputResolution::TenEightyP)),
|
||||
@@ -332,10 +335,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
fn resolution_none_defaults_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..happy_horse_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_happy_horse_1p0(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
assert!(matches!(draft.resolution, Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+18
-10
@@ -117,7 +117,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
@@ -149,7 +152,7 @@ fn plan_output_resolution(
|
||||
}
|
||||
}
|
||||
|
||||
// Seedance2p0 supports batch counts of 1, 2, and 4 only.
|
||||
// Seedance2p0 supports batch counts of 1 through 4.
|
||||
fn plan_batch_count(
|
||||
video_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
@@ -159,7 +162,9 @@ fn plan_batch_count(
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1 => Ok(KinoviBatchCount::One),
|
||||
2 => Ok(KinoviBatchCount::Two),
|
||||
3 => Ok(KinoviBatchCount::Three),
|
||||
4 => Ok(KinoviBatchCount::Four),
|
||||
// 5 and above:
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
@@ -167,12 +172,8 @@ fn plan_batch_count(
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade => {
|
||||
Ok(if count < 4 { KinoviBatchCount::Four } else { KinoviBatchCount::Four })
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade => {
|
||||
Ok(if count < 4 { KinoviBatchCount::Two } else { KinoviBatchCount::Four })
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade => Ok(KinoviBatchCount::Four),
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(KinoviBatchCount::Four),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -291,6 +292,13 @@ mod tests {
|
||||
assert!(matches!(draft.batch_count, KinoviBatchCount::Two));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_three() {
|
||||
let builder = GenerateVideoRequestBuilder { video_batch_count: Some(3), ..kinovi_web_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0(builder));
|
||||
assert!(matches!(draft.batch_count, KinoviBatchCount::Three));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_four() {
|
||||
let builder = GenerateVideoRequestBuilder { video_batch_count: Some(4), ..kinovi_web_builder() };
|
||||
@@ -388,10 +396,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
fn resolution_none_defaults_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..kinovi_web_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+24
-1
@@ -119,6 +119,13 @@ mod tests {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Two), 494);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_3() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Three), 198);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Three), 247);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Three), 741);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Four), 264);
|
||||
@@ -145,6 +152,11 @@ mod tests {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Two), 62);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_3() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Three), 93);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 124);
|
||||
@@ -169,6 +181,11 @@ mod tests {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Two), 371);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_1080p_batch_3() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Three), 556);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_1080p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Four), 741);
|
||||
@@ -196,6 +213,11 @@ mod tests {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourK, 5, KinoviBatchCount::Two), 824);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_4k_batch_3() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourK, 5, KinoviBatchCount::Three), 1235);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_4k_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourK, 5, KinoviBatchCount::Four), 1647);
|
||||
@@ -413,7 +435,8 @@ mod tests {
|
||||
fn from_draft_720p_default() {
|
||||
let draft = make_draft(5, 1, None, false);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(cost.resolution.is_none()); // None defaults to 720p pricing
|
||||
// Unset resolution is pinned to explicit 720p at build time.
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(!cost.has_video_reference);
|
||||
|
||||
+18
-10
@@ -118,7 +118,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
@@ -163,7 +166,7 @@ fn plan_output_resolution(
|
||||
}
|
||||
}
|
||||
|
||||
// Seedance2p0Fast supports batch counts of 1, 2, and 4 only.
|
||||
// Seedance2p0Fast supports batch counts of 1 through 4.
|
||||
fn plan_batch_count(
|
||||
video_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
@@ -173,7 +176,9 @@ fn plan_batch_count(
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1 => Ok(KinoviBatchCount::One),
|
||||
2 => Ok(KinoviBatchCount::Two),
|
||||
3 => Ok(KinoviBatchCount::Three),
|
||||
4 => Ok(KinoviBatchCount::Four),
|
||||
// 5 and above:
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
@@ -181,12 +186,8 @@ fn plan_batch_count(
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade => {
|
||||
Ok(if count < 4 { KinoviBatchCount::Four } else { KinoviBatchCount::Four })
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade => {
|
||||
Ok(if count < 4 { KinoviBatchCount::Two } else { KinoviBatchCount::Four })
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade => Ok(KinoviBatchCount::Four),
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(KinoviBatchCount::Four),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -307,6 +308,13 @@ mod tests {
|
||||
assert!(matches!(draft.batch_count, KinoviBatchCount::Two));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_three() {
|
||||
let builder = GenerateVideoRequestBuilder { video_batch_count: Some(3), ..kinovi_web_fast_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder));
|
||||
assert!(matches!(draft.batch_count, KinoviBatchCount::Three));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_four() {
|
||||
let builder = GenerateVideoRequestBuilder { video_batch_count: Some(4), ..kinovi_web_fast_builder() };
|
||||
@@ -416,10 +424,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
fn resolution_none_defaults_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..kinovi_web_fast_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+12
-1
@@ -119,6 +119,11 @@ mod tests {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 116);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_3() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Three), 173);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 231);
|
||||
@@ -143,6 +148,11 @@ mod tests {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Two), 58);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_3() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Three), 87);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 116);
|
||||
@@ -256,7 +266,8 @@ mod tests {
|
||||
fn from_draft_720p_default() {
|
||||
let draft = make_draft(5, 1, None, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert!(cost.resolution.is_none());
|
||||
// Unset resolution is pinned to explicit 720p at build time.
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(!cost.has_video_reference);
|
||||
|
||||
+6
-3
@@ -116,7 +116,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
@@ -429,10 +432,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
fn resolution_none_defaults_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -151,7 +151,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
|
||||
+8
-3
@@ -73,10 +73,15 @@ impl KinoviSeedance2p5CostState {
|
||||
}
|
||||
};
|
||||
|
||||
// NB: The kinovi calculator clamps too; clamping here as well keeps this
|
||||
// state's math correct even if it's read directly.
|
||||
// NB: The kinovi calculator clamps measured values and bills the
|
||||
// worst-case maximum for unknown (None) or zero inputs. Zero passes
|
||||
// through unclamped so that worst-case fallback applies — clamping it up
|
||||
// to the minimum here would understate the cost.
|
||||
let total_input_seconds = self.total_input_seconds
|
||||
.map(|seconds| seconds.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS));
|
||||
.map(|seconds| match seconds {
|
||||
0 => 0,
|
||||
measured => measured.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS),
|
||||
});
|
||||
|
||||
let pricing_request = GenerateSeedance2p5Request {
|
||||
prompt: String::new(),
|
||||
|
||||
+6
-3
@@ -134,7 +134,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
@@ -439,10 +442,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
fn resolution_none_defaults_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..preview_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p5_preview(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
pub mod artcraft;
|
||||
pub mod fal;
|
||||
pub mod gmicloud;
|
||||
pub mod first_party;
|
||||
pub mod grok_api;
|
||||
pub mod kinovi;
|
||||
pub mod kinovi;
|
||||
|
||||
-36
@@ -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
|
||||
|
||||
@@ -119,7 +119,7 @@ pub async fn generate_happy_horse_1p0(
|
||||
model_type: KinoviModelTypeRaw::HappyHorse1p0,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio: map_aspect_ratio(req.aspect_ratio),
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
batch_count: map_batch_count(req.batch_count),
|
||||
duration_seconds: req.duration_seconds,
|
||||
start_frame_url: req.start_frame_url,
|
||||
@@ -159,10 +159,12 @@ fn map_aspect_ratio(ar: Option<KinoviHappyHorse1p0AspectRatio>) -> KinoviAspectR
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviHappyHorse1p0OutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviHappyHorse1p0OutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviHappyHorse1p0OutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
KinoviHappyHorse1p0OutputResolution::TenEightyP => KinoviOutputResolutionRaw::TenEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p.
|
||||
Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviHappyHorse1p0OutputResolution::TenEightyP) => KinoviOutputResolutionRaw::TenEightyP,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ pub enum KinoviSeedance2p0OutputResolution {
|
||||
pub enum KinoviSeedance2p0BatchCount {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
@@ -124,6 +125,7 @@ impl GenerateSeedance2p0Request {
|
||||
let batch_multiplier: u64 = match self.batch_count {
|
||||
None | Some(KinoviSeedance2p0BatchCount::One) => 1,
|
||||
Some(KinoviSeedance2p0BatchCount::Two) => 2,
|
||||
Some(KinoviSeedance2p0BatchCount::Three) => 3,
|
||||
Some(KinoviSeedance2p0BatchCount::Four) => 4,
|
||||
};
|
||||
|
||||
@@ -181,7 +183,7 @@ pub async fn generate_seedance_2p0(
|
||||
model_type: KinoviModelTypeRaw::Seedance2Pro,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio: map_aspect_ratio(req.aspect_ratio),
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
batch_count: map_batch_count(req.batch_count),
|
||||
duration_seconds: req.duration_seconds,
|
||||
start_frame_url: req.start_frame_url,
|
||||
@@ -222,12 +224,16 @@ fn map_aspect_ratio(ar: Option<KinoviSeedance2p0AspectRatio>) -> KinoviAspectRat
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviSeedance2p0OutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviSeedance2p0OutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviSeedance2p0OutputResolution::FourEightyP => KinoviOutputResolutionRaw::FourEightyP,
|
||||
KinoviSeedance2p0OutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
KinoviSeedance2p0OutputResolution::TenEightyP => KinoviOutputResolutionRaw::TenEightyP,
|
||||
KinoviSeedance2p0OutputResolution::FourK => KinoviOutputResolutionRaw::FourK,
|
||||
Some(KinoviSeedance2p0OutputResolution::FourEightyP) => KinoviOutputResolutionRaw::FourEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p. Never leave the resolution to the Kinovi
|
||||
// server-side default; a silent upstream change would make supplier cost
|
||||
// diverge from what we billed.
|
||||
Some(KinoviSeedance2p0OutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviSeedance2p0OutputResolution::TenEightyP) => KinoviOutputResolutionRaw::TenEightyP,
|
||||
Some(KinoviSeedance2p0OutputResolution::FourK) => KinoviOutputResolutionRaw::FourK,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +241,7 @@ fn map_batch_count(bc: Option<KinoviSeedance2p0BatchCount>) -> KinoviBatchCountR
|
||||
match bc {
|
||||
Some(KinoviSeedance2p0BatchCount::One) | None => KinoviBatchCountRaw::One,
|
||||
Some(KinoviSeedance2p0BatchCount::Two) => KinoviBatchCountRaw::Two,
|
||||
Some(KinoviSeedance2p0BatchCount::Three) => KinoviBatchCountRaw::Three,
|
||||
Some(KinoviSeedance2p0BatchCount::Four) => KinoviBatchCountRaw::Four,
|
||||
}
|
||||
}
|
||||
@@ -689,6 +696,13 @@ mod tests {
|
||||
assert_eq!(batch2, base * 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3_triples() {
|
||||
let base = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch3 = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Three)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(batch3, base * 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_quadruples() {
|
||||
let base = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
|
||||
+16
-4
@@ -59,6 +59,7 @@ pub enum KinoviSeedance2p0FastOutputResolution {
|
||||
pub enum KinoviSeedance2p0FastBatchCount {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
@@ -114,6 +115,7 @@ impl GenerateSeedance2p0FastRequest {
|
||||
let batch_multiplier: u64 = match self.batch_count {
|
||||
None | Some(KinoviSeedance2p0FastBatchCount::One) => 1,
|
||||
Some(KinoviSeedance2p0FastBatchCount::Two) => 2,
|
||||
Some(KinoviSeedance2p0FastBatchCount::Three) => 3,
|
||||
Some(KinoviSeedance2p0FastBatchCount::Four) => 4,
|
||||
};
|
||||
|
||||
@@ -171,7 +173,7 @@ pub async fn generate_seedance_2p0_fast(
|
||||
model_type: KinoviModelTypeRaw::Seedance2Fast,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio: map_aspect_ratio(req.aspect_ratio),
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
batch_count: map_batch_count(req.batch_count),
|
||||
duration_seconds: req.duration_seconds,
|
||||
start_frame_url: req.start_frame_url,
|
||||
@@ -212,10 +214,12 @@ fn map_aspect_ratio(ar: Option<KinoviSeedance2p0FastAspectRatio>) -> KinoviAspec
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviSeedance2p0FastOutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviSeedance2p0FastOutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviSeedance2p0FastOutputResolution::FourEightyP => KinoviOutputResolutionRaw::FourEightyP,
|
||||
KinoviSeedance2p0FastOutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviSeedance2p0FastOutputResolution::FourEightyP) => KinoviOutputResolutionRaw::FourEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p.
|
||||
Some(KinoviSeedance2p0FastOutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +227,7 @@ fn map_batch_count(bc: Option<KinoviSeedance2p0FastBatchCount>) -> KinoviBatchCo
|
||||
match bc {
|
||||
Some(KinoviSeedance2p0FastBatchCount::One) | None => KinoviBatchCountRaw::One,
|
||||
Some(KinoviSeedance2p0FastBatchCount::Two) => KinoviBatchCountRaw::Two,
|
||||
Some(KinoviSeedance2p0FastBatchCount::Three) => KinoviBatchCountRaw::Three,
|
||||
Some(KinoviSeedance2p0FastBatchCount::Four) => KinoviBatchCountRaw::Four,
|
||||
}
|
||||
}
|
||||
@@ -465,6 +470,13 @@ mod tests {
|
||||
assert_eq!(batch2, base * 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_3_triples() {
|
||||
let base = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch3 = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Three)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(batch3, base * 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_quadruples() {
|
||||
let base = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
|
||||
+8
-6
@@ -179,7 +179,7 @@ fn to_raw_request(req: GenerateSeedance2p0MiniRequest) -> WorkflowRunTaskRequest
|
||||
model_type: KinoviModelTypeRaw::Seedance2Mini,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio: map_aspect_ratio(req.aspect_ratio),
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
batch_count: map_batch_count(req.batch_count),
|
||||
duration_seconds: req.duration_seconds,
|
||||
start_frame_url: req.start_frame_url,
|
||||
@@ -205,10 +205,12 @@ fn map_aspect_ratio(ar: Option<KinoviSeedance2p0MiniAspectRatio>) -> KinoviAspec
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviSeedance2p0MiniOutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviSeedance2p0MiniOutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviSeedance2p0MiniOutputResolution::FourEightyP => KinoviOutputResolutionRaw::FourEightyP,
|
||||
KinoviSeedance2p0MiniOutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviSeedance2p0MiniOutputResolution::FourEightyP) => KinoviOutputResolutionRaw::FourEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p.
|
||||
Some(KinoviSeedance2p0MiniOutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,11 +568,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_resolution_maps_to_none() {
|
||||
fn default_resolution_maps_to_720p() {
|
||||
let mut req = sample();
|
||||
req.output_resolution = None;
|
||||
let raw = to_raw_request(req);
|
||||
assert!(raw.output_resolution.is_none());
|
||||
assert!(matches!(raw.output_resolution, Some(KinoviOutputResolutionRaw::SevenTwentyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -145,9 +145,12 @@ impl GenerateSeedance2p5Request {
|
||||
Some(KinoviSeedance2p5OutputResolution::FourEightyP) => 16u64,
|
||||
Some(KinoviSeedance2p5OutputResolution::SevenTwentyP) | None => 35u64,
|
||||
};
|
||||
let input_seconds = self.total_input_seconds
|
||||
.unwrap_or(MAX_BILLED_INPUT_SECONDS)
|
||||
.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS);
|
||||
// An unknown (None) or zero input duration bills the worst-case
|
||||
// maximum — never default toward the minimum.
|
||||
let input_seconds = match self.total_input_seconds {
|
||||
None | Some(0) => MAX_BILLED_INPUT_SECONDS,
|
||||
Some(seconds) => seconds.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS),
|
||||
};
|
||||
let seconds = u64::from(self.duration_seconds) + u64::from(input_seconds);
|
||||
(rate, seconds)
|
||||
} else {
|
||||
@@ -231,7 +234,7 @@ fn to_raw_request(req: GenerateSeedance2p5Request) -> WorkflowRunTaskRequest {
|
||||
model_type: KinoviModelTypeRaw::Seedance2p5,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio,
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
duration_seconds: req.duration_seconds,
|
||||
batch_count: KinoviBatchCountRaw::One,
|
||||
start_frame_url,
|
||||
@@ -257,10 +260,12 @@ fn map_aspect_ratio(ar: Option<KinoviSeedance2p5AspectRatio>) -> KinoviAspectRat
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviSeedance2p5OutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviSeedance2p5OutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviSeedance2p5OutputResolution::FourEightyP => KinoviOutputResolutionRaw::FourEightyP,
|
||||
KinoviSeedance2p5OutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviSeedance2p5OutputResolution::FourEightyP) => KinoviOutputResolutionRaw::FourEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p.
|
||||
Some(KinoviSeedance2p5OutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,11 +488,12 @@ mod tests {
|
||||
fn video_reference_rate_is_cheaper_per_second() {
|
||||
// 16 < 26 and 35 < 59: the with-references rate is lower per billed
|
||||
// second (the input seconds are where the money goes). Compare at
|
||||
// zero input seconds — the unknown-duration default assumes the
|
||||
// 30-second maximum, which would swamp the rate comparison.
|
||||
assert!(video_ref_480(10, Some(0)).calculate_costs().kinovi_credits
|
||||
// the minimum billed input (1s clamps to 4) — unknown or zero input
|
||||
// assumes the 30-second maximum, which would swamp the rate
|
||||
// comparison.
|
||||
assert!(video_ref_480(10, Some(1)).calculate_costs().kinovi_credits
|
||||
< t2v_480(10).calculate_costs().kinovi_credits);
|
||||
assert!(video_ref_720(10, Some(0)).calculate_costs().kinovi_credits
|
||||
assert!(video_ref_720(10, Some(1)).calculate_costs().kinovi_credits
|
||||
< t2v_720(10).calculate_costs().kinovi_credits);
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -122,7 +122,7 @@ fn to_raw_request(req: GenerateSeedance2p5PreviewRequest) -> WorkflowRunTaskRequ
|
||||
model_type: KinoviModelTypeRaw::Seedance2p5Preview,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio: map_aspect_ratio(req.aspect_ratio),
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
duration_seconds: req.duration_seconds,
|
||||
batch_count: KinoviBatchCountRaw::One,
|
||||
start_frame_url: None,
|
||||
@@ -148,10 +148,12 @@ fn map_aspect_ratio(ar: Option<KinoviSeedance2p5PreviewAspectRatio>) -> KinoviAs
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviSeedance2p5PreviewOutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviSeedance2p5PreviewOutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviSeedance2p5PreviewOutputResolution::FourEightyP => KinoviOutputResolutionRaw::FourEightyP,
|
||||
KinoviSeedance2p5PreviewOutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviSeedance2p5PreviewOutputResolution::FourEightyP) => KinoviOutputResolutionRaw::FourEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p.
|
||||
Some(KinoviSeedance2p5PreviewOutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-11
@@ -33,7 +33,8 @@ pub struct KinoviGenerateVideoRequest {
|
||||
pub aspect_ratio: KinoviAspectRatio,
|
||||
|
||||
/// The resolution
|
||||
/// Output resolution quality (480p, 720p, 1080p). None defaults to 720p.
|
||||
/// Output resolution quality (480p, 720p, 1080p). None resolves to an
|
||||
/// explicit 720p on the wire, matching how estimate_credits() prices None.
|
||||
/// (Kinovi terms this "outputResolution" in the API, which is confusingly named)
|
||||
pub output_resolution: Option<KinoviOutputResolution>,
|
||||
|
||||
@@ -197,25 +198,26 @@ impl KinoviAspectRatio {
|
||||
}
|
||||
}
|
||||
|
||||
/// Output resolution quality. When omitted, defaults to 720p.
|
||||
/// Output resolution quality. Always sent explicitly on the wire — we never
|
||||
/// omit the field and rely on the Kinovi server-side default, because the
|
||||
/// billed price assumes a specific resolution and the two must not diverge.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum KinoviOutputResolution {
|
||||
/// 480p
|
||||
FourEightyP,
|
||||
/// 720p (default — omitting the field gives this)
|
||||
/// 720p (what unset requests resolve to)
|
||||
SevenTwentyP,
|
||||
/// 1080p
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
impl KinoviOutputResolution {
|
||||
/// Returns the API string to send, or None for 720p (the default, which is
|
||||
/// expressed by omitting the field entirely).
|
||||
pub fn as_api_str(&self) -> Option<&'static str> {
|
||||
/// The API string to send.
|
||||
pub fn as_api_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::FourEightyP => Some("480p"),
|
||||
Self::SevenTwentyP => None, // Default — omit from request
|
||||
Self::TenEightyP => Some("1080p"),
|
||||
Self::FourEightyP => "480p",
|
||||
Self::SevenTwentyP => "720p",
|
||||
Self::TenEightyP => "1080p",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,7 +345,9 @@ pub async fn generate_video(args: GenerateVideoArgs<'_>) -> Result<GenerateVideo
|
||||
model: req.model_type.as_api_str(),
|
||||
duration,
|
||||
mode: video_input_mode,
|
||||
output_resolution: req.output_resolution.and_then(|r| r.as_api_str()),
|
||||
// ALWAYS sent explicitly, defaulting unset to 720p — must match
|
||||
// estimate_credits(), which prices None as 720p.
|
||||
output_resolution: Some(req.output_resolution.unwrap_or(KinoviOutputResolution::SevenTwentyP).as_api_str()),
|
||||
face_blur_mode,
|
||||
character_ids: req.character_ids,
|
||||
uploaded_urls,
|
||||
@@ -1391,7 +1395,7 @@ mod tests {
|
||||
async fn test_720p() -> AnyhowResult<()> {
|
||||
setup_test_logging(LevelFilter::Trace);
|
||||
let session = test_session()?;
|
||||
// 720p = None (default, outputResolution field omitted)
|
||||
// 720p = None (unset; now sent explicitly as "720p" on the wire)
|
||||
let prompt = "A corgi running through a field of stars";
|
||||
let args = make_args_with_prompt(prompt, &session, KinoviModelType::Seedance2Pro, None);
|
||||
let result = generate_video(args).await?;
|
||||
|
||||
+26
-33
@@ -139,12 +139,14 @@ impl KinoviAspectRatioRaw {
|
||||
}
|
||||
}
|
||||
|
||||
/// Output resolution quality. When omitted, defaults to 720p.
|
||||
/// Output resolution quality. Always sent explicitly on the wire — we never
|
||||
/// omit the field and rely on the Kinovi server-side default, because the
|
||||
/// billed price assumes a specific resolution and the two must not diverge.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum KinoviOutputResolutionRaw {
|
||||
/// 480p
|
||||
FourEightyP,
|
||||
/// 720p (default — omitting the field gives this)
|
||||
/// 720p (what unset requests resolve to)
|
||||
SevenTwentyP,
|
||||
/// 1080p
|
||||
TenEightyP,
|
||||
@@ -154,20 +156,8 @@ pub enum KinoviOutputResolutionRaw {
|
||||
}
|
||||
|
||||
impl KinoviOutputResolutionRaw {
|
||||
/// Returns the API string to send, or None for 720p (the default, which is
|
||||
/// expressed by omitting the field entirely).
|
||||
pub fn as_api_str(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::FourEightyP => Some("480p"),
|
||||
Self::SevenTwentyP => None, // Default — omit from request
|
||||
Self::TenEightyP => Some("1080p"),
|
||||
Self::FourK => Some("4k"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The API string including the "720p" default, for models that always
|
||||
/// send `outputResolution` explicitly (Seedance 2.5 Preview).
|
||||
pub fn as_explicit_api_str(&self) -> &'static str {
|
||||
/// The API string to send.
|
||||
pub fn as_api_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::FourEightyP => "480p",
|
||||
Self::SevenTwentyP => "720p",
|
||||
@@ -289,9 +279,10 @@ impl KinoviModelTypeRaw {
|
||||
matches!(self, Self::Seedance2p5)
|
||||
}
|
||||
|
||||
/// Whether the default field values (`outputResolution: "720p"`,
|
||||
/// `faceBlurMode: "off"`) are always sent explicitly rather than omitted.
|
||||
/// The 2.5 family always sends both.
|
||||
/// Whether the default `faceBlurMode: "off"` is always sent explicitly
|
||||
/// rather than omitted. The 2.5 family always sends it.
|
||||
/// (`outputResolution` is always sent explicitly for EVERY model — see
|
||||
/// `build_batch_request` — so it is no longer governed by this flag.)
|
||||
fn always_sends_default_fields(&self) -> bool {
|
||||
matches!(self, Self::Seedance2p5Preview | Self::Seedance2p5)
|
||||
}
|
||||
@@ -533,11 +524,13 @@ fn build_batch_request(req: WorkflowRunTaskRequest) -> BatchRequest {
|
||||
None => None,
|
||||
};
|
||||
|
||||
let output_resolution = if req.model_type.always_sends_default_fields() {
|
||||
// ALWAYS send the resolution explicitly, defaulting unset to 720p. We used
|
||||
// to omit the field for 720p (mimicking the Kinovi web UI) and let the
|
||||
// server default apply, but that let the supplier-side resolution — and
|
||||
// therefore the supplier cost — float free of what we billed. Pin it.
|
||||
let output_resolution = {
|
||||
let resolution = req.output_resolution.unwrap_or(KinoviOutputResolutionRaw::SevenTwentyP);
|
||||
Some(resolution.as_explicit_api_str())
|
||||
} else {
|
||||
req.output_resolution.and_then(|r| r.as_api_str())
|
||||
Some(resolution.as_api_str())
|
||||
};
|
||||
|
||||
let batch_count_value = req.batch_count.as_u8();
|
||||
@@ -661,30 +654,30 @@ mod tests {
|
||||
|
||||
// ── Output-resolution serialization ──
|
||||
//
|
||||
// `outputResolution` is sent only when non-default; 720p (the default) omits
|
||||
// the field. 4K serializes to the literal "4k".
|
||||
// `outputResolution` is always sent explicitly; 720p serializes to the
|
||||
// literal "720p" and 4K to the literal "4k".
|
||||
|
||||
mod output_resolution_serialization_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn four_k_maps_to_4k() {
|
||||
assert_eq!(KinoviOutputResolutionRaw::FourK.as_api_str(), Some("4k"));
|
||||
assert_eq!(KinoviOutputResolutionRaw::FourK.as_api_str(), "4k");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_k_serializes_on_the_wire() {
|
||||
let api_params = base_api_params(KinoviOutputResolutionRaw::FourK.as_api_str());
|
||||
let api_params = base_api_params(Some(KinoviOutputResolutionRaw::FourK.as_api_str()));
|
||||
let json = serde_json::to_string(&api_params).unwrap();
|
||||
assert!(json.contains(r#""outputResolution":"4k""#), "expected outputResolution in {json}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_720p_omits_output_resolution() {
|
||||
assert_eq!(KinoviOutputResolutionRaw::SevenTwentyP.as_api_str(), None);
|
||||
let api_params = base_api_params(KinoviOutputResolutionRaw::SevenTwentyP.as_api_str());
|
||||
fn default_720p_serializes_explicitly() {
|
||||
assert_eq!(KinoviOutputResolutionRaw::SevenTwentyP.as_api_str(), "720p");
|
||||
let api_params = base_api_params(Some(KinoviOutputResolutionRaw::SevenTwentyP.as_api_str()));
|
||||
let json = serde_json::to_string(&api_params).unwrap();
|
||||
assert!(!json.contains("outputResolution"), "expected no outputResolution in {json}");
|
||||
assert!(json.contains(r#""outputResolution":"720p""#), "expected outputResolution in {json}");
|
||||
}
|
||||
|
||||
fn base_api_params(output_resolution: Option<&'static str>) -> ApiParams {
|
||||
@@ -760,7 +753,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mini_720p_omits_output_resolution() {
|
||||
fn mini_unset_resolution_sends_explicit_720p() {
|
||||
let body = build_batch_request(mini_request(
|
||||
KinoviAspectRatioRaw::Landscape16x9,
|
||||
None,
|
||||
@@ -768,7 +761,7 @@ mod tests {
|
||||
));
|
||||
let json = serde_json::to_string(&body).unwrap();
|
||||
assert!(json.contains(r#""aspectRatio":"16:9""#), "{json}");
|
||||
assert!(!json.contains("outputResolution"), "{json}");
|
||||
assert!(json.contains(r#""outputResolution":"720p""#), "{json}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -124,6 +124,7 @@ pub async fn run(state: &ArtcraftState, args: GenerateVideoArgs) -> anyhow::Resu
|
||||
duration_seconds: Some(duration),
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
estimate_only: None,
|
||||
};
|
||||
|
||||
info!("Sending request to omni endpoint (idempotency_token={})...", idempotency_token);
|
||||
|
||||
+2
-2
@@ -78,8 +78,6 @@ fn video_model_to_common_model_type(model: RouterVideoModel) -> Option<CommonMod
|
||||
RouterVideoModel::ViduQ3Turbo => 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<CommonMod
|
||||
RouterVideoModel::GrokImagineVideo1p5 => 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,
|
||||
|
||||
@@ -177,8 +177,6 @@ pub enum ListVideoModelsVideoModel {
|
||||
Seedance2p0Fast,
|
||||
Seedance2p0BytePlus,
|
||||
Seedance2p0BytePlusFast,
|
||||
Seedance2p0Ultra,
|
||||
Seedance2p0UltraFast,
|
||||
Seedance2p0BytePlusUltra,
|
||||
Seedance2p0BytePlusUltraFast,
|
||||
Seedance2p0Mini,
|
||||
@@ -216,8 +214,6 @@ impl From<ApiClientVideoModel> 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<ListVideoModelsVideoModel> 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(),
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+5
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// The deprecated Seedance 2.0 Ultra variants must stay referenced by this
|
||||
// enum's own coders, variant lists, and tests; suppress the lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -204,8 +208,10 @@ pub enum InferenceModelType {
|
||||
Seedance2p0BytePlus,
|
||||
#[serde(rename = "seedance_2p0_bp_fast")]
|
||||
Seedance2p0BytePlusFast,
|
||||
#[deprecated(note = "Never launched and unroutable; kept only so historical database rows keep decoding")]
|
||||
#[serde(rename = "seedance_2p0_u")]
|
||||
Seedance2p0Ultra,
|
||||
#[deprecated(note = "Never launched and unroutable; kept only so historical database rows keep decoding")]
|
||||
#[serde(rename = "seedance_2p0_u_fast")]
|
||||
Seedance2p0UltraFast,
|
||||
#[serde(rename = "seedance_2p0_bpu")]
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
+5
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+6
@@ -1,3 +1,7 @@
|
||||
// The deprecated Seedance 2.0 Ultra variants must stay referenced by this
|
||||
// enum's own coders, variant lists, and tests; suppress the lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
@@ -53,8 +57,10 @@ pub enum UploadedVideoNoteReportedModelType {
|
||||
Seedance2p0BytePlus,
|
||||
#[serde(rename = "seedance_2p0_bp_fast")]
|
||||
Seedance2p0BytePlusFast,
|
||||
#[deprecated(note = "Never launched and unroutable; kept only so historical database rows keep decoding")]
|
||||
#[serde(rename = "seedance_2p0_u")]
|
||||
Seedance2p0Ultra,
|
||||
#[deprecated(note = "Never launched and unroutable; kept only so historical database rows keep decoding")]
|
||||
#[serde(rename = "seedance_2p0_u_fast")]
|
||||
Seedance2p0UltraFast,
|
||||
#[serde(rename = "seedance_2p0_bpu")]
|
||||
|
||||
+6
@@ -1,3 +1,7 @@
|
||||
// The deprecated Seedance 2.0 Ultra variants must stay referenced by this
|
||||
// enum's own coders, variant lists, and tests; suppress the lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
@@ -53,8 +57,10 @@ pub enum UploadedVideoDetectedModelType {
|
||||
Seedance2p0BytePlus,
|
||||
#[serde(rename = "seedance_2p0_bp_fast")]
|
||||
Seedance2p0BytePlusFast,
|
||||
#[deprecated(note = "Never launched and unroutable; kept only so historical database rows keep decoding")]
|
||||
#[serde(rename = "seedance_2p0_u")]
|
||||
Seedance2p0Ultra,
|
||||
#[deprecated(note = "Never launched and unroutable; kept only so historical database rows keep decoding")]
|
||||
#[serde(rename = "seedance_2p0_u_fast")]
|
||||
Seedance2p0UltraFast,
|
||||
#[serde(rename = "seedance_2p0_bpu")]
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// Deprecated model variants (e.g. the Seedance 2.0 Ultra tier) must keep
|
||||
// their classification arms here so historical rows still map; suppress the
|
||||
// deprecation lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// The deprecated Seedance 2.0 Ultra variants must stay referenced by this
|
||||
// enum's own coders, variant lists, and tests; suppress the lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -175,8 +179,10 @@ pub enum CommonModelType {
|
||||
Seedance2p0BytePlus,
|
||||
#[serde(rename = "seedance_2p0_bp_fast")]
|
||||
Seedance2p0BytePlusFast,
|
||||
#[deprecated(note = "Never launched and unroutable; kept only so historical database rows keep decoding")]
|
||||
#[serde(rename = "seedance_2p0_u")]
|
||||
Seedance2p0Ultra,
|
||||
#[deprecated(note = "Never launched and unroutable; kept only so historical database rows keep decoding")]
|
||||
#[serde(rename = "seedance_2p0_u_fast")]
|
||||
Seedance2p0UltraFast,
|
||||
#[serde(rename = "seedance_2p0_bpu")]
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// The deprecated Seedance 2.0 Ultra variants must stay referenced by this
|
||||
// enum's own coders, variant lists, and tests; suppress the lint file-wide.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Video models available for generation.
|
||||
@@ -74,9 +78,11 @@ pub enum CommonVideoModel {
|
||||
#[serde(rename = "seedance_2p0_bp_fast")]
|
||||
Seedance2p0BytePlusFast,
|
||||
|
||||
#[deprecated(note = "Never launched and unroutable; kept only so historical database rows keep decoding")]
|
||||
#[serde(rename = "seedance_2p0_u")]
|
||||
Seedance2p0Ultra,
|
||||
|
||||
#[deprecated(note = "Never launched and unroutable; kept only so historical database rows keep decoding")]
|
||||
#[serde(rename = "seedance_2p0_u_fast")]
|
||||
Seedance2p0UltraFast,
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
+9
-9
@@ -53,7 +53,7 @@ pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_options: Some(vec![1, 2, 3, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
@@ -100,7 +100,7 @@ pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_options: Some(vec![1, 2, 3, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
@@ -149,7 +149,7 @@ pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_options: Some(vec![1, 2, 3, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
@@ -196,7 +196,7 @@ pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_options: Some(vec![1, 2, 3, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
@@ -245,7 +245,7 @@ pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_options: Some(vec![1, 2, 3, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
@@ -291,7 +291,7 @@ pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
CommonBitrate::Normal,
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_options: Some(vec![1, 2, 3, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
@@ -338,7 +338,7 @@ pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_options: Some(vec![1, 2, 3, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
@@ -385,7 +385,7 @@ pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_options: Some(vec![1, 2, 3, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
@@ -432,7 +432,7 @@ pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_options: Some(vec![1, 2, 3, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
|
||||
+6
-2
@@ -147,8 +147,12 @@ fn map_video_model(model: CommonVideoModel) -> Result<RouterVideoModel, HandlerE
|
||||
CommonVideoModel::Seedance1p5Pro => 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.
|
||||
#[allow(deprecated)]
|
||||
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,
|
||||
|
||||
-2
@@ -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,
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::first_party_minimax_h3::minimax_h3_ultra_cost::estimate_minimax_h3_ultra_cost_usd_cents;
|
||||
use artcraft_router::generate::generate_video::providers::first_party::minimax_h3_ultra_cost::estimate_minimax_h3_ultra_cost_usd_cents;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::helpers::bill_wallet::bill_wallet;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::helpers::write_prompt_records::{
|
||||
write_prompt_records, WritePromptRecordsArgs,
|
||||
@@ -86,7 +86,7 @@ pub async fn enqueue_first_party_minimax_h3_job(
|
||||
None
|
||||
}
|
||||
FirstPartyMinimaxH3Model::Ultra => {
|
||||
Some(estimate_minimax_h3_ultra_cost_usd_cents(request))
|
||||
Some(estimate_minimax_h3_ultra_cost_usd_cents(request.duration_seconds, request.resolution))
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
-1
@@ -5,4 +5,3 @@
|
||||
//! scheduler runs the jobs later.
|
||||
|
||||
pub mod enqueue_first_party_minimax_h3_job;
|
||||
pub mod minimax_h3_ultra_cost;
|
||||
|
||||
-2
@@ -83,8 +83,6 @@ pub async fn run_pipeline_v2(args: RunPipelineV2Args<'_>) -> Result<PipelineResu
|
||||
RouterVideoModel::Seedance2p5Preview => 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,
|
||||
|
||||
+357
-308
@@ -12,7 +12,7 @@ use enums::common::generation::common_resolution::CommonResolution;
|
||||
use enums::common::generation::common_video_model::CommonVideoModel;
|
||||
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::tests::support::{
|
||||
assert_generation_fails_and_charges_nothing, assert_reference_video_charge_then_refund,
|
||||
assert_generation_fails_and_charges_nothing, assert_reference_video_charge_then_refund, assert_references_charge_more,
|
||||
assert_successful_generation_charges, assert_variant_charges_premium, Batch, CreditsDelta,
|
||||
ExpectedCredits, Seconds, TestHarness,
|
||||
};
|
||||
@@ -31,18 +31,18 @@ mod seedance_2p0_fast {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(4), Batch(1), ExpectedCredits(21)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(26)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(1), ExpectedCredits(31)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(1), ExpectedCredits(36)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(8), Batch(1), ExpectedCredits(41)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(1), ExpectedCredits(47)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(10), Batch(1), ExpectedCredits(52)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(1), ExpectedCredits(57)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(12), Batch(1), ExpectedCredits(62)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(1), ExpectedCredits(67)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(14), Batch(1), ExpectedCredits(73)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(1), ExpectedCredits(78)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(4), Batch(1), ExpectedCredits(26)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(32)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(1), ExpectedCredits(38)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(1), ExpectedCredits(45)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(8), Batch(1), ExpectedCredits(51)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(1), ExpectedCredits(58)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(10), Batch(1), ExpectedCredits(64)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(1), ExpectedCredits(70)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(12), Batch(1), ExpectedCredits(77)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(1), ExpectedCredits(83)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(14), Batch(1), ExpectedCredits(90)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(1), ExpectedCredits(96)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(4), Batch(1), ExpectedCredits(51)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(64)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(1), ExpectedCredits(76)),
|
||||
@@ -97,19 +97,19 @@ mod seedance_2p0_fast {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(26)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(52)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(104)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(104)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(104)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(104)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(7), ExpectedCredits(104)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(8), ExpectedCredits(104)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(9), ExpectedCredits(104)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(104)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(32)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(64)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(96)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(128)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(128)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(128)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(7), ExpectedCredits(128)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(8), ExpectedCredits(128)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(9), ExpectedCredits(128)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(128)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(64)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(127)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(191)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(255)),
|
||||
@@ -119,7 +119,7 @@ mod seedance_2p0_fast {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(64)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(127)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(191)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(255)),
|
||||
@@ -129,7 +129,7 @@ mod seedance_2p0_fast {
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(64)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(127)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(191)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(255)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(255)),
|
||||
@@ -153,26 +153,26 @@ mod seedance_2p0_fast {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(62)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(145)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(187)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(228)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(269)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(311)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(77)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(134)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(230)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(282)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(333)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(384)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(153)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(356)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(267)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(458)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(662)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(764)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(153)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(356)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(267)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(458)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(662)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(764)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(153)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(356)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(267)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(458)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(662)),
|
||||
@@ -262,7 +262,7 @@ mod seedance_2p0_fast {
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(43)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(85)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(169)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(127)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(169)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(169)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(169)),
|
||||
@@ -272,7 +272,7 @@ mod seedance_2p0_fast {
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(169)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(78)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(156)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(234)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(312)),
|
||||
@@ -282,7 +282,7 @@ mod seedance_2p0_fast {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(78)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(156)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(234)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(312)),
|
||||
@@ -292,7 +292,7 @@ mod seedance_2p0_fast {
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(78)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(156)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(234)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(312)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(312)),
|
||||
@@ -311,6 +311,22 @@ mod seedance_2p0_fast {
|
||||
|
||||
/// With video references Fast switches to its with-references rate card (480p 8.41, 720p 15.60 cents/s, ceil-rounded). Charges are asserted on the refunded ledger entry (the fixture media is unreachable).
|
||||
/// Spot checks across duration x batch x resolution.
|
||||
/// References must be CHARGED more than the identical no-reference
|
||||
/// request, at every resolution and across durations.
|
||||
#[tokio::test]
|
||||
#[cfg_attr(feature = "skip_database_tests", ignore)]
|
||||
async fn video_references_charge_more_than_no_references() {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
for resolution in [CommonResolution::FourEightyP, CommonResolution::SevenTwentyP] {
|
||||
for seconds in [5u16, 10, 15] {
|
||||
assert_references_charge_more(
|
||||
&harness, CommonVideoModel::Seedance2p0Fast, Some(resolution), Seconds(seconds),
|
||||
).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(feature = "skip_database_tests", ignore)]
|
||||
async fn video_references_charge_spot_checked_combinations() {
|
||||
@@ -318,25 +334,25 @@ mod seedance_2p0_fast {
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(101)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(236)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(177)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(303)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(371)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(438)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(505)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(188)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(437)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(328)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(562)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(687)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(812)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(936)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(188)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(437)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(328)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(562)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(687)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(812)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(936)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(188)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(437)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(328)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(562)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(687)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(812)),
|
||||
@@ -433,7 +449,7 @@ mod seedance_2p0_bp_fast {
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(45)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(90)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(135)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(180)),
|
||||
@@ -443,7 +459,7 @@ mod seedance_2p0_bp_fast {
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
@@ -453,7 +469,7 @@ mod seedance_2p0_bp_fast {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
@@ -463,7 +479,7 @@ mod seedance_2p0_bp_fast {
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
@@ -488,25 +504,25 @@ mod seedance_2p0_bp_fast {
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(108)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(252)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(189)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(324)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(396)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(468)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(540)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(420)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(1200)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(420)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1200)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(420)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
@@ -528,54 +544,54 @@ mod seedance_2p0_bp_fast {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(4), Batch(1), ExpectedCredits(36)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(45)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(1), ExpectedCredits(54)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(1), ExpectedCredits(63)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(8), Batch(1), ExpectedCredits(72)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(1), ExpectedCredits(81)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(10), Batch(1), ExpectedCredits(90)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(1), ExpectedCredits(99)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(12), Batch(1), ExpectedCredits(108)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(1), ExpectedCredits(117)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(14), Batch(1), ExpectedCredits(126)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(1), ExpectedCredits(135)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(4), Batch(1), ExpectedCredits(80)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(1), ExpectedCredits(120)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(1), ExpectedCredits(140)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(8), Batch(1), ExpectedCredits(160)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(1), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(10), Batch(1), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(1), ExpectedCredits(220)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(12), Batch(1), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(1), ExpectedCredits(260)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(14), Batch(1), ExpectedCredits(280)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(1), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(80)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(120)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(140)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(160)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(220)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(260)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(280)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(80)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(120)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(140)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(160)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(220)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(260)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(280)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(4), Batch(1), ExpectedCredits(39)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(49)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(1), ExpectedCredits(58)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(1), ExpectedCredits(68)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(8), Batch(1), ExpectedCredits(78)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(1), ExpectedCredits(87)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(10), Batch(1), ExpectedCredits(97)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(1), ExpectedCredits(107)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(12), Batch(1), ExpectedCredits(116)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(1), ExpectedCredits(126)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(14), Batch(1), ExpectedCredits(136)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(1), ExpectedCredits(145)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(4), Batch(1), ExpectedCredits(82)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(1), ExpectedCredits(123)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(1), ExpectedCredits(144)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(8), Batch(1), ExpectedCredits(164)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(1), ExpectedCredits(185)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(10), Batch(1), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(1), ExpectedCredits(226)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(12), Batch(1), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(1), ExpectedCredits(267)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(14), Batch(1), ExpectedCredits(287)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(1), ExpectedCredits(308)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(82)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(123)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(144)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(164)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(185)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(226)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(267)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(287)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(308)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(82)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(123)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(144)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(164)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(185)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(226)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(267)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(287)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(308)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -594,46 +610,46 @@ mod seedance_2p0_bp_fast {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(45)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(90)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(7), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(8), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(9), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(7), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(49)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(97)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(145)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(7), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(8), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(9), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(308)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(7), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(308)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(308)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(410)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -645,36 +661,52 @@ mod seedance_2p0_bp_fast {
|
||||
|
||||
/// The BytePlus Fast rate card is flat: reference videos never change the price. Charges are asserted on the refunded ledger entry (the fixture media is unreachable).
|
||||
/// Spot checks across duration x batch x resolution.
|
||||
/// References must be CHARGED more than the identical no-reference
|
||||
/// request, at every resolution and across durations.
|
||||
#[tokio::test]
|
||||
#[cfg_attr(feature = "skip_database_tests", ignore)]
|
||||
async fn video_references_charge_more_than_no_references() {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
for resolution in [CommonResolution::FourEightyP, CommonResolution::SevenTwentyP] {
|
||||
for seconds in [5u16, 10, 15] {
|
||||
assert_references_charge_more(
|
||||
&harness, CommonVideoModel::Seedance2p0BytePlusFast, Some(resolution), Seconds(seconds),
|
||||
).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(feature = "skip_database_tests", ignore)]
|
||||
async fn video_references_charge_spot_checked_combinations() {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(108)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(252)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(324)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(396)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(468)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(540)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(1200)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1200)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(1200)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(116)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(203)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(348)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(425)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(502)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(579)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(431)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(738)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(902)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(1066)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(1230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(431)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(738)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(902)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(1066)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1230)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(431)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(738)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(902)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(1066)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(1230)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -783,7 +815,7 @@ mod seedance_2p0_bpu_fast {
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(45)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(90)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(135)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(180)),
|
||||
@@ -793,7 +825,7 @@ mod seedance_2p0_bpu_fast {
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
@@ -803,7 +835,7 @@ mod seedance_2p0_bpu_fast {
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
@@ -813,7 +845,7 @@ mod seedance_2p0_bpu_fast {
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
@@ -838,25 +870,25 @@ mod seedance_2p0_bpu_fast {
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(108)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(252)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(189)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(324)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(396)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(468)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(540)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(420)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(1200)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(420)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1200)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(420)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
@@ -878,54 +910,54 @@ mod seedance_2p0_bpu_fast {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(4), Batch(1), ExpectedCredits(36)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(45)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(1), ExpectedCredits(54)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(1), ExpectedCredits(63)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(8), Batch(1), ExpectedCredits(72)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(1), ExpectedCredits(81)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(10), Batch(1), ExpectedCredits(90)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(1), ExpectedCredits(99)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(12), Batch(1), ExpectedCredits(108)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(1), ExpectedCredits(117)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(14), Batch(1), ExpectedCredits(126)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(1), ExpectedCredits(135)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(4), Batch(1), ExpectedCredits(80)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(1), ExpectedCredits(120)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(1), ExpectedCredits(140)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(8), Batch(1), ExpectedCredits(160)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(1), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(10), Batch(1), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(1), ExpectedCredits(220)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(12), Batch(1), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(1), ExpectedCredits(260)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(14), Batch(1), ExpectedCredits(280)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(1), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(80)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(120)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(140)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(160)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(220)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(260)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(280)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(80)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(120)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(140)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(160)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(220)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(260)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(280)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(300)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(4), Batch(1), ExpectedCredits(39)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(49)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(1), ExpectedCredits(58)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(1), ExpectedCredits(68)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(8), Batch(1), ExpectedCredits(78)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(1), ExpectedCredits(87)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(10), Batch(1), ExpectedCredits(97)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(1), ExpectedCredits(107)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(12), Batch(1), ExpectedCredits(116)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(1), ExpectedCredits(126)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(14), Batch(1), ExpectedCredits(136)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(1), ExpectedCredits(145)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(4), Batch(1), ExpectedCredits(82)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(1), ExpectedCredits(123)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(1), ExpectedCredits(144)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(8), Batch(1), ExpectedCredits(164)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(1), ExpectedCredits(185)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(10), Batch(1), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(1), ExpectedCredits(226)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(12), Batch(1), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(1), ExpectedCredits(267)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(14), Batch(1), ExpectedCredits(287)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(1), ExpectedCredits(308)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(82)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(123)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(144)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(164)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(185)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(226)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(267)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(287)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(308)),
|
||||
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(82)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(123)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(144)),
|
||||
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(164)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(185)),
|
||||
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(226)),
|
||||
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(267)),
|
||||
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(287)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(308)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -944,46 +976,46 @@ mod seedance_2p0_bpu_fast {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(45)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(90)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(7), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(8), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(9), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(180)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(7), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(100)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(200)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(400)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(49)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(97)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(145)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(7), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(8), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(9), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(193)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(308)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(7), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(308)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(103)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(205)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(308)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(410)),
|
||||
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(410)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -995,36 +1027,52 @@ mod seedance_2p0_bpu_fast {
|
||||
|
||||
/// The BytePlus Fast rate card is flat: reference videos never change the price. Charges are asserted on the refunded ledger entry (the fixture media is unreachable).
|
||||
/// Spot checks across duration x batch x resolution.
|
||||
/// References must be CHARGED more than the identical no-reference
|
||||
/// request, at every resolution and across durations.
|
||||
#[tokio::test]
|
||||
#[cfg_attr(feature = "skip_database_tests", ignore)]
|
||||
async fn video_references_charge_more_than_no_references() {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
for resolution in [CommonResolution::FourEightyP, CommonResolution::SevenTwentyP] {
|
||||
for seconds in [5u16, 10, 15] {
|
||||
assert_references_charge_more(
|
||||
&harness, CommonVideoModel::Seedance2p0BytePlusUltraFast, Some(resolution), Seconds(seconds),
|
||||
).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[cfg_attr(feature = "skip_database_tests", ignore)]
|
||||
async fn video_references_charge_spot_checked_combinations() {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(108)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(252)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(324)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(396)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(468)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(540)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(1200)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1200)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(240)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(560)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(720)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(880)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(1040)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(1200)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(116)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(203)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(348)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(425)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(502)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(579)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(431)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(738)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(902)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(1066)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(1230)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(431)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(738)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(902)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(1066)),
|
||||
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(1230)),
|
||||
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(246)),
|
||||
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(431)),
|
||||
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(738)),
|
||||
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(902)),
|
||||
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(1066)),
|
||||
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(1230)),
|
||||
];
|
||||
|
||||
for (resolution, seconds, batch, expected) in cases {
|
||||
@@ -1035,16 +1083,17 @@ 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)]
|
||||
#[allow(deprecated)]
|
||||
async fn seedance_2p0_ultra_fast_is_unroutable_and_charges_nothing() {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
@@ -1066,7 +1115,7 @@ mod premium {
|
||||
let harness = TestHarness::create().await;
|
||||
|
||||
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits, ExpectedCredits, CreditsDelta)] = &[
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(26), ExpectedCredits(45), CreditsDelta(19)),
|
||||
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(32), ExpectedCredits(45), CreditsDelta(13)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(64), ExpectedCredits(100), CreditsDelta(36)),
|
||||
(Some(CommonResolution::SevenTwentyP), Seconds(10), Batch(1), ExpectedCredits(127), ExpectedCredits(200), CreditsDelta(73)),
|
||||
];
|
||||
@@ -1081,25 +1130,28 @@ 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() {
|
||||
#[allow(deprecated)]
|
||||
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<u64, crate::http_server::common_responses::common_web_error::CommonWebError> {
|
||||
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 +1160,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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+731
-683
File diff suppressed because it is too large
Load Diff
+395
-346
File diff suppressed because it is too large
Load Diff
+53
@@ -4,6 +4,7 @@
|
||||
//! with dummy Actix HTTP requests as a fixture user.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -794,6 +795,58 @@ pub async fn assert_generation_fails_and_charges_nothing(
|
||||
);
|
||||
}
|
||||
|
||||
/// Run the same generation twice — once without and once with a reference
|
||||
/// video, as two independent fixture users — and assert the referenced
|
||||
/// generation is CHARGED strictly more credits. The no-reference generation
|
||||
/// succeeds outright; the referenced one uses an unreachable fixture (charge
|
||||
/// then refund), so its charge is read from the ledger debit entry.
|
||||
pub async fn assert_references_charge_more(
|
||||
harness: &TestHarness,
|
||||
model: CommonVideoModel,
|
||||
resolution: Option<enums::common::generation::common_resolution::CommonResolution>,
|
||||
Seconds(duration_seconds): Seconds,
|
||||
) {
|
||||
let no_ref_user = harness.create_funded_user(STARTING_CREDITS).await;
|
||||
let mut request = base_generate_request(model);
|
||||
request.resolution = resolution;
|
||||
request.duration_seconds = Some(duration_seconds);
|
||||
let response = harness
|
||||
.post_generate(&no_ref_user, request)
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("{:?} {:?} {}s: no-reference generation failed: {:?}", model, resolution, duration_seconds, err)
|
||||
});
|
||||
assert!(response.success);
|
||||
let no_ref_debit = STARTING_CREDITS - harness.wallet_balance(&no_ref_user).await;
|
||||
|
||||
let ref_user = harness.create_funded_user(STARTING_CREDITS).await;
|
||||
let video_token = mysql_testing::fixtures::media_files::create_test_video_media_file(
|
||||
&harness.pool,
|
||||
&ref_user.user_token,
|
||||
Some(6_000),
|
||||
)
|
||||
.await
|
||||
.expect("create video media file fixture");
|
||||
let mut request = base_generate_request(model);
|
||||
request.resolution = resolution;
|
||||
request.duration_seconds = Some(duration_seconds);
|
||||
request.reference_video_media_tokens = Some(vec![video_token]);
|
||||
// The unreachable fixture fails after billing; the charge is in the ledger.
|
||||
let _ = harness.post_generate(&ref_user, request).await;
|
||||
let entries = harness.ledger_entries(&ref_user).await;
|
||||
let ref_debit = entries
|
||||
.iter()
|
||||
.find(|entry| entry.credits_delta < 0)
|
||||
.unwrap_or_else(|| panic!("{:?} {:?} {}s: no ledger debit for referenced generation", model, resolution, duration_seconds));
|
||||
let ref_debit = u64::try_from(-ref_debit.credits_delta).expect("positive debit");
|
||||
|
||||
assert!(
|
||||
ref_debit > no_ref_debit,
|
||||
"{:?} {:?} {}s: references must charge more (ref {} vs no-ref {})",
|
||||
model, resolution, duration_seconds, ref_debit, no_ref_debit,
|
||||
);
|
||||
}
|
||||
|
||||
/// Assert the variant model charges a premium over the base model: runs one
|
||||
/// generation on EACH model, asserts both exact charges, and that
|
||||
/// `variant == base + delta`. (A delta of 0 is allowed where ceil-rounding
|
||||
|
||||
-2
@@ -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,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user