mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-31 15:37:49 +00:00
new struct for costs
This commit is contained in:
1 parent
8497a8c83a
commit
277e820b53
15 files changed
+334
-200
No files matched your search
+2
-2
@@ -66,8 +66,8 @@ impl KinoviSeedance2p0CostState {
|
||||
};
|
||||
|
||||
let costs = pricing_request.calculate_costs();
|
||||
let cost_in_credits = costs.kinovi_credits;
|
||||
let cost_in_usd_cents = costs.usd_cents_rounded_up;
|
||||
let cost_in_credits = costs.total_cost.kinovi_credits;
|
||||
let cost_in_usd_cents = costs.total_cost.usd_cents_rounded_up;
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits),
|
||||
|
||||
+2
-2
@@ -66,8 +66,8 @@ impl KinoviSeedance2p0FastCostState {
|
||||
};
|
||||
|
||||
let costs = pricing_request.calculate_costs();
|
||||
let cost_in_credits = costs.kinovi_credits;
|
||||
let cost_in_usd_cents = costs.usd_cents_rounded_up;
|
||||
let cost_in_credits = costs.total_cost.kinovi_credits;
|
||||
let cost_in_usd_cents = costs.total_cost.usd_cents_rounded_up;
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits),
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/// Kinovi credits per US dollar.
|
||||
/// Credit package: 22,000 credits for $114 (~192.98 credits/$1, rounded to 193).
|
||||
pub const CREDITS_PER_DOLLAR: u64 = 193;
|
||||
+2
-3
@@ -1,8 +1,7 @@
|
||||
/// Kinovi credits per US dollar.
|
||||
/// Credit package: 22,000 credits for $114 (~192.98 credits/$1, rounded to 193).
|
||||
const CREDITS_PER_DOLLAR: u64 = 193;
|
||||
use crate::cost::constants::CREDITS_PER_DOLLAR;
|
||||
|
||||
/// The cost of a Kinovi generation, in both native credits and USD cents.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct KinoviGenerationCost {
|
||||
/// The cost of the generation in Kinovi credits.
|
||||
pub kinovi_credits: u64,
|
||||
@@ -0,0 +1,69 @@
|
||||
use crate::cost::kinovi_generation_cost::KinoviGenerationCost;
|
||||
|
||||
/// The cost of a Kinovi Seedance generation, with the video-reference
|
||||
/// surcharge broken out from the base price.
|
||||
///
|
||||
/// `total_cost` covers `base_credits_cost + video_reference_surcharge_cost`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct KinoviSeedanceGenerationCost {
|
||||
/// The full cost (base + any surcharge), in credits and USD cents.
|
||||
pub total_cost: KinoviGenerationCost,
|
||||
|
||||
/// The base generation cost in Kinovi credits (resolution rate × output
|
||||
/// duration × batch count), excluding any surcharges.
|
||||
pub base_credits_cost: u64,
|
||||
|
||||
/// The video-reference surcharge in Kinovi credits, when one or more
|
||||
/// reference videos are attached. `None` when no reference videos are
|
||||
/// attached.
|
||||
pub video_reference_surcharge_cost: Option<u64>,
|
||||
}
|
||||
|
||||
impl KinoviSeedanceGenerationCost {
|
||||
/// Build a cost from base credits plus an optional video-reference
|
||||
/// surcharge. The total (and its USD conversion) is derived.
|
||||
pub fn from_base_and_surcharge(
|
||||
base_credits_cost: u64,
|
||||
video_reference_surcharge_cost: Option<u64>,
|
||||
) -> Self {
|
||||
let total_credits = base_credits_cost + video_reference_surcharge_cost.unwrap_or(0);
|
||||
Self {
|
||||
total_cost: KinoviGenerationCost::from_kinovi_credits(total_credits),
|
||||
base_credits_cost,
|
||||
video_reference_surcharge_cost,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn without_surcharge() {
|
||||
let cost = KinoviSeedanceGenerationCost::from_base_and_surcharge(200, None);
|
||||
assert_eq!(cost.base_credits_cost, 200);
|
||||
assert_eq!(cost.video_reference_surcharge_cost, None);
|
||||
assert_eq!(cost.total_cost.kinovi_credits, 200);
|
||||
// 20000/193 = 103.63 -> 104.
|
||||
assert_eq!(cost.total_cost.usd_cents_rounded_up, 104);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_surcharge() {
|
||||
let cost = KinoviSeedanceGenerationCost::from_base_and_surcharge(200, Some(40));
|
||||
assert_eq!(cost.base_credits_cost, 200);
|
||||
assert_eq!(cost.video_reference_surcharge_cost, Some(40));
|
||||
assert_eq!(cost.total_cost.kinovi_credits, 240);
|
||||
// 24000/193 = 124.35 -> 125.
|
||||
assert_eq!(cost.total_cost.usd_cents_rounded_up, 125);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn total_is_base_plus_surcharge() {
|
||||
let cost = KinoviSeedanceGenerationCost::from_base_and_surcharge(450, Some(90));
|
||||
assert_eq!(
|
||||
cost.total_cost.kinovi_credits,
|
||||
cost.base_credits_cost + cost.video_reference_surcharge_cost.unwrap());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod constants;
|
||||
pub mod kinovi_generation_cost;
|
||||
pub mod kinovi_seedance_generation_cost;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::cost::kinovi_generation_cost::KinoviGenerationCost;
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::requests::generate_image::generate_image::{
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::cost::kinovi_generation_cost::KinoviGenerationCost;
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::requests::generate_image::generate_image::{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::cost::kinovi_generation_cost::KinoviGenerationCost;
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::requests::generate_image::generate_image::{
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod cost;
|
||||
pub mod image;
|
||||
pub mod video;
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::cost::kinovi_generation_cost::KinoviGenerationCost;
|
||||
use crate::requests::kinovi_host::KinoviHost;
|
||||
use crate::requests::workflow_run_task::workflow_run_task::{
|
||||
workflow_run_task, KinoviAspectRatioRaw, KinoviBatchCountRaw,
|
||||
|
||||
+136
-105
@@ -1,6 +1,6 @@
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::cost::kinovi_seedance_generation_cost::KinoviSeedanceGenerationCost;
|
||||
use crate::requests::kinovi_host::KinoviHost;
|
||||
use crate::requests::workflow_run_task::workflow_run_task::{
|
||||
workflow_run_task, KinoviAspectRatioRaw, KinoviBatchCountRaw,
|
||||
@@ -80,7 +80,7 @@ impl GenerateSeedance2p0Request {
|
||||
///
|
||||
/// Attaching reference VIDEOS adds a per-output-second surcharge (see the
|
||||
/// pricing table below). Reference images and audio are free.
|
||||
pub fn calculate_costs(&self) -> KinoviGenerationCost {
|
||||
pub fn calculate_costs(&self) -> KinoviSeedanceGenerationCost {
|
||||
let credits_per_second: u64 = match self.output_resolution {
|
||||
Some(KinoviSeedance2p0OutputResolution::FourEightyP) => 15,
|
||||
Some(KinoviSeedance2p0OutputResolution::SevenTwentyP) | None => 40,
|
||||
@@ -108,15 +108,24 @@ impl GenerateSeedance2p0Request {
|
||||
0
|
||||
};
|
||||
|
||||
let per_video = u64::from(self.duration_seconds)
|
||||
* (credits_per_second + video_reference_surcharge_per_second);
|
||||
let batch_multiplier: u64 = match self.batch_count {
|
||||
None | Some(KinoviSeedance2p0BatchCount::One) => 1,
|
||||
Some(KinoviSeedance2p0BatchCount::Two) => 2,
|
||||
Some(KinoviSeedance2p0BatchCount::Four) => 4,
|
||||
};
|
||||
|
||||
KinoviGenerationCost::from_kinovi_credits(per_video * batch_multiplier)
|
||||
let duration = u64::from(self.duration_seconds);
|
||||
let base_credits_cost = duration * credits_per_second * batch_multiplier;
|
||||
let video_reference_surcharge_cost = if self.has_video_reference() {
|
||||
Some(duration * video_reference_surcharge_per_second * batch_multiplier)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
KinoviSeedanceGenerationCost::from_base_and_surcharge(
|
||||
base_credits_cost,
|
||||
video_reference_surcharge_cost,
|
||||
)
|
||||
}
|
||||
|
||||
fn has_video_reference(&self) -> bool {
|
||||
@@ -128,14 +137,14 @@ impl GenerateSeedance2p0Request {
|
||||
/// Estimate the credit cost for this generation request.
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.calculate_costs().kinovi_credits as u32
|
||||
self.calculate_costs().total_cost.kinovi_credits as u32
|
||||
}
|
||||
|
||||
/// Estimate the USD cost in cents for this generation request.
|
||||
/// NB: Rounds UP fractional cents (the historical behavior rounded to nearest).
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_cost_in_usd_cents(&self) -> u64 {
|
||||
self.calculate_costs().usd_cents_rounded_up
|
||||
self.calculate_costs().total_cost.usd_cents_rounded_up
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,19 +278,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r480(3).calculate_costs().kinovi_credits, 45);
|
||||
assert_eq!(r480(4).calculate_costs().kinovi_credits, 60);
|
||||
assert_eq!(r480(5).calculate_costs().kinovi_credits, 75);
|
||||
assert_eq!(r480(6).calculate_costs().kinovi_credits, 90);
|
||||
assert_eq!(r480(7).calculate_costs().kinovi_credits, 105);
|
||||
assert_eq!(r480(8).calculate_costs().kinovi_credits, 120);
|
||||
assert_eq!(r480(9).calculate_costs().kinovi_credits, 135);
|
||||
assert_eq!(r480(10).calculate_costs().kinovi_credits, 150);
|
||||
assert_eq!(r480(11).calculate_costs().kinovi_credits, 165);
|
||||
assert_eq!(r480(12).calculate_costs().kinovi_credits, 180);
|
||||
assert_eq!(r480(13).calculate_costs().kinovi_credits, 195);
|
||||
assert_eq!(r480(14).calculate_costs().kinovi_credits, 210);
|
||||
assert_eq!(r480(15).calculate_costs().kinovi_credits, 225);
|
||||
assert_eq!(r480(3).calculate_costs().total_cost.kinovi_credits, 45);
|
||||
assert_eq!(r480(4).calculate_costs().total_cost.kinovi_credits, 60);
|
||||
assert_eq!(r480(5).calculate_costs().total_cost.kinovi_credits, 75);
|
||||
assert_eq!(r480(6).calculate_costs().total_cost.kinovi_credits, 90);
|
||||
assert_eq!(r480(7).calculate_costs().total_cost.kinovi_credits, 105);
|
||||
assert_eq!(r480(8).calculate_costs().total_cost.kinovi_credits, 120);
|
||||
assert_eq!(r480(9).calculate_costs().total_cost.kinovi_credits, 135);
|
||||
assert_eq!(r480(10).calculate_costs().total_cost.kinovi_credits, 150);
|
||||
assert_eq!(r480(11).calculate_costs().total_cost.kinovi_credits, 165);
|
||||
assert_eq!(r480(12).calculate_costs().total_cost.kinovi_credits, 180);
|
||||
assert_eq!(r480(13).calculate_costs().total_cost.kinovi_credits, 195);
|
||||
assert_eq!(r480(14).calculate_costs().total_cost.kinovi_credits, 210);
|
||||
assert_eq!(r480(15).calculate_costs().total_cost.kinovi_credits, 225);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,25 +301,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r720(3).calculate_costs().kinovi_credits, 120);
|
||||
assert_eq!(r720(4).calculate_costs().kinovi_credits, 160);
|
||||
assert_eq!(r720(5).calculate_costs().kinovi_credits, 200);
|
||||
assert_eq!(r720(6).calculate_costs().kinovi_credits, 240);
|
||||
assert_eq!(r720(7).calculate_costs().kinovi_credits, 280);
|
||||
assert_eq!(r720(8).calculate_costs().kinovi_credits, 320);
|
||||
assert_eq!(r720(9).calculate_costs().kinovi_credits, 360);
|
||||
assert_eq!(r720(10).calculate_costs().kinovi_credits, 400);
|
||||
assert_eq!(r720(11).calculate_costs().kinovi_credits, 440);
|
||||
assert_eq!(r720(12).calculate_costs().kinovi_credits, 480);
|
||||
assert_eq!(r720(13).calculate_costs().kinovi_credits, 520);
|
||||
assert_eq!(r720(14).calculate_costs().kinovi_credits, 560);
|
||||
assert_eq!(r720(15).calculate_costs().kinovi_credits, 600);
|
||||
assert_eq!(r720(3).calculate_costs().total_cost.kinovi_credits, 120);
|
||||
assert_eq!(r720(4).calculate_costs().total_cost.kinovi_credits, 160);
|
||||
assert_eq!(r720(5).calculate_costs().total_cost.kinovi_credits, 200);
|
||||
assert_eq!(r720(6).calculate_costs().total_cost.kinovi_credits, 240);
|
||||
assert_eq!(r720(7).calculate_costs().total_cost.kinovi_credits, 280);
|
||||
assert_eq!(r720(8).calculate_costs().total_cost.kinovi_credits, 320);
|
||||
assert_eq!(r720(9).calculate_costs().total_cost.kinovi_credits, 360);
|
||||
assert_eq!(r720(10).calculate_costs().total_cost.kinovi_credits, 400);
|
||||
assert_eq!(r720(11).calculate_costs().total_cost.kinovi_credits, 440);
|
||||
assert_eq!(r720(12).calculate_costs().total_cost.kinovi_credits, 480);
|
||||
assert_eq!(r720(13).calculate_costs().total_cost.kinovi_credits, 520);
|
||||
assert_eq!(r720(14).calculate_costs().total_cost.kinovi_credits, 560);
|
||||
assert_eq!(r720(15).calculate_costs().total_cost.kinovi_credits, 600);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_720p_same_as_default() {
|
||||
let default = r720(5).calculate_costs().kinovi_credits;
|
||||
let explicit = build_request(5, Some(KinoviSeedance2p0OutputResolution::SevenTwentyP), None).calculate_costs().kinovi_credits;
|
||||
let default = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let explicit = build_request(5, Some(KinoviSeedance2p0OutputResolution::SevenTwentyP), None).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(default, explicit);
|
||||
}
|
||||
}
|
||||
@@ -322,19 +331,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r1080(3).calculate_costs().kinovi_credits, 270);
|
||||
assert_eq!(r1080(4).calculate_costs().kinovi_credits, 360);
|
||||
assert_eq!(r1080(5).calculate_costs().kinovi_credits, 450);
|
||||
assert_eq!(r1080(6).calculate_costs().kinovi_credits, 540);
|
||||
assert_eq!(r1080(7).calculate_costs().kinovi_credits, 630);
|
||||
assert_eq!(r1080(8).calculate_costs().kinovi_credits, 720);
|
||||
assert_eq!(r1080(9).calculate_costs().kinovi_credits, 810);
|
||||
assert_eq!(r1080(10).calculate_costs().kinovi_credits, 900);
|
||||
assert_eq!(r1080(11).calculate_costs().kinovi_credits, 990);
|
||||
assert_eq!(r1080(12).calculate_costs().kinovi_credits, 1080);
|
||||
assert_eq!(r1080(13).calculate_costs().kinovi_credits, 1170);
|
||||
assert_eq!(r1080(14).calculate_costs().kinovi_credits, 1260);
|
||||
assert_eq!(r1080(15).calculate_costs().kinovi_credits, 1350);
|
||||
assert_eq!(r1080(3).calculate_costs().total_cost.kinovi_credits, 270);
|
||||
assert_eq!(r1080(4).calculate_costs().total_cost.kinovi_credits, 360);
|
||||
assert_eq!(r1080(5).calculate_costs().total_cost.kinovi_credits, 450);
|
||||
assert_eq!(r1080(6).calculate_costs().total_cost.kinovi_credits, 540);
|
||||
assert_eq!(r1080(7).calculate_costs().total_cost.kinovi_credits, 630);
|
||||
assert_eq!(r1080(8).calculate_costs().total_cost.kinovi_credits, 720);
|
||||
assert_eq!(r1080(9).calculate_costs().total_cost.kinovi_credits, 810);
|
||||
assert_eq!(r1080(10).calculate_costs().total_cost.kinovi_credits, 900);
|
||||
assert_eq!(r1080(11).calculate_costs().total_cost.kinovi_credits, 990);
|
||||
assert_eq!(r1080(12).calculate_costs().total_cost.kinovi_credits, 1080);
|
||||
assert_eq!(r1080(13).calculate_costs().total_cost.kinovi_credits, 1170);
|
||||
assert_eq!(r1080(14).calculate_costs().total_cost.kinovi_credits, 1260);
|
||||
assert_eq!(r1080(15).calculate_costs().total_cost.kinovi_credits, 1350);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,39 +354,39 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_1_is_base() {
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let explicit = build_request(5, None, Some(KinoviSeedance2p0BatchCount::One)).calculate_costs().kinovi_credits;
|
||||
let base = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let explicit = build_request(5, None, Some(KinoviSeedance2p0BatchCount::One)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(base, explicit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_doubles() {
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
let base = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch2 = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Two)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_quadruples() {
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let batch4 = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
let base = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch4 = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_multiplier_applies_to_1080p() {
|
||||
let base = r1080(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0OutputResolution::TenEightyP), Some(KinoviSeedance2p0BatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0OutputResolution::TenEightyP), Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
let base = r1080(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0OutputResolution::TenEightyP), Some(KinoviSeedance2p0BatchCount::Two)).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0OutputResolution::TenEightyP), Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_multiplier_applies_to_480p() {
|
||||
let base = r480(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0OutputResolution::FourEightyP), Some(KinoviSeedance2p0BatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0OutputResolution::FourEightyP), Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
let base = r480(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0OutputResolution::FourEightyP), Some(KinoviSeedance2p0BatchCount::Two)).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0OutputResolution::FourEightyP), Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
@@ -390,9 +399,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_scales_with_duration() {
|
||||
let c3 = r720(3).calculate_costs().kinovi_credits;
|
||||
let c10 = r720(10).calculate_costs().kinovi_credits;
|
||||
let c15 = r720(15).calculate_costs().kinovi_credits;
|
||||
let c3 = r720(3).calculate_costs().total_cost.kinovi_credits;
|
||||
let c10 = r720(10).calculate_costs().total_cost.kinovi_credits;
|
||||
let c15 = r720(15).calculate_costs().total_cost.kinovi_credits;
|
||||
assert!(c3 < c10);
|
||||
assert!(c10 < c15);
|
||||
}
|
||||
@@ -400,9 +409,9 @@ mod tests {
|
||||
#[test]
|
||||
fn resolution_ordering() {
|
||||
for dur in 3..=15u8 {
|
||||
let c480 = build_request(dur, Some(KinoviSeedance2p0OutputResolution::FourEightyP), None).calculate_costs().kinovi_credits;
|
||||
let c720 = build_request(dur, None, None).calculate_costs().kinovi_credits;
|
||||
let c1080 = build_request(dur, Some(KinoviSeedance2p0OutputResolution::TenEightyP), None).calculate_costs().kinovi_credits;
|
||||
let c480 = build_request(dur, Some(KinoviSeedance2p0OutputResolution::FourEightyP), None).calculate_costs().total_cost.kinovi_credits;
|
||||
let c720 = build_request(dur, None, None).calculate_costs().total_cost.kinovi_credits;
|
||||
let c1080 = build_request(dur, Some(KinoviSeedance2p0OutputResolution::TenEightyP), None).calculate_costs().total_cost.kinovi_credits;
|
||||
assert!(c480 < c720, "480p should be cheaper than 720p at {}s", dur);
|
||||
assert!(c720 < c1080, "720p should be cheaper than 1080p at {}s", dur);
|
||||
}
|
||||
@@ -420,48 +429,60 @@ mod tests {
|
||||
fn costs_480p_5s() {
|
||||
// 15 credits/s × 5s = 75 credits; 7500/193 = 38.86 → 39¢
|
||||
let costs = r480(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 75);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 39);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 75);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 39);
|
||||
assert_eq!(costs.base_credits_cost, 75);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn costs_720p_5s() {
|
||||
// 40 credits/s × 5s = 200 credits; 20000/193 = 103.63 → 104¢
|
||||
let costs = r720(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 200);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 104);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 200);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 104);
|
||||
assert_eq!(costs.base_credits_cost, 200);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn costs_1080p_5s() {
|
||||
// 90 credits/s × 5s = 450 credits; 45000/193 = 233.16 → rounds UP to 234¢
|
||||
let costs = r1080(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 450);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 234);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 450);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 234);
|
||||
assert_eq!(costs.base_credits_cost, 450);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn costs_720p_15s() {
|
||||
// 40 credits/s × 15s = 600 credits; 60000/193 = 310.88 → 311¢
|
||||
let costs = r720(15).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 600);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 311);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 600);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 311);
|
||||
assert_eq!(costs.base_credits_cost, 600);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn costs_1080p_15s() {
|
||||
// 90 credits/s × 15s = 1350 credits; 135000/193 = 699.48 → rounds UP to 700¢
|
||||
let costs = r1080(15).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 1350);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 700);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 1350);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 700);
|
||||
assert_eq!(costs.base_credits_cost, 1350);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn costs_batch_4_720p_5s() {
|
||||
// 200 credits × 4 = 800 credits; 80000/193 = 414.51 → 415¢
|
||||
let costs = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 800);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 415);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 800);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 415);
|
||||
assert_eq!(costs.base_credits_cost, 800);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
/// The deprecated shims return the corresponding struct fields.
|
||||
@@ -470,8 +491,8 @@ mod tests {
|
||||
fn deprecated_methods_delegate() {
|
||||
let request = r720(5);
|
||||
let costs = request.calculate_costs();
|
||||
assert_eq!(u64::from(request.estimate_credits()), costs.kinovi_credits);
|
||||
assert_eq!(request.estimate_cost_in_usd_cents(), costs.usd_cents_rounded_up);
|
||||
assert_eq!(u64::from(request.estimate_credits()), costs.total_cost.kinovi_credits);
|
||||
assert_eq!(request.estimate_cost_in_usd_cents(), costs.total_cost.usd_cents_rounded_up);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,47 +510,54 @@ mod tests {
|
||||
}
|
||||
|
||||
/// The full base + surcharge table from Kinovi's pricing page
|
||||
/// ("With Video Uploads", 10 sec video ref).
|
||||
/// ("With Video Uploads", 10 sec video ref). Asserts every field:
|
||||
/// base, surcharge, and the derived total.
|
||||
#[test]
|
||||
fn kinovi_pricing_table_with_video_reference() {
|
||||
let cases: &[(fn(u8) -> GenerateSeedance2p0Request, u8, u64)] = &[
|
||||
// (request, duration, base credits, surcharge credits)
|
||||
let cases: &[(fn(u8) -> GenerateSeedance2p0Request, u8, u64, u64)] = &[
|
||||
// 480p: base + 4/s
|
||||
(r480, 4, 60 + 16),
|
||||
(r480, 5, 75 + 20),
|
||||
(r480, 10, 150 + 40),
|
||||
(r480, 15, 225 + 60),
|
||||
(r480, 4, 60, 16),
|
||||
(r480, 5, 75, 20),
|
||||
(r480, 10, 150, 40),
|
||||
(r480, 15, 225, 60),
|
||||
// 720p: base + 8/s
|
||||
(r720, 4, 160 + 32),
|
||||
(r720, 5, 200 + 40),
|
||||
(r720, 10, 400 + 80),
|
||||
(r720, 15, 600 + 120),
|
||||
(r720, 4, 160, 32),
|
||||
(r720, 5, 200, 40),
|
||||
(r720, 10, 400, 80),
|
||||
(r720, 15, 600, 120),
|
||||
// 1080p: base + 18/s
|
||||
(r1080, 4, 360 + 72),
|
||||
(r1080, 5, 450 + 90),
|
||||
(r1080, 10, 900 + 180),
|
||||
(r1080, 15, 1350 + 270),
|
||||
(r1080, 4, 360, 72),
|
||||
(r1080, 5, 450, 90),
|
||||
(r1080, 10, 900, 180),
|
||||
(r1080, 15, 1350, 270),
|
||||
];
|
||||
|
||||
for (make, duration, expected_credits) in cases {
|
||||
for (make, duration, base, surcharge) in cases {
|
||||
let costs = with_video_ref(make(*duration)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, *expected_credits,
|
||||
"duration {duration}s should cost {expected_credits} credits with a video reference");
|
||||
assert_eq!(costs.base_credits_cost, *base, "base for {duration}s");
|
||||
assert_eq!(costs.video_reference_surcharge_cost, Some(*surcharge), "surcharge for {duration}s");
|
||||
assert_eq!(costs.total_cost.kinovi_credits, base + surcharge, "total for {duration}s");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn surcharge_includes_usd_cents() {
|
||||
// 720p 5s + video ref = 240 credits; 24000/193 = 124.35 → 125¢
|
||||
// 720p 5s + video ref = 200 + 40 = 240 credits; 24000/193 = 124.35 → 125¢
|
||||
let costs = with_video_ref(r720(5)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 240);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 125);
|
||||
assert_eq!(costs.base_credits_cost, 200);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, Some(40));
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 240);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 125);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_video_reference_list_has_no_surcharge() {
|
||||
let mut request = r720(5);
|
||||
request.reference_video_urls = Some(vec![]);
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 200);
|
||||
let costs = request.calculate_costs();
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 200);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
/// Surcharge is flat per generation regardless of how many reference
|
||||
@@ -541,7 +569,7 @@ mod tests {
|
||||
"https://example.com/a.mp4".to_string(),
|
||||
"https://example.com/b.mp4".to_string(),
|
||||
]);
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 240);
|
||||
assert_eq!(request.calculate_costs().total_cost.kinovi_credits, 240);
|
||||
}
|
||||
|
||||
/// The surcharge applies per generated video, so batches multiply it.
|
||||
@@ -549,7 +577,10 @@ mod tests {
|
||||
fn batch_multiplies_surcharge() {
|
||||
let request = with_video_ref(build_request(5, None, Some(KinoviSeedance2p0BatchCount::Two)));
|
||||
// (200 base + 40 surcharge) × 2 = 480 credits
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 480);
|
||||
let costs = request.calculate_costs();
|
||||
assert_eq!(costs.base_credits_cost, 400);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, Some(80));
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 480);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,7 +588,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_does_not_affect_credits() {
|
||||
let baseline = r720(5).calculate_costs().kinovi_credits;
|
||||
let baseline = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
|
||||
let ratios = [
|
||||
KinoviSeedance2p0AspectRatio::Landscape16x9,
|
||||
@@ -584,7 +615,7 @@ mod tests {
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
assert_eq!(
|
||||
req.calculate_costs().kinovi_credits, baseline,
|
||||
req.calculate_costs().total_cost.kinovi_credits, baseline,
|
||||
"Aspect ratio {:?} should not change credits from baseline {}", ar, baseline,
|
||||
);
|
||||
}
|
||||
|
||||
+111
-82
@@ -1,6 +1,6 @@
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::cost::kinovi_seedance_generation_cost::KinoviSeedanceGenerationCost;
|
||||
use crate::requests::kinovi_host::KinoviHost;
|
||||
use crate::requests::workflow_run_task::workflow_run_task::{
|
||||
workflow_run_task, KinoviAspectRatioRaw, KinoviBatchCountRaw,
|
||||
@@ -78,7 +78,7 @@ impl GenerateSeedance2p0FastRequest {
|
||||
///
|
||||
/// Attaching reference VIDEOS adds a per-output-second surcharge (see the
|
||||
/// pricing table below). Reference images and audio are free.
|
||||
pub fn calculate_costs(&self) -> KinoviGenerationCost {
|
||||
pub fn calculate_costs(&self) -> KinoviSeedanceGenerationCost {
|
||||
let credits_per_second: u64 = match self.output_resolution {
|
||||
Some(KinoviSeedance2p0FastOutputResolution::FourEightyP) => 14,
|
||||
Some(KinoviSeedance2p0FastOutputResolution::SevenTwentyP) | None => 28,
|
||||
@@ -103,15 +103,24 @@ impl GenerateSeedance2p0FastRequest {
|
||||
0
|
||||
};
|
||||
|
||||
let per_video = u64::from(self.duration_seconds)
|
||||
* (credits_per_second + video_reference_surcharge_per_second);
|
||||
let batch_multiplier: u64 = match self.batch_count {
|
||||
None | Some(KinoviSeedance2p0FastBatchCount::One) => 1,
|
||||
Some(KinoviSeedance2p0FastBatchCount::Two) => 2,
|
||||
Some(KinoviSeedance2p0FastBatchCount::Four) => 4,
|
||||
};
|
||||
|
||||
KinoviGenerationCost::from_kinovi_credits(per_video * batch_multiplier)
|
||||
let duration = u64::from(self.duration_seconds);
|
||||
let base_credits_cost = duration * credits_per_second * batch_multiplier;
|
||||
let video_reference_surcharge_cost = if self.has_video_reference() {
|
||||
Some(duration * video_reference_surcharge_per_second * batch_multiplier)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
KinoviSeedanceGenerationCost::from_base_and_surcharge(
|
||||
base_credits_cost,
|
||||
video_reference_surcharge_cost,
|
||||
)
|
||||
}
|
||||
|
||||
fn has_video_reference(&self) -> bool {
|
||||
@@ -123,14 +132,14 @@ impl GenerateSeedance2p0FastRequest {
|
||||
/// Estimate the credit cost for this generation request.
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.calculate_costs().kinovi_credits as u32
|
||||
self.calculate_costs().total_cost.kinovi_credits as u32
|
||||
}
|
||||
|
||||
/// Estimate the USD cost in cents for this generation request.
|
||||
/// NB: Rounds UP fractional cents (the historical behavior rounded to nearest).
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_cost_in_usd_cents(&self) -> u64 {
|
||||
self.calculate_costs().usd_cents_rounded_up
|
||||
self.calculate_costs().total_cost.usd_cents_rounded_up
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,19 +268,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r480(3).calculate_costs().kinovi_credits, 42);
|
||||
assert_eq!(r480(4).calculate_costs().kinovi_credits, 56);
|
||||
assert_eq!(r480(5).calculate_costs().kinovi_credits, 70);
|
||||
assert_eq!(r480(6).calculate_costs().kinovi_credits, 84);
|
||||
assert_eq!(r480(7).calculate_costs().kinovi_credits, 98);
|
||||
assert_eq!(r480(8).calculate_costs().kinovi_credits, 112);
|
||||
assert_eq!(r480(9).calculate_costs().kinovi_credits, 126);
|
||||
assert_eq!(r480(10).calculate_costs().kinovi_credits, 140);
|
||||
assert_eq!(r480(11).calculate_costs().kinovi_credits, 154);
|
||||
assert_eq!(r480(12).calculate_costs().kinovi_credits, 168);
|
||||
assert_eq!(r480(13).calculate_costs().kinovi_credits, 182);
|
||||
assert_eq!(r480(14).calculate_costs().kinovi_credits, 196);
|
||||
assert_eq!(r480(15).calculate_costs().kinovi_credits, 210);
|
||||
assert_eq!(r480(3).calculate_costs().total_cost.kinovi_credits, 42);
|
||||
assert_eq!(r480(4).calculate_costs().total_cost.kinovi_credits, 56);
|
||||
assert_eq!(r480(5).calculate_costs().total_cost.kinovi_credits, 70);
|
||||
assert_eq!(r480(6).calculate_costs().total_cost.kinovi_credits, 84);
|
||||
assert_eq!(r480(7).calculate_costs().total_cost.kinovi_credits, 98);
|
||||
assert_eq!(r480(8).calculate_costs().total_cost.kinovi_credits, 112);
|
||||
assert_eq!(r480(9).calculate_costs().total_cost.kinovi_credits, 126);
|
||||
assert_eq!(r480(10).calculate_costs().total_cost.kinovi_credits, 140);
|
||||
assert_eq!(r480(11).calculate_costs().total_cost.kinovi_credits, 154);
|
||||
assert_eq!(r480(12).calculate_costs().total_cost.kinovi_credits, 168);
|
||||
assert_eq!(r480(13).calculate_costs().total_cost.kinovi_credits, 182);
|
||||
assert_eq!(r480(14).calculate_costs().total_cost.kinovi_credits, 196);
|
||||
assert_eq!(r480(15).calculate_costs().total_cost.kinovi_credits, 210);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,25 +291,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r720(3).calculate_costs().kinovi_credits, 84);
|
||||
assert_eq!(r720(4).calculate_costs().kinovi_credits, 112);
|
||||
assert_eq!(r720(5).calculate_costs().kinovi_credits, 140);
|
||||
assert_eq!(r720(6).calculate_costs().kinovi_credits, 168);
|
||||
assert_eq!(r720(7).calculate_costs().kinovi_credits, 196);
|
||||
assert_eq!(r720(8).calculate_costs().kinovi_credits, 224);
|
||||
assert_eq!(r720(9).calculate_costs().kinovi_credits, 252);
|
||||
assert_eq!(r720(10).calculate_costs().kinovi_credits, 280);
|
||||
assert_eq!(r720(11).calculate_costs().kinovi_credits, 308);
|
||||
assert_eq!(r720(12).calculate_costs().kinovi_credits, 336);
|
||||
assert_eq!(r720(13).calculate_costs().kinovi_credits, 364);
|
||||
assert_eq!(r720(14).calculate_costs().kinovi_credits, 392);
|
||||
assert_eq!(r720(15).calculate_costs().kinovi_credits, 420);
|
||||
assert_eq!(r720(3).calculate_costs().total_cost.kinovi_credits, 84);
|
||||
assert_eq!(r720(4).calculate_costs().total_cost.kinovi_credits, 112);
|
||||
assert_eq!(r720(5).calculate_costs().total_cost.kinovi_credits, 140);
|
||||
assert_eq!(r720(6).calculate_costs().total_cost.kinovi_credits, 168);
|
||||
assert_eq!(r720(7).calculate_costs().total_cost.kinovi_credits, 196);
|
||||
assert_eq!(r720(8).calculate_costs().total_cost.kinovi_credits, 224);
|
||||
assert_eq!(r720(9).calculate_costs().total_cost.kinovi_credits, 252);
|
||||
assert_eq!(r720(10).calculate_costs().total_cost.kinovi_credits, 280);
|
||||
assert_eq!(r720(11).calculate_costs().total_cost.kinovi_credits, 308);
|
||||
assert_eq!(r720(12).calculate_costs().total_cost.kinovi_credits, 336);
|
||||
assert_eq!(r720(13).calculate_costs().total_cost.kinovi_credits, 364);
|
||||
assert_eq!(r720(14).calculate_costs().total_cost.kinovi_credits, 392);
|
||||
assert_eq!(r720(15).calculate_costs().total_cost.kinovi_credits, 420);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_720p_same_as_default() {
|
||||
let default = r720(5).calculate_costs().kinovi_credits;
|
||||
let explicit = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::SevenTwentyP), None).calculate_costs().kinovi_credits;
|
||||
let default = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let explicit = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::SevenTwentyP), None).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(default, explicit);
|
||||
}
|
||||
}
|
||||
@@ -312,30 +321,30 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_1_is_base() {
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let explicit = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::One)).calculate_costs().kinovi_credits;
|
||||
let base = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let explicit = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::One)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(base, explicit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_doubles() {
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
let base = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch2 = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Two)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_quadruples() {
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let batch4 = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
let base = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch4 = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Four)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_multiplier_applies_to_480p() {
|
||||
let base = r480(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), Some(KinoviSeedance2p0FastBatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), Some(KinoviSeedance2p0FastBatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
let base = r480(5).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), Some(KinoviSeedance2p0FastBatchCount::Two)).calculate_costs().total_cost.kinovi_credits;
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), Some(KinoviSeedance2p0FastBatchCount::Four)).calculate_costs().total_cost.kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
@@ -348,9 +357,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_scales_with_duration() {
|
||||
let c3 = r720(3).calculate_costs().kinovi_credits;
|
||||
let c10 = r720(10).calculate_costs().kinovi_credits;
|
||||
let c15 = r720(15).calculate_costs().kinovi_credits;
|
||||
let c3 = r720(3).calculate_costs().total_cost.kinovi_credits;
|
||||
let c10 = r720(10).calculate_costs().total_cost.kinovi_credits;
|
||||
let c15 = r720(15).calculate_costs().total_cost.kinovi_credits;
|
||||
assert!(c3 < c10);
|
||||
assert!(c10 < c15);
|
||||
}
|
||||
@@ -358,8 +367,8 @@ mod tests {
|
||||
#[test]
|
||||
fn resolution_ordering() {
|
||||
for dur in 3..=15u8 {
|
||||
let c480 = build_request(dur, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), None).calculate_costs().kinovi_credits;
|
||||
let c720 = build_request(dur, None, None).calculate_costs().kinovi_credits;
|
||||
let c480 = build_request(dur, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), None).calculate_costs().total_cost.kinovi_credits;
|
||||
let c720 = build_request(dur, None, None).calculate_costs().total_cost.kinovi_credits;
|
||||
assert!(c480 < c720, "480p should be cheaper than 720p at {}s", dur);
|
||||
}
|
||||
}
|
||||
@@ -376,40 +385,50 @@ mod tests {
|
||||
fn costs_480p_5s() {
|
||||
// 14 credits/s × 5s = 70 credits; 7000/193 = 36.27 → rounds UP to 37¢
|
||||
let costs = r480(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 70);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 37);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 70);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 37);
|
||||
assert_eq!(costs.base_credits_cost, 70);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn costs_720p_5s() {
|
||||
// 28 credits/s × 5s = 140 credits; 14000/193 = 72.54 → 73¢
|
||||
let costs = r720(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 140);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 73);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 140);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 73);
|
||||
assert_eq!(costs.base_credits_cost, 140);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn costs_720p_15s() {
|
||||
// 28 credits/s × 15s = 420 credits; 42000/193 = 217.62 → 218¢
|
||||
let costs = r720(15).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 420);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 218);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 420);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 218);
|
||||
assert_eq!(costs.base_credits_cost, 420);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn costs_480p_15s() {
|
||||
// 14 credits/s × 15s = 210 credits; 21000/193 = 108.81 → 109¢
|
||||
let costs = r480(15).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 210);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 109);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 210);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 109);
|
||||
assert_eq!(costs.base_credits_cost, 210);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn costs_batch_2_720p_5s() {
|
||||
// 140 credits × 2 = 280 credits; 28000/193 = 145.07 → 146¢
|
||||
let costs = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Two)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 280);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 146);
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 280);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 146);
|
||||
assert_eq!(costs.base_credits_cost, 280);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
/// The deprecated shims return the corresponding struct fields.
|
||||
@@ -418,8 +437,8 @@ mod tests {
|
||||
fn deprecated_methods_delegate() {
|
||||
let request = r720(5);
|
||||
let costs = request.calculate_costs();
|
||||
assert_eq!(u64::from(request.estimate_credits()), costs.kinovi_credits);
|
||||
assert_eq!(request.estimate_cost_in_usd_cents(), costs.usd_cents_rounded_up);
|
||||
assert_eq!(u64::from(request.estimate_credits()), costs.total_cost.kinovi_credits);
|
||||
assert_eq!(request.estimate_cost_in_usd_cents(), costs.total_cost.usd_cents_rounded_up);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,42 +457,49 @@ mod tests {
|
||||
}
|
||||
|
||||
/// The full base + surcharge table from Kinovi's pricing page
|
||||
/// ("Seedance 2.0 Fast With Video Ref", 10 sec video ref).
|
||||
/// ("Seedance 2.0 Fast With Video Ref", 10 sec video ref). Asserts
|
||||
/// every field: base, surcharge, and the derived total.
|
||||
#[test]
|
||||
fn kinovi_pricing_table_with_video_reference() {
|
||||
let cases: &[(fn(u8) -> GenerateSeedance2p0FastRequest, u8, u64)] = &[
|
||||
// (request, duration, base credits, surcharge credits)
|
||||
let cases: &[(fn(u8) -> GenerateSeedance2p0FastRequest, u8, u64, u64)] = &[
|
||||
// 480p: base + 4/s
|
||||
(r480, 4, 56 + 16),
|
||||
(r480, 5, 70 + 20),
|
||||
(r480, 10, 140 + 40),
|
||||
(r480, 15, 210 + 60),
|
||||
(r480, 4, 56, 16),
|
||||
(r480, 5, 70, 20),
|
||||
(r480, 10, 140, 40),
|
||||
(r480, 15, 210, 60),
|
||||
// 720p: base + 6/s
|
||||
(r720, 4, 112 + 24),
|
||||
(r720, 5, 140 + 30),
|
||||
(r720, 10, 280 + 60),
|
||||
(r720, 15, 420 + 90),
|
||||
(r720, 4, 112, 24),
|
||||
(r720, 5, 140, 30),
|
||||
(r720, 10, 280, 60),
|
||||
(r720, 15, 420, 90),
|
||||
];
|
||||
|
||||
for (make, duration, expected_credits) in cases {
|
||||
for (make, duration, base, surcharge) in cases {
|
||||
let costs = with_video_ref(make(*duration)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, *expected_credits,
|
||||
"duration {duration}s should cost {expected_credits} credits with a video reference");
|
||||
assert_eq!(costs.base_credits_cost, *base, "base for {duration}s");
|
||||
assert_eq!(costs.video_reference_surcharge_cost, Some(*surcharge), "surcharge for {duration}s");
|
||||
assert_eq!(costs.total_cost.kinovi_credits, base + surcharge, "total for {duration}s");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn surcharge_includes_usd_cents() {
|
||||
// 720p 5s + video ref = 170 credits; 17000/193 = 88.08 → 89¢
|
||||
// 720p 5s + video ref = 140 + 30 = 170 credits; 17000/193 = 88.08 → 89¢
|
||||
let costs = with_video_ref(r720(5)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 170);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 89);
|
||||
assert_eq!(costs.base_credits_cost, 140);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, Some(30));
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 170);
|
||||
assert_eq!(costs.total_cost.usd_cents_rounded_up, 89);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_video_reference_list_has_no_surcharge() {
|
||||
let mut request = r720(5);
|
||||
request.reference_video_urls = Some(vec![]);
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 140);
|
||||
let costs = request.calculate_costs();
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 140);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, None);
|
||||
}
|
||||
|
||||
/// Surcharge is flat per generation regardless of how many reference
|
||||
@@ -485,7 +511,7 @@ mod tests {
|
||||
"https://example.com/a.mp4".to_string(),
|
||||
"https://example.com/b.mp4".to_string(),
|
||||
]);
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 170);
|
||||
assert_eq!(request.calculate_costs().total_cost.kinovi_credits, 170);
|
||||
}
|
||||
|
||||
/// The surcharge applies per generated video, so batches multiply it.
|
||||
@@ -493,7 +519,10 @@ mod tests {
|
||||
fn batch_multiplies_surcharge() {
|
||||
let request = with_video_ref(build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Two)));
|
||||
// (140 base + 30 surcharge) × 2 = 340 credits
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 340);
|
||||
let costs = request.calculate_costs();
|
||||
assert_eq!(costs.base_credits_cost, 280);
|
||||
assert_eq!(costs.video_reference_surcharge_cost, Some(60));
|
||||
assert_eq!(costs.total_cost.kinovi_credits, 340);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,7 +530,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_does_not_affect_credits() {
|
||||
let baseline = r720(5).calculate_costs().kinovi_credits;
|
||||
let baseline = r720(5).calculate_costs().total_cost.kinovi_credits;
|
||||
|
||||
let ratios = [
|
||||
KinoviSeedance2p0FastAspectRatio::Landscape16x9,
|
||||
@@ -528,7 +557,7 @@ mod tests {
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
assert_eq!(
|
||||
req.calculate_costs().kinovi_credits, baseline,
|
||||
req.calculate_costs().total_cost.kinovi_credits, baseline,
|
||||
"Aspect ratio {:?} should not change credits from baseline {}", ar, baseline,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
pub (crate) mod test_utils;
|
||||
|
||||
// User API
|
||||
pub mod cost;
|
||||
pub mod creds;
|
||||
pub mod error;
|
||||
pub mod generate;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::cost::kinovi_generation_cost::KinoviGenerationCost;
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_bad_request_api_error::Seedance2ProBadRequestApiError;
|
||||
use crate::error::seedance2pro_client_error::Seedance2ProClientError;
|
||||
|
||||
Reference in new issue
Block a user