mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-22 09:39:31 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41d209d46f |
+68
-1
@@ -1,6 +1,8 @@
|
||||
use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum;
|
||||
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::client_error::ClientError;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::providers::artcraft::build_common::{
|
||||
build_artcraft_omni_video_request, SupportedResolutions, UltraWideSupport,
|
||||
@@ -9,7 +11,13 @@ use crate::generate::generate_video::providers::artcraft::kling_1_6_pro::request
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use crate::generate::generate_video::video_generation_request::VideoGenerationRequest;
|
||||
|
||||
pub fn build_artcraft_kling_1_6_pro(builder: GenerateVideoRequestBuilder) -> Result<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
pub fn build_artcraft_kling_1_6_pro(mut builder: GenerateVideoRequestBuilder) -> Result<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
// Kling 1.6 Pro serves only {5, 10} seconds. Pre-plan the duration with the
|
||||
// SAME normalization the dispatching provider uses, so the billed duration
|
||||
// always matches the generated one.
|
||||
let strategy = builder.request_mismatch_mitigation_strategy;
|
||||
builder.duration_seconds = plan_kling_1_6_pro_duration(builder.duration_seconds, strategy)?;
|
||||
|
||||
let request = build_artcraft_omni_video_request(
|
||||
builder,
|
||||
CommonVideoModelEnum::Kling16Pro,
|
||||
@@ -19,3 +27,62 @@ pub fn build_artcraft_kling_1_6_pro(builder: GenerateVideoRequestBuilder) -> Res
|
||||
let state = ArtcraftKling16ProRequestState { request };
|
||||
Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftKling16Pro(state)))
|
||||
}
|
||||
|
||||
/// Mirrors the Fal-side `plan_duration` for Kling 1.6 Pro: unsupported
|
||||
/// durations upgrade to 10s (PayMoreUpgrade) or downgrade to 5s
|
||||
/// (PayLessDowngrade).
|
||||
fn plan_kling_1_6_pro_duration(
|
||||
duration_seconds: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<u16>, ArtcraftRouterError> {
|
||||
match duration_seconds {
|
||||
None => Ok(None),
|
||||
Some(5) | Some(10) => Ok(duration_seconds),
|
||||
Some(other) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "duration_seconds",
|
||||
value: format!("{}", other),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade => Ok(Some(10)),
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(Some(5)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
mod duration_planning {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn supported_durations_pass_through() {
|
||||
let s = RequestMismatchMitigationStrategy::PayMoreUpgrade;
|
||||
assert_eq!(plan_kling_1_6_pro_duration(None, s).unwrap(), None);
|
||||
assert_eq!(plan_kling_1_6_pro_duration(Some(5), s).unwrap(), Some(5));
|
||||
assert_eq!(plan_kling_1_6_pro_duration(Some(10), s).unwrap(), Some(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_duration_upgrades_to_ten() {
|
||||
let s = RequestMismatchMitigationStrategy::PayMoreUpgrade;
|
||||
assert_eq!(plan_kling_1_6_pro_duration(Some(4), s).unwrap(), Some(10));
|
||||
assert_eq!(plan_kling_1_6_pro_duration(Some(7), s).unwrap(), Some(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_duration_downgrades_to_five() {
|
||||
let s = RequestMismatchMitigationStrategy::PayLessDowngrade;
|
||||
assert_eq!(plan_kling_1_6_pro_duration(Some(4), s).unwrap(), Some(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_duration_errors_in_strict_mode() {
|
||||
let s = RequestMismatchMitigationStrategy::ErrorOut;
|
||||
assert!(plan_kling_1_6_pro_duration(Some(4), s).is_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -15,8 +15,8 @@ impl ArtcraftKling16ProCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Mirrors fal_client kling_v1p6_pro: 5s = 48¢, 10s = 95¢.
|
||||
let cost_in_usd_cents: u64 = if self.is_ten_seconds { 95 } else { 48 };
|
||||
// 5s = 52¢, 10s = 103¢.
|
||||
let cost_in_usd_cents: u64 = if self.is_ten_seconds { 103 } else { 52 };
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -51,11 +51,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five_seconds_is_48() { assert_eq!(cost_cents(Some(5)), 48); }
|
||||
fn five_seconds_is_52() { assert_eq!(cost_cents(Some(5)), 52); }
|
||||
|
||||
#[test]
|
||||
fn ten_seconds_is_95() { assert_eq!(cost_cents(Some(10)), 95); }
|
||||
fn ten_seconds_is_103() { assert_eq!(cost_cents(Some(10)), 103); }
|
||||
|
||||
#[test]
|
||||
fn default_duration_is_5s_priced_at_48() { assert_eq!(cost_cents(None), 48); }
|
||||
fn default_duration_is_5s_priced_at_52() { assert_eq!(cost_cents(None), 52); }
|
||||
}
|
||||
|
||||
+2
-1
@@ -20,7 +20,8 @@ pub fn build_artcraft_seedance_1p0_lite(mut builder: GenerateVideoRequestBuilder
|
||||
let request = build_artcraft_omni_video_request(
|
||||
builder,
|
||||
CommonVideoModelEnum::Seedance10Lite,
|
||||
SupportedResolutions::Fast,
|
||||
// The model serves 480p/720p/1080p; 1080p must plan (and price) as 1080p.
|
||||
SupportedResolutions::Full,
|
||||
UltraWideSupport::Supported,
|
||||
)?;
|
||||
let state = ArtcraftSeedance10LiteRequestState { request };
|
||||
|
||||
+20
-14
@@ -23,14 +23,15 @@ impl ArtcraftSeedance10LiteCostState {
|
||||
};
|
||||
let resolution = match request.request.resolution {
|
||||
Some(CommonResolutionEnum::FourEightyP) => Seedance1LiteResolution::FourEightyP,
|
||||
Some(CommonResolutionEnum::TenEightyP) => Seedance1LiteResolution::TenEightyP,
|
||||
_ => Seedance1LiteResolution::SevenTwentyP,
|
||||
};
|
||||
Self { duration, resolution }
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Delegate to the Fal client's cost calculator to guarantee parity with
|
||||
// what we charge through the underlying provider.
|
||||
// Derived from the Fal client's cost calculator so the price tracks
|
||||
// upstream pricing across every resolution and duration.
|
||||
let req = Seedance1LiteRequest {
|
||||
image_url: String::new(),
|
||||
end_frame_image_url: None,
|
||||
@@ -41,7 +42,7 @@ impl ArtcraftSeedance10LiteCostState {
|
||||
camera_fixed: false,
|
||||
seed: None,
|
||||
};
|
||||
let cost_in_usd_cents = req.calculate_cost_in_cents();
|
||||
let cost_in_usd_cents = (req.calculate_cost_in_cents() * 21).div_ceil(20);
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -75,22 +76,27 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_is_720p_5s_priced_at_18() {
|
||||
// 720p + 5s is the special-cased price from the fal calculator: 18¢.
|
||||
assert_eq!(cost_cents(None, None), 18);
|
||||
fn default_is_720p_5s_priced_at_19() {
|
||||
assert_eq!(cost_cents(None, None), 19);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn p720_5s_is_18() { assert_eq!(cost_cents(Some(5), Some(RouterResolution::SevenTwentyP)), 18); }
|
||||
fn p480_5s_is_10() { assert_eq!(cost_cents(Some(5), Some(RouterResolution::FourEightyP)), 10); }
|
||||
|
||||
#[test]
|
||||
fn p720_10s_is_95() {
|
||||
// 1280×720×30×10/1024 = 270000 tokens × $1.8/M = $0.486 → ceil = 49¢ ...
|
||||
// No wait — fal kling not seedance. The Seedance 1.0 Lite rate is different.
|
||||
// Trust the fal calculator: assert whatever the actual computed value is.
|
||||
let v = cost_cents(Some(10), Some(RouterResolution::SevenTwentyP));
|
||||
assert!(v > 18, "10s should cost more than 5s, got {}", v);
|
||||
}
|
||||
fn p480_10s_is_18() { assert_eq!(cost_cents(Some(10), Some(RouterResolution::FourEightyP)), 18); }
|
||||
|
||||
#[test]
|
||||
fn p720_5s_is_19() { assert_eq!(cost_cents(Some(5), Some(RouterResolution::SevenTwentyP)), 19); }
|
||||
|
||||
#[test]
|
||||
fn p720_10s_is_52() { assert_eq!(cost_cents(Some(10), Some(RouterResolution::SevenTwentyP)), 52); }
|
||||
|
||||
#[test]
|
||||
fn p1080_5s_is_58() { assert_eq!(cost_cents(Some(5), Some(RouterResolution::TenEightyP)), 58); }
|
||||
|
||||
#[test]
|
||||
fn p1080_10s_is_116() { assert_eq!(cost_cents(Some(10), Some(RouterResolution::TenEightyP)), 116); }
|
||||
|
||||
#[test]
|
||||
fn p480_5s_cheaper_than_p720_5s() {
|
||||
|
||||
Reference in New Issue
Block a user