mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-01 07:15:54 -05:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfc36215cb | ||
|
|
da6a6c8441 | ||
|
|
5cacd89ab8 | ||
|
|
85e4c15598 | ||
|
|
a10b49b4c6 | ||
|
|
bc198875ef | ||
|
|
178e6de3c6 | ||
|
|
b65372beb7 | ||
|
|
5f04b6dbee | ||
|
|
9a57fa0c3a | ||
|
|
c96d979585 | ||
|
|
343c2a0e47 | ||
|
|
48d2092edb |
+37
-15
@@ -22,14 +22,15 @@ use crate::generate::generate_image::generate_image_request_builder::GenerateIma
|
||||
/// Per-model build functions are expected to validate model-specific
|
||||
/// constraints (e.g. unsupported aspect ratios) BEFORE calling this helper.
|
||||
/// Validation that's identical across every Artcraft image model — batch count
|
||||
/// (1..=4) and image_inputs shape — lives here.
|
||||
/// (1..=[`max_batch_count`]) and image_inputs shape — lives here.
|
||||
pub fn build_artcraft_omni_image_request(
|
||||
builder: GenerateImageRequestBuilder,
|
||||
model: CommonImageModelEnum,
|
||||
) -> Result<OmniGenImageCostAndGenerateRequest, ArtcraftRouterError> {
|
||||
let strategy = builder.request_mismatch_mitigation_strategy;
|
||||
|
||||
let image_batch_count = plan_batch_count(builder.image_batch_count, strategy)?;
|
||||
let image_batch_count =
|
||||
plan_batch_count_with_max(builder.image_batch_count, strategy, max_batch_count(model))?;
|
||||
let image_media_tokens = resolve_image_list_ref(builder.image_inputs.clone())?;
|
||||
|
||||
let aspect_ratio = builder.aspect_ratio.map(to_aspect_ratio_enum);
|
||||
@@ -52,26 +53,47 @@ pub fn build_artcraft_omni_image_request(
|
||||
})
|
||||
}
|
||||
|
||||
/// The largest batch each Artcraft image model accepts. Seedream 5.0 Pro and
|
||||
/// Ultra accept batches of up to 8; every other model caps at 4.
|
||||
pub fn max_batch_count(model: CommonImageModelEnum) -> u16 {
|
||||
match model {
|
||||
CommonImageModelEnum::Seedream5p0Pro | CommonImageModelEnum::Seedream5p0ProUltra => 8,
|
||||
_ => 4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate batch count (1..=4). Mirrors the v1 plan-level validation that
|
||||
/// every Artcraft image model performs.
|
||||
pub fn plan_batch_count(
|
||||
image_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<u16, ArtcraftRouterError> {
|
||||
plan_batch_count_with_max(image_batch_count, strategy, 4)
|
||||
}
|
||||
|
||||
/// Validate batch count (1..=`max`), clamping or erroring per the mitigation
|
||||
/// strategy when the request exceeds the model's cap.
|
||||
pub fn plan_batch_count_with_max(
|
||||
image_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
max: u16,
|
||||
) -> Result<u16, ArtcraftRouterError> {
|
||||
let count = image_batch_count.unwrap_or(1);
|
||||
match count {
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1..=4 => Ok(count),
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(4),
|
||||
},
|
||||
if count == 0 {
|
||||
return Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations));
|
||||
}
|
||||
if count <= max {
|
||||
return Ok(count);
|
||||
}
|
||||
match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(max),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+74
-5
@@ -65,7 +65,6 @@ impl ArtcraftSeedance2p0CostState {
|
||||
let usd_cents = seedance_2p0_four_k_usd_cents(
|
||||
self.duration_seconds,
|
||||
self.batch_count,
|
||||
self.has_video_reference,
|
||||
);
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+131
-14
@@ -1,6 +1,5 @@
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_four_k_usd_cents;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp::request::ArtcraftSeedance2p0BytePlusRequestState;
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
|
||||
@@ -12,6 +11,24 @@ const CENTS_PER_SECOND_480P: f64 = 10.0;
|
||||
const CENTS_PER_SECOND_720P: f64 = 25.0;
|
||||
const CENTS_PER_SECOND_1080P: f64 = 50.0;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
/// reference videos are attached. Held as integer hundredths so the math is
|
||||
/// exact; rounded up to whole cents once, after multiplying by
|
||||
/// duration × batch.
|
||||
/// 480p: 10.20 ¢/s
|
||||
/// 720p: 25.70 ¢/s
|
||||
/// 1080p: 57.80 ¢/s
|
||||
/// 4K: 119.00 ¢/s
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P: u64 = 1_020;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P: u64 = 2_570;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_1080P: u64 = 5_780;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_4K: u64 = 11_900;
|
||||
|
||||
/// 4K output price with no reference videos, in hundredths of a USD cent
|
||||
/// per second (95.00 ¢/s), rounded up to whole cents after multiplying by
|
||||
/// duration × batch.
|
||||
const CENTI_CENTS_PER_SECOND_4K: u64 = 9_500;
|
||||
|
||||
pub struct ArtcraftSeedance2p0BytePlusCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
@@ -32,12 +49,24 @@ impl ArtcraftSeedance2p0BytePlusCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
if self.has_video_reference {
|
||||
let usd_cents = self.with_video_reference_usd_cents();
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
};
|
||||
}
|
||||
|
||||
if self.resolution == CommonResolution::FourK {
|
||||
let usd_cents = seedance_2p0_four_k_usd_cents(
|
||||
self.duration_seconds,
|
||||
self.batch_count,
|
||||
self.has_video_reference,
|
||||
);
|
||||
let total_centi_cents = CENTI_CENTS_PER_SECOND_4K
|
||||
* self.duration_seconds as u64
|
||||
* self.batch_count as u64;
|
||||
let usd_cents = total_centi_cents.div_ceil(100);
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
@@ -67,6 +96,22 @@ impl ArtcraftSeedance2p0BytePlusCostState {
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Price when one or more reference videos are attached.
|
||||
fn with_video_reference_usd_cents(&self) -> u64 {
|
||||
let centi_cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P,
|
||||
CommonResolution::TenEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_1080P,
|
||||
CommonResolution::FourK => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_4K,
|
||||
// Everything else (including 720p) prices at 720p.
|
||||
_ => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P,
|
||||
};
|
||||
|
||||
let seconds = self.duration_seconds as u64 * self.batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
(centi_cents_per_second * seconds).div_ceil(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+41
-1
@@ -9,10 +9,20 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_fast::
|
||||
const CENTS_PER_SECOND_480P: f64 = 9.0;
|
||||
const CENTS_PER_SECOND_720P: f64 = 20.0;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
/// reference videos are attached. Held as integer hundredths so the math is
|
||||
/// exact; rounded up to whole cents once, after multiplying by
|
||||
/// duration × batch.
|
||||
/// 480p: 9.65 ¢/s
|
||||
/// 720p: 20.50 ¢/s
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P: u64 = 965;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P: u64 = 2_050;
|
||||
|
||||
pub struct ArtcraftSeedance2p0BytePlusFastCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub batch_count: u16,
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p0BytePlusFastCostState {
|
||||
@@ -21,10 +31,26 @@ impl ArtcraftSeedance2p0BytePlusFastCostState {
|
||||
.unwrap_or(CommonResolution::SevenTwentyP);
|
||||
let duration_seconds = request.request.duration_seconds.unwrap_or(5);
|
||||
let batch_count = request.request.video_batch_count.unwrap_or(1);
|
||||
Self { resolution, duration_seconds, batch_count }
|
||||
let has_video_reference = request.request.reference_video_media_tokens
|
||||
.as_ref()
|
||||
.is_some_and(|tokens| !tokens.is_empty());
|
||||
Self { resolution, duration_seconds, batch_count, has_video_reference }
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
if self.has_video_reference {
|
||||
let usd_cents = self.with_video_reference_usd_cents();
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
};
|
||||
}
|
||||
|
||||
let cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => CENTS_PER_SECOND_480P,
|
||||
_ => CENTS_PER_SECOND_720P,
|
||||
@@ -42,6 +68,20 @@ impl ArtcraftSeedance2p0BytePlusFastCostState {
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Price when one or more reference videos are attached.
|
||||
fn with_video_reference_usd_cents(&self) -> u64 {
|
||||
let centi_cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P,
|
||||
// Everything else (including 720p) prices at 720p.
|
||||
_ => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P,
|
||||
};
|
||||
|
||||
let seconds = self.duration_seconds as u64 * self.batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
(centi_cents_per_second * seconds).div_ceil(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+33
-33
@@ -65,18 +65,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 14);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 50);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 15);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 36);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 54);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 63);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 23);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 45);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 68);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +85,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 88);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 131);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 37);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 46);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 91);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 137);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 53);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 105);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 158);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 55);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 110);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 165);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -112,31 +112,31 @@ mod tests {
|
||||
/// apply). Once, batch 8 executed in full but billed as batch 4.
|
||||
#[test]
|
||||
fn every_batch_size_720p_5s() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 88);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 131);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 175);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 219);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 262);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 306);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 349);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 46);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 91);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 137);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 182);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 228);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 273);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 319);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 364);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_batch_size_480p_5s() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 50);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 66);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 82);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 99);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 115);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 131);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 36);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 54);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 71);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 89);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 107);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 125);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 142);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_eight_with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 419);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 438);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+131
-14
@@ -1,6 +1,5 @@
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_four_k_usd_cents;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu::request::ArtcraftSeedance2p0BytePlusUltraRequestState;
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
|
||||
@@ -12,6 +11,24 @@ const CENTS_PER_SECOND_480P: f64 = 10.0;
|
||||
const CENTS_PER_SECOND_720P: f64 = 25.0;
|
||||
const CENTS_PER_SECOND_1080P: f64 = 50.0;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
/// reference videos are attached. Held as integer hundredths so the math is
|
||||
/// exact; rounded up to whole cents once, after multiplying by
|
||||
/// duration × batch.
|
||||
/// 480p: 10.20 ¢/s
|
||||
/// 720p: 25.70 ¢/s
|
||||
/// 1080p: 57.80 ¢/s
|
||||
/// 4K: 119.00 ¢/s
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P: u64 = 1_020;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P: u64 = 2_570;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_1080P: u64 = 5_780;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_4K: u64 = 11_900;
|
||||
|
||||
/// 4K output price with no reference videos, in hundredths of a USD cent
|
||||
/// per second (95.00 ¢/s), rounded up to whole cents after multiplying by
|
||||
/// duration × batch.
|
||||
const CENTI_CENTS_PER_SECOND_4K: u64 = 9_500;
|
||||
|
||||
pub struct ArtcraftSeedance2p0BytePlusUltraCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
@@ -32,12 +49,24 @@ impl ArtcraftSeedance2p0BytePlusUltraCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
if self.has_video_reference {
|
||||
let usd_cents = self.with_video_reference_usd_cents();
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
};
|
||||
}
|
||||
|
||||
if self.resolution == CommonResolution::FourK {
|
||||
let usd_cents = seedance_2p0_four_k_usd_cents(
|
||||
self.duration_seconds,
|
||||
self.batch_count,
|
||||
self.has_video_reference,
|
||||
);
|
||||
let total_centi_cents = CENTI_CENTS_PER_SECOND_4K
|
||||
* self.duration_seconds as u64
|
||||
* self.batch_count as u64;
|
||||
let usd_cents = total_centi_cents.div_ceil(100);
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
@@ -67,6 +96,22 @@ impl ArtcraftSeedance2p0BytePlusUltraCostState {
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Price when one or more reference videos are attached.
|
||||
fn with_video_reference_usd_cents(&self) -> u64 {
|
||||
let centi_cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P,
|
||||
CommonResolution::TenEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_1080P,
|
||||
CommonResolution::FourK => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_4K,
|
||||
// Everything else (including 720p) prices at 720p.
|
||||
_ => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P,
|
||||
};
|
||||
|
||||
let seconds = self.duration_seconds as u64 * self.batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
(centi_cents_per_second * seconds).div_ceil(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+41
-1
@@ -9,10 +9,20 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_fast:
|
||||
const CENTS_PER_SECOND_480P: f64 = 9.0;
|
||||
const CENTS_PER_SECOND_720P: f64 = 20.0;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
/// reference videos are attached. Held as integer hundredths so the math is
|
||||
/// exact; rounded up to whole cents once, after multiplying by
|
||||
/// duration × batch.
|
||||
/// 480p: 9.65 ¢/s
|
||||
/// 720p: 20.50 ¢/s
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P: u64 = 965;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P: u64 = 2_050;
|
||||
|
||||
pub struct ArtcraftSeedance2p0BytePlusUltraFastCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub batch_count: u16,
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p0BytePlusUltraFastCostState {
|
||||
@@ -21,10 +31,26 @@ impl ArtcraftSeedance2p0BytePlusUltraFastCostState {
|
||||
.unwrap_or(CommonResolution::SevenTwentyP);
|
||||
let duration_seconds = request.request.duration_seconds.unwrap_or(5);
|
||||
let batch_count = request.request.video_batch_count.unwrap_or(1);
|
||||
Self { resolution, duration_seconds, batch_count }
|
||||
let has_video_reference = request.request.reference_video_media_tokens
|
||||
.as_ref()
|
||||
.is_some_and(|tokens| !tokens.is_empty());
|
||||
Self { resolution, duration_seconds, batch_count, has_video_reference }
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
if self.has_video_reference {
|
||||
let usd_cents = self.with_video_reference_usd_cents();
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
};
|
||||
}
|
||||
|
||||
let cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => CENTS_PER_SECOND_480P,
|
||||
_ => CENTS_PER_SECOND_720P,
|
||||
@@ -42,6 +68,20 @@ impl ArtcraftSeedance2p0BytePlusUltraFastCostState {
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Price when one or more reference videos are attached.
|
||||
fn with_video_reference_usd_cents(&self) -> u64 {
|
||||
let centi_cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P,
|
||||
// Everything else (including 720p) prices at 720p.
|
||||
_ => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P,
|
||||
};
|
||||
|
||||
let seconds = self.duration_seconds as u64 * self.batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
(centi_cents_per_second * seconds).div_ceil(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+33
-33
@@ -65,18 +65,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 14);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 50);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 15);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 36);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 54);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 63);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 23);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 45);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 68);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +85,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 88);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 131);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 37);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 46);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 91);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 137);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 53);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 105);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 158);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 55);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 110);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 165);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -112,31 +112,31 @@ mod tests {
|
||||
/// apply). Once, batch 8 executed in full but billed as batch 4.
|
||||
#[test]
|
||||
fn every_batch_size_720p_5s() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 88);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 131);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 175);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 219);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 262);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 306);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 349);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 46);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 91);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 137);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 182);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 228);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 273);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 319);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 364);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_batch_size_480p_5s() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 50);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 66);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 82);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 99);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 115);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 131);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 36);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 54);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 71);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 89);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 107);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 125);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 142);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_eight_with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 419);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 438);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+8
-8
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+33
-33
@@ -65,18 +65,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 13);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 49);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 14);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 52);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 62);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 22);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 44);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 66);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +85,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 87);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 130);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 36);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 45);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 89);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 134);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 52);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 104);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 156);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 43);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 54);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 108);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 161);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -112,31 +112,31 @@ mod tests {
|
||||
/// apply). Once, batch 8 executed in full but billed as batch 4.
|
||||
#[test]
|
||||
fn every_batch_size_720p_5s() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 87);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 130);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 173);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 217);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 260);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 303);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 346);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 45);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 2, false), 89);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 3, false), 134);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 4, false), 178);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 5, false), 223);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 6, false), 267);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 7, false), 312);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, false), 356);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_batch_size_480p_5s() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 49);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 65);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 82);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 98);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 114);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 130);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 18);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 2, false), 35);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 3, false), 52);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 4, false), 69);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 5, false), 87);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 6, false), 104);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 7, false), 121);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 8, false), 138);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_eight_with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 415);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 8, true), 429);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+13
-10
@@ -13,7 +13,7 @@ pub struct ArtcraftSeedance2p5CostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub has_video_references: bool,
|
||||
pub total_input_seconds: u16,
|
||||
pub maybe_total_input_seconds: Option<u16>,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p5CostState {
|
||||
@@ -30,20 +30,21 @@ impl ArtcraftSeedance2p5CostState {
|
||||
resolution,
|
||||
duration_seconds,
|
||||
has_video_references,
|
||||
total_input_seconds: request.total_input_seconds.unwrap_or(0),
|
||||
maybe_total_input_seconds: request.total_input_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Input-second clamping (the 4..=30 billing range) happens inside the
|
||||
// shared pricing function.
|
||||
let total_input_seconds = self.total_input_seconds;
|
||||
// Input-second clamping (the 4..=30 billing range) and the
|
||||
// unknown-or-zero-input failsafe happen inside the shared pricing
|
||||
// function.
|
||||
let maybe_total_input_seconds = self.maybe_total_input_seconds;
|
||||
|
||||
let usd_cents = seedance_2p5_usd_cents(
|
||||
self.resolution,
|
||||
self.duration_seconds,
|
||||
self.has_video_references,
|
||||
total_input_seconds,
|
||||
maybe_total_input_seconds,
|
||||
);
|
||||
|
||||
// ArtCraft credits: 100 credits = $1.00, so credits = cents.
|
||||
@@ -115,10 +116,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_input_seconds_bill_the_four_second_minimum() {
|
||||
// No measured input still bills the 4-second total minimum:
|
||||
// 7.24279835 ¢/s × (10+4) = 101.40 → 102¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 102);
|
||||
fn missing_or_zero_input_seconds_bill_the_thirty_second_maximum() {
|
||||
// FAILSAFE: an unmeasured (or zero) input bills the 30-second MAXIMUM,
|
||||
// matching the provider client's fallback:
|
||||
// 7.24279835 ¢/s × (10+30) = 289.71 → 290¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 290);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, Some(0)), 290);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+13
-10
@@ -14,7 +14,7 @@ pub struct ArtcraftSeedance2p5UltraCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub has_video_references: bool,
|
||||
pub total_input_seconds: u16,
|
||||
pub maybe_total_input_seconds: Option<u16>,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p5UltraCostState {
|
||||
@@ -31,20 +31,21 @@ impl ArtcraftSeedance2p5UltraCostState {
|
||||
resolution,
|
||||
duration_seconds,
|
||||
has_video_references,
|
||||
total_input_seconds: request.total_input_seconds.unwrap_or(0),
|
||||
maybe_total_input_seconds: request.total_input_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Input-second clamping (the 4..=30 billing range) happens inside the
|
||||
// shared pricing function.
|
||||
let total_input_seconds = self.total_input_seconds;
|
||||
// Input-second clamping (the 4..=30 billing range) and the
|
||||
// unknown-or-zero-input failsafe happen inside the shared pricing
|
||||
// function.
|
||||
let maybe_total_input_seconds = self.maybe_total_input_seconds;
|
||||
|
||||
let usd_cents = seedance_2p5_ultra_usd_cents(
|
||||
self.resolution,
|
||||
self.duration_seconds,
|
||||
self.has_video_references,
|
||||
total_input_seconds,
|
||||
maybe_total_input_seconds,
|
||||
);
|
||||
|
||||
// ArtCraft credits: 100 credits = $1.00, so credits = cents.
|
||||
@@ -133,10 +134,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_input_seconds_bill_the_four_second_minimum() {
|
||||
// No measured input still bills the 4-second total minimum:
|
||||
// 8.55967078 ¢/s × (10+4) = 119.84 → 120¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 120);
|
||||
fn missing_or_zero_input_seconds_bill_the_thirty_second_maximum() {
|
||||
// FAILSAFE: an unmeasured (or zero) input bills the 30-second MAXIMUM,
|
||||
// matching the provider client's fallback:
|
||||
// 8.55967078 ¢/s × (10+30) = 342.39 → 343¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 343);
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, Some(0)), 343);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+86
-97
@@ -11,30 +11,19 @@ use kinovi_web_client::generate::video::generate_seedance_2p5::{
|
||||
// in hundredths of a USD cent per second so the math is exact integer arithmetic
|
||||
// (no floating point), then rounded up to whole cents.
|
||||
|
||||
/// 4K output price, in hundredths of a USD cent per second (86.60 ¢/s).
|
||||
const FOUR_K_CENTI_CENTS_PER_SECOND: u64 = 8660;
|
||||
/// 4K output price, in hundredths of a USD cent per second (89.30 ¢/s).
|
||||
const FOUR_K_CENTI_CENTS_PER_SECOND: u64 = 8930;
|
||||
|
||||
/// Extra hundredths of a USD cent per second when a reference video is attached
|
||||
/// at 4K (17.20 ¢/s surcharge).
|
||||
const FOUR_K_VIDEO_REFERENCE_SURCHARGE_CENTI_CENTS_PER_SECOND: u64 = 1720;
|
||||
|
||||
/// ArtCraft's price (USD cents) for Seedance 2.0 at 4K.
|
||||
///
|
||||
/// A reference video adds a per-second surcharge (4K with and without a video
|
||||
/// reference are priced differently). The cost scales with duration and batch
|
||||
/// count, rounded up to whole cents.
|
||||
/// ArtCraft's price (USD cents) for Seedance 2.0 at 4K with no reference
|
||||
/// videos attached. Each model prices reference videos through its own
|
||||
/// with-reference rate card. The cost scales with duration and batch count,
|
||||
/// rounded up to whole cents.
|
||||
pub fn seedance_2p0_four_k_usd_cents(
|
||||
duration_seconds: u16,
|
||||
batch_count: u16,
|
||||
has_video_reference: bool,
|
||||
) -> u64 {
|
||||
let mut centi_cents_per_second = FOUR_K_CENTI_CENTS_PER_SECOND;
|
||||
if has_video_reference {
|
||||
centi_cents_per_second += FOUR_K_VIDEO_REFERENCE_SURCHARGE_CENTI_CENTS_PER_SECOND;
|
||||
}
|
||||
|
||||
let total_centi_cents =
|
||||
centi_cents_per_second * duration_seconds as u64 * batch_count as u64;
|
||||
FOUR_K_CENTI_CENTS_PER_SECOND * duration_seconds as u64 * batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
total_centi_cents.div_ceil(100)
|
||||
@@ -51,22 +40,22 @@ pub fn seedance_2p0_four_k_usd_cents(
|
||||
// rate sets and helpers below.
|
||||
|
||||
/// Regular Mini — 480p price, USD cents per second.
|
||||
const MINI_CENTS_PER_SECOND_480P: f64 = 3.24074074;
|
||||
const MINI_CENTS_PER_SECOND_480P: f64 = 3.45;
|
||||
/// Regular Mini — 480p reference-video surcharge, USD cents per second.
|
||||
const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.86419753;
|
||||
const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.90;
|
||||
/// Regular Mini — 720p price, USD cents per second.
|
||||
const MINI_CENTS_PER_SECOND_720P: f64 = 8.64197531;
|
||||
const MINI_CENTS_PER_SECOND_720P: f64 = 8.90;
|
||||
/// Regular Mini — 720p reference-video surcharge, USD cents per second.
|
||||
const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.72839506;
|
||||
const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.80;
|
||||
|
||||
/// BytePlus / BytePlus Ultra Mini — 480p price, USD cents per second.
|
||||
const BYTEPLUS_MINI_CENTS_PER_SECOND_480P: f64 = 3.27160494;
|
||||
const BYTEPLUS_MINI_CENTS_PER_SECOND_480P: f64 = 3.55;
|
||||
/// BytePlus / BytePlus Ultra Mini — 480p reference-video surcharge, USD cents per second.
|
||||
const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.87242798;
|
||||
const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.95;
|
||||
/// BytePlus / BytePlus Ultra Mini — 720p price, USD cents per second.
|
||||
const BYTEPLUS_MINI_CENTS_PER_SECOND_720P: f64 = 8.72427984;
|
||||
const BYTEPLUS_MINI_CENTS_PER_SECOND_720P: f64 = 9.10;
|
||||
/// BytePlus / BytePlus Ultra Mini — 720p reference-video surcharge, USD cents per second.
|
||||
const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.74485597;
|
||||
const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.85;
|
||||
|
||||
/// ArtCraft's price (USD cents) for the regular Seedance 2.0 Mini.
|
||||
///
|
||||
@@ -165,7 +154,7 @@ pub fn seedance_2p5_usd_cents(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
has_video_references: bool,
|
||||
total_input_seconds: u16,
|
||||
maybe_total_input_seconds: Option<u16>,
|
||||
) -> u64 {
|
||||
let (cents_per_second, billed_seconds) = if has_video_references {
|
||||
let rate = match resolution {
|
||||
@@ -175,8 +164,18 @@ pub fn seedance_2p5_usd_cents(
|
||||
};
|
||||
// The TOTAL input duration clamps to the 4..=30 second billing range
|
||||
// (three 1s videos sum to 3 and bill 4; three 3s videos bill 9).
|
||||
let billed_input_seconds = total_input_seconds
|
||||
.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS));
|
||||
//
|
||||
// FAILSAFE: an unknown (never probed) or zero input duration bills the
|
||||
// 30-second MAXIMUM, matching the provider client's own fallback. It
|
||||
// must never default toward the minimum — billing 4 input seconds for
|
||||
// an unmeasured input while the provider assumes 30 sells input seconds
|
||||
// far below cost.
|
||||
let billed_input_seconds = match maybe_total_input_seconds {
|
||||
None | Some(0) => u16::from(MAX_BILLED_INPUT_SECONDS),
|
||||
Some(seconds) => {
|
||||
seconds.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS))
|
||||
}
|
||||
};
|
||||
(rate, u64::from(duration_seconds) + u64::from(billed_input_seconds))
|
||||
} else {
|
||||
let rate = match resolution {
|
||||
@@ -209,7 +208,7 @@ pub fn seedance_2p5_ultra_usd_cents(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
has_video_references: bool,
|
||||
total_input_seconds: u16,
|
||||
maybe_total_input_seconds: Option<u16>,
|
||||
) -> u64 {
|
||||
let (cents_per_second, billed_seconds) = if has_video_references {
|
||||
let rate = match resolution {
|
||||
@@ -219,8 +218,18 @@ pub fn seedance_2p5_ultra_usd_cents(
|
||||
};
|
||||
// The TOTAL input duration clamps to the 4..=30 second billing range
|
||||
// (three 1s videos sum to 3 and bill 4; three 3s videos bill 9).
|
||||
let billed_input_seconds = total_input_seconds
|
||||
.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS));
|
||||
//
|
||||
// FAILSAFE: an unknown (never probed) or zero input duration bills the
|
||||
// 30-second MAXIMUM, matching the provider client's own fallback. It
|
||||
// must never default toward the minimum — billing 4 input seconds for
|
||||
// an unmeasured input while the provider assumes 30 sells input seconds
|
||||
// far below cost.
|
||||
let billed_input_seconds = match maybe_total_input_seconds {
|
||||
None | Some(0) => u16::from(MAX_BILLED_INPUT_SECONDS),
|
||||
Some(seconds) => {
|
||||
seconds.clamp(u16::from(MIN_BILLED_INPUT_SECONDS), u16::from(MAX_BILLED_INPUT_SECONDS))
|
||||
}
|
||||
};
|
||||
(rate, u64::from(duration_seconds) + u64::from(billed_input_seconds))
|
||||
} else {
|
||||
let rate = match resolution {
|
||||
@@ -263,37 +272,17 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn four_k_without_video_reference() {
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(4, 1, false), 347);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 1, false), 433);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(10, 1, false), 866);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(15, 1, false), 1299);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_k_with_video_reference() {
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(4, 1, true), 416);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 1, true), 519);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(10, 1, true), 1038);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(15, 1, true), 1557);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_reference_always_costs_more() {
|
||||
for &duration in &[4u16, 5, 10, 15] {
|
||||
assert!(
|
||||
seedance_2p0_four_k_usd_cents(duration, 1, true)
|
||||
> seedance_2p0_four_k_usd_cents(duration, 1, false),
|
||||
"video reference should cost more at {duration}s",
|
||||
);
|
||||
}
|
||||
fn four_k_base_prices() {
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(4, 1), 358);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 1), 447);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(10, 1), 893);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(15, 1), 1340);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_multiplies() {
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 2, false), 866);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 4, false), 1732);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 2, true), 1038);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 2), 893);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 4), 1786);
|
||||
}
|
||||
|
||||
// ── Seedance 2.0 Mini ──
|
||||
@@ -310,34 +299,34 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 13);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 33);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 49);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 14);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 18);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 35);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 52);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 21);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 42);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 62);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 18);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 22);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 44);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 66);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 35);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 44);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 87);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 130);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 36);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 45);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 89);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 134);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 42);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 52);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 104);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 156);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 43);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 54);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 108);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 161);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -364,9 +353,9 @@ mod tests {
|
||||
fn batch_count_scales_the_total() {
|
||||
// Batch is baked in before the single round-up, so batched totals can be
|
||||
// a cent under N× the single price.
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 87);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 173);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 33);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 89);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 178);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 35);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,34 +373,34 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 14);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 33);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 50);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 15);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 18);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 36);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 54);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 21);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 42);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 63);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 18);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 23);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 45);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 68);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 35);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 44);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 88);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 131);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 37);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 46);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 91);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 137);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 42);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 53);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 105);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 158);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 44);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 55);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 110);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 165);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -439,9 +428,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_count_scales_the_total() {
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 88);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 175);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 33);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 91);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 182);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 36);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -11,8 +11,8 @@ impl ArtcraftVeo3CostState {
|
||||
pub fn from_request(request: &ArtcraftVeo3RequestState) -> Self {
|
||||
Self {
|
||||
duration_seconds: duration_seconds_for_cost(request.request.duration_seconds),
|
||||
// v1 legacy Veo 3 handler defaults generate_audio to false.
|
||||
generate_audio: request.request.generate_audio.unwrap_or(false),
|
||||
// Unset defaults to audio on.
|
||||
generate_audio: request.request.generate_audio.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_default_is_off() {
|
||||
// None → audio defaults to false.
|
||||
assert_eq!(cost_cents(Some(8), None), cost_cents(Some(8), Some(false)));
|
||||
fn audio_default_is_on() {
|
||||
assert_eq!(cost_cents(Some(8), None), cost_cents(Some(8), Some(true)));
|
||||
}
|
||||
}
|
||||
|
||||
+46
-14
@@ -1,22 +1,32 @@
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video::providers::artcraft::veo_3_fast::request::ArtcraftVeo3FastRequestState;
|
||||
|
||||
/// Per-second rates in hundredths of a US cent.
|
||||
const CENTI_CENTS_PER_SECOND_AUDIO_OFF: u64 = 1_150;
|
||||
const CENTI_CENTS_PER_SECOND_AUDIO_ON: u64 = 1_725;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftVeo3FastCostState {
|
||||
pub duration_seconds: u64,
|
||||
pub generate_audio: bool,
|
||||
}
|
||||
|
||||
impl ArtcraftVeo3FastCostState {
|
||||
pub fn from_request(request: &ArtcraftVeo3FastRequestState) -> Self {
|
||||
Self {
|
||||
// v1 legacy Veo 3 Fast handler defaults generate_audio to false.
|
||||
generate_audio: request.request.generate_audio.unwrap_or(false),
|
||||
duration_seconds: duration_seconds_for_cost(request.request.duration_seconds),
|
||||
// Unset defaults to audio on.
|
||||
generate_audio: request.request.generate_audio.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Flat per-video price: 88¢ audio off, 132¢ audio on.
|
||||
let cost_in_usd_cents: u64 = if self.generate_audio { 132 } else { 88 };
|
||||
let rate = if self.generate_audio {
|
||||
CENTI_CENTS_PER_SECOND_AUDIO_ON
|
||||
} else {
|
||||
CENTI_CENTS_PER_SECOND_AUDIO_OFF
|
||||
};
|
||||
let cost_in_usd_cents = (rate * self.duration_seconds).div_ceil(100);
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -30,6 +40,16 @@ impl ArtcraftVeo3FastCostState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors Veo 3's `duration_seconds_for_cost`: s≤4 → 4, s≤6 → 6, else → 8
|
||||
/// (incl. None and 7+).
|
||||
fn duration_seconds_for_cost(d: Option<u16>) -> u64 {
|
||||
match d {
|
||||
Some(s) if s <= 4 => 4,
|
||||
Some(s) if s <= 6 => 6,
|
||||
_ => 8,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
@@ -49,22 +69,34 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_off_is_88() { assert_eq!(cost_cents(Some(8), Some(false)), 88); }
|
||||
fn audio_off_4s_is_46() { assert_eq!(cost_cents(Some(4), Some(false)), 46); }
|
||||
|
||||
#[test]
|
||||
fn audio_on_is_132() { assert_eq!(cost_cents(Some(8), Some(true)), 132); }
|
||||
fn audio_off_6s_is_69() { assert_eq!(cost_cents(Some(6), Some(false)), 69); }
|
||||
|
||||
#[test]
|
||||
fn audio_default_is_off() {
|
||||
assert_eq!(cost_cents(Some(8), None), 88);
|
||||
fn audio_off_8s_is_92() { assert_eq!(cost_cents(Some(8), Some(false)), 92); }
|
||||
|
||||
#[test]
|
||||
fn audio_on_4s_is_69() { assert_eq!(cost_cents(Some(4), Some(true)), 69); }
|
||||
|
||||
#[test]
|
||||
fn audio_on_6s_is_104() {
|
||||
// 1725 × 6 = 10350 hundredth-cents → 104 cents (rounded up).
|
||||
assert_eq!(cost_cents(Some(6), Some(true)), 104);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_does_not_affect_cost() {
|
||||
// v1 always bills 8s regardless of duration_seconds.
|
||||
assert_eq!(cost_cents(Some(4), Some(false)), 88);
|
||||
assert_eq!(cost_cents(Some(6), Some(false)), 88);
|
||||
assert_eq!(cost_cents(Some(8), Some(false)), 88);
|
||||
assert_eq!(cost_cents(None, Some(false)), 88);
|
||||
fn audio_on_8s_is_138() { assert_eq!(cost_cents(Some(8), Some(true)), 138); }
|
||||
|
||||
#[test]
|
||||
fn default_duration_is_8s() {
|
||||
assert_eq!(cost_cents(None, Some(false)), cost_cents(Some(8), Some(false)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_default_is_on() {
|
||||
assert_eq!(cost_cents(Some(8), None), cost_cents(Some(8), Some(true)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-32
@@ -3,7 +3,6 @@
|
||||
//! TODO(2026-08-06): Placeholder rates copied from the existing Minimax H3
|
||||
//! pricing. Finalize once first-party GPU inference costs are known.
|
||||
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest;
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
/// Per-second rates in hundredths of a US cent.
|
||||
@@ -15,12 +14,15 @@ const DEFAULT_DURATION_SECONDS: u64 = 5;
|
||||
|
||||
/// Estimate the user-facing Minimax H3 Ultra cost in USD cents.
|
||||
/// System credits = cents (same convention as the v2 pipeline).
|
||||
pub fn estimate_minimax_h3_ultra_cost_usd_cents(request: &OmniGenVideoCostAndGenerateRequest) -> u64 {
|
||||
let duration_seconds = request.duration_seconds
|
||||
pub fn estimate_minimax_h3_ultra_cost_usd_cents(
|
||||
maybe_duration_seconds: Option<u16>,
|
||||
maybe_resolution: Option<CommonResolution>,
|
||||
) -> u64 {
|
||||
let duration_seconds = maybe_duration_seconds
|
||||
.map(u64::from)
|
||||
.unwrap_or(DEFAULT_DURATION_SECONDS);
|
||||
|
||||
let rate = if is_2k(request.resolution) {
|
||||
let rate = if is_2k(maybe_resolution) {
|
||||
HIGH_RES_RATE_HUNDREDTH_CENTS_PER_SEC
|
||||
} else {
|
||||
LOW_RES_RATE_HUNDREDTH_CENTS_PER_SEC
|
||||
@@ -52,46 +54,28 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_resolution_5s_is_150() {
|
||||
assert_eq!(cost_cents(None, None), 150);
|
||||
assert_eq!(estimate_minimax_h3_ultra_cost_usd_cents(None, None), 150);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_res_5s_is_92() {
|
||||
assert_eq!(cost_cents(Some(5), Some(CommonResolution::SevenTwentyP)), 92);
|
||||
assert_eq!(
|
||||
estimate_minimax_h3_ultra_cost_usd_cents(Some(5), Some(CommonResolution::SevenTwentyP)),
|
||||
92,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_res_10s_is_299() {
|
||||
assert_eq!(cost_cents(Some(10), Some(CommonResolution::TenEightyP)), 299);
|
||||
assert_eq!(
|
||||
estimate_minimax_h3_ultra_cost_usd_cents(Some(10), Some(CommonResolution::TenEightyP)),
|
||||
299,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn odd_duration_rounds_up_to_whole_cents() {
|
||||
// 7s high res: 2990 × 7 = 20930 hundredth-cents → 210 cents.
|
||||
assert_eq!(cost_cents(Some(7), None), 210);
|
||||
}
|
||||
|
||||
fn cost_cents(duration_seconds: Option<u16>, resolution: Option<CommonResolution>) -> u64 {
|
||||
let request = OmniGenVideoCostAndGenerateRequest {
|
||||
idempotency_token: None,
|
||||
model: None,
|
||||
prompt: None,
|
||||
negative_prompt: None,
|
||||
start_frame_image_media_token: None,
|
||||
end_frame_image_media_token: None,
|
||||
reference_image_media_tokens: None,
|
||||
reference_video_media_tokens: None,
|
||||
reference_audio_media_tokens: None,
|
||||
reference_character_tokens: None,
|
||||
resolution,
|
||||
aspect_ratio: None,
|
||||
bitrate: None,
|
||||
quality: None,
|
||||
duration_seconds,
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
estimate_only: None,
|
||||
};
|
||||
estimate_minimax_h3_ultra_cost_usd_cents(&request)
|
||||
assert_eq!(estimate_minimax_h3_ultra_cost_usd_cents(Some(7), None), 210);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
//! Models fulfilled on first-party infrastructure; enqueued directly by
|
||||
//! storyteller-web rather than routed.
|
||||
|
||||
pub mod minimax_h3_ultra_cost;
|
||||
+6
-3
@@ -100,7 +100,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviHappyHorse1p0OutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP)),
|
||||
|
||||
Some(RouterResolution::SevenTwentyP) => Ok(Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP)),
|
||||
Some(RouterResolution::TenEightyP) => Ok(Some(KinoviHappyHorse1p0OutputResolution::TenEightyP)),
|
||||
@@ -332,10 +335,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
fn resolution_none_defaults_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..happy_horse_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_happy_horse_1p0(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
assert!(matches!(draft.resolution, Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+6
-3
@@ -117,7 +117,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
@@ -393,10 +396,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
fn resolution_none_defaults_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..kinovi_web_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+2
-1
@@ -435,7 +435,8 @@ mod tests {
|
||||
fn from_draft_720p_default() {
|
||||
let draft = make_draft(5, 1, None, false);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(cost.resolution.is_none()); // None defaults to 720p pricing
|
||||
// Unset resolution is pinned to explicit 720p at build time.
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(!cost.has_video_reference);
|
||||
|
||||
+6
-3
@@ -118,7 +118,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
@@ -421,10 +424,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
fn resolution_none_defaults_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..kinovi_web_fast_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+2
-1
@@ -266,7 +266,8 @@ mod tests {
|
||||
fn from_draft_720p_default() {
|
||||
let draft = make_draft(5, 1, None, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert!(cost.resolution.is_none());
|
||||
// Unset resolution is pinned to explicit 720p at build time.
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(!cost.has_video_reference);
|
||||
|
||||
+6
-3
@@ -116,7 +116,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
@@ -429,10 +432,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
fn resolution_none_defaults_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -151,7 +151,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
|
||||
+8
-3
@@ -73,10 +73,15 @@ impl KinoviSeedance2p5CostState {
|
||||
}
|
||||
};
|
||||
|
||||
// NB: The kinovi calculator clamps too; clamping here as well keeps this
|
||||
// state's math correct even if it's read directly.
|
||||
// NB: The kinovi calculator clamps measured values and bills the
|
||||
// worst-case maximum for unknown (None) or zero inputs. Zero passes
|
||||
// through unclamped so that worst-case fallback applies — clamping it up
|
||||
// to the minimum here would understate the cost.
|
||||
let total_input_seconds = self.total_input_seconds
|
||||
.map(|seconds| seconds.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS));
|
||||
.map(|seconds| match seconds {
|
||||
0 => 0,
|
||||
measured => measured.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS),
|
||||
});
|
||||
|
||||
let pricing_request = GenerateSeedance2p5Request {
|
||||
prompt: String::new(),
|
||||
|
||||
+6
-3
@@ -134,7 +134,10 @@ fn plan_output_resolution(
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
// Unset defaults to explicit 720p. Cost estimation prices unset as 720p,
|
||||
// so the request must pin 720p too — never leave the resolution up to the
|
||||
// provider's server-side default, or billing and supplier cost can diverge.
|
||||
None => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
@@ -439,10 +442,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
fn resolution_none_defaults_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..preview_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p5_preview(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
pub mod artcraft;
|
||||
pub mod fal;
|
||||
pub mod first_party;
|
||||
pub mod grok_api;
|
||||
pub mod kinovi;
|
||||
pub mod kinovi;
|
||||
|
||||
@@ -119,7 +119,7 @@ pub async fn generate_happy_horse_1p0(
|
||||
model_type: KinoviModelTypeRaw::HappyHorse1p0,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio: map_aspect_ratio(req.aspect_ratio),
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
batch_count: map_batch_count(req.batch_count),
|
||||
duration_seconds: req.duration_seconds,
|
||||
start_frame_url: req.start_frame_url,
|
||||
@@ -159,10 +159,12 @@ fn map_aspect_ratio(ar: Option<KinoviHappyHorse1p0AspectRatio>) -> KinoviAspectR
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviHappyHorse1p0OutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviHappyHorse1p0OutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviHappyHorse1p0OutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
KinoviHappyHorse1p0OutputResolution::TenEightyP => KinoviOutputResolutionRaw::TenEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p.
|
||||
Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviHappyHorse1p0OutputResolution::TenEightyP) => KinoviOutputResolutionRaw::TenEightyP,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ pub async fn generate_seedance_2p0(
|
||||
model_type: KinoviModelTypeRaw::Seedance2Pro,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio: map_aspect_ratio(req.aspect_ratio),
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
batch_count: map_batch_count(req.batch_count),
|
||||
duration_seconds: req.duration_seconds,
|
||||
start_frame_url: req.start_frame_url,
|
||||
@@ -224,12 +224,16 @@ fn map_aspect_ratio(ar: Option<KinoviSeedance2p0AspectRatio>) -> KinoviAspectRat
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviSeedance2p0OutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviSeedance2p0OutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviSeedance2p0OutputResolution::FourEightyP => KinoviOutputResolutionRaw::FourEightyP,
|
||||
KinoviSeedance2p0OutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
KinoviSeedance2p0OutputResolution::TenEightyP => KinoviOutputResolutionRaw::TenEightyP,
|
||||
KinoviSeedance2p0OutputResolution::FourK => KinoviOutputResolutionRaw::FourK,
|
||||
Some(KinoviSeedance2p0OutputResolution::FourEightyP) => KinoviOutputResolutionRaw::FourEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p. Never leave the resolution to the Kinovi
|
||||
// server-side default; a silent upstream change would make supplier cost
|
||||
// diverge from what we billed.
|
||||
Some(KinoviSeedance2p0OutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviSeedance2p0OutputResolution::TenEightyP) => KinoviOutputResolutionRaw::TenEightyP,
|
||||
Some(KinoviSeedance2p0OutputResolution::FourK) => KinoviOutputResolutionRaw::FourK,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -173,7 +173,7 @@ pub async fn generate_seedance_2p0_fast(
|
||||
model_type: KinoviModelTypeRaw::Seedance2Fast,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio: map_aspect_ratio(req.aspect_ratio),
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
batch_count: map_batch_count(req.batch_count),
|
||||
duration_seconds: req.duration_seconds,
|
||||
start_frame_url: req.start_frame_url,
|
||||
@@ -214,10 +214,12 @@ fn map_aspect_ratio(ar: Option<KinoviSeedance2p0FastAspectRatio>) -> KinoviAspec
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviSeedance2p0FastOutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviSeedance2p0FastOutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviSeedance2p0FastOutputResolution::FourEightyP => KinoviOutputResolutionRaw::FourEightyP,
|
||||
KinoviSeedance2p0FastOutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviSeedance2p0FastOutputResolution::FourEightyP) => KinoviOutputResolutionRaw::FourEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p.
|
||||
Some(KinoviSeedance2p0FastOutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -179,7 +179,7 @@ fn to_raw_request(req: GenerateSeedance2p0MiniRequest) -> WorkflowRunTaskRequest
|
||||
model_type: KinoviModelTypeRaw::Seedance2Mini,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio: map_aspect_ratio(req.aspect_ratio),
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
batch_count: map_batch_count(req.batch_count),
|
||||
duration_seconds: req.duration_seconds,
|
||||
start_frame_url: req.start_frame_url,
|
||||
@@ -205,10 +205,12 @@ fn map_aspect_ratio(ar: Option<KinoviSeedance2p0MiniAspectRatio>) -> KinoviAspec
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviSeedance2p0MiniOutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviSeedance2p0MiniOutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviSeedance2p0MiniOutputResolution::FourEightyP => KinoviOutputResolutionRaw::FourEightyP,
|
||||
KinoviSeedance2p0MiniOutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviSeedance2p0MiniOutputResolution::FourEightyP) => KinoviOutputResolutionRaw::FourEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p.
|
||||
Some(KinoviSeedance2p0MiniOutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,11 +568,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_resolution_maps_to_none() {
|
||||
fn default_resolution_maps_to_720p() {
|
||||
let mut req = sample();
|
||||
req.output_resolution = None;
|
||||
let raw = to_raw_request(req);
|
||||
assert!(raw.output_resolution.is_none());
|
||||
assert!(matches!(raw.output_resolution, Some(KinoviOutputResolutionRaw::SevenTwentyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -145,9 +145,12 @@ impl GenerateSeedance2p5Request {
|
||||
Some(KinoviSeedance2p5OutputResolution::FourEightyP) => 16u64,
|
||||
Some(KinoviSeedance2p5OutputResolution::SevenTwentyP) | None => 35u64,
|
||||
};
|
||||
let input_seconds = self.total_input_seconds
|
||||
.unwrap_or(MAX_BILLED_INPUT_SECONDS)
|
||||
.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS);
|
||||
// An unknown (None) or zero input duration bills the worst-case
|
||||
// maximum — never default toward the minimum.
|
||||
let input_seconds = match self.total_input_seconds {
|
||||
None | Some(0) => MAX_BILLED_INPUT_SECONDS,
|
||||
Some(seconds) => seconds.clamp(MIN_BILLED_INPUT_SECONDS, MAX_BILLED_INPUT_SECONDS),
|
||||
};
|
||||
let seconds = u64::from(self.duration_seconds) + u64::from(input_seconds);
|
||||
(rate, seconds)
|
||||
} else {
|
||||
@@ -231,7 +234,7 @@ fn to_raw_request(req: GenerateSeedance2p5Request) -> WorkflowRunTaskRequest {
|
||||
model_type: KinoviModelTypeRaw::Seedance2p5,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio,
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
duration_seconds: req.duration_seconds,
|
||||
batch_count: KinoviBatchCountRaw::One,
|
||||
start_frame_url,
|
||||
@@ -257,10 +260,12 @@ fn map_aspect_ratio(ar: Option<KinoviSeedance2p5AspectRatio>) -> KinoviAspectRat
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviSeedance2p5OutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviSeedance2p5OutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviSeedance2p5OutputResolution::FourEightyP => KinoviOutputResolutionRaw::FourEightyP,
|
||||
KinoviSeedance2p5OutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviSeedance2p5OutputResolution::FourEightyP) => KinoviOutputResolutionRaw::FourEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p.
|
||||
Some(KinoviSeedance2p5OutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,11 +488,12 @@ mod tests {
|
||||
fn video_reference_rate_is_cheaper_per_second() {
|
||||
// 16 < 26 and 35 < 59: the with-references rate is lower per billed
|
||||
// second (the input seconds are where the money goes). Compare at
|
||||
// zero input seconds — the unknown-duration default assumes the
|
||||
// 30-second maximum, which would swamp the rate comparison.
|
||||
assert!(video_ref_480(10, Some(0)).calculate_costs().kinovi_credits
|
||||
// the minimum billed input (1s clamps to 4) — unknown or zero input
|
||||
// assumes the 30-second maximum, which would swamp the rate
|
||||
// comparison.
|
||||
assert!(video_ref_480(10, Some(1)).calculate_costs().kinovi_credits
|
||||
< t2v_480(10).calculate_costs().kinovi_credits);
|
||||
assert!(video_ref_720(10, Some(0)).calculate_costs().kinovi_credits
|
||||
assert!(video_ref_720(10, Some(1)).calculate_costs().kinovi_credits
|
||||
< t2v_720(10).calculate_costs().kinovi_credits);
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -122,7 +122,7 @@ fn to_raw_request(req: GenerateSeedance2p5PreviewRequest) -> WorkflowRunTaskRequ
|
||||
model_type: KinoviModelTypeRaw::Seedance2p5Preview,
|
||||
prompt: req.prompt,
|
||||
aspect_ratio: map_aspect_ratio(req.aspect_ratio),
|
||||
output_resolution: req.output_resolution.map(map_output_resolution),
|
||||
output_resolution: Some(map_output_resolution(req.output_resolution)),
|
||||
duration_seconds: req.duration_seconds,
|
||||
batch_count: KinoviBatchCountRaw::One,
|
||||
start_frame_url: None,
|
||||
@@ -148,10 +148,12 @@ fn map_aspect_ratio(ar: Option<KinoviSeedance2p5PreviewAspectRatio>) -> KinoviAs
|
||||
}
|
||||
}
|
||||
|
||||
fn map_output_resolution(res: KinoviSeedance2p5PreviewOutputResolution) -> KinoviOutputResolutionRaw {
|
||||
fn map_output_resolution(res: Option<KinoviSeedance2p5PreviewOutputResolution>) -> KinoviOutputResolutionRaw {
|
||||
match res {
|
||||
KinoviSeedance2p5PreviewOutputResolution::FourEightyP => KinoviOutputResolutionRaw::FourEightyP,
|
||||
KinoviSeedance2p5PreviewOutputResolution::SevenTwentyP => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
Some(KinoviSeedance2p5PreviewOutputResolution::FourEightyP) => KinoviOutputResolutionRaw::FourEightyP,
|
||||
// Unset resolves to 720p — MUST stay in lockstep with calculate_costs(),
|
||||
// which prices None as 720p.
|
||||
Some(KinoviSeedance2p5PreviewOutputResolution::SevenTwentyP) | None => KinoviOutputResolutionRaw::SevenTwentyP,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-11
@@ -33,7 +33,8 @@ pub struct KinoviGenerateVideoRequest {
|
||||
pub aspect_ratio: KinoviAspectRatio,
|
||||
|
||||
/// The resolution
|
||||
/// Output resolution quality (480p, 720p, 1080p). None defaults to 720p.
|
||||
/// Output resolution quality (480p, 720p, 1080p). None resolves to an
|
||||
/// explicit 720p on the wire, matching how estimate_credits() prices None.
|
||||
/// (Kinovi terms this "outputResolution" in the API, which is confusingly named)
|
||||
pub output_resolution: Option<KinoviOutputResolution>,
|
||||
|
||||
@@ -197,25 +198,26 @@ impl KinoviAspectRatio {
|
||||
}
|
||||
}
|
||||
|
||||
/// Output resolution quality. When omitted, defaults to 720p.
|
||||
/// Output resolution quality. Always sent explicitly on the wire — we never
|
||||
/// omit the field and rely on the Kinovi server-side default, because the
|
||||
/// billed price assumes a specific resolution and the two must not diverge.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum KinoviOutputResolution {
|
||||
/// 480p
|
||||
FourEightyP,
|
||||
/// 720p (default — omitting the field gives this)
|
||||
/// 720p (what unset requests resolve to)
|
||||
SevenTwentyP,
|
||||
/// 1080p
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
impl KinoviOutputResolution {
|
||||
/// Returns the API string to send, or None for 720p (the default, which is
|
||||
/// expressed by omitting the field entirely).
|
||||
pub fn as_api_str(&self) -> Option<&'static str> {
|
||||
/// The API string to send.
|
||||
pub fn as_api_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::FourEightyP => Some("480p"),
|
||||
Self::SevenTwentyP => None, // Default — omit from request
|
||||
Self::TenEightyP => Some("1080p"),
|
||||
Self::FourEightyP => "480p",
|
||||
Self::SevenTwentyP => "720p",
|
||||
Self::TenEightyP => "1080p",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,7 +345,9 @@ pub async fn generate_video(args: GenerateVideoArgs<'_>) -> Result<GenerateVideo
|
||||
model: req.model_type.as_api_str(),
|
||||
duration,
|
||||
mode: video_input_mode,
|
||||
output_resolution: req.output_resolution.and_then(|r| r.as_api_str()),
|
||||
// ALWAYS sent explicitly, defaulting unset to 720p — must match
|
||||
// estimate_credits(), which prices None as 720p.
|
||||
output_resolution: Some(req.output_resolution.unwrap_or(KinoviOutputResolution::SevenTwentyP).as_api_str()),
|
||||
face_blur_mode,
|
||||
character_ids: req.character_ids,
|
||||
uploaded_urls,
|
||||
@@ -1391,7 +1395,7 @@ mod tests {
|
||||
async fn test_720p() -> AnyhowResult<()> {
|
||||
setup_test_logging(LevelFilter::Trace);
|
||||
let session = test_session()?;
|
||||
// 720p = None (default, outputResolution field omitted)
|
||||
// 720p = None (unset; now sent explicitly as "720p" on the wire)
|
||||
let prompt = "A corgi running through a field of stars";
|
||||
let args = make_args_with_prompt(prompt, &session, KinoviModelType::Seedance2Pro, None);
|
||||
let result = generate_video(args).await?;
|
||||
|
||||
+26
-33
@@ -139,12 +139,14 @@ impl KinoviAspectRatioRaw {
|
||||
}
|
||||
}
|
||||
|
||||
/// Output resolution quality. When omitted, defaults to 720p.
|
||||
/// Output resolution quality. Always sent explicitly on the wire — we never
|
||||
/// omit the field and rely on the Kinovi server-side default, because the
|
||||
/// billed price assumes a specific resolution and the two must not diverge.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum KinoviOutputResolutionRaw {
|
||||
/// 480p
|
||||
FourEightyP,
|
||||
/// 720p (default — omitting the field gives this)
|
||||
/// 720p (what unset requests resolve to)
|
||||
SevenTwentyP,
|
||||
/// 1080p
|
||||
TenEightyP,
|
||||
@@ -154,20 +156,8 @@ pub enum KinoviOutputResolutionRaw {
|
||||
}
|
||||
|
||||
impl KinoviOutputResolutionRaw {
|
||||
/// Returns the API string to send, or None for 720p (the default, which is
|
||||
/// expressed by omitting the field entirely).
|
||||
pub fn as_api_str(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::FourEightyP => Some("480p"),
|
||||
Self::SevenTwentyP => None, // Default — omit from request
|
||||
Self::TenEightyP => Some("1080p"),
|
||||
Self::FourK => Some("4k"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The API string including the "720p" default, for models that always
|
||||
/// send `outputResolution` explicitly (Seedance 2.5 Preview).
|
||||
pub fn as_explicit_api_str(&self) -> &'static str {
|
||||
/// The API string to send.
|
||||
pub fn as_api_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::FourEightyP => "480p",
|
||||
Self::SevenTwentyP => "720p",
|
||||
@@ -289,9 +279,10 @@ impl KinoviModelTypeRaw {
|
||||
matches!(self, Self::Seedance2p5)
|
||||
}
|
||||
|
||||
/// Whether the default field values (`outputResolution: "720p"`,
|
||||
/// `faceBlurMode: "off"`) are always sent explicitly rather than omitted.
|
||||
/// The 2.5 family always sends both.
|
||||
/// Whether the default `faceBlurMode: "off"` is always sent explicitly
|
||||
/// rather than omitted. The 2.5 family always sends it.
|
||||
/// (`outputResolution` is always sent explicitly for EVERY model — see
|
||||
/// `build_batch_request` — so it is no longer governed by this flag.)
|
||||
fn always_sends_default_fields(&self) -> bool {
|
||||
matches!(self, Self::Seedance2p5Preview | Self::Seedance2p5)
|
||||
}
|
||||
@@ -533,11 +524,13 @@ fn build_batch_request(req: WorkflowRunTaskRequest) -> BatchRequest {
|
||||
None => None,
|
||||
};
|
||||
|
||||
let output_resolution = if req.model_type.always_sends_default_fields() {
|
||||
// ALWAYS send the resolution explicitly, defaulting unset to 720p. We used
|
||||
// to omit the field for 720p (mimicking the Kinovi web UI) and let the
|
||||
// server default apply, but that let the supplier-side resolution — and
|
||||
// therefore the supplier cost — float free of what we billed. Pin it.
|
||||
let output_resolution = {
|
||||
let resolution = req.output_resolution.unwrap_or(KinoviOutputResolutionRaw::SevenTwentyP);
|
||||
Some(resolution.as_explicit_api_str())
|
||||
} else {
|
||||
req.output_resolution.and_then(|r| r.as_api_str())
|
||||
Some(resolution.as_api_str())
|
||||
};
|
||||
|
||||
let batch_count_value = req.batch_count.as_u8();
|
||||
@@ -661,30 +654,30 @@ mod tests {
|
||||
|
||||
// ── Output-resolution serialization ──
|
||||
//
|
||||
// `outputResolution` is sent only when non-default; 720p (the default) omits
|
||||
// the field. 4K serializes to the literal "4k".
|
||||
// `outputResolution` is always sent explicitly; 720p serializes to the
|
||||
// literal "720p" and 4K to the literal "4k".
|
||||
|
||||
mod output_resolution_serialization_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn four_k_maps_to_4k() {
|
||||
assert_eq!(KinoviOutputResolutionRaw::FourK.as_api_str(), Some("4k"));
|
||||
assert_eq!(KinoviOutputResolutionRaw::FourK.as_api_str(), "4k");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_k_serializes_on_the_wire() {
|
||||
let api_params = base_api_params(KinoviOutputResolutionRaw::FourK.as_api_str());
|
||||
let api_params = base_api_params(Some(KinoviOutputResolutionRaw::FourK.as_api_str()));
|
||||
let json = serde_json::to_string(&api_params).unwrap();
|
||||
assert!(json.contains(r#""outputResolution":"4k""#), "expected outputResolution in {json}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_720p_omits_output_resolution() {
|
||||
assert_eq!(KinoviOutputResolutionRaw::SevenTwentyP.as_api_str(), None);
|
||||
let api_params = base_api_params(KinoviOutputResolutionRaw::SevenTwentyP.as_api_str());
|
||||
fn default_720p_serializes_explicitly() {
|
||||
assert_eq!(KinoviOutputResolutionRaw::SevenTwentyP.as_api_str(), "720p");
|
||||
let api_params = base_api_params(Some(KinoviOutputResolutionRaw::SevenTwentyP.as_api_str()));
|
||||
let json = serde_json::to_string(&api_params).unwrap();
|
||||
assert!(!json.contains("outputResolution"), "expected no outputResolution in {json}");
|
||||
assert!(json.contains(r#""outputResolution":"720p""#), "expected outputResolution in {json}");
|
||||
}
|
||||
|
||||
fn base_api_params(output_resolution: Option<&'static str>) -> ApiParams {
|
||||
@@ -760,7 +753,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mini_720p_omits_output_resolution() {
|
||||
fn mini_unset_resolution_sends_explicit_720p() {
|
||||
let body = build_batch_request(mini_request(
|
||||
KinoviAspectRatioRaw::Landscape16x9,
|
||||
None,
|
||||
@@ -768,7 +761,7 @@ mod tests {
|
||||
));
|
||||
let json = serde_json::to_string(&body).unwrap();
|
||||
assert!(json.contains(r#""aspectRatio":"16:9""#), "{json}");
|
||||
assert!(!json.contains("outputResolution"), "{json}");
|
||||
assert!(json.contains(r#""outputResolution":"720p""#), "{json}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -124,6 +124,7 @@ pub async fn run(state: &ArtcraftState, args: GenerateVideoArgs) -> anyhow::Resu
|
||||
duration_seconds: Some(duration),
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
estimate_only: None,
|
||||
};
|
||||
|
||||
info!("Sending request to omni endpoint (idempotency_token={})...", idempotency_token);
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::first_party_minimax_h3::minimax_h3_ultra_cost::estimate_minimax_h3_ultra_cost_usd_cents;
|
||||
use artcraft_router::generate::generate_video::providers::first_party::minimax_h3_ultra_cost::estimate_minimax_h3_ultra_cost_usd_cents;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::helpers::bill_wallet::bill_wallet;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::helpers::write_prompt_records::{
|
||||
write_prompt_records, WritePromptRecordsArgs,
|
||||
@@ -86,7 +86,7 @@ pub async fn enqueue_first_party_minimax_h3_job(
|
||||
None
|
||||
}
|
||||
FirstPartyMinimaxH3Model::Ultra => {
|
||||
Some(estimate_minimax_h3_ultra_cost_usd_cents(request))
|
||||
Some(estimate_minimax_h3_ultra_cost_usd_cents(request.duration_seconds, request.resolution))
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
-1
@@ -5,4 +5,3 @@
|
||||
//! scheduler runs the jobs later.
|
||||
|
||||
pub mod enqueue_first_party_minimax_h3_job;
|
||||
pub mod minimax_h3_ultra_cost;
|
||||
|
||||
+302
-254
@@ -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)),
|
||||
];
|
||||
|
||||
+731
-683
File diff suppressed because it is too large
Load Diff
+365
-317
@@ -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 {
|
||||
|
||||
+53
@@ -4,6 +4,7 @@
|
||||
//! with dummy Actix HTTP requests as a fixture user.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -794,6 +795,58 @@ pub async fn assert_generation_fails_and_charges_nothing(
|
||||
);
|
||||
}
|
||||
|
||||
/// Run the same generation twice — once without and once with a reference
|
||||
/// video, as two independent fixture users — and assert the referenced
|
||||
/// generation is CHARGED strictly more credits. The no-reference generation
|
||||
/// succeeds outright; the referenced one uses an unreachable fixture (charge
|
||||
/// then refund), so its charge is read from the ledger debit entry.
|
||||
pub async fn assert_references_charge_more(
|
||||
harness: &TestHarness,
|
||||
model: CommonVideoModel,
|
||||
resolution: Option<enums::common::generation::common_resolution::CommonResolution>,
|
||||
Seconds(duration_seconds): Seconds,
|
||||
) {
|
||||
let no_ref_user = harness.create_funded_user(STARTING_CREDITS).await;
|
||||
let mut request = base_generate_request(model);
|
||||
request.resolution = resolution;
|
||||
request.duration_seconds = Some(duration_seconds);
|
||||
let response = harness
|
||||
.post_generate(&no_ref_user, request)
|
||||
.await
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("{:?} {:?} {}s: no-reference generation failed: {:?}", model, resolution, duration_seconds, err)
|
||||
});
|
||||
assert!(response.success);
|
||||
let no_ref_debit = STARTING_CREDITS - harness.wallet_balance(&no_ref_user).await;
|
||||
|
||||
let ref_user = harness.create_funded_user(STARTING_CREDITS).await;
|
||||
let video_token = mysql_testing::fixtures::media_files::create_test_video_media_file(
|
||||
&harness.pool,
|
||||
&ref_user.user_token,
|
||||
Some(6_000),
|
||||
)
|
||||
.await
|
||||
.expect("create video media file fixture");
|
||||
let mut request = base_generate_request(model);
|
||||
request.resolution = resolution;
|
||||
request.duration_seconds = Some(duration_seconds);
|
||||
request.reference_video_media_tokens = Some(vec![video_token]);
|
||||
// The unreachable fixture fails after billing; the charge is in the ledger.
|
||||
let _ = harness.post_generate(&ref_user, request).await;
|
||||
let entries = harness.ledger_entries(&ref_user).await;
|
||||
let ref_debit = entries
|
||||
.iter()
|
||||
.find(|entry| entry.credits_delta < 0)
|
||||
.unwrap_or_else(|| panic!("{:?} {:?} {}s: no ledger debit for referenced generation", model, resolution, duration_seconds));
|
||||
let ref_debit = u64::try_from(-ref_debit.credits_delta).expect("positive debit");
|
||||
|
||||
assert!(
|
||||
ref_debit > no_ref_debit,
|
||||
"{:?} {:?} {}s: references must charge more (ref {} vs no-ref {})",
|
||||
model, resolution, duration_seconds, ref_debit, no_ref_debit,
|
||||
);
|
||||
}
|
||||
|
||||
/// Assert the variant model charges a premium over the base model: runs one
|
||||
/// generation on EACH model, asserts both exact charges, and that
|
||||
/// `variant == base + delta`. (A delta of 0 is allowed where ceil-rounding
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -94,8 +94,19 @@ function PortalTooltip({
|
||||
useEffect(() => {
|
||||
if (!isShowing) return;
|
||||
const handleScroll = () => checkVisibilityAndUpdatePosition();
|
||||
// Escape dismisses the tooltip — required because the outside-safe marker
|
||||
// below makes the enclosing Modal defer its own Escape handling to us.
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
if (e.key !== "Escape") return;
|
||||
if (showTimeoutRef.current) clearTimeout(showTimeoutRef.current);
|
||||
setIsShowing(false);
|
||||
};
|
||||
window.addEventListener("scroll", handleScroll, true);
|
||||
return () => window.removeEventListener("scroll", handleScroll, true);
|
||||
window.addEventListener("keydown", handleKey);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", handleScroll, true);
|
||||
window.removeEventListener("keydown", handleKey);
|
||||
};
|
||||
}, [isShowing, checkVisibilityAndUpdatePosition]);
|
||||
|
||||
return (
|
||||
@@ -112,6 +123,9 @@ function PortalTooltip({
|
||||
createPortal(
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
// Body-portaled, so inside a Radix modal (promptbox focus mode)
|
||||
// clicks here must not count as outside clicks on the dialog.
|
||||
data-modal-outside-safe=""
|
||||
onMouseEnter={() => setIsHoveringTooltip(true)}
|
||||
onMouseLeave={() => setIsHoveringTooltip(false)}
|
||||
style={{
|
||||
@@ -228,6 +242,9 @@ function InfoHint({ content }: { content: ReactNode }) {
|
||||
{open &&
|
||||
createPortal(
|
||||
<div
|
||||
// Body-portaled, so inside a Radix modal (promptbox focus mode)
|
||||
// taps here must not count as outside clicks on the dialog.
|
||||
data-modal-outside-safe=""
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: position.top,
|
||||
@@ -548,7 +565,9 @@ function SubmenuFlyout({
|
||||
}, [isShowing, updatePosition, setIsShowing]);
|
||||
|
||||
// Dismiss on outside press — needed for the click/touch open path, where
|
||||
// there is no hover-out to close the flyout.
|
||||
// there is no hover-out to close the flyout. Escape also dismisses: the
|
||||
// outside-safe marker on the panel makes the enclosing Modal defer its own
|
||||
// Escape handling while the flyout is open.
|
||||
useEffect(() => {
|
||||
if (!isShowing) return;
|
||||
const onPointerDown = (e: MouseEvent) => {
|
||||
@@ -557,8 +576,15 @@ function SubmenuFlyout({
|
||||
if (panelRef.current?.contains(target)) return;
|
||||
setIsShowing(false);
|
||||
};
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") setIsShowing(false);
|
||||
};
|
||||
window.addEventListener("mousedown", onPointerDown, true);
|
||||
return () => window.removeEventListener("mousedown", onPointerDown, true);
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => {
|
||||
window.removeEventListener("mousedown", onPointerDown, true);
|
||||
window.removeEventListener("keydown", onKey);
|
||||
};
|
||||
}, [isShowing, setIsShowing]);
|
||||
|
||||
return (
|
||||
@@ -582,8 +608,20 @@ function SubmenuFlyout({
|
||||
createPortal(
|
||||
<div
|
||||
ref={panelRef}
|
||||
// Body-portaled: a modal Radix dialog (promptbox focus mode) sets
|
||||
// pointer-events: none on <body>, so re-enable them explicitly or
|
||||
// the flyout can never be hovered and closes on row mouse-leave.
|
||||
// The outside-safe marker keeps clicks in here from counting as
|
||||
// outside clicks that would close the dialog.
|
||||
data-modal-outside-safe=""
|
||||
onMouseEnter={cancelClose}
|
||||
onMouseLeave={scheduleClose}
|
||||
// The focus-mode modal's scroll lock (react-remove-scroll) blocks
|
||||
// any wheel/touch event that bubbles to document from outside the
|
||||
// dialog — and this body-portaled panel counts as outside. Stop
|
||||
// propagation so the list's native scrolling keeps working.
|
||||
onWheel={(e) => e.stopPropagation()}
|
||||
onTouchMove={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: position.top,
|
||||
@@ -591,7 +629,7 @@ function SubmenuFlyout({
|
||||
transform: "translateY(-50%)",
|
||||
zIndex: 9999,
|
||||
}}
|
||||
className="w-[340px] rounded-lg border border-ui-panel-border bg-ui-panel p-1.5 text-base-fg shadow-xl"
|
||||
className="pointer-events-auto w-[340px] rounded-lg border border-ui-panel-border bg-ui-panel p-1.5 text-base-fg shadow-xl"
|
||||
>
|
||||
<div className="mb-1 mt-0.5 px-1.5 text-sm font-normal text-base-fg opacity-70">
|
||||
{item.label}
|
||||
|
||||
@@ -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 &&
|
||||
@@ -1492,9 +1497,47 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
|
||||
(e: React.ClipboardEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
const text = e.clipboardData.getData("text/plain");
|
||||
document.execCommand("insertText", false, text);
|
||||
const el = editorRef.current;
|
||||
const sel = window.getSelection();
|
||||
if (!text || !el || !sel?.rangeCount) return;
|
||||
|
||||
// Not execCommand("insertText"): Chromium runs a separate editing
|
||||
// command per pasted line (each re-canonicalizing positions over the
|
||||
// whole document), so large multi-line prompts froze the tab for
|
||||
// seconds. Splice in a single text node instead — whitespace-pre-wrap
|
||||
// renders its "\n"s — and let handleInput do the usual serialize +
|
||||
// rebuild. This skips the native undo stack, but any rebuild
|
||||
// (mentions, newlines) already invalidated it.
|
||||
try {
|
||||
const range = sel.getRangeAt(0);
|
||||
// A boundary inside an atomic chip would drop the pasted text into
|
||||
// the contenteditable=false span, where serialization ignores it —
|
||||
// snap such boundaries to the chip's outside edge.
|
||||
const startChip = chipContaining(el, range.startContainer);
|
||||
const endChip = chipContaining(el, range.endContainer);
|
||||
if (range.collapsed) {
|
||||
if (startChip) {
|
||||
range.setStartAfter(startChip);
|
||||
range.collapse(true);
|
||||
}
|
||||
} else {
|
||||
if (startChip) range.setStartBefore(startChip);
|
||||
if (endChip) range.setEndAfter(endChip);
|
||||
}
|
||||
range.deleteContents();
|
||||
const node = document.createTextNode(text);
|
||||
range.insertNode(node);
|
||||
range.setStartAfter(node);
|
||||
range.collapse(true);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
} catch (err) {
|
||||
console.debug("handlePaste: DOM changed during insert", err);
|
||||
return;
|
||||
}
|
||||
handleInput();
|
||||
},
|
||||
[],
|
||||
[handleInput],
|
||||
);
|
||||
|
||||
// Copy/cut must yield plain text with mention labels intact ("@Name"),
|
||||
@@ -1523,7 +1566,12 @@ export const MentionTextarea = forwardRef<HTMLDivElement, MentionTextareaProps>(
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative flex-1 min-w-0 pb-[7px]">
|
||||
// min-h-0: in the focus-mode modal this wrapper is a flex child of a
|
||||
// height-bounded column; without it, flexbox's min-height:auto keeps the
|
||||
// wrapper at full content height, the modal's overflow-hidden clips it,
|
||||
// and the editor's own overflow-y-auto never engages (the "focus mode
|
||||
// doesn't scroll" bug). Inline, the row is content-sized, so it's a no-op.
|
||||
<div className="relative flex-1 min-w-0 min-h-0 pb-[7px]">
|
||||
{!value && placeholder && (
|
||||
<div
|
||||
className={twMerge(
|
||||
@@ -1592,6 +1640,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}
|
||||
|
||||
@@ -306,11 +306,10 @@ export const PromptBox2D = ({
|
||||
}
|
||||
};
|
||||
|
||||
const getCurrentAspectRatioIcon = () => {
|
||||
const getCurrentAspectRatioIcon = (): LucideIcon => {
|
||||
const selected = aspectRatioList.find((item) => item.selected);
|
||||
if (!selected || !selected.icon) return RectangleHorizontalIcon;
|
||||
const iconElement = selected.icon as React.ReactElement<{ icon: LucideIcon }>;
|
||||
return iconElement.props.icon;
|
||||
if (!selected?.icon) return RectangleHorizontalIcon;
|
||||
return (selected.icon as React.ReactElement).type as LucideIcon;
|
||||
};
|
||||
|
||||
// const getCurrentAspectRatio = (): EnqueueEditImageSize => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ChevronDownIcon, ChevronUpIcon, EraserIcon, FrameIcon, MaximizeIcon, MousePointerIcon, MoveIcon, PenIcon, RectangleHorizontalIcon, RectangleVerticalIcon, Redo2Icon, RotateCcwIcon, SquareIcon, SquarePenIcon } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button, GenerateButton } from "@storyteller/ui-button";
|
||||
import { ButtonIconSelect } from "@storyteller/ui-button-icon-select";
|
||||
@@ -174,11 +175,10 @@ export const PromptBoxEdit = ({
|
||||
setResolution(selectedItem.label.toLowerCase() as any);
|
||||
};
|
||||
|
||||
const getCurrentAspectRatioIcon = () => {
|
||||
const getCurrentAspectRatioIcon = (): LucideIcon => {
|
||||
const selected = aspectRatioList.find((item) => item.selected);
|
||||
if (!selected || !selected.icon) return RectangleHorizontalIcon;
|
||||
const iconElement = selected.icon as React.ReactElement<{ icon: any }>;
|
||||
return iconElement.props.icon;
|
||||
if (!selected?.icon) return RectangleHorizontalIcon;
|
||||
return (selected.icon as React.ReactElement).type as LucideIcon;
|
||||
};
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
useCallback,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@@ -260,7 +254,7 @@ export const ReferenceDeck = ({
|
||||
onClick={handleAddClick}
|
||||
className="glass flex aspect-square w-14 -rotate-6 flex-col items-center justify-center gap-0.5 rounded-lg border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all duration-200 hover:rotate-0 hover:scale-105 hover:bg-ui-controls/60 dark:border-white/25"
|
||||
>
|
||||
<PlusIcon className="text-lg opacity-80" />
|
||||
<PlusIcon className="text-lg opacity-80" />
|
||||
<span className="text-[9px] font-medium leading-none opacity-70">
|
||||
{emptyLabel}
|
||||
</span>
|
||||
@@ -277,8 +271,10 @@ export const ReferenceDeck = ({
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass w-max rounded-xl border border-white/10 p-2 shadow-2xl",
|
||||
// In-flow (fullscreen modal): cap at the container width so the
|
||||
// cards wrap into rows instead of overflowing the modal.
|
||||
alwaysExpanded
|
||||
? "relative"
|
||||
? "relative max-w-full"
|
||||
: twMerge(
|
||||
"absolute bottom-0 left-0 z-40 origin-bottom-left transition-all duration-200 ease-out",
|
||||
expanded
|
||||
@@ -302,12 +298,19 @@ export const ReferenceDeck = ({
|
||||
onClick={onClearAll}
|
||||
className="flex items-center gap-1 rounded px-1 py-0.5 text-[11px] text-base-fg/60 transition-colors hover:text-red-400"
|
||||
>
|
||||
<Trash2Icon className="h-2.5 w-2.5" />
|
||||
<Trash2Icon className="h-2.5 w-2.5" />
|
||||
Clear all
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{/* Rows are 56px cards + 8px gap; cap at three rows in the fullscreen
|
||||
modal and scroll past that so a huge deck can't crush the editor. */}
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex flex-wrap gap-2",
|
||||
alwaysExpanded && "max-h-[184px] overflow-y-auto overscroll-contain",
|
||||
)}
|
||||
>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
@@ -361,7 +364,7 @@ export const ReferenceDeck = ({
|
||||
onClick={handleAddClick}
|
||||
className="glass flex aspect-square w-14 shrink-0 items-center justify-center rounded-lg border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all hover:bg-ui-controls/60 dark:border-white/25"
|
||||
>
|
||||
<PlusIcon className="text-xl opacity-80" />
|
||||
<PlusIcon className="text-xl opacity-80" />
|
||||
</button>,
|
||||
)}
|
||||
</div>
|
||||
@@ -370,7 +373,16 @@ export const ReferenceDeck = ({
|
||||
|
||||
if (alwaysExpanded) {
|
||||
return (
|
||||
<div className={twMerge("relative flex shrink-0 items-center", className)}>
|
||||
// No shrink-0 here: hosts may place this in a flex row (webapp focus
|
||||
// mode), where the w-max panel makes the root's preferred width the
|
||||
// full unwrapped card row. min-w-0 + shrink lets the root collapse to
|
||||
// the row's width so the panel's max-w-full has a bounded reference.
|
||||
<div
|
||||
className={twMerge(
|
||||
"relative flex min-w-0 max-w-full items-center",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<DeckStyles />
|
||||
{expandedPanel}
|
||||
<DeckPreviewModal
|
||||
@@ -428,7 +440,7 @@ export const ReferenceDeck = ({
|
||||
))}
|
||||
{overflowCount > 0 && (
|
||||
<div className="pointer-events-none absolute -right-1 -top-1 z-10 rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-bold text-white shadow">
|
||||
+{overflowCount}
|
||||
{items.length}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user