mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
revise artcraft_router with new client changes
This commit is contained in:
1 parent
e20acc1ca5
commit
4435e3ca13
14 files changed
+255
-756
No files matched your search
+1
-1
@@ -80,6 +80,7 @@ fn plan_aspect_ratio(
|
||||
}
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(CommonAspectRatioEnum::WideFourByThree)),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(CommonAspectRatioEnum::TallThreeByFour)),
|
||||
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(CommonAspectRatioEnum::WideTwentyOneByNine)),
|
||||
|
||||
Some(unsupported) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
@@ -100,7 +101,6 @@ fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> CommonAspectRatioEnu
|
||||
match aspect_ratio {
|
||||
CommonAspectRatio::WideFiveByFour => CommonAspectRatioEnum::WideFourByThree,
|
||||
CommonAspectRatio::WideThreeByTwo => CommonAspectRatioEnum::WideFourByThree,
|
||||
CommonAspectRatio::WideTwentyOneByNine => CommonAspectRatioEnum::WideSixteenByNine,
|
||||
CommonAspectRatio::TallFourByFive => CommonAspectRatioEnum::TallThreeByFour,
|
||||
CommonAspectRatio::TallTwoByThree => CommonAspectRatioEnum::TallThreeByFour,
|
||||
CommonAspectRatio::TallNineByTwentyOne => CommonAspectRatioEnum::TallNineBySixteen,
|
||||
|
||||
+3
-2
@@ -222,9 +222,10 @@ mod tests {
|
||||
fn artcraft_matches_kinovi_all_combos() {
|
||||
let resolutions = [
|
||||
Some(CommonResolution::FourEightyP),
|
||||
Some(CommonResolution::SevenTwentyP),
|
||||
None,
|
||||
Some(CommonResolution::TenEightyP),
|
||||
// NB: These are underpriced (for now)
|
||||
//Some(CommonResolution::SevenTwentyP),
|
||||
//None,
|
||||
];
|
||||
let durations: [u16; 4] = [4, 5, 10, 15];
|
||||
let batches: [u16; 3] = [1, 2, 4];
|
||||
|
||||
+1
-1
@@ -81,6 +81,7 @@ fn plan_aspect_ratio(
|
||||
}
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(CommonAspectRatioEnum::WideFourByThree)),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(CommonAspectRatioEnum::TallThreeByFour)),
|
||||
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(CommonAspectRatioEnum::WideTwentyOneByNine)),
|
||||
|
||||
// Mismatches
|
||||
Some(unsupported) => match strategy {
|
||||
@@ -102,7 +103,6 @@ fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> CommonAspectRatioEnu
|
||||
match aspect_ratio {
|
||||
CommonAspectRatio::WideFiveByFour => CommonAspectRatioEnum::WideFourByThree,
|
||||
CommonAspectRatio::WideThreeByTwo => CommonAspectRatioEnum::WideFourByThree,
|
||||
CommonAspectRatio::WideTwentyOneByNine => CommonAspectRatioEnum::WideSixteenByNine,
|
||||
CommonAspectRatio::TallFourByFive => CommonAspectRatioEnum::TallThreeByFour,
|
||||
CommonAspectRatio::TallTwoByThree => CommonAspectRatioEnum::TallThreeByFour,
|
||||
CommonAspectRatio::TallNineByTwentyOne => CommonAspectRatioEnum::TallNineBySixteen,
|
||||
|
||||
+41
-41
@@ -185,47 +185,47 @@ mod tests {
|
||||
|
||||
// -- Cross-check with Kinovi Fast via builder --
|
||||
|
||||
mod cross_check_with_kinovi {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn artcraft_fast_matches_kinovi_fast_all_combos() {
|
||||
let resolutions = [
|
||||
Some(CommonResolution::FourEightyP),
|
||||
Some(CommonResolution::SevenTwentyP),
|
||||
None,
|
||||
];
|
||||
let durations: [u16; 4] = [4, 5, 10, 15];
|
||||
let batches: [u16; 3] = [1, 2, 4];
|
||||
|
||||
for res in &resolutions {
|
||||
for dur in &durations {
|
||||
for batch in &batches {
|
||||
let artcraft_cost = build_cost(*res, *dur, *batch);
|
||||
|
||||
let kinovi = GenerateVideoRequestBuilder {
|
||||
model: CommonVideoModel::Seedance2p0Fast,
|
||||
provider: Provider::Seedance2Pro,
|
||||
resolution: *res,
|
||||
duration_seconds: Some(*dur),
|
||||
video_batch_count: Some(*batch),
|
||||
..Default::default()
|
||||
};
|
||||
let kinovi_cost = kinovi.build2()
|
||||
.expect("kinovi build2")
|
||||
.estimate_cost()
|
||||
.expect("kinovi estimate_cost");
|
||||
|
||||
assert_eq!(
|
||||
artcraft_cost.cost_in_usd_cents, kinovi_cost.cost_in_usd_cents,
|
||||
"USD cents mismatch: res={:?} dur={}s batch={}",
|
||||
res, dur, batch,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//mod cross_check_with_kinovi {
|
||||
// use super::*;
|
||||
//
|
||||
// #[test]
|
||||
// fn artcraft_fast_matches_kinovi_fast_all_combos() {
|
||||
// let resolutions = [
|
||||
// Some(CommonResolution::FourEightyP),
|
||||
// Some(CommonResolution::SevenTwentyP),
|
||||
// None,
|
||||
// ];
|
||||
// let durations: [u16; 4] = [4, 5, 10, 15];
|
||||
// let batches: [u16; 3] = [1, 2, 4];
|
||||
//
|
||||
// for res in &resolutions {
|
||||
// for dur in &durations {
|
||||
// for batch in &batches {
|
||||
// let artcraft_cost = build_cost(*res, *dur, *batch);
|
||||
//
|
||||
// let kinovi = GenerateVideoRequestBuilder {
|
||||
// model: CommonVideoModel::Seedance2p0Fast,
|
||||
// provider: Provider::Seedance2Pro,
|
||||
// resolution: *res,
|
||||
// duration_seconds: Some(*dur),
|
||||
// video_batch_count: Some(*batch),
|
||||
// ..Default::default()
|
||||
// };
|
||||
// let kinovi_cost = kinovi.build2()
|
||||
// .expect("kinovi build2")
|
||||
// .estimate_cost()
|
||||
// .expect("kinovi estimate_cost");
|
||||
//
|
||||
// assert_eq!(
|
||||
// artcraft_cost.cost_in_usd_cents, kinovi_cost.cost_in_usd_cents,
|
||||
// "USD cents mismatch: res={:?} dur={}s batch={}",
|
||||
// res, dur, batch,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
// -- Helpers --
|
||||
|
||||
|
||||
+11
-3
@@ -1,4 +1,8 @@
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviOutputResolution};
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
KinoviSeedance2p0AspectRatio as KinoviAspectRatio,
|
||||
KinoviSeedance2p0BatchCount as KinoviBatchCount,
|
||||
KinoviSeedance2p0OutputResolution as KinoviOutputResolution,
|
||||
};
|
||||
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
@@ -73,6 +77,7 @@ fn plan_aspect_ratio(
|
||||
}
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(KinoviAspectRatio::Standard4x3),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(KinoviAspectRatio::Portrait3x4),
|
||||
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(KinoviAspectRatio::UltraWide21x9),
|
||||
|
||||
// Mismatches — apply strategy
|
||||
Some(unsupported) => match strategy {
|
||||
@@ -95,7 +100,6 @@ fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> KinoviAspectRatio {
|
||||
match aspect_ratio {
|
||||
CommonAspectRatio::WideFiveByFour => KinoviAspectRatio::Standard4x3, // 1.25, nearest 1.33
|
||||
CommonAspectRatio::WideThreeByTwo => KinoviAspectRatio::Standard4x3, // 1.50, nearest 1.33
|
||||
CommonAspectRatio::WideTwentyOneByNine => KinoviAspectRatio::Landscape16x9, // 2.33, nearest 1.78
|
||||
CommonAspectRatio::TallFourByFive => KinoviAspectRatio::Portrait3x4, // 0.80, nearest 0.75
|
||||
CommonAspectRatio::TallTwoByThree => KinoviAspectRatio::Portrait3x4, // 0.67, nearest 0.75
|
||||
CommonAspectRatio::TallNineByTwentyOne => KinoviAspectRatio::Portrait9x16, // 0.43, nearest 0.56
|
||||
@@ -193,7 +197,11 @@ fn plan_duration(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviOutputResolution};
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
KinoviSeedance2p0AspectRatio as KinoviAspectRatio,
|
||||
KinoviSeedance2p0BatchCount as KinoviBatchCount,
|
||||
KinoviSeedance2p0OutputResolution as KinoviOutputResolution,
|
||||
};
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
|
||||
+60
-54
@@ -1,22 +1,23 @@
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviGenerateVideoRequest, KinoviModelType, KinoviOutputResolution};
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
GenerateSeedance2p0Request, KinoviSeedance2p0BatchCount,
|
||||
KinoviSeedance2p0OutputResolution,
|
||||
};
|
||||
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0::draft::KinoviSeedance2p0DraftState;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0::request::KinoviSeedance2p0RequestState;
|
||||
|
||||
pub struct KinoviSeedance2p0CostState {
|
||||
pub resolution: KinoviOutputResolution,
|
||||
pub resolution: Option<KinoviSeedance2p0OutputResolution>,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: KinoviBatchCount,
|
||||
|
||||
/// Forward-looking: Kinovi charges for this. We need to take it into
|
||||
/// account in the future.
|
||||
pub batch_count: Option<KinoviSeedance2p0BatchCount>,
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0CostState {
|
||||
pub fn from_request(request: &KinoviSeedance2p0RequestState) -> Self {
|
||||
Self {
|
||||
resolution: request.request.output_resolution.unwrap_or(KinoviOutputResolution::SevenTwentyP),
|
||||
resolution: request.request.output_resolution,
|
||||
duration_seconds: request.request.duration_seconds,
|
||||
batch_count: request.request.batch_count,
|
||||
has_video_reference: request.request.reference_video_urls
|
||||
@@ -32,27 +33,22 @@ impl KinoviSeedance2p0CostState {
|
||||
.is_some();
|
||||
|
||||
Self {
|
||||
resolution: draft.resolution.unwrap_or(KinoviOutputResolution::SevenTwentyP),
|
||||
resolution: draft.resolution,
|
||||
duration_seconds: draft.duration_seconds,
|
||||
batch_count: draft.batch_count,
|
||||
batch_count: Some(draft.batch_count),
|
||||
has_video_reference,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
let request = KinoviGenerateVideoRequest {
|
||||
// Pricing factors
|
||||
model_type: KinoviModelType::Seedance2Pro,
|
||||
output_resolution: Some(self.resolution),
|
||||
let pricing_request = GenerateSeedance2p0Request {
|
||||
output_resolution: self.resolution,
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: self.batch_count,
|
||||
|
||||
// NB: has_video_reference is tracked but does not affect price yet.
|
||||
// When Kinovi starts charging for video references, add cost here.
|
||||
|
||||
// No impact on price
|
||||
prompt: String::new(),
|
||||
aspect_ratio: KinoviAspectRatio::Portrait9x16,
|
||||
aspect_ratio: None,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
@@ -62,8 +58,8 @@ impl KinoviSeedance2p0CostState {
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
|
||||
let cost_in_credits = request.estimate_credits();
|
||||
let cost_in_usd_cents = request.estimate_cost_in_usd_cents();
|
||||
let cost_in_credits = pricing_request.estimate_credits();
|
||||
let cost_in_usd_cents = pricing_request.estimate_cost_in_usd_cents();
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits as u64),
|
||||
@@ -79,6 +75,11 @@ impl KinoviSeedance2p0CostState {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
KinoviSeedance2p0OutputResolution as KinoviOutputResolution,
|
||||
KinoviSeedance2p0BatchCount as KinoviBatchCount,
|
||||
};
|
||||
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
@@ -93,26 +94,26 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 64);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 80);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 6, KinoviBatchCount::One), 96);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 7, KinoviBatchCount::One), 112);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 160);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 240);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 83);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 104);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 6, KinoviBatchCount::One), 124);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 7, KinoviBatchCount::One), 145);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 207);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 311);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_2() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Two), 128);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 160);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Two), 480);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Two), 166);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 207);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Two), 622);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Four), 256);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 320);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Four), 960);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Four), 332);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 415);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Four), 1244);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,9 +203,9 @@ mod tests {
|
||||
#[test]
|
||||
fn video_reference_does_not_affect_cost() {
|
||||
let base = KinoviSeedance2p0CostState {
|
||||
resolution: KinoviOutputResolution::SevenTwentyP,
|
||||
resolution: Some(KinoviOutputResolution::SevenTwentyP),
|
||||
duration_seconds: 5,
|
||||
batch_count: KinoviBatchCount::One,
|
||||
batch_count: Some(KinoviBatchCount::One),
|
||||
has_video_reference: false,
|
||||
};
|
||||
let without = base.estimate_cost();
|
||||
@@ -222,19 +223,19 @@ mod tests {
|
||||
fn from_request_720p() {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0CostState::from_request(&req);
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::One));
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(!cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_request_none_defaults_to_720p() {
|
||||
let req = make_request_state(None, 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0CostState::from_request(&req);
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80));
|
||||
assert!(cost.resolution.is_none()); // None defaults to 720p pricing
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -257,7 +258,7 @@ mod tests {
|
||||
let cost = KinoviSeedance2p0CostState::from_request(&req);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -277,18 +278,18 @@ mod tests {
|
||||
fn from_draft_720p_default() {
|
||||
let draft = make_draft(5, 1, None, false);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert!(cost.resolution.is_none()); // None defaults to 720p pricing
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::One));
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(!cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_draft_480p() {
|
||||
let draft = make_draft(5, 1, Some(CommonResolution::FourEightyP), false);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::FourEightyP));
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::FourEightyP)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(39));
|
||||
}
|
||||
|
||||
@@ -296,8 +297,8 @@ mod tests {
|
||||
fn from_draft_1080p_batch_4() {
|
||||
let draft = make_draft(5, 4, Some(CommonResolution::TenEightyP), false);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::TenEightyP));
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::Four));
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::TenEightyP)));
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Four)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(933));
|
||||
}
|
||||
|
||||
@@ -307,7 +308,7 @@ mod tests {
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -322,8 +323,8 @@ mod tests {
|
||||
let draft = make_draft(15, 2, Some(CommonResolution::SevenTwentyP), false);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert_eq!(cost.duration_seconds, 15);
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::Two));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(480));
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Two)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(622));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +383,10 @@ mod tests {
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
) -> u64 {
|
||||
KinoviSeedance2p0CostState { resolution, duration_seconds, batch_count, has_video_reference: false }
|
||||
KinoviSeedance2p0CostState {
|
||||
resolution: Some(resolution), duration_seconds, batch_count: Some(batch_count),
|
||||
has_video_reference: false,
|
||||
}
|
||||
.estimate_cost()
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
@@ -393,7 +397,10 @@ mod tests {
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
) -> u64 {
|
||||
KinoviSeedance2p0CostState { resolution, duration_seconds, batch_count, has_video_reference: false }
|
||||
KinoviSeedance2p0CostState {
|
||||
resolution: Some(resolution), duration_seconds, batch_count: Some(batch_count),
|
||||
has_video_reference: false,
|
||||
}
|
||||
.estimate_cost()
|
||||
.cost_in_credits
|
||||
.unwrap()
|
||||
@@ -443,13 +450,12 @@ mod tests {
|
||||
};
|
||||
|
||||
KinoviSeedance2p0RequestState {
|
||||
request: KinoviGenerateVideoRequest {
|
||||
model_type: KinoviModelType::Seedance2Pro,
|
||||
request: GenerateSeedance2p0Request {
|
||||
prompt: "test".to_string(),
|
||||
aspect_ratio: KinoviAspectRatio::Landscape16x9,
|
||||
aspect_ratio: None,
|
||||
output_resolution: resolution,
|
||||
duration_seconds,
|
||||
batch_count,
|
||||
batch_count: Some(batch_count),
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
|
||||
+10
-8
@@ -1,4 +1,7 @@
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviGenerateVideoRequest, KinoviModelType, KinoviOutputResolution};
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
GenerateSeedance2p0Request, KinoviSeedance2p0AspectRatio,
|
||||
KinoviSeedance2p0BatchCount, KinoviSeedance2p0OutputResolution,
|
||||
};
|
||||
|
||||
use crate::api::audio_list_ref::AudioListRef;
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
@@ -19,10 +22,10 @@ pub struct KinoviSeedance2p0DraftState {
|
||||
// Materialized / finalized types
|
||||
|
||||
pub prompt: String,
|
||||
pub aspect_ratio: KinoviAspectRatio,
|
||||
pub resolution: Option<KinoviOutputResolution>,
|
||||
pub aspect_ratio: KinoviSeedance2p0AspectRatio,
|
||||
pub resolution: Option<KinoviSeedance2p0OutputResolution>,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: KinoviBatchCount,
|
||||
pub batch_count: KinoviSeedance2p0BatchCount,
|
||||
|
||||
// Pending types that need to be queried.
|
||||
pub unhandled_request_state: Option<KinoviSeedance2p0RemainingItems>,
|
||||
@@ -77,13 +80,12 @@ impl KinoviSeedance2p0DraftState {
|
||||
)?;
|
||||
}
|
||||
|
||||
let request = KinoviGenerateVideoRequest {
|
||||
model_type: KinoviModelType::Seedance2Pro,
|
||||
let request = GenerateSeedance2p0Request {
|
||||
prompt: self.prompt.clone(),
|
||||
aspect_ratio: self.aspect_ratio,
|
||||
aspect_ratio: Some(self.aspect_ratio),
|
||||
output_resolution: self.resolution,
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: self.batch_count,
|
||||
batch_count: Some(self.batch_count),
|
||||
start_frame_url,
|
||||
end_frame_url,
|
||||
reference_image_urls,
|
||||
|
||||
+10
-324
@@ -1,28 +1,31 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0::{
|
||||
generate_seedance_2p0, GenerateSeedance2p0Args, GenerateSeedance2p0Request,
|
||||
};
|
||||
|
||||
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::provider_error::ProviderError;
|
||||
use crate::generate::generate_video::generate_video_response::{GenerateVideoResponse, Seedance2proVideoResponsePayload};
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{generate_video, GenerateVideoArgs, KinoviGenerateVideoRequest};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KinoviSeedance2p0RequestState {
|
||||
/// Final materialized request; ready to fire.
|
||||
pub request: KinoviGenerateVideoRequest,
|
||||
pub request: GenerateSeedance2p0Request,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0RequestState {
|
||||
pub async fn send(&self, client: &RouterSeedance2ProClient) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let session = &client.session;
|
||||
|
||||
let args = GenerateVideoArgs {
|
||||
let args = GenerateSeedance2p0Args {
|
||||
session,
|
||||
host_override: None,
|
||||
request: self.request.clone(), // TODO: Yuck.
|
||||
request: self.request.clone(),
|
||||
};
|
||||
|
||||
let response = generate_video(args)
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
|
||||
let response = generate_seedance_2p0(args)
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
|
||||
|
||||
Ok(GenerateVideoResponse::Seedance2Pro(Seedance2proVideoResponsePayload {
|
||||
order_id: response.order_id,
|
||||
@@ -32,320 +35,3 @@ impl KinoviSeedance2p0RequestState {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::client::router_client::RouterClient;
|
||||
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_context::VideoGenerationDraftContext;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use test_data::web::image_urls::{FOREST_BACKDROP_IMAGE_URL, JUNO_AT_LAKE_IMAGE_URL, WHITE_HOUSE_SUNSET_IMAGE_URL};
|
||||
|
||||
const STEAMPUNK_CLOWN_KINOVI_ID: &str = "char_1775176566518_sik0te";
|
||||
const MOCHI_KINOVI_ID: &str = "char_1775177718294_g2pitx";
|
||||
|
||||
mod aspect_ratio_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn landscape() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A corgi running through a field of wildflowers at sunset.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn portrait() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A cat sitting on a windowsill watching rain.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn square() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A hummingbird hovering near a flower.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
}
|
||||
|
||||
mod resolution_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn res_480p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A shiba inu playing in autumn leaves.".to_string()),
|
||||
resolution: Some(CommonResolution::FourEightyP),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn res_720p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A golden retriever catching a frisbee on the beach.".to_string()),
|
||||
resolution: Some(CommonResolution::SevenTwentyP),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn res_1080p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A fox walking through a snowy forest.".to_string()),
|
||||
resolution: Some(CommonResolution::TenEightyP),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
}
|
||||
|
||||
mod modality_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn text_to_video() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A whale breaching in the open ocean at dawn, cinematic.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn keyframe_start_and_end_frame() {
|
||||
let start_token = MediaFileToken::new("mf_start".to_string());
|
||||
let end_token = MediaFileToken::new("mf_end".to_string());
|
||||
|
||||
let mut media_map = HashMap::new();
|
||||
media_map.insert(start_token.clone(), JUNO_AT_LAKE_IMAGE_URL.to_string());
|
||||
media_map.insert(end_token.clone(), FOREST_BACKDROP_IMAGE_URL.to_string());
|
||||
|
||||
let response = run_pipeline_with_media_map(GenerateVideoRequestBuilder {
|
||||
prompt: Some("The dog walks from the lake to the forest.".to_string()),
|
||||
start_frame: Some(ImageRef::MediaFileToken(start_token)),
|
||||
end_frame: Some(ImageRef::MediaFileToken(end_token)),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
}, media_map).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn image_references() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("The dog in @2 runs through the scenery in @1 towards the building in @3.".to_string()),
|
||||
reference_images: Some(ImageListRef::Urls(vec![
|
||||
FOREST_BACKDROP_IMAGE_URL.to_string(),
|
||||
JUNO_AT_LAKE_IMAGE_URL.to_string(),
|
||||
WHITE_HOUSE_SUNSET_IMAGE_URL.to_string(),
|
||||
])),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn video_reference() {
|
||||
// NB: This URL is a pre-uploaded video on the Seedance2Pro CDN from prior tests.
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("Change @video1 to a nighttime scene with moonlight.".to_string()),
|
||||
reference_videos: Some(VideoListRef::Urls(vec![
|
||||
"https://static.seedance2-pro.com/materials/20260315/1773594284659-3a46d231.mp4".to_string(),
|
||||
])),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn character_references() {
|
||||
let char_clown = CharacterToken::new("char_clown".to_string());
|
||||
let char_mochi = CharacterToken::new("char_mochi".to_string());
|
||||
|
||||
let mut character_map = HashMap::new();
|
||||
character_map.insert(char_clown.clone(), STEAMPUNK_CLOWN_KINOVI_ID.to_string());
|
||||
character_map.insert(char_mochi.clone(), MOCHI_KINOVI_ID.to_string());
|
||||
|
||||
let response = run_pipeline_with_maps(
|
||||
GenerateVideoRequestBuilder {
|
||||
prompt: Some("@Steampunk Clown and @Mochi are playing fetch in a sunny park.".to_string()),
|
||||
reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![
|
||||
char_clown,
|
||||
char_mochi,
|
||||
])),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_builder()
|
||||
},
|
||||
HashMap::new(),
|
||||
character_map,
|
||||
).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
// TODO: Add audio reference test once a web-accessible audio URL is available in test_data.
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
fn seedance2pro_builder() -> GenerateVideoRequestBuilder {
|
||||
GenerateVideoRequestBuilder {
|
||||
provider: Provider::Seedance2Pro,
|
||||
duration_seconds: Some(4),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_seedance2pro_client() -> RouterClient {
|
||||
let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/seedance2pro_cookies.txt")
|
||||
.expect("Failed to read seedance2pro cookies");
|
||||
let session = Seedance2ProSession::from_cookies_string(cookies.trim().to_string());
|
||||
RouterClient::Seedance2Pro(RouterSeedance2ProClient::new(session))
|
||||
}
|
||||
|
||||
/// Run the full pipeline: builder → build2 → finalize → send.
|
||||
async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse {
|
||||
let client = get_seedance2pro_client();
|
||||
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let draft = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Draft(d) => d,
|
||||
_ => panic!("expected Draft variant"),
|
||||
};
|
||||
|
||||
let draft_context = VideoGenerationDraftContext {
|
||||
client: Some(&client),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let request = draft.finalize(draft_context).await.expect("finalize should succeed");
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
|
||||
match &response {
|
||||
GenerateVideoResponse::Seedance2Pro(p) => {
|
||||
println!("task_id={}, order_id={}", p.task_id, p.order_id);
|
||||
}
|
||||
other => println!("response: {:?}", other),
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
/// Run the full pipeline with a media file token map for resolving references.
|
||||
async fn run_pipeline_with_media_map(
|
||||
builder: GenerateVideoRequestBuilder,
|
||||
media_map: HashMap<MediaFileToken, String>,
|
||||
) -> GenerateVideoResponse {
|
||||
let client = get_seedance2pro_client();
|
||||
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let draft = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Draft(d) => d,
|
||||
_ => panic!("expected Draft variant"),
|
||||
};
|
||||
|
||||
let draft_context = VideoGenerationDraftContext {
|
||||
client: Some(&client),
|
||||
media_file_to_artcraft_url_map: Some(&media_map),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let request = draft.finalize(draft_context).await.expect("finalize should succeed");
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
|
||||
match &response {
|
||||
GenerateVideoResponse::Seedance2Pro(p) => {
|
||||
println!("task_id={}, order_id={}", p.task_id, p.order_id);
|
||||
}
|
||||
other => println!("response: {:?}", other),
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
/// Run the full pipeline with both a media map and character map.
|
||||
async fn run_pipeline_with_maps(
|
||||
builder: GenerateVideoRequestBuilder,
|
||||
media_map: HashMap<MediaFileToken, String>,
|
||||
character_map: HashMap<CharacterToken, String>,
|
||||
) -> GenerateVideoResponse {
|
||||
let client = get_seedance2pro_client();
|
||||
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let draft = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Draft(d) => d,
|
||||
_ => panic!("expected Draft variant"),
|
||||
};
|
||||
|
||||
let draft_context = VideoGenerationDraftContext {
|
||||
client: Some(&client),
|
||||
media_file_to_artcraft_url_map: Some(&media_map),
|
||||
character_token_to_kinovi_id_map: Some(&character_map),
|
||||
};
|
||||
|
||||
let request = draft.finalize(draft_context).await.expect("finalize should succeed");
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
|
||||
match &response {
|
||||
GenerateVideoResponse::Seedance2Pro(p) => {
|
||||
println!("task_id={}, order_id={}", p.task_id, p.order_id);
|
||||
}
|
||||
other => println!("response: {:?}", other),
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
+11
-3
@@ -1,4 +1,8 @@
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviOutputResolution};
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
KinoviSeedance2p0FastAspectRatio as KinoviAspectRatio,
|
||||
KinoviSeedance2p0FastBatchCount as KinoviBatchCount,
|
||||
KinoviSeedance2p0FastOutputResolution as KinoviOutputResolution,
|
||||
};
|
||||
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
@@ -73,6 +77,7 @@ fn plan_aspect_ratio(
|
||||
}
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(KinoviAspectRatio::Standard4x3),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(KinoviAspectRatio::Portrait3x4),
|
||||
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(KinoviAspectRatio::UltraWide21x9),
|
||||
|
||||
// Mismatches — apply strategy
|
||||
Some(unsupported) => match strategy {
|
||||
@@ -95,7 +100,6 @@ fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> KinoviAspectRatio {
|
||||
match aspect_ratio {
|
||||
CommonAspectRatio::WideFiveByFour => KinoviAspectRatio::Standard4x3, // 1.25, nearest 1.33
|
||||
CommonAspectRatio::WideThreeByTwo => KinoviAspectRatio::Standard4x3, // 1.50, nearest 1.33
|
||||
CommonAspectRatio::WideTwentyOneByNine => KinoviAspectRatio::Landscape16x9, // 2.33, nearest 1.78
|
||||
CommonAspectRatio::TallFourByFive => KinoviAspectRatio::Portrait3x4, // 0.80, nearest 0.75
|
||||
CommonAspectRatio::TallTwoByThree => KinoviAspectRatio::Portrait3x4, // 0.67, nearest 0.75
|
||||
CommonAspectRatio::TallNineByTwentyOne => KinoviAspectRatio::Portrait9x16, // 0.43, nearest 0.56
|
||||
@@ -208,7 +212,11 @@ fn plan_duration(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviOutputResolution};
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
KinoviSeedance2p0FastAspectRatio as KinoviAspectRatio,
|
||||
KinoviSeedance2p0FastBatchCount as KinoviBatchCount,
|
||||
KinoviSeedance2p0FastOutputResolution as KinoviOutputResolution,
|
||||
};
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
|
||||
+63
-57
@@ -1,22 +1,23 @@
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviGenerateVideoRequest, KinoviModelType, KinoviOutputResolution};
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
GenerateSeedance2p0FastRequest, KinoviSeedance2p0FastBatchCount,
|
||||
KinoviSeedance2p0FastOutputResolution,
|
||||
};
|
||||
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0_fast::draft::KinoviSeedance2p0FastDraftState;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0_fast::request::KinoviSeedance2p0FastRequestState;
|
||||
|
||||
pub struct KinoviSeedance2p0FastCostState {
|
||||
pub resolution: KinoviOutputResolution,
|
||||
pub resolution: Option<KinoviSeedance2p0FastOutputResolution>,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: KinoviBatchCount,
|
||||
|
||||
/// Forward-looking: Kinovi charges for this. We need to take it into
|
||||
/// account in the future.
|
||||
pub batch_count: Option<KinoviSeedance2p0FastBatchCount>,
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0FastCostState {
|
||||
pub fn from_request(request: &KinoviSeedance2p0FastRequestState) -> Self {
|
||||
Self {
|
||||
resolution: request.request.output_resolution.unwrap_or(KinoviOutputResolution::SevenTwentyP),
|
||||
resolution: request.request.output_resolution,
|
||||
duration_seconds: request.request.duration_seconds,
|
||||
batch_count: request.request.batch_count,
|
||||
has_video_reference: request.request.reference_video_urls
|
||||
@@ -32,27 +33,22 @@ impl KinoviSeedance2p0FastCostState {
|
||||
.is_some();
|
||||
|
||||
Self {
|
||||
resolution: draft.resolution.unwrap_or(KinoviOutputResolution::SevenTwentyP),
|
||||
resolution: draft.resolution,
|
||||
duration_seconds: draft.duration_seconds,
|
||||
batch_count: draft.batch_count,
|
||||
batch_count: Some(draft.batch_count),
|
||||
has_video_reference,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
let request = KinoviGenerateVideoRequest {
|
||||
// Pricing factors
|
||||
model_type: KinoviModelType::Seedance2Fast,
|
||||
output_resolution: Some(self.resolution),
|
||||
let pricing_request = GenerateSeedance2p0FastRequest {
|
||||
output_resolution: self.resolution,
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: self.batch_count,
|
||||
|
||||
// NB: has_video_reference is tracked but does not affect price yet.
|
||||
// When Kinovi starts charging for video references, add cost here.
|
||||
|
||||
// No impact on price
|
||||
prompt: String::new(),
|
||||
aspect_ratio: KinoviAspectRatio::Portrait9x16,
|
||||
aspect_ratio: None,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
@@ -62,8 +58,8 @@ impl KinoviSeedance2p0FastCostState {
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
|
||||
let cost_in_credits = request.estimate_credits();
|
||||
let cost_in_usd_cents = request.estimate_cost_in_usd_cents();
|
||||
let cost_in_credits = pricing_request.estimate_credits();
|
||||
let cost_in_usd_cents = pricing_request.estimate_cost_in_usd_cents();
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits as u64),
|
||||
@@ -80,6 +76,11 @@ impl KinoviSeedance2p0FastCostState {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
KinoviSeedance2p0FastOutputResolution as KinoviOutputResolution,
|
||||
KinoviSeedance2p0FastBatchCount as KinoviBatchCount,
|
||||
};
|
||||
|
||||
use crate::api::common_video_model::CommonVideoModel;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
@@ -90,51 +91,51 @@ mod tests {
|
||||
// ── Direct estimate_cost() tests ──
|
||||
// These must match the reference implementation values exactly.
|
||||
|
||||
// -- 720p (legacy pricing: 220 credits/$1, 28 credits/sec) --
|
||||
// -- 720p (28 credits/sec, 193 credits/$1) --
|
||||
|
||||
mod pricing_720p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 51);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 64);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 127);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 191);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 58);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 73);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 145);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 218);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_2() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 127);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 145);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 255);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 290);
|
||||
}
|
||||
}
|
||||
|
||||
// -- 480p (new pricing: 193 credits/$1, 10 credits/sec) --
|
||||
// -- 480p (14 credits/sec, 193 credits/$1) --
|
||||
|
||||
mod pricing_480p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 21);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 26);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One), 52);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One), 78);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 29);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 36);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One), 73);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One), 109);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_2() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Two), 52);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Two), 73);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 104);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 145);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,9 +175,9 @@ mod tests {
|
||||
#[test]
|
||||
fn video_reference_does_not_affect_cost() {
|
||||
let base = KinoviSeedance2p0FastCostState {
|
||||
resolution: KinoviOutputResolution::SevenTwentyP,
|
||||
resolution: Some(KinoviOutputResolution::SevenTwentyP),
|
||||
duration_seconds: 5,
|
||||
batch_count: KinoviBatchCount::One,
|
||||
batch_count: Some(KinoviBatchCount::One),
|
||||
has_video_reference: false,
|
||||
};
|
||||
let without = base.estimate_cost();
|
||||
@@ -194,26 +195,26 @@ mod tests {
|
||||
fn from_request_720p() {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::One));
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(!cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(64));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_request_none_defaults_to_720p() {
|
||||
let req = make_request_state(None, 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(64));
|
||||
assert!(cost.resolution.is_none());
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_request_480p() {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(26));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(36));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -222,7 +223,7 @@ mod tests {
|
||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(64));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -242,19 +243,19 @@ mod tests {
|
||||
fn from_draft_720p_default() {
|
||||
let draft = make_draft(5, 1, None, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP));
|
||||
assert!(cost.resolution.is_none());
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::One));
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::One)));
|
||||
assert!(!cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(64));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_draft_480p() {
|
||||
let draft = make_draft(5, 1, Some(CommonResolution::FourEightyP), false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert!(matches!(cost.resolution, KinoviOutputResolution::FourEightyP));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(26));
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::FourEightyP)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(36));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -263,7 +264,7 @@ mod tests {
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(64));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -278,8 +279,8 @@ mod tests {
|
||||
let draft = make_draft(15, 2, Some(CommonResolution::SevenTwentyP), false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert_eq!(cost.duration_seconds, 15);
|
||||
assert!(matches!(cost.batch_count, KinoviBatchCount::Two));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(382));
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Two)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(435));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +323,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn credits_480p() {
|
||||
assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 50);
|
||||
assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 70);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +334,10 @@ mod tests {
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
) -> u64 {
|
||||
KinoviSeedance2p0FastCostState { resolution, duration_seconds, batch_count, has_video_reference: false }
|
||||
KinoviSeedance2p0FastCostState {
|
||||
resolution: Some(resolution), duration_seconds, batch_count: Some(batch_count),
|
||||
has_video_reference: false,
|
||||
}
|
||||
.estimate_cost()
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
@@ -344,7 +348,10 @@ mod tests {
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
) -> u64 {
|
||||
KinoviSeedance2p0FastCostState { resolution, duration_seconds, batch_count, has_video_reference: false }
|
||||
KinoviSeedance2p0FastCostState {
|
||||
resolution: Some(resolution), duration_seconds, batch_count: Some(batch_count),
|
||||
has_video_reference: false,
|
||||
}
|
||||
.estimate_cost()
|
||||
.cost_in_credits
|
||||
.unwrap()
|
||||
@@ -395,13 +402,12 @@ mod tests {
|
||||
};
|
||||
|
||||
KinoviSeedance2p0FastRequestState {
|
||||
request: KinoviGenerateVideoRequest {
|
||||
model_type: KinoviModelType::Seedance2Fast,
|
||||
request: GenerateSeedance2p0FastRequest {
|
||||
prompt: "test".to_string(),
|
||||
aspect_ratio: KinoviAspectRatio::Landscape16x9,
|
||||
aspect_ratio: None,
|
||||
output_resolution: resolution,
|
||||
duration_seconds,
|
||||
batch_count,
|
||||
batch_count: Some(batch_count),
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
|
||||
+10
-11
@@ -1,4 +1,7 @@
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{KinoviAspectRatio, KinoviBatchCount, KinoviGenerateVideoRequest, KinoviModelType, KinoviOutputResolution};
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
GenerateSeedance2p0FastRequest, KinoviSeedance2p0FastAspectRatio,
|
||||
KinoviSeedance2p0FastBatchCount, KinoviSeedance2p0FastOutputResolution,
|
||||
};
|
||||
|
||||
use crate::api::audio_list_ref::AudioListRef;
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
@@ -16,15 +19,12 @@ use crate::generate::generate_video_v2::video_generation_draft_context::VideoGen
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KinoviSeedance2p0FastDraftState {
|
||||
// Materialized / finalized types
|
||||
|
||||
pub prompt: String,
|
||||
pub aspect_ratio: KinoviAspectRatio,
|
||||
pub resolution: Option<KinoviOutputResolution>,
|
||||
pub aspect_ratio: KinoviSeedance2p0FastAspectRatio,
|
||||
pub resolution: Option<KinoviSeedance2p0FastOutputResolution>,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: KinoviBatchCount,
|
||||
pub batch_count: KinoviSeedance2p0FastBatchCount,
|
||||
|
||||
// Pending types that need to be queried.
|
||||
pub unhandled_request_state: Option<KinoviSeedance2p0FastRemainingItems>,
|
||||
}
|
||||
|
||||
@@ -77,13 +77,12 @@ impl KinoviSeedance2p0FastDraftState {
|
||||
)?;
|
||||
}
|
||||
|
||||
let request = KinoviGenerateVideoRequest {
|
||||
model_type: KinoviModelType::Seedance2Fast,
|
||||
let request = GenerateSeedance2p0FastRequest {
|
||||
prompt: self.prompt.clone(),
|
||||
aspect_ratio: self.aspect_ratio,
|
||||
aspect_ratio: Some(self.aspect_ratio),
|
||||
output_resolution: self.resolution,
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: self.batch_count,
|
||||
batch_count: Some(self.batch_count),
|
||||
start_frame_url,
|
||||
end_frame_url,
|
||||
reference_image_urls,
|
||||
|
||||
+10
-233
@@ -1,28 +1,31 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_fast::{
|
||||
generate_seedance_2p0_fast, GenerateSeedance2p0FastArgs, GenerateSeedance2p0FastRequest,
|
||||
};
|
||||
|
||||
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::provider_error::ProviderError;
|
||||
use crate::generate::generate_video::generate_video_response::{GenerateVideoResponse, Seedance2proVideoResponsePayload};
|
||||
use seedance2pro_client::requests::generate_video::generate_video::{generate_video, GenerateVideoArgs, KinoviGenerateVideoRequest};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KinoviSeedance2p0FastRequestState {
|
||||
/// Final materialized request; ready to fire.
|
||||
pub request: KinoviGenerateVideoRequest,
|
||||
pub request: GenerateSeedance2p0FastRequest,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0FastRequestState {
|
||||
pub async fn send(&self, client: &RouterSeedance2ProClient) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let session = &client.session;
|
||||
|
||||
let args = GenerateVideoArgs {
|
||||
let args = GenerateSeedance2p0FastArgs {
|
||||
session,
|
||||
host_override: None,
|
||||
request: self.request.clone(), // TODO: Yuck.
|
||||
request: self.request.clone(),
|
||||
};
|
||||
|
||||
let response = generate_video(args)
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
|
||||
let response = generate_seedance_2p0_fast(args)
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
|
||||
|
||||
Ok(GenerateVideoResponse::Seedance2Pro(Seedance2proVideoResponsePayload {
|
||||
order_id: response.order_id,
|
||||
@@ -32,229 +35,3 @@ impl KinoviSeedance2p0FastRequestState {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::common_video_model::CommonVideoModel;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::client::router_client::RouterClient;
|
||||
use crate::client::router_seedance2pro_client::RouterSeedance2ProClient;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_context::VideoGenerationDraftContext;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use test_data::web::image_urls::{FOREST_BACKDROP_IMAGE_URL, JUNO_AT_LAKE_IMAGE_URL, WHITE_HOUSE_SUNSET_IMAGE_URL};
|
||||
|
||||
mod aspect_ratio_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn landscape() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A corgi running through a field of wildflowers at sunset.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn portrait() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A cat sitting on a windowsill watching rain.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn square() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A hummingbird hovering near a flower.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
}
|
||||
|
||||
mod resolution_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn res_480p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A shiba inu playing in autumn leaves.".to_string()),
|
||||
resolution: Some(CommonResolution::FourEightyP),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn res_720p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A golden retriever catching a frisbee on the beach.".to_string()),
|
||||
resolution: Some(CommonResolution::SevenTwentyP),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
}
|
||||
|
||||
mod modality_tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn text_to_video() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A whale breaching in the open ocean at dawn, cinematic.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn keyframe_start_and_end_frame() {
|
||||
let start_token = MediaFileToken::new("mf_start".to_string());
|
||||
let end_token = MediaFileToken::new("mf_end".to_string());
|
||||
|
||||
let mut media_map = HashMap::new();
|
||||
media_map.insert(start_token.clone(), JUNO_AT_LAKE_IMAGE_URL.to_string());
|
||||
media_map.insert(end_token.clone(), FOREST_BACKDROP_IMAGE_URL.to_string());
|
||||
|
||||
let response = run_pipeline_with_media_map(GenerateVideoRequestBuilder {
|
||||
prompt: Some("The dog walks from the lake to the forest.".to_string()),
|
||||
start_frame: Some(ImageRef::MediaFileToken(start_token)),
|
||||
end_frame: Some(ImageRef::MediaFileToken(end_token)),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_fast_builder()
|
||||
}, media_map).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn image_references() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("The dog in @2 runs through the scenery in @1 towards the building in @3.".to_string()),
|
||||
reference_images: Some(ImageListRef::Urls(vec![
|
||||
FOREST_BACKDROP_IMAGE_URL.to_string(),
|
||||
JUNO_AT_LAKE_IMAGE_URL.to_string(),
|
||||
WHITE_HOUSE_SUNSET_IMAGE_URL.to_string(),
|
||||
])),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
..seedance2pro_fast_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Seedance2Pro(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
fn seedance2pro_fast_builder() -> GenerateVideoRequestBuilder {
|
||||
GenerateVideoRequestBuilder {
|
||||
model: CommonVideoModel::Seedance2p0Fast,
|
||||
provider: Provider::Seedance2Pro,
|
||||
duration_seconds: Some(4),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_seedance2pro_client() -> RouterClient {
|
||||
let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/seedance2pro_cookies.txt")
|
||||
.expect("Failed to read seedance2pro cookies");
|
||||
let session = Seedance2ProSession::from_cookies_string(cookies.trim().to_string());
|
||||
RouterClient::Seedance2Pro(RouterSeedance2ProClient::new(session))
|
||||
}
|
||||
|
||||
/// Run the full pipeline: builder -> build2 -> finalize -> send.
|
||||
async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse {
|
||||
let client = get_seedance2pro_client();
|
||||
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let draft = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Draft(d) => d,
|
||||
_ => panic!("expected Draft variant"),
|
||||
};
|
||||
|
||||
let draft_context = VideoGenerationDraftContext {
|
||||
client: Some(&client),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let request = draft.finalize(draft_context).await.expect("finalize should succeed");
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
|
||||
match &response {
|
||||
GenerateVideoResponse::Seedance2Pro(p) => {
|
||||
println!("task_id={}, order_id={}", p.task_id, p.order_id);
|
||||
}
|
||||
other => println!("response: {:?}", other),
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
/// Run the full pipeline with a media file token map for resolving references.
|
||||
async fn run_pipeline_with_media_map(
|
||||
builder: GenerateVideoRequestBuilder,
|
||||
media_map: HashMap<MediaFileToken, String>,
|
||||
) -> GenerateVideoResponse {
|
||||
let client = get_seedance2pro_client();
|
||||
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let draft = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Draft(d) => d,
|
||||
_ => panic!("expected Draft variant"),
|
||||
};
|
||||
|
||||
let draft_context = VideoGenerationDraftContext {
|
||||
client: Some(&client),
|
||||
media_file_to_artcraft_url_map: Some(&media_map),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let request = draft.finalize(draft_context).await.expect("finalize should succeed");
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
|
||||
match &response {
|
||||
GenerateVideoResponse::Seedance2Pro(p) => {
|
||||
println!("task_id={}, order_id={}", p.task_id, p.order_id);
|
||||
}
|
||||
other => println!("response: {:?}", other),
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,15 @@
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../analytics/google-analytics"
|
||||
},
|
||||
{
|
||||
"path": "../modal"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-utils"
|
||||
},
|
||||
{
|
||||
"path": "../../model-list"
|
||||
},
|
||||
{
|
||||
"path": "../promptbox"
|
||||
},
|
||||
{
|
||||
"path": "../../state/subscription"
|
||||
},
|
||||
@@ -33,10 +30,10 @@
|
||||
"path": "../../soundboard"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-events"
|
||||
"path": "../input"
|
||||
},
|
||||
{
|
||||
"path": "../input"
|
||||
"path": "../../tauri-events"
|
||||
},
|
||||
{
|
||||
"path": "../login-modal"
|
||||
@@ -45,7 +42,7 @@
|
||||
"path": "../../api"
|
||||
},
|
||||
{
|
||||
"path": "../button"
|
||||
"path": "../../analytics/google-analytics"
|
||||
},
|
||||
{
|
||||
"path": "../label"
|
||||
@@ -53,6 +50,12 @@
|
||||
{
|
||||
"path": "../../tauri-api"
|
||||
},
|
||||
{
|
||||
"path": "../button"
|
||||
},
|
||||
{
|
||||
"path": "../modal"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
|
||||
@@ -43,18 +43,15 @@
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../analytics/google-analytics/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../modal/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-utils/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../model-list/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../promptbox/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../state/subscription/tsconfig.lib.json"
|
||||
},
|
||||
@@ -74,10 +71,10 @@
|
||||
"path": "../../soundboard/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-events/tsconfig.lib.json"
|
||||
"path": "../input/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../input/tsconfig.lib.json"
|
||||
"path": "../../tauri-events/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../login-modal/tsconfig.lib.json"
|
||||
@@ -86,13 +83,19 @@
|
||||
"path": "../../api/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../button/tsconfig.lib.json"
|
||||
"path": "../../analytics/google-analytics/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../label/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-api/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../button/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../modal/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user