Compare commits

..
Author SHA1 Message Date
Hanashi 5d869b9918 fix margins 2026-08-10 23:18:48 -04:00
Hanashi c01f1fd860 fix 4k margins 2026-08-10 22:38:55 -04:00
Hanashi d8dfcedc85 fix for "impossible" condition (we always probe duration, but failsafe) 2026-08-10 22:08:28 -04:00
Hanashi 1fea22b65b margin boost 2026-08-10 22:00:38 -04:00
BFlat 48d2092edb Promptbox: image ref mention chips (#1880)
- `@ImageN` mentions now render as thumbnail chips, same as characters
- Chip menu (Replace / Preview / Remove) works on image chips; Replace
lists same-type refs only
- Chip menu shows type-aware subtitle, empty text, and fallback icon
- Fix: webapp chip Preview showed thumbnail-size image - now passes
full-res `fullUrl`
- Video/audio mentions unchanged (still colored text); website's old
copy untouched
2026-08-10 21:50:49 +07:00
hanashi 865fcb8b56 batch sizes (#1879) 2026-08-10 01:59:09 -04:00
21 changed files with 2194 additions and 1562 deletions
@@ -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),
@@ -243,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]
@@ -481,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);
}
}
}
@@ -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)]
@@ -179,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);
}
}
@@ -243,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);
}
}
}
@@ -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)]
@@ -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]
@@ -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)]
@@ -179,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);
}
}
@@ -243,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);
}
}
}
@@ -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)]
@@ -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]
@@ -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
@@ -149,25 +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), 78);
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);
}
}
@@ -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]
@@ -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]
@@ -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]
@@ -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);
}
}
}
@@ -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(),
@@ -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 {
@@ -483,11 +486,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);
}
@@ -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,16 +97,16 @@ 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(78)),
(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(191)),
@@ -153,12 +153,12 @@ 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(109)),
(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(267)),
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(458)),
@@ -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() {
@@ -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(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)),
(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(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)),
(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(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)),
(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(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)),
(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(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(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(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(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)),
(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 {
@@ -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(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)),
(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(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)),
(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(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)),
(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(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)),
(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(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(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(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(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)),
(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 {
@@ -1067,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)),
];
@@ -16,7 +16,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, base_generate_request,
Batch, CreditsDelta, ExpectedCredits, Seconds, TestHarness,
};
@@ -70,18 +70,18 @@ mod seedance_2p0 {
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(606)),
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(653)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(699)),
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(347)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(433)),
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(520)),
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(607)),
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(693)),
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(780)),
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(866)),
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(953)),
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1040)),
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1126)),
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1213)),
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1299)),
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(358)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(447)),
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(536)),
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(626)),
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(715)),
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(804)),
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(893)),
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(983)),
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1072)),
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1161)),
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1251)),
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1340)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -129,16 +129,16 @@ mod seedance_2p0 {
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(933)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(933)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(933)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(433)),
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(866)),
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1299)),
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(447)),
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(893)),
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1340)),
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(1786)),
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(1786)),
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(1786)),
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(1786)),
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(1786)),
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(1786)),
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(1786)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -173,12 +173,12 @@ mod seedance_2p0 {
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(2052)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(2425)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(2798)),
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1040)),
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(1819)),
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(3118)),
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(3811)),
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(4504)),
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(5196)),
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1072)),
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(1876)),
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(3215)),
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(3930)),
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(4644)),
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(5358)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -313,6 +313,22 @@ mod seedance_2p0 {
/// With video references Seedance 2.0 switches to its with-references rate card (480p 8.81, 720p 22.05, 1080p 51.10, 4K 113.80 cents/s, ceil-rounded). The unreachable fixture media fails the upload after billing, so the exact charge is asserted on the refunded ledger entry.
/// 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, CommonResolution::TenEightyP, CommonResolution::FourK] {
for seconds in [5u16, 10, 15] {
assert_references_charge_more(
&harness, CommonVideoModel::Seedance2p0, Some(resolution), Seconds(seconds),
).await;
}
}
}
#[tokio::test]
#[cfg_attr(feature = "skip_database_tests", ignore)]
async fn video_references_charge_spot_checked_combinations() {
@@ -426,18 +442,18 @@ mod seedance_2p0_bp {
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(650)),
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(700)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(750)),
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(347)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(433)),
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(520)),
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(607)),
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(693)),
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(780)),
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(866)),
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(953)),
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1040)),
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1126)),
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1213)),
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1299)),
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(475)),
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(570)),
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(665)),
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(760)),
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(855)),
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(950)),
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(1045)),
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1140)),
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1235)),
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1330)),
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1425)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -485,16 +501,16 @@ mod seedance_2p0_bp {
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(1000)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(433)),
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(866)),
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1299)),
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(475)),
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(950)),
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1425)),
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(1900)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -529,12 +545,12 @@ mod seedance_2p0_bp {
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(2200)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(2600)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(3000)),
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1040)),
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(1819)),
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(3118)),
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(3811)),
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(4504)),
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(5196)),
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1140)),
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(1995)),
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(3420)),
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(4180)),
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(4940)),
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(5700)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -552,54 +568,54 @@ mod seedance_2p0_bp {
let harness = TestHarness::create().await;
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
(Some(CommonResolution::FourEightyP), Seconds(4), Batch(1), ExpectedCredits(40)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(50)),
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(1), ExpectedCredits(60)),
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(1), ExpectedCredits(70)),
(Some(CommonResolution::FourEightyP), Seconds(8), Batch(1), ExpectedCredits(80)),
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(1), ExpectedCredits(90)),
(Some(CommonResolution::FourEightyP), Seconds(10), Batch(1), ExpectedCredits(100)),
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(1), ExpectedCredits(110)),
(Some(CommonResolution::FourEightyP), Seconds(12), Batch(1), ExpectedCredits(120)),
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(1), ExpectedCredits(130)),
(Some(CommonResolution::FourEightyP), Seconds(14), Batch(1), ExpectedCredits(140)),
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(1), ExpectedCredits(150)),
(Some(CommonResolution::SevenTwentyP), Seconds(4), Batch(1), ExpectedCredits(100)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(125)),
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(1), ExpectedCredits(150)),
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(1), ExpectedCredits(175)),
(Some(CommonResolution::SevenTwentyP), Seconds(8), Batch(1), ExpectedCredits(200)),
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(1), ExpectedCredits(225)),
(Some(CommonResolution::SevenTwentyP), Seconds(10), Batch(1), ExpectedCredits(250)),
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(1), ExpectedCredits(275)),
(Some(CommonResolution::SevenTwentyP), Seconds(12), Batch(1), ExpectedCredits(300)),
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(1), ExpectedCredits(325)),
(Some(CommonResolution::SevenTwentyP), Seconds(14), Batch(1), ExpectedCredits(350)),
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(1), ExpectedCredits(375)),
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(200)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(250)),
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(300)),
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(350)),
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(400)),
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(450)),
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(500)),
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(550)),
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(600)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(650)),
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(700)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(750)),
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(416)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(519)),
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(623)),
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(727)),
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(831)),
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(935)),
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(1038)),
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(1142)),
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1246)),
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1350)),
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1454)),
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1557)),
(Some(CommonResolution::FourEightyP), Seconds(4), Batch(1), ExpectedCredits(41)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(51)),
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(1), ExpectedCredits(62)),
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(1), ExpectedCredits(72)),
(Some(CommonResolution::FourEightyP), Seconds(8), Batch(1), ExpectedCredits(82)),
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(1), ExpectedCredits(92)),
(Some(CommonResolution::FourEightyP), Seconds(10), Batch(1), ExpectedCredits(102)),
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(1), ExpectedCredits(113)),
(Some(CommonResolution::FourEightyP), Seconds(12), Batch(1), ExpectedCredits(123)),
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(1), ExpectedCredits(133)),
(Some(CommonResolution::FourEightyP), Seconds(14), Batch(1), ExpectedCredits(143)),
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(1), ExpectedCredits(153)),
(Some(CommonResolution::SevenTwentyP), Seconds(4), Batch(1), ExpectedCredits(103)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(129)),
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(1), ExpectedCredits(155)),
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(1), ExpectedCredits(180)),
(Some(CommonResolution::SevenTwentyP), Seconds(8), Batch(1), ExpectedCredits(206)),
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(1), ExpectedCredits(232)),
(Some(CommonResolution::SevenTwentyP), Seconds(10), Batch(1), ExpectedCredits(257)),
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(1), ExpectedCredits(283)),
(Some(CommonResolution::SevenTwentyP), Seconds(12), Batch(1), ExpectedCredits(309)),
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(1), ExpectedCredits(335)),
(Some(CommonResolution::SevenTwentyP), Seconds(14), Batch(1), ExpectedCredits(360)),
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(1), ExpectedCredits(386)),
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(232)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(289)),
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(347)),
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(405)),
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(463)),
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(521)),
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(578)),
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(636)),
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(694)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(752)),
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(810)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(867)),
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(476)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(595)),
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(714)),
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(833)),
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(952)),
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(1071)),
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(1190)),
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(1309)),
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1428)),
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1547)),
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1666)),
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1785)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -618,46 +634,46 @@ mod seedance_2p0_bp {
let harness = TestHarness::create().await;
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(50)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(100)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(150)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(7), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(8), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(9), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(200)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(125)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(250)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(375)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(7), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(500)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(250)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(500)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(750)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(1000)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(519)),
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(1038)),
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1557)),
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(2076)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(51)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(102)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(153)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(7), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(8), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(9), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(204)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(129)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(257)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(386)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(7), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(514)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(289)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(578)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(867)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(1156)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(595)),
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(1190)),
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1785)),
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(2380)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -669,36 +685,52 @@ mod seedance_2p0_bp {
/// The BytePlus rate cards are flat below 4K: reference videos only change the 4K price (a 17.20 cents/s surcharge). 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, CommonResolution::TenEightyP, CommonResolution::FourK] {
for seconds in [5u16, 10, 15] {
assert_references_charge_more(
&harness, CommonVideoModel::Seedance2p0BytePlus, 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(120)),
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(210)),
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(360)),
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(440)),
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(520)),
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(600)),
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(300)),
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(525)),
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(900)),
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(1100)),
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(1300)),
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(1500)),
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(600)),
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(1050)),
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(1800)),
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(2200)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(2600)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(3000)),
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1246)),
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(2180)),
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(3737)),
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(4568)),
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(5398)),
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(6228)),
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(123)),
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(215)),
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(368)),
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(449)),
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(531)),
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(612)),
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(309)),
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(540)),
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(926)),
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(1131)),
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(1337)),
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(1542)),
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(694)),
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(1214)),
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(2081)),
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(2544)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(3006)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(3468)),
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1428)),
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(2499)),
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(4284)),
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(5236)),
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(6188)),
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(7140)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -775,18 +807,18 @@ mod seedance_2p0_bpu {
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(650)),
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(700)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(750)),
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(347)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(433)),
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(520)),
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(607)),
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(693)),
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(780)),
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(866)),
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(953)),
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1040)),
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1126)),
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1213)),
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1299)),
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(475)),
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(570)),
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(665)),
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(760)),
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(855)),
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(950)),
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(1045)),
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1140)),
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1235)),
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1330)),
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1425)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -834,16 +866,16 @@ mod seedance_2p0_bpu {
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(1000)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(433)),
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(866)),
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1299)),
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(1732)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(475)),
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(950)),
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1425)),
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(1900)),
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(1900)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -878,12 +910,12 @@ mod seedance_2p0_bpu {
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(2200)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(2600)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(3000)),
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1040)),
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(1819)),
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(3118)),
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(3811)),
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(4504)),
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(5196)),
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1140)),
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(1995)),
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(3420)),
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(4180)),
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(4940)),
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(5700)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -901,54 +933,54 @@ mod seedance_2p0_bpu {
let harness = TestHarness::create().await;
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
(Some(CommonResolution::FourEightyP), Seconds(4), Batch(1), ExpectedCredits(40)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(50)),
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(1), ExpectedCredits(60)),
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(1), ExpectedCredits(70)),
(Some(CommonResolution::FourEightyP), Seconds(8), Batch(1), ExpectedCredits(80)),
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(1), ExpectedCredits(90)),
(Some(CommonResolution::FourEightyP), Seconds(10), Batch(1), ExpectedCredits(100)),
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(1), ExpectedCredits(110)),
(Some(CommonResolution::FourEightyP), Seconds(12), Batch(1), ExpectedCredits(120)),
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(1), ExpectedCredits(130)),
(Some(CommonResolution::FourEightyP), Seconds(14), Batch(1), ExpectedCredits(140)),
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(1), ExpectedCredits(150)),
(Some(CommonResolution::SevenTwentyP), Seconds(4), Batch(1), ExpectedCredits(100)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(125)),
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(1), ExpectedCredits(150)),
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(1), ExpectedCredits(175)),
(Some(CommonResolution::SevenTwentyP), Seconds(8), Batch(1), ExpectedCredits(200)),
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(1), ExpectedCredits(225)),
(Some(CommonResolution::SevenTwentyP), Seconds(10), Batch(1), ExpectedCredits(250)),
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(1), ExpectedCredits(275)),
(Some(CommonResolution::SevenTwentyP), Seconds(12), Batch(1), ExpectedCredits(300)),
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(1), ExpectedCredits(325)),
(Some(CommonResolution::SevenTwentyP), Seconds(14), Batch(1), ExpectedCredits(350)),
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(1), ExpectedCredits(375)),
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(200)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(250)),
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(300)),
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(350)),
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(400)),
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(450)),
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(500)),
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(550)),
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(600)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(650)),
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(700)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(750)),
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(416)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(519)),
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(623)),
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(727)),
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(831)),
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(935)),
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(1038)),
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(1142)),
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1246)),
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1350)),
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1454)),
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1557)),
(Some(CommonResolution::FourEightyP), Seconds(4), Batch(1), ExpectedCredits(41)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(51)),
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(1), ExpectedCredits(62)),
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(1), ExpectedCredits(72)),
(Some(CommonResolution::FourEightyP), Seconds(8), Batch(1), ExpectedCredits(82)),
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(1), ExpectedCredits(92)),
(Some(CommonResolution::FourEightyP), Seconds(10), Batch(1), ExpectedCredits(102)),
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(1), ExpectedCredits(113)),
(Some(CommonResolution::FourEightyP), Seconds(12), Batch(1), ExpectedCredits(123)),
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(1), ExpectedCredits(133)),
(Some(CommonResolution::FourEightyP), Seconds(14), Batch(1), ExpectedCredits(143)),
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(1), ExpectedCredits(153)),
(Some(CommonResolution::SevenTwentyP), Seconds(4), Batch(1), ExpectedCredits(103)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(129)),
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(1), ExpectedCredits(155)),
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(1), ExpectedCredits(180)),
(Some(CommonResolution::SevenTwentyP), Seconds(8), Batch(1), ExpectedCredits(206)),
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(1), ExpectedCredits(232)),
(Some(CommonResolution::SevenTwentyP), Seconds(10), Batch(1), ExpectedCredits(257)),
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(1), ExpectedCredits(283)),
(Some(CommonResolution::SevenTwentyP), Seconds(12), Batch(1), ExpectedCredits(309)),
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(1), ExpectedCredits(335)),
(Some(CommonResolution::SevenTwentyP), Seconds(14), Batch(1), ExpectedCredits(360)),
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(1), ExpectedCredits(386)),
(Some(CommonResolution::TenEightyP), Seconds(4), Batch(1), ExpectedCredits(232)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(289)),
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(1), ExpectedCredits(347)),
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(1), ExpectedCredits(405)),
(Some(CommonResolution::TenEightyP), Seconds(8), Batch(1), ExpectedCredits(463)),
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(1), ExpectedCredits(521)),
(Some(CommonResolution::TenEightyP), Seconds(10), Batch(1), ExpectedCredits(578)),
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(1), ExpectedCredits(636)),
(Some(CommonResolution::TenEightyP), Seconds(12), Batch(1), ExpectedCredits(694)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(1), ExpectedCredits(752)),
(Some(CommonResolution::TenEightyP), Seconds(14), Batch(1), ExpectedCredits(810)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(1), ExpectedCredits(867)),
(Some(CommonResolution::FourK), Seconds(4), Batch(1), ExpectedCredits(476)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(595)),
(Some(CommonResolution::FourK), Seconds(6), Batch(1), ExpectedCredits(714)),
(Some(CommonResolution::FourK), Seconds(7), Batch(1), ExpectedCredits(833)),
(Some(CommonResolution::FourK), Seconds(8), Batch(1), ExpectedCredits(952)),
(Some(CommonResolution::FourK), Seconds(9), Batch(1), ExpectedCredits(1071)),
(Some(CommonResolution::FourK), Seconds(10), Batch(1), ExpectedCredits(1190)),
(Some(CommonResolution::FourK), Seconds(11), Batch(1), ExpectedCredits(1309)),
(Some(CommonResolution::FourK), Seconds(12), Batch(1), ExpectedCredits(1428)),
(Some(CommonResolution::FourK), Seconds(13), Batch(1), ExpectedCredits(1547)),
(Some(CommonResolution::FourK), Seconds(14), Batch(1), ExpectedCredits(1666)),
(Some(CommonResolution::FourK), Seconds(15), Batch(1), ExpectedCredits(1785)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -967,46 +999,46 @@ mod seedance_2p0_bpu {
let harness = TestHarness::create().await;
let cases: &[(Option<CommonResolution>, Seconds, Batch, ExpectedCredits)] = &[
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(50)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(100)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(150)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(7), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(8), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(9), ExpectedCredits(200)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(200)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(125)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(250)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(375)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(7), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(500)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(500)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(250)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(500)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(750)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(1000)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(1000)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(519)),
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(1038)),
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1557)),
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(2076)),
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(2076)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(1), ExpectedCredits(51)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(2), ExpectedCredits(102)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(3), ExpectedCredits(153)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(4), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(5), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(6), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(7), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(8), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(9), ExpectedCredits(204)),
(Some(CommonResolution::FourEightyP), Seconds(5), Batch(10), ExpectedCredits(204)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(1), ExpectedCredits(129)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(2), ExpectedCredits(257)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(3), ExpectedCredits(386)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(4), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(5), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(6), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(7), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(9), ExpectedCredits(514)),
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(10), ExpectedCredits(514)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(1), ExpectedCredits(289)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(2), ExpectedCredits(578)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(3), ExpectedCredits(867)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(4), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(5), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(6), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(7), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(8), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(9), ExpectedCredits(1156)),
(Some(CommonResolution::TenEightyP), Seconds(5), Batch(10), ExpectedCredits(1156)),
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(595)),
(Some(CommonResolution::FourK), Seconds(5), Batch(2), ExpectedCredits(1190)),
(Some(CommonResolution::FourK), Seconds(5), Batch(3), ExpectedCredits(1785)),
(Some(CommonResolution::FourK), Seconds(5), Batch(4), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(5), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(6), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(7), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(8), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(9), ExpectedCredits(2380)),
(Some(CommonResolution::FourK), Seconds(5), Batch(10), ExpectedCredits(2380)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -1018,36 +1050,52 @@ mod seedance_2p0_bpu {
/// The BytePlus rate cards are flat below 4K: reference videos only change the 4K price (a 17.20 cents/s surcharge). 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, CommonResolution::TenEightyP, CommonResolution::FourK] {
for seconds in [5u16, 10, 15] {
assert_references_charge_more(
&harness, CommonVideoModel::Seedance2p0BytePlusUltra, 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(120)),
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(210)),
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(360)),
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(440)),
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(520)),
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(600)),
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(300)),
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(525)),
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(900)),
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(1100)),
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(1300)),
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(1500)),
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(600)),
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(1050)),
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(1800)),
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(2200)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(2600)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(3000)),
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1246)),
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(2180)),
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(3737)),
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(4568)),
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(5398)),
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(6228)),
(Some(CommonResolution::FourEightyP), Seconds(6), Batch(2), ExpectedCredits(123)),
(Some(CommonResolution::FourEightyP), Seconds(7), Batch(3), ExpectedCredits(215)),
(Some(CommonResolution::FourEightyP), Seconds(9), Batch(4), ExpectedCredits(368)),
(Some(CommonResolution::FourEightyP), Seconds(11), Batch(5), ExpectedCredits(449)),
(Some(CommonResolution::FourEightyP), Seconds(13), Batch(8), ExpectedCredits(531)),
(Some(CommonResolution::FourEightyP), Seconds(15), Batch(10), ExpectedCredits(612)),
(Some(CommonResolution::SevenTwentyP), Seconds(6), Batch(2), ExpectedCredits(309)),
(Some(CommonResolution::SevenTwentyP), Seconds(7), Batch(3), ExpectedCredits(540)),
(Some(CommonResolution::SevenTwentyP), Seconds(9), Batch(4), ExpectedCredits(926)),
(Some(CommonResolution::SevenTwentyP), Seconds(11), Batch(5), ExpectedCredits(1131)),
(Some(CommonResolution::SevenTwentyP), Seconds(13), Batch(8), ExpectedCredits(1337)),
(Some(CommonResolution::SevenTwentyP), Seconds(15), Batch(10), ExpectedCredits(1542)),
(Some(CommonResolution::TenEightyP), Seconds(6), Batch(2), ExpectedCredits(694)),
(Some(CommonResolution::TenEightyP), Seconds(7), Batch(3), ExpectedCredits(1214)),
(Some(CommonResolution::TenEightyP), Seconds(9), Batch(4), ExpectedCredits(2081)),
(Some(CommonResolution::TenEightyP), Seconds(11), Batch(5), ExpectedCredits(2544)),
(Some(CommonResolution::TenEightyP), Seconds(13), Batch(8), ExpectedCredits(3006)),
(Some(CommonResolution::TenEightyP), Seconds(15), Batch(10), ExpectedCredits(3468)),
(Some(CommonResolution::FourK), Seconds(6), Batch(2), ExpectedCredits(1428)),
(Some(CommonResolution::FourK), Seconds(7), Batch(3), ExpectedCredits(2499)),
(Some(CommonResolution::FourK), Seconds(9), Batch(4), ExpectedCredits(4284)),
(Some(CommonResolution::FourK), Seconds(11), Batch(5), ExpectedCredits(5236)),
(Some(CommonResolution::FourK), Seconds(13), Batch(8), ExpectedCredits(6188)),
(Some(CommonResolution::FourK), Seconds(15), Batch(10), ExpectedCredits(7140)),
];
for (resolution, seconds, batch, expected) in cases {
@@ -1099,10 +1147,10 @@ mod premium {
// Batch 8 is accepted but downgrades to the platform max of 4 on both
// execution and billing, so it prices identically to batch 4.
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(320), ExpectedCredits(500), CreditsDelta(180)),
// 4K carries NO premium: base and BytePlus tiers share one 4K rate card
// (86.6 cents/s), so the delta is zero — including at scale.
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(433), ExpectedCredits(433), CreditsDelta(0)),
(Some(CommonResolution::FourK), Seconds(15), Batch(4), ExpectedCredits(5196), ExpectedCredits(5196), CreditsDelta(0)),
// 4K premium: the base tier prices at 89.3 cents/s while the BytePlus
// tiers price at 95.0 cents/s.
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(447), ExpectedCredits(475), CreditsDelta(28)),
(Some(CommonResolution::FourK), Seconds(15), Batch(4), ExpectedCredits(5358), ExpectedCredits(5700), CreditsDelta(342)),
];
for (resolution, seconds, batch, base, variant, delta) in cases {
@@ -1130,10 +1178,10 @@ mod premium {
// Batch 8 is accepted but downgrades to the platform max of 4 on both
// execution and billing, so it prices identically to batch 4.
(Some(CommonResolution::SevenTwentyP), Seconds(5), Batch(8), ExpectedCredits(320), ExpectedCredits(500), CreditsDelta(180)),
// 4K carries NO premium: base and BytePlus tiers share one 4K rate card
// (86.6 cents/s), so the delta is zero — including at scale.
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(433), ExpectedCredits(433), CreditsDelta(0)),
(Some(CommonResolution::FourK), Seconds(15), Batch(4), ExpectedCredits(5196), ExpectedCredits(5196), CreditsDelta(0)),
// 4K premium: the base tier prices at 89.3 cents/s while the BytePlus
// tiers price at 95.0 cents/s.
(Some(CommonResolution::FourK), Seconds(5), Batch(1), ExpectedCredits(447), ExpectedCredits(475), CreditsDelta(28)),
(Some(CommonResolution::FourK), Seconds(15), Batch(4), ExpectedCredits(5358), ExpectedCredits(5700), CreditsDelta(342)),
];
for (resolution, seconds, batch, base, variant, delta) in cases {
@@ -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
@@ -603,6 +603,10 @@ export default function CreateVideo() {
label: `@Image${i + 1}`,
type: "image" as const,
preview: img.url,
// `url` is the thumbnail — the chip Preview modal renders at
// natural size, so it needs the full-res image (same fallback
// the deck cards use).
fullPreview: img.fullUrl ?? img.url,
})),
...referenceVideos.map((vid, i) => ({
label: `@Video${i + 1}`,
@@ -1,7 +1,14 @@
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { twMerge } from "tailwind-merge";
import { ChevronLeftIcon, EyeIcon, RefreshCwIcon, Trash2Icon, UserIcon } from "lucide-react";
import {
ChevronLeftIcon,
EyeIcon,
ImageIcon,
RefreshCwIcon,
Trash2Icon,
UserIcon,
} from "lucide-react";
import type { MentionItem } from "./MentionTextarea";
const VIEWPORT_MARGIN = 8;
@@ -15,6 +22,15 @@ const OPEN_GRACE_MS = 700;
const MENU_ROW =
"flex w-full items-center gap-2 rounded-md px-2 py-2 text-sm text-base-fg transition-colors hover:bg-ui-controls/60 cursor-pointer";
// Header subtitle and empty-replace-list copy per chip type.
const TYPE_COPY: Record<MentionItem["type"], { title: string; empty: string }> =
{
character: { title: "Character", empty: "No other characters" },
image: { title: "Image reference", empty: "No other images" },
video: { title: "Video reference", empty: "No other videos" },
audio: { title: "Audio reference", empty: "No other audio" },
};
export interface MentionChipMenuProps {
/** Viewport-space rect of the clicked chip. */
anchorRect: DOMRect;
@@ -26,9 +42,11 @@ export interface MentionChipMenuProps {
anchorNode?: HTMLElement;
/** Canonical mention label including the "@", e.g. "@robot cartoon". */
currentLabel: string;
/** Avatar of the currently attached character, shown in the menu header. */
/** Chip type — drives the header subtitle and fallback avatar icon. */
currentType?: MentionItem["type"];
/** Thumbnail of the current mention, shown in the menu header. */
currentPreview?: string;
/** Characters offered as replacements (current one excluded by the caller). */
/** Same-type mentions offered as replacements (current one excluded by the caller). */
replaceItems: MentionItem[];
onReplace: (item: MentionItem) => void;
onPreview: () => void;
@@ -37,9 +55,9 @@ export interface MentionChipMenuProps {
}
/**
* Floating menu for an inline character-mention chip: Replace / Preview /
* Remove, with the Replace action swapping to a second "Back" panel listing
* the other available characters.
* Floating menu for an inline mention chip (character or image ref):
* Replace / Preview / Remove, with the Replace action swapping to a second
* "Back" panel listing the other same-type mentions.
*
* Portaled to document.body with fixed positioning — the promptbox `.glass`
* container (backdrop-blur) is a containing block that would trap
@@ -49,6 +67,7 @@ export function MentionChipMenu({
anchorRect,
anchorNode,
currentLabel,
currentType = "character",
currentPreview,
replaceItems,
onReplace,
@@ -193,7 +212,11 @@ export function MentionChipMenu({
aria-label={`View ${currentName}`}
className="group/avatar relative shrink-0 cursor-pointer overflow-hidden rounded-md"
>
<ChipAvatar preview={currentPreview} name={currentName} />
<ChipAvatar
preview={currentPreview}
name={currentName}
type={currentType}
/>
<span className="absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 transition-opacity group-hover/avatar:opacity-100">
<EyeIcon className="h-3 w-3 text-white" />
</span>
@@ -202,7 +225,9 @@ export function MentionChipMenu({
<div className="truncate text-sm font-medium text-base-fg">
{currentName}
</div>
<div className="text-[11px] text-base-fg/50">Character</div>
<div className="text-[11px] text-base-fg/50">
{TYPE_COPY[currentType].title}
</div>
</div>
</div>
<div className="my-1 border-t border-ui-panel-border" />
@@ -247,7 +272,7 @@ export function MentionChipMenu({
<div className="max-h-64 overflow-y-auto">
{replaceItems.length === 0 && (
<div className="px-2 py-3 text-center text-xs text-base-fg/50">
No other characters
{TYPE_COPY[currentType].empty}
</div>
)}
{replaceItems.map((item, i) => (
@@ -258,7 +283,11 @@ export function MentionChipMenu({
onPointerDown={handleRowKeyActivate}
onClick={() => onReplace(item)}
>
<ChipAvatar preview={item.preview} name={item.label} />
<ChipAvatar
preview={item.preview}
name={item.label}
type={item.type}
/>
<span className="min-w-0 flex-1 truncate text-left">
{item.label.replace(/^@/, "")}
</span>
@@ -272,13 +301,22 @@ export function MentionChipMenu({
);
}
function ChipAvatar({ preview, name }: { preview?: string; name: string }) {
function ChipAvatar({
preview,
name,
type = "character",
}: {
preview?: string;
name: string;
type?: MentionItem["type"];
}) {
const FallbackIcon = type === "character" ? UserIcon : ImageIcon;
return (
<div className="flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-md border border-white/10 bg-black/20">
{preview ? (
<img src={preview} alt={name} className="h-full w-full object-cover" />
) : (
<UserIcon className="h-3.5 w-3.5 text-base-fg/60" />
<FallbackIcon className="h-3.5 w-3.5 text-base-fg/60" />
)}
</div>
);
@@ -135,6 +135,15 @@ function isChip(node: Node): node is HTMLElement {
);
}
/**
* Mention types that render as atomic thumbnail chips (vs colored text).
* Video/audio refs stay as text: audio has no thumbnail, and a video frame
* shrunk to chip size is unreadable.
*/
function rendersAsChip(item: MentionItem): boolean {
return item.type === "character" || item.type === "image";
}
/** Nearest chip element enclosing `node` (up to `root`), or null. */
function chipContaining(root: HTMLElement, node: Node | null): HTMLElement | null {
let current: Node | null = node;
@@ -697,11 +706,11 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
return m;
}, [mentionItems]);
// Lowercased labels that render as atomic chips (characters only).
// Lowercased labels that render as atomic chips (characters + images).
const chipLabels = useMemo(() => {
const s = new Set<string>();
for (const item of mentionItems) {
if (item.type === "character") s.add(item.label.toLowerCase());
if (rendersAsChip(item)) s.add(item.label.toLowerCase());
}
return s;
}, [mentionItems]);
@@ -743,9 +752,9 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
);
/**
* Markup for one atomic character chip. data-mention keeps the exact
* typed text so serialization is lossless; the visible chip shows the
* name without the "@".
* Markup for one atomic mention chip (character or image ref).
* data-mention keeps the exact typed text so serialization is lossless;
* the visible chip shows the name without the "@".
*/
const buildChipHTML = useCallback(
(item: MentionItem, matchText: string): string => {
@@ -766,8 +775,8 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
[lowerColorMap],
);
// Build innerHTML with mentions inline: character mentions render as
// atomic thumbnail chips, other reference types as colored text.
// Build innerHTML with mentions inline: character and image-ref mentions
// render as atomic thumbnail chips, other reference types as colored text.
const buildHTML = useCallback(
(text: string): string => {
if (!text) return "";
@@ -794,7 +803,7 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
html += escapeHTML(text.slice(lastIndex, match.index));
}
if (item?.type === "character") {
if (item && rendersAsChip(item)) {
html += buildChipHTML(item, fullMatch);
endsWithChip = true;
} else if (color) {
@@ -1282,39 +1291,35 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
}
}, [chipMenu, selectChipRange, handleInput, chipStart, value, onChange]);
const handleChipPreview = useCallback(() => {
if (!chipMenu) return;
const item =
(chipMenu.token &&
mentionItems.find(
(i) => i.type === "character" && i.token === chipMenu.token,
)) ||
resolveItem(chipMenu.label);
setChipMenu(null);
if (item) setPreviewItem(item);
}, [chipMenu, mentionItems, resolveItem]);
/** The mention item the open chip menu refers to (token beats label). */
const chipMenuItem = useMemo(() => {
if (!chipMenu) return undefined;
return (
(chipMenu.token &&
mentionItems.find(
(i) => i.type === "character" && i.token === chipMenu.token,
)) ||
mentionItems.find((i) => i.token === chipMenu.token)) ||
resolveItem(chipMenu.label)
);
}, [chipMenu, mentionItems, resolveItem]);
const handleChipPreview = useCallback(() => {
if (!chipMenu) return;
setChipMenu(null);
if (chipMenuItem) setPreviewItem(chipMenuItem);
}, [chipMenu, chipMenuItem]);
// Swap candidates: other mentions of the same type as the clicked chip
// (characters swap with characters, image refs with image refs).
const replaceItems = useMemo(() => {
if (!chipMenu) return [];
const chipType = chipMenuItem?.type ?? "character";
return mentionItems.filter(
(i) =>
i.type === "character" &&
i.type === chipType &&
(chipMenu.token
? i.token !== chipMenu.token
: i.label.toLowerCase() !== chipMenu.label.toLowerCase()),
);
}, [chipMenu, mentionItems]);
}, [chipMenu, chipMenuItem, mentionItems]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
@@ -1355,8 +1360,8 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
// Atomic chip deletion: native contenteditable=false removal is
// inconsistent across browsers (Safari especially), so when the caret
// sits at a character-mention boundary, delete the whole label from
// the plain-text value in one keypress.
// sits at a chip-mention boundary, delete the whole label from the
// plain-text value in one keypress.
if (
(e.key === "Backspace" || e.key === "Delete") &&
chipLabels.size > 0 &&
@@ -1592,6 +1597,7 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
anchorRect={chipMenu.rect}
anchorNode={chipMenu.node}
currentLabel={chipMenu.label}
currentType={chipMenuItem?.type ?? "character"}
currentPreview={chipMenuItem ? chipMenuItem.preview : undefined}
replaceItems={replaceItems}
onReplace={handleChipReplace}