mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-08 02:52:12 +00:00
clamp seconds
This commit is contained in:
1 parent
845de872ca
commit
7f0154efe4
3 files changed
+62
-9
No files matched your search
+18
-2
@@ -1,4 +1,5 @@
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
use kinovi_web_client::generate::video::generate_seedance_2p5::MAX_BILLED_INPUT_SECONDS;
|
||||
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p5_usd_cents;
|
||||
@@ -7,7 +8,9 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p5::request:
|
||||
/// Seedance 2.5 pricing depends on the resolution, the output duration, and
|
||||
/// — when reference videos are attached — the total seconds of reference
|
||||
/// video input, which are billed on top of the output duration (at a lower
|
||||
/// per-second rate). No batching.
|
||||
/// per-second rate). Input seconds are clamped to
|
||||
/// [`MAX_BILLED_INPUT_SECONDS`] — the model accepts at most 30 seconds of
|
||||
/// video. No batching.
|
||||
pub struct ArtcraftSeedance2p5CostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
@@ -34,11 +37,14 @@ impl ArtcraftSeedance2p5CostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
let total_input_seconds = self.total_input_seconds
|
||||
.min(u16::from(MAX_BILLED_INPUT_SECONDS));
|
||||
|
||||
let usd_cents = seedance_2p5_usd_cents(
|
||||
self.resolution,
|
||||
self.duration_seconds,
|
||||
self.has_video_references,
|
||||
self.total_input_seconds,
|
||||
total_input_seconds,
|
||||
);
|
||||
|
||||
// ArtCraft credits: 100 credits = $1.00, so credits = cents.
|
||||
@@ -114,6 +120,16 @@ mod tests {
|
||||
// 7.24279835 ¢/s × 10 = 72.43 → 73¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 10, None), 73);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_seconds_clamp_to_max_billed_input_seconds() {
|
||||
// 200 input seconds clamp to 30: 7.24279835 ¢/s × (30+30) = 434.57 → 435¢.
|
||||
assert_eq!(cents_with_video_refs(Some(RouterResolution::FourEightyP), 30, Some(200)), 435);
|
||||
assert_eq!(
|
||||
cents_with_video_refs(Some(RouterResolution::FourEightyP), 30, Some(200)),
|
||||
cents_with_video_refs(Some(RouterResolution::FourEightyP), 30, Some(30)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod credits_tests {
|
||||
|
||||
+20
-2
@@ -1,5 +1,6 @@
|
||||
use kinovi_web_client::generate::video::generate_seedance_2p5::{
|
||||
GenerateSeedance2p5Request, KinoviSeedance2p5Modality, KinoviSeedance2p5OutputResolution,
|
||||
MAX_BILLED_INPUT_SECONDS,
|
||||
};
|
||||
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
@@ -10,7 +11,9 @@ use crate::generate::generate_video::providers::kinovi::seedance_2p5::request::K
|
||||
/// Seedance 2.5 pricing depends on the resolution, the output duration, and
|
||||
/// — when reference videos are attached — the total seconds of reference
|
||||
/// video input, which are billed on top of the output duration (at a lower
|
||||
/// per-second rate).
|
||||
/// per-second rate). Input seconds are clamped to
|
||||
/// [`MAX_BILLED_INPUT_SECONDS`] — the model accepts at most 30 seconds of
|
||||
/// video.
|
||||
pub struct KinoviSeedance2p5CostState {
|
||||
pub resolution: Option<KinoviSeedance2p5OutputResolution>,
|
||||
pub duration_seconds: u8,
|
||||
@@ -70,12 +73,17 @@ impl KinoviSeedance2p5CostState {
|
||||
}
|
||||
};
|
||||
|
||||
// NB: The kinovi calculator clamps too; clamping here as well keeps this
|
||||
// state's math correct even if it's read directly.
|
||||
let total_input_seconds = self.total_input_seconds
|
||||
.map(|seconds| seconds.min(MAX_BILLED_INPUT_SECONDS));
|
||||
|
||||
let pricing_request = GenerateSeedance2p5Request {
|
||||
prompt: String::new(),
|
||||
modality,
|
||||
output_resolution: self.resolution,
|
||||
duration_seconds: self.duration_seconds,
|
||||
total_input_seconds: self.total_input_seconds,
|
||||
total_input_seconds,
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
|
||||
@@ -153,6 +161,16 @@ mod tests {
|
||||
fn missing_input_seconds_bill_output_duration_only() {
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::FourEightyP), 10, true, None), 160);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_seconds_clamp_to_max_billed_input_seconds() {
|
||||
// 200 input seconds clamp to 30: 30s output + 30 = 60 billed seconds.
|
||||
assert_eq!(credits(Some(KinoviOutputResolution::FourEightyP), 30, true, Some(200)), 16 * 60);
|
||||
assert_eq!(
|
||||
credits(Some(KinoviOutputResolution::FourEightyP), 30, true, Some(200)),
|
||||
credits(Some(KinoviOutputResolution::FourEightyP), 30, true, Some(30)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── USD cents (rounded up) ──
|
||||
|
||||
@@ -7,6 +7,12 @@ use crate::requests::workflow_run_task::workflow_run_task::{
|
||||
KinoviOutputResolutionRaw, WorkflowRunTaskArgs, WorkflowRunTaskRequest,
|
||||
};
|
||||
|
||||
// ── Constants ──
|
||||
|
||||
/// Seedance 2.5 supports at most 30 seconds of video: reference-video input
|
||||
/// seconds beyond this are clamped for billing.
|
||||
pub const MAX_BILLED_INPUT_SECONDS: u8 = 30;
|
||||
|
||||
// ── Args ──
|
||||
|
||||
pub struct GenerateSeedance2p5Args<'a> {
|
||||
@@ -111,9 +117,10 @@ pub enum KinoviSeedance2p5OutputResolution {
|
||||
//
|
||||
// With video references, the rate drops but the billed seconds are the
|
||||
// output duration PLUS the total seconds of reference video input
|
||||
// (`total_input_seconds`, summed across all reference videos). E.g. a 30s
|
||||
// output with a 10s video reference bills as 40 seconds; a 30s output with
|
||||
// two 7s references (14s total input) bills as 44 seconds.
|
||||
// (`total_input_seconds`, summed across all reference videos and clamped to
|
||||
// [`MAX_BILLED_INPUT_SECONDS`]). E.g. a 30s output with a 10s video
|
||||
// reference bills as 40 seconds; a 30s output with two 7s references (14s
|
||||
// total input) bills as 44 seconds.
|
||||
//
|
||||
// Default resolution (None) is 720p.
|
||||
// Credit package: 525,000 credits for $2,159.0909 (~243 credits/$1).
|
||||
@@ -132,8 +139,10 @@ impl GenerateSeedance2p5Request {
|
||||
Some(KinoviSeedance2p5OutputResolution::FourEightyP) => 16u64,
|
||||
Some(KinoviSeedance2p5OutputResolution::SevenTwentyP) | None => 35u64,
|
||||
};
|
||||
let seconds = u64::from(self.duration_seconds)
|
||||
+ u64::from(self.total_input_seconds.unwrap_or(0));
|
||||
let input_seconds = self.total_input_seconds
|
||||
.unwrap_or(MAX_BILLED_INPUT_SECONDS)
|
||||
.min(MAX_BILLED_INPUT_SECONDS);
|
||||
let seconds = u64::from(self.duration_seconds) + u64::from(input_seconds);
|
||||
(rate, seconds)
|
||||
} else {
|
||||
let rate = match self.output_resolution {
|
||||
@@ -389,6 +398,16 @@ mod tests {
|
||||
assert_eq!(video_ref_720(10, None).calculate_costs().kinovi_credits, 350.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_seconds_clamp_to_max_billed_input_seconds() {
|
||||
// 200 input seconds clamp to 30: 30s output + 30 = 60 billed seconds.
|
||||
assert_eq!(video_ref_480(30, Some(200)).calculate_costs().kinovi_credits, (16 * 60) as f64);
|
||||
assert_eq!(
|
||||
video_ref_480(30, Some(200)).calculate_costs(),
|
||||
video_ref_480(30, Some(MAX_BILLED_INPUT_SECONDS)).calculate_costs(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_references_still_use_video_reference_rates() {
|
||||
let mut request = video_ref_480(10, Some(5));
|
||||
|
||||
Reference in new issue
Block a user