mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-23 10:09:29 -04:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dda51ba50 | ||
|
|
cd47304db9 | ||
|
|
6543590fea | ||
|
|
786181917d | ||
|
|
31c51c5ab6 |
@@ -65,6 +65,15 @@ pub enum RouterVideoModel {
|
||||
#[serde(rename = "seedance_2p0_bpu_fast")]
|
||||
Seedance2p0BytePlusUltraFast,
|
||||
|
||||
#[serde(rename = "seedance_2p0_mini")]
|
||||
Seedance2p0Mini,
|
||||
|
||||
#[serde(rename = "seedance_2p0_bp_mini")]
|
||||
Seedance2p0BytePlusMini,
|
||||
|
||||
#[serde(rename = "seedance_2p0_bpu_mini")]
|
||||
Seedance2p0BytePlusUltraMini,
|
||||
|
||||
#[serde(rename = "happy_horse_1p0")]
|
||||
HappyHorse1p0,
|
||||
|
||||
@@ -95,3 +104,23 @@ pub enum RouterVideoModel {
|
||||
#[serde(rename = "preview_model_fast")]
|
||||
PreviewModelFast,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn assert_serde_round_trip(model: RouterVideoModel, expected: &str) {
|
||||
let json = serde_json::to_string(&model).unwrap();
|
||||
assert_eq!(json, format!("\"{expected}\""));
|
||||
let parsed: RouterVideoModel = serde_json::from_str(&json).unwrap();
|
||||
// RouterVideoModel isn't PartialEq, so round-trip back to the wire form.
|
||||
assert_eq!(serde_json::to_string(&parsed).unwrap(), json);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seedance_2p0_mini_variants_serialize() {
|
||||
assert_serde_round_trip(RouterVideoModel::Seedance2p0Mini, "seedance_2p0_mini");
|
||||
assert_serde_round_trip(RouterVideoModel::Seedance2p0BytePlusMini, "seedance_2p0_bp_mini");
|
||||
assert_serde_round_trip(RouterVideoModel::Seedance2p0BytePlusUltraMini, "seedance_2p0_bpu_mini");
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -32,6 +32,9 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp::build
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_fast::build::build_artcraft_seedance_2p0_bp_fast;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu::build::build_artcraft_seedance_2p0_bpu;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_fast::build::build_artcraft_seedance_2p0_bpu_fast;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_mini::build::build_artcraft_seedance_2p0_mini;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_mini::build::build_artcraft_seedance_2p0_bp_mini;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_mini::build::build_artcraft_seedance_2p0_bpu_mini;
|
||||
use crate::generate::generate_video::providers::artcraft::sora_2::build::build_artcraft_sora_2;
|
||||
use crate::generate::generate_video::providers::artcraft::sora_2_pro::build::build_artcraft_sora_2_pro;
|
||||
use crate::generate::generate_video::providers::artcraft::veo_2::build::build_artcraft_veo_2;
|
||||
@@ -62,6 +65,7 @@ use crate::generate::generate_video::providers::fal::veo_3_fast::build::build_fa
|
||||
use crate::generate::generate_video::providers::fal::veo_3p1::build::build_fal_veo_3p1;
|
||||
use crate::generate::generate_video::providers::fal::veo_3p1_fast::build::build_fal_veo_3p1_fast;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::build::build_kinovi_seedance_2p0_fast;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::build::build_kinovi_seedance_2p0_mini;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
|
||||
/// RouterProvider-agnostic video generation request. Distilled by `build2()` into a
|
||||
@@ -176,6 +180,9 @@ impl GenerateVideoRequestBuilder {
|
||||
(RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusFast) => build_artcraft_seedance_2p0_bp_fast(self),
|
||||
(RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusUltra) => build_artcraft_seedance_2p0_bpu(self),
|
||||
(RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusUltraFast) => build_artcraft_seedance_2p0_bpu_fast(self),
|
||||
(RouterProvider::Artcraft, RouterVideoModel::Seedance2p0Mini) => build_artcraft_seedance_2p0_mini(self),
|
||||
(RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusMini) => build_artcraft_seedance_2p0_bp_mini(self),
|
||||
(RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusUltraMini) => build_artcraft_seedance_2p0_bpu_mini(self),
|
||||
(RouterProvider::Artcraft, RouterVideoModel::PreviewModel) => build_artcraft_preview_model(self),
|
||||
(RouterProvider::Artcraft, RouterVideoModel::PreviewModelFast) => build_artcraft_preview_model_fast(self),
|
||||
(RouterProvider::Artcraft, RouterVideoModel::Seedance10Lite) => build_artcraft_seedance_1p0_lite(self),
|
||||
@@ -214,6 +221,9 @@ impl GenerateVideoRequestBuilder {
|
||||
(RouterProvider::Seedance2Pro, RouterVideoModel::HappyHorse1p0) => build_kinovi_happy_horse_1p0(self),
|
||||
(RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0) => build_kinovi_seedance_2p0(self),
|
||||
(RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0Fast) => build_kinovi_seedance_2p0_fast(self),
|
||||
(RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0Mini) => build_kinovi_seedance_2p0_mini(self),
|
||||
(RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0BytePlusMini) => build_kinovi_seedance_2p0_mini(self),
|
||||
(RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0BytePlusUltraMini) => build_kinovi_seedance_2p0_mini(self),
|
||||
_ => self.unsupported_provider_and_model(),
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -26,6 +26,9 @@ pub mod seedance_2p0_bp;
|
||||
pub mod seedance_2p0_bp_fast;
|
||||
pub mod seedance_2p0_bpu;
|
||||
pub mod seedance_2p0_bpu_fast;
|
||||
pub mod seedance_2p0_mini;
|
||||
pub mod seedance_2p0_bp_mini;
|
||||
pub mod seedance_2p0_bpu_mini;
|
||||
pub mod sora_2;
|
||||
pub mod sora_2_pro;
|
||||
pub mod veo_2;
|
||||
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum;
|
||||
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
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,
|
||||
};
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_mini::request::ArtcraftSeedance2p0BytePlusMiniRequestState;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use crate::generate::generate_video::video_generation_request::VideoGenerationRequest;
|
||||
|
||||
pub fn build_artcraft_seedance_2p0_bp_mini(builder: GenerateVideoRequestBuilder) -> Result<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
// Seedance 2.0 Mini supports 480p and 720p only (higher resolutions downgrade
|
||||
// to 720p), and all six aspect ratios including 21:9.
|
||||
let request = build_artcraft_omni_video_request(
|
||||
builder,
|
||||
CommonVideoModelEnum::Seedance2p0BytePlusMini,
|
||||
SupportedResolutions::Fast,
|
||||
UltraWideSupport::Supported,
|
||||
)?;
|
||||
let state = ArtcraftSeedance2p0BytePlusMiniRequestState { request };
|
||||
Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0BytePlusMini(state)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioEnum;
|
||||
use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum;
|
||||
use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum;
|
||||
use enums::common::generation::common_bitrate::CommonBitrate as CommonBitrateEnum;
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
use crate::api::router_aspect_ratio::RouterAspectRatio;
|
||||
use crate::api::router_bitrate::RouterBitrate;
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use crate::generate::generate_video::video_generation_request::VideoGenerationRequest;
|
||||
|
||||
use super::*;
|
||||
|
||||
mod field_conversions {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn prompt_is_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.prompt = Some("test".to_string()); }));
|
||||
assert_eq!(req.request.prompt, Some("test".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_is_correct() {
|
||||
let req = unwrap_request(builder_with(|_| {}));
|
||||
assert!(matches!(req.request.model, Some(CommonVideoModelEnum::Seedance2p0BytePlusMini)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(10); }));
|
||||
assert_eq!(req.request.duration_seconds, Some(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_clamped_to_max() {
|
||||
let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(99); }));
|
||||
assert_eq!(req.request.duration_seconds, Some(15));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(4); }));
|
||||
assert_eq!(req.request.video_batch_count, Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_clamped_to_platform_max() {
|
||||
// The ArtCraft platform caps batch at 4 and downgrades higher requests.
|
||||
let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(8); }));
|
||||
assert_eq!(req.request.video_batch_count, Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bitrate_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.bitrate = Some(RouterBitrate::High); }));
|
||||
assert_eq!(req.request.bitrate, Some(CommonBitrateEnum::High));
|
||||
}
|
||||
}
|
||||
|
||||
mod aspect_ratio_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn wide() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideSixteenByNine); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideSixteenByNine));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ultra_wide() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideTwentyOneByNine); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideTwentyOneByNine));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tall() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::TallNineBySixteen); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::TallNineBySixteen));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn square() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::Square); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::Square));
|
||||
}
|
||||
}
|
||||
|
||||
mod resolution_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn res_480p() {
|
||||
let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::FourEightyP); }));
|
||||
assert_eq!(req.request.resolution, Some(CommonResolutionEnum::FourEightyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_720p() {
|
||||
let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::SevenTwentyP); }));
|
||||
assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_1080p_downgrades_to_720p() {
|
||||
let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::TenEightyP); }));
|
||||
assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_1080p_error_out() {
|
||||
let result = build_artcraft_seedance_2p0_bp_mini(GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0BytePlusMini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
resolution: Some(RouterResolution::TenEightyP),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..Default::default()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_stays_none() {
|
||||
let req = unwrap_request(builder_with(|_| {}));
|
||||
assert!(req.request.resolution.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
mod media_token_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn start_frame_token_passed_through() {
|
||||
let token = MediaFileToken::new("mf_start".to_string());
|
||||
let req = unwrap_request(builder_with(|b| { b.start_frame = Some(ImageRef::MediaFileToken(token.clone())); }));
|
||||
assert_eq!(req.request.start_frame_image_media_token, Some(token));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_start_frame_rejected() {
|
||||
let result = build_artcraft_seedance_2p0_bp_mini(GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0BytePlusMini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
start_frame: Some(ImageRef::Url("https://example.com".to_string())),
|
||||
..Default::default()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_image_tokens_passed_through() {
|
||||
let tokens = vec![
|
||||
MediaFileToken::new("mf_a".to_string()),
|
||||
MediaFileToken::new("mf_b".to_string()),
|
||||
];
|
||||
let req = unwrap_request(builder_with(|b| { b.reference_images = Some(ImageListRef::MediaFileTokens(tokens.clone())); }));
|
||||
assert_eq!(req.request.reference_image_media_tokens, Some(tokens));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_tokens_passed_through() {
|
||||
let tokens = vec![
|
||||
CharacterToken::new("char_a".to_string()),
|
||||
CharacterToken::new("char_b".to_string()),
|
||||
];
|
||||
let req = unwrap_request(builder_with(|b| { b.reference_character_tokens = Some(CharacterListRef::CharacterTokens(tokens.clone())); }));
|
||||
assert_eq!(req.request.reference_character_tokens, Some(tokens));
|
||||
}
|
||||
}
|
||||
|
||||
fn builder_with(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder {
|
||||
let mut builder = GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0BytePlusMini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
duration_seconds: Some(5),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
};
|
||||
f(&mut builder);
|
||||
builder
|
||||
}
|
||||
|
||||
fn unwrap_request(builder: GenerateVideoRequestBuilder) -> ArtcraftSeedance2p0BytePlusMiniRequestState {
|
||||
let result = build_artcraft_seedance_2p0_bp_mini(builder).expect("build should succeed");
|
||||
match result {
|
||||
VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0BytePlusMini(state)) => state,
|
||||
_ => panic!("expected ArtcraftSeedance2p0BytePlusMini request"),
|
||||
}
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_byteplus_mini_usd_cents;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_mini::request::ArtcraftSeedance2p0BytePlusMiniRequestState;
|
||||
|
||||
pub struct ArtcraftSeedance2p0BytePlusMiniCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub batch_count: u16,
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p0BytePlusMiniCostState {
|
||||
pub fn from_request(request: &ArtcraftSeedance2p0BytePlusMiniRequestState) -> Self {
|
||||
let resolution = request.request.resolution
|
||||
.unwrap_or(CommonResolution::SevenTwentyP);
|
||||
let duration_seconds = request.request.duration_seconds.unwrap_or(5);
|
||||
let batch_count = request.request.video_batch_count.unwrap_or(1);
|
||||
let has_video_reference = request.request.reference_video_media_tokens
|
||||
.as_ref()
|
||||
.is_some_and(|tokens| !tokens.is_empty());
|
||||
|
||||
Self { resolution, duration_seconds, batch_count, has_video_reference }
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// BytePlus / BytePlus Ultra Mini share a higher-margin rate set; see
|
||||
// seedance_common::seedance_2p0_byteplus_mini_usd_cents.
|
||||
let usd_cents = seedance_2p0_byteplus_mini_usd_cents(
|
||||
self.resolution,
|
||||
self.duration_seconds,
|
||||
self.batch_count,
|
||||
self.has_video_reference,
|
||||
);
|
||||
|
||||
// ArtCraft credits: 100 credits = $1.00, so credits = cents.
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
|
||||
// Every combination, driven through the full build pipeline. These prices are
|
||||
// the supplier's per-combo cost plus a 5% margin, rounded up to whole cents.
|
||||
|
||||
mod pricing_480p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 14);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 63);
|
||||
}
|
||||
}
|
||||
|
||||
mod pricing_720p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 88);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 131);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 53);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 105);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 158);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_defaults_to_720p() {
|
||||
assert_eq!(cents_no_ref(None, 5, 1), cents(RouterResolution::SevenTwentyP, 5, 1, false));
|
||||
}
|
||||
}
|
||||
|
||||
mod credits_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn credits_equal_usd_cents_all_combos() {
|
||||
let resolutions = [
|
||||
Some(RouterResolution::FourEightyP),
|
||||
Some(RouterResolution::SevenTwentyP),
|
||||
None,
|
||||
];
|
||||
for res in resolutions {
|
||||
for dur in [4u16, 5, 10, 15] {
|
||||
for batch in [1u16, 2, 4] {
|
||||
for has_ref in [false, true] {
|
||||
let cost = build_cost(res, dur, batch, has_ref);
|
||||
assert_eq!(cost.cost_in_credits, cost.cost_in_usd_cents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_cost(
|
||||
resolution: Option<RouterResolution>,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
with_video_reference: bool,
|
||||
) -> VideoGenerationCostEstimate {
|
||||
let reference_videos = with_video_reference.then(|| {
|
||||
VideoListRef::MediaFileTokens(vec![MediaFileToken::new("mf_ref".to_string())])
|
||||
});
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0BytePlusMini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
resolution,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: Some(video_batch_count),
|
||||
reference_videos,
|
||||
..Default::default()
|
||||
};
|
||||
builder.build2()
|
||||
.expect("build2 should succeed")
|
||||
.estimate_cost()
|
||||
.expect("estimate_cost should succeed")
|
||||
}
|
||||
|
||||
fn cents(
|
||||
resolution: RouterResolution,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
with_video_reference: bool,
|
||||
) -> u64 {
|
||||
build_cost(Some(resolution), duration_seconds, video_batch_count, with_video_reference)
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn cents_no_ref(
|
||||
resolution: Option<RouterResolution>,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
) -> u64 {
|
||||
build_cost(resolution, duration_seconds, video_batch_count, false)
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest;
|
||||
|
||||
use crate::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video::providers::artcraft::request_common::send_artcraft_omni_video_request;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftSeedance2p0BytePlusMiniRequestState {
|
||||
pub request: OmniGenVideoCostAndGenerateRequest,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p0BytePlusMiniRequestState {
|
||||
pub async fn send(&self, client: &RouterArtcraftClient) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
send_artcraft_omni_video_request(&self.request, client).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::router_aspect_ratio::RouterAspectRatio;
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use crate::client::router_client::RouterClient;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
|
||||
#[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 corgi running through a field of wildflowers at sunset.".to_string()),
|
||||
aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine),
|
||||
..mini_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Artcraft(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[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(RouterResolution::FourEightyP),
|
||||
..mini_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Artcraft(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
fn mini_builder() -> GenerateVideoRequestBuilder {
|
||||
GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0BytePlusMini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
duration_seconds: Some(4),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_artcraft_client() -> RouterClient {
|
||||
let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/artcraft_cookies.txt")
|
||||
.expect("Failed to read artcraft cookies");
|
||||
let cookies = cookies.trim().to_string();
|
||||
let credentials = StorytellerCredentialSet::parse_multi_cookie_header(&cookies)
|
||||
.expect("Failed to parse cookies")
|
||||
.expect("No credentials found");
|
||||
RouterClient::Artcraft(RouterArtcraftClient::new(ApiHost::Storyteller, credentials))
|
||||
}
|
||||
|
||||
async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse {
|
||||
let client = get_artcraft_client();
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let request = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Request(r) => r,
|
||||
_ => panic!("expected Request variant (Artcraft skips draft)"),
|
||||
};
|
||||
request.send_request(&client).await.expect("send_request should succeed")
|
||||
}
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum;
|
||||
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
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,
|
||||
};
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_mini::request::ArtcraftSeedance2p0BytePlusUltraMiniRequestState;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use crate::generate::generate_video::video_generation_request::VideoGenerationRequest;
|
||||
|
||||
pub fn build_artcraft_seedance_2p0_bpu_mini(builder: GenerateVideoRequestBuilder) -> Result<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
// Seedance 2.0 Mini supports 480p and 720p only (higher resolutions downgrade
|
||||
// to 720p), and all six aspect ratios including 21:9.
|
||||
let request = build_artcraft_omni_video_request(
|
||||
builder,
|
||||
CommonVideoModelEnum::Seedance2p0BytePlusUltraMini,
|
||||
SupportedResolutions::Fast,
|
||||
UltraWideSupport::Supported,
|
||||
)?;
|
||||
let state = ArtcraftSeedance2p0BytePlusUltraMiniRequestState { request };
|
||||
Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltraMini(state)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioEnum;
|
||||
use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum;
|
||||
use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum;
|
||||
use enums::common::generation::common_bitrate::CommonBitrate as CommonBitrateEnum;
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
use crate::api::router_aspect_ratio::RouterAspectRatio;
|
||||
use crate::api::router_bitrate::RouterBitrate;
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use crate::generate::generate_video::video_generation_request::VideoGenerationRequest;
|
||||
|
||||
use super::*;
|
||||
|
||||
mod field_conversions {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn prompt_is_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.prompt = Some("test".to_string()); }));
|
||||
assert_eq!(req.request.prompt, Some("test".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_is_correct() {
|
||||
let req = unwrap_request(builder_with(|_| {}));
|
||||
assert!(matches!(req.request.model, Some(CommonVideoModelEnum::Seedance2p0BytePlusUltraMini)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(10); }));
|
||||
assert_eq!(req.request.duration_seconds, Some(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_clamped_to_max() {
|
||||
let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(99); }));
|
||||
assert_eq!(req.request.duration_seconds, Some(15));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(4); }));
|
||||
assert_eq!(req.request.video_batch_count, Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_clamped_to_platform_max() {
|
||||
// The ArtCraft platform caps batch at 4 and downgrades higher requests.
|
||||
let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(8); }));
|
||||
assert_eq!(req.request.video_batch_count, Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bitrate_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.bitrate = Some(RouterBitrate::High); }));
|
||||
assert_eq!(req.request.bitrate, Some(CommonBitrateEnum::High));
|
||||
}
|
||||
}
|
||||
|
||||
mod aspect_ratio_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn wide() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideSixteenByNine); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideSixteenByNine));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ultra_wide() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideTwentyOneByNine); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideTwentyOneByNine));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tall() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::TallNineBySixteen); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::TallNineBySixteen));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn square() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::Square); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::Square));
|
||||
}
|
||||
}
|
||||
|
||||
mod resolution_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn res_480p() {
|
||||
let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::FourEightyP); }));
|
||||
assert_eq!(req.request.resolution, Some(CommonResolutionEnum::FourEightyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_720p() {
|
||||
let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::SevenTwentyP); }));
|
||||
assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_1080p_downgrades_to_720p() {
|
||||
let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::TenEightyP); }));
|
||||
assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_1080p_error_out() {
|
||||
let result = build_artcraft_seedance_2p0_bpu_mini(GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0BytePlusUltraMini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
resolution: Some(RouterResolution::TenEightyP),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..Default::default()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_stays_none() {
|
||||
let req = unwrap_request(builder_with(|_| {}));
|
||||
assert!(req.request.resolution.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
mod media_token_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn start_frame_token_passed_through() {
|
||||
let token = MediaFileToken::new("mf_start".to_string());
|
||||
let req = unwrap_request(builder_with(|b| { b.start_frame = Some(ImageRef::MediaFileToken(token.clone())); }));
|
||||
assert_eq!(req.request.start_frame_image_media_token, Some(token));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_start_frame_rejected() {
|
||||
let result = build_artcraft_seedance_2p0_bpu_mini(GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0BytePlusUltraMini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
start_frame: Some(ImageRef::Url("https://example.com".to_string())),
|
||||
..Default::default()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_image_tokens_passed_through() {
|
||||
let tokens = vec![
|
||||
MediaFileToken::new("mf_a".to_string()),
|
||||
MediaFileToken::new("mf_b".to_string()),
|
||||
];
|
||||
let req = unwrap_request(builder_with(|b| { b.reference_images = Some(ImageListRef::MediaFileTokens(tokens.clone())); }));
|
||||
assert_eq!(req.request.reference_image_media_tokens, Some(tokens));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_tokens_passed_through() {
|
||||
let tokens = vec![
|
||||
CharacterToken::new("char_a".to_string()),
|
||||
CharacterToken::new("char_b".to_string()),
|
||||
];
|
||||
let req = unwrap_request(builder_with(|b| { b.reference_character_tokens = Some(CharacterListRef::CharacterTokens(tokens.clone())); }));
|
||||
assert_eq!(req.request.reference_character_tokens, Some(tokens));
|
||||
}
|
||||
}
|
||||
|
||||
fn builder_with(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder {
|
||||
let mut builder = GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0BytePlusUltraMini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
duration_seconds: Some(5),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
};
|
||||
f(&mut builder);
|
||||
builder
|
||||
}
|
||||
|
||||
fn unwrap_request(builder: GenerateVideoRequestBuilder) -> ArtcraftSeedance2p0BytePlusUltraMiniRequestState {
|
||||
let result = build_artcraft_seedance_2p0_bpu_mini(builder).expect("build should succeed");
|
||||
match result {
|
||||
VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltraMini(state)) => state,
|
||||
_ => panic!("expected ArtcraftSeedance2p0BytePlusUltraMini request"),
|
||||
}
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_byteplus_mini_usd_cents;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_mini::request::ArtcraftSeedance2p0BytePlusUltraMiniRequestState;
|
||||
|
||||
pub struct ArtcraftSeedance2p0BytePlusUltraMiniCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub batch_count: u16,
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p0BytePlusUltraMiniCostState {
|
||||
pub fn from_request(request: &ArtcraftSeedance2p0BytePlusUltraMiniRequestState) -> Self {
|
||||
let resolution = request.request.resolution
|
||||
.unwrap_or(CommonResolution::SevenTwentyP);
|
||||
let duration_seconds = request.request.duration_seconds.unwrap_or(5);
|
||||
let batch_count = request.request.video_batch_count.unwrap_or(1);
|
||||
let has_video_reference = request.request.reference_video_media_tokens
|
||||
.as_ref()
|
||||
.is_some_and(|tokens| !tokens.is_empty());
|
||||
|
||||
Self { resolution, duration_seconds, batch_count, has_video_reference }
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// BytePlus / BytePlus Ultra Mini share a higher-margin rate set; see
|
||||
// seedance_common::seedance_2p0_byteplus_mini_usd_cents.
|
||||
let usd_cents = seedance_2p0_byteplus_mini_usd_cents(
|
||||
self.resolution,
|
||||
self.duration_seconds,
|
||||
self.batch_count,
|
||||
self.has_video_reference,
|
||||
);
|
||||
|
||||
// ArtCraft credits: 100 credits = $1.00, so credits = cents.
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
|
||||
// Every combination, driven through the full build pipeline. These prices are
|
||||
// the supplier's per-combo cost plus a 5% margin, rounded up to whole cents.
|
||||
|
||||
mod pricing_480p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 14);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 63);
|
||||
}
|
||||
}
|
||||
|
||||
mod pricing_720p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 88);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 131);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 53);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 105);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 158);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_defaults_to_720p() {
|
||||
assert_eq!(cents_no_ref(None, 5, 1), cents(RouterResolution::SevenTwentyP, 5, 1, false));
|
||||
}
|
||||
}
|
||||
|
||||
mod credits_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn credits_equal_usd_cents_all_combos() {
|
||||
let resolutions = [
|
||||
Some(RouterResolution::FourEightyP),
|
||||
Some(RouterResolution::SevenTwentyP),
|
||||
None,
|
||||
];
|
||||
for res in resolutions {
|
||||
for dur in [4u16, 5, 10, 15] {
|
||||
for batch in [1u16, 2, 4] {
|
||||
for has_ref in [false, true] {
|
||||
let cost = build_cost(res, dur, batch, has_ref);
|
||||
assert_eq!(cost.cost_in_credits, cost.cost_in_usd_cents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_cost(
|
||||
resolution: Option<RouterResolution>,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
with_video_reference: bool,
|
||||
) -> VideoGenerationCostEstimate {
|
||||
let reference_videos = with_video_reference.then(|| {
|
||||
VideoListRef::MediaFileTokens(vec![MediaFileToken::new("mf_ref".to_string())])
|
||||
});
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0BytePlusUltraMini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
resolution,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: Some(video_batch_count),
|
||||
reference_videos,
|
||||
..Default::default()
|
||||
};
|
||||
builder.build2()
|
||||
.expect("build2 should succeed")
|
||||
.estimate_cost()
|
||||
.expect("estimate_cost should succeed")
|
||||
}
|
||||
|
||||
fn cents(
|
||||
resolution: RouterResolution,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
with_video_reference: bool,
|
||||
) -> u64 {
|
||||
build_cost(Some(resolution), duration_seconds, video_batch_count, with_video_reference)
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn cents_no_ref(
|
||||
resolution: Option<RouterResolution>,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
) -> u64 {
|
||||
build_cost(resolution, duration_seconds, video_batch_count, false)
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest;
|
||||
|
||||
use crate::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video::providers::artcraft::request_common::send_artcraft_omni_video_request;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftSeedance2p0BytePlusUltraMiniRequestState {
|
||||
pub request: OmniGenVideoCostAndGenerateRequest,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p0BytePlusUltraMiniRequestState {
|
||||
pub async fn send(&self, client: &RouterArtcraftClient) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
send_artcraft_omni_video_request(&self.request, client).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::router_aspect_ratio::RouterAspectRatio;
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use crate::client::router_client::RouterClient;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
|
||||
#[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 corgi running through a field of wildflowers at sunset.".to_string()),
|
||||
aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine),
|
||||
..mini_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Artcraft(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[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(RouterResolution::FourEightyP),
|
||||
..mini_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Artcraft(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
fn mini_builder() -> GenerateVideoRequestBuilder {
|
||||
GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0BytePlusUltraMini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
duration_seconds: Some(4),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_artcraft_client() -> RouterClient {
|
||||
let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/artcraft_cookies.txt")
|
||||
.expect("Failed to read artcraft cookies");
|
||||
let cookies = cookies.trim().to_string();
|
||||
let credentials = StorytellerCredentialSet::parse_multi_cookie_header(&cookies)
|
||||
.expect("Failed to parse cookies")
|
||||
.expect("No credentials found");
|
||||
RouterClient::Artcraft(RouterArtcraftClient::new(ApiHost::Storyteller, credentials))
|
||||
}
|
||||
|
||||
async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse {
|
||||
let client = get_artcraft_client();
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let request = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Request(r) => r,
|
||||
_ => panic!("expected Request variant (Artcraft skips draft)"),
|
||||
};
|
||||
request.send_request(&client).await.expect("send_request should succeed")
|
||||
}
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum;
|
||||
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
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,
|
||||
};
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_mini::request::ArtcraftSeedance2p0MiniRequestState;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use crate::generate::generate_video::video_generation_request::VideoGenerationRequest;
|
||||
|
||||
pub fn build_artcraft_seedance_2p0_mini(builder: GenerateVideoRequestBuilder) -> Result<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
// Seedance 2.0 Mini supports 480p and 720p only (higher resolutions downgrade
|
||||
// to 720p), and all six aspect ratios including 21:9.
|
||||
let request = build_artcraft_omni_video_request(
|
||||
builder,
|
||||
CommonVideoModelEnum::Seedance2p0Mini,
|
||||
SupportedResolutions::Fast,
|
||||
UltraWideSupport::Supported,
|
||||
)?;
|
||||
let state = ArtcraftSeedance2p0MiniRequestState { request };
|
||||
Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0Mini(state)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioEnum;
|
||||
use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum;
|
||||
use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum;
|
||||
use enums::common::generation::common_bitrate::CommonBitrate as CommonBitrateEnum;
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
use crate::api::router_aspect_ratio::RouterAspectRatio;
|
||||
use crate::api::router_bitrate::RouterBitrate;
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use crate::generate::generate_video::video_generation_request::VideoGenerationRequest;
|
||||
|
||||
use super::*;
|
||||
|
||||
mod field_conversions {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn prompt_is_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.prompt = Some("test".to_string()); }));
|
||||
assert_eq!(req.request.prompt, Some("test".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_is_correct() {
|
||||
let req = unwrap_request(builder_with(|_| {}));
|
||||
assert!(matches!(req.request.model, Some(CommonVideoModelEnum::Seedance2p0Mini)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(10); }));
|
||||
assert_eq!(req.request.duration_seconds, Some(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_clamped_to_max() {
|
||||
let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(99); }));
|
||||
assert_eq!(req.request.duration_seconds, Some(15));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(4); }));
|
||||
assert_eq!(req.request.video_batch_count, Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_clamped_to_platform_max() {
|
||||
// The ArtCraft platform caps batch at 4 and downgrades higher requests.
|
||||
let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(8); }));
|
||||
assert_eq!(req.request.video_batch_count, Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bitrate_passed_through() {
|
||||
let req = unwrap_request(builder_with(|b| { b.bitrate = Some(RouterBitrate::High); }));
|
||||
assert_eq!(req.request.bitrate, Some(CommonBitrateEnum::High));
|
||||
}
|
||||
}
|
||||
|
||||
mod aspect_ratio_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn wide() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideSixteenByNine); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideSixteenByNine));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ultra_wide() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideTwentyOneByNine); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideTwentyOneByNine));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tall() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::TallNineBySixteen); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::TallNineBySixteen));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn square() {
|
||||
let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::Square); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::Square));
|
||||
}
|
||||
}
|
||||
|
||||
mod resolution_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn res_480p() {
|
||||
let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::FourEightyP); }));
|
||||
assert_eq!(req.request.resolution, Some(CommonResolutionEnum::FourEightyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_720p() {
|
||||
let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::SevenTwentyP); }));
|
||||
assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_1080p_downgrades_to_720p() {
|
||||
let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::TenEightyP); }));
|
||||
assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_1080p_error_out() {
|
||||
let result = build_artcraft_seedance_2p0_mini(GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0Mini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
resolution: Some(RouterResolution::TenEightyP),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..Default::default()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_stays_none() {
|
||||
let req = unwrap_request(builder_with(|_| {}));
|
||||
assert!(req.request.resolution.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
mod media_token_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn start_frame_token_passed_through() {
|
||||
let token = MediaFileToken::new("mf_start".to_string());
|
||||
let req = unwrap_request(builder_with(|b| { b.start_frame = Some(ImageRef::MediaFileToken(token.clone())); }));
|
||||
assert_eq!(req.request.start_frame_image_media_token, Some(token));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_start_frame_rejected() {
|
||||
let result = build_artcraft_seedance_2p0_mini(GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0Mini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
start_frame: Some(ImageRef::Url("https://example.com".to_string())),
|
||||
..Default::default()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_image_tokens_passed_through() {
|
||||
let tokens = vec![
|
||||
MediaFileToken::new("mf_a".to_string()),
|
||||
MediaFileToken::new("mf_b".to_string()),
|
||||
];
|
||||
let req = unwrap_request(builder_with(|b| { b.reference_images = Some(ImageListRef::MediaFileTokens(tokens.clone())); }));
|
||||
assert_eq!(req.request.reference_image_media_tokens, Some(tokens));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_tokens_passed_through() {
|
||||
let tokens = vec![
|
||||
CharacterToken::new("char_a".to_string()),
|
||||
CharacterToken::new("char_b".to_string()),
|
||||
];
|
||||
let req = unwrap_request(builder_with(|b| { b.reference_character_tokens = Some(CharacterListRef::CharacterTokens(tokens.clone())); }));
|
||||
assert_eq!(req.request.reference_character_tokens, Some(tokens));
|
||||
}
|
||||
}
|
||||
|
||||
fn builder_with(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder {
|
||||
let mut builder = GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0Mini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
duration_seconds: Some(5),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
};
|
||||
f(&mut builder);
|
||||
builder
|
||||
}
|
||||
|
||||
fn unwrap_request(builder: GenerateVideoRequestBuilder) -> ArtcraftSeedance2p0MiniRequestState {
|
||||
let result = build_artcraft_seedance_2p0_mini(builder).expect("build should succeed");
|
||||
match result {
|
||||
VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0Mini(state)) => state,
|
||||
_ => panic!("expected ArtcraftSeedance2p0Mini request"),
|
||||
}
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_mini_usd_cents;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_mini::request::ArtcraftSeedance2p0MiniRequestState;
|
||||
|
||||
pub struct ArtcraftSeedance2p0MiniCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
pub batch_count: u16,
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p0MiniCostState {
|
||||
pub fn from_request(request: &ArtcraftSeedance2p0MiniRequestState) -> Self {
|
||||
let resolution = request.request.resolution
|
||||
.unwrap_or(CommonResolution::SevenTwentyP);
|
||||
let duration_seconds = request.request.duration_seconds.unwrap_or(5);
|
||||
let batch_count = request.request.video_batch_count.unwrap_or(1);
|
||||
let has_video_reference = request.request.reference_video_media_tokens
|
||||
.as_ref()
|
||||
.is_some_and(|tokens| !tokens.is_empty());
|
||||
|
||||
Self { resolution, duration_seconds, batch_count, has_video_reference }
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Seedance 2.0 Mini is priced uniformly across its variants; see
|
||||
// seedance_common::seedance_2p0_mini_usd_cents.
|
||||
let usd_cents = seedance_2p0_mini_usd_cents(
|
||||
self.resolution,
|
||||
self.duration_seconds,
|
||||
self.batch_count,
|
||||
self.has_video_reference,
|
||||
);
|
||||
|
||||
// ArtCraft credits: 100 credits = $1.00, so credits = cents.
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
|
||||
// Every combination, driven through the full build pipeline. These prices are
|
||||
// the supplier's per-combo cost plus a 5% margin, rounded up to whole cents.
|
||||
|
||||
mod pricing_480p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 13);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 49);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 62);
|
||||
}
|
||||
}
|
||||
|
||||
mod pricing_720p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 87);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 130);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 52);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 104);
|
||||
assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 156);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_defaults_to_720p() {
|
||||
assert_eq!(cents_no_ref(None, 5, 1), cents(RouterResolution::SevenTwentyP, 5, 1, false));
|
||||
}
|
||||
}
|
||||
|
||||
mod credits_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn credits_equal_usd_cents_all_combos() {
|
||||
let resolutions = [
|
||||
Some(RouterResolution::FourEightyP),
|
||||
Some(RouterResolution::SevenTwentyP),
|
||||
None,
|
||||
];
|
||||
for res in resolutions {
|
||||
for dur in [4u16, 5, 10, 15] {
|
||||
for batch in [1u16, 2, 4] {
|
||||
for has_ref in [false, true] {
|
||||
let cost = build_cost(res, dur, batch, has_ref);
|
||||
assert_eq!(cost.cost_in_credits, cost.cost_in_usd_cents);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_cost(
|
||||
resolution: Option<RouterResolution>,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
with_video_reference: bool,
|
||||
) -> VideoGenerationCostEstimate {
|
||||
let reference_videos = with_video_reference.then(|| {
|
||||
VideoListRef::MediaFileTokens(vec![MediaFileToken::new("mf_ref".to_string())])
|
||||
});
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0Mini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
resolution,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: Some(video_batch_count),
|
||||
reference_videos,
|
||||
..Default::default()
|
||||
};
|
||||
builder.build2()
|
||||
.expect("build2 should succeed")
|
||||
.estimate_cost()
|
||||
.expect("estimate_cost should succeed")
|
||||
}
|
||||
|
||||
fn cents(
|
||||
resolution: RouterResolution,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
with_video_reference: bool,
|
||||
) -> u64 {
|
||||
build_cost(Some(resolution), duration_seconds, video_batch_count, with_video_reference)
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn cents_no_ref(
|
||||
resolution: Option<RouterResolution>,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
) -> u64 {
|
||||
build_cost(resolution, duration_seconds, video_batch_count, false)
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest;
|
||||
|
||||
use crate::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video::providers::artcraft::request_common::send_artcraft_omni_video_request;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftSeedance2p0MiniRequestState {
|
||||
pub request: OmniGenVideoCostAndGenerateRequest,
|
||||
}
|
||||
|
||||
impl ArtcraftSeedance2p0MiniRequestState {
|
||||
pub async fn send(&self, client: &RouterArtcraftClient) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
send_artcraft_omni_video_request(&self.request, client).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::router_aspect_ratio::RouterAspectRatio;
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use crate::client::router_client::RouterClient;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
|
||||
#[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 corgi running through a field of wildflowers at sunset.".to_string()),
|
||||
aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine),
|
||||
..mini_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Artcraft(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
#[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(RouterResolution::FourEightyP),
|
||||
..mini_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Artcraft(_)));
|
||||
assert_eq!(1, 2, "Inspect output above");
|
||||
}
|
||||
|
||||
fn mini_builder() -> GenerateVideoRequestBuilder {
|
||||
GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0Mini,
|
||||
provider: RouterProvider::Artcraft,
|
||||
duration_seconds: Some(4),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_artcraft_client() -> RouterClient {
|
||||
let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/artcraft_cookies.txt")
|
||||
.expect("Failed to read artcraft cookies");
|
||||
let cookies = cookies.trim().to_string();
|
||||
let credentials = StorytellerCredentialSet::parse_multi_cookie_header(&cookies)
|
||||
.expect("Failed to parse cookies")
|
||||
.expect("No credentials found");
|
||||
RouterClient::Artcraft(RouterArtcraftClient::new(ApiHost::Storyteller, credentials))
|
||||
}
|
||||
|
||||
async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse {
|
||||
let client = get_artcraft_client();
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let request = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Request(r) => r,
|
||||
_ => panic!("expected Request variant (Artcraft skips draft)"),
|
||||
};
|
||||
request.send_request(&client).await.expect("send_request should succeed")
|
||||
}
|
||||
}
|
||||
+253
@@ -2,6 +2,8 @@
|
||||
//!
|
||||
//! ArtCraft credits equal USD cents (100 credits = $1.00).
|
||||
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
|
||||
// 4K is priced uniformly across the non-Fast Seedance 2.0 models. Rates are held
|
||||
// in hundredths of a USD cent per second so the math is exact integer arithmetic
|
||||
// (no floating point), then rounded up to whole cents.
|
||||
@@ -35,6 +37,107 @@ pub fn seedance_2p0_four_k_usd_cents(
|
||||
total_centi_cents.div_ceil(100)
|
||||
}
|
||||
|
||||
// ── Seedance 2.0 Mini pricing ──
|
||||
//
|
||||
// Mini offers only 480p and 720p. The rates below are the per-second USER price
|
||||
// in USD cents (margin already included); a reference video adds a per-second
|
||||
// surcharge. Rates are fractional, so the total is rounded UP to a whole cent
|
||||
// once at the end (after multiplying by duration × batch).
|
||||
//
|
||||
// The regular Mini and the BytePlus / BytePlus Ultra Minis carry DIFFERENT
|
||||
// margins, so they have separate rate sets and helpers below.
|
||||
|
||||
/// Regular Mini — 480p price, USD cents per second.
|
||||
const MINI_CENTS_PER_SECOND_480P: f64 = 3.24074074;
|
||||
/// Regular Mini — 480p reference-video surcharge, USD cents per second.
|
||||
const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.86419753;
|
||||
/// Regular Mini — 720p price, USD cents per second.
|
||||
const MINI_CENTS_PER_SECOND_720P: f64 = 8.64197531;
|
||||
/// Regular Mini — 720p reference-video surcharge, USD cents per second.
|
||||
const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.72839506;
|
||||
|
||||
/// BytePlus / BytePlus Ultra Mini — 480p price, USD cents per second.
|
||||
const BYTEPLUS_MINI_CENTS_PER_SECOND_480P: f64 = 3.27160494;
|
||||
/// BytePlus / BytePlus Ultra Mini — 480p reference-video surcharge, USD cents per second.
|
||||
const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.87242798;
|
||||
/// BytePlus / BytePlus Ultra Mini — 720p price, USD cents per second.
|
||||
const BYTEPLUS_MINI_CENTS_PER_SECOND_720P: f64 = 8.72427984;
|
||||
/// BytePlus / BytePlus Ultra Mini — 720p reference-video surcharge, USD cents per second.
|
||||
const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.74485597;
|
||||
|
||||
/// ArtCraft's user-facing price (USD cents) for the regular Seedance 2.0 Mini.
|
||||
///
|
||||
/// Only 480p and 720p are offered; any other resolution prices at 720p. A
|
||||
/// reference video adds the per-second surcharge. The fractional total is
|
||||
/// rounded UP to a whole cent.
|
||||
pub fn seedance_2p0_mini_usd_cents(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
batch_count: u16,
|
||||
has_video_reference: bool,
|
||||
) -> u64 {
|
||||
mini_usd_cents(
|
||||
resolution,
|
||||
duration_seconds,
|
||||
batch_count,
|
||||
has_video_reference,
|
||||
MINI_CENTS_PER_SECOND_480P,
|
||||
MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P,
|
||||
MINI_CENTS_PER_SECOND_720P,
|
||||
MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P,
|
||||
)
|
||||
}
|
||||
|
||||
/// ArtCraft's user-facing price (USD cents) for the Seedance 2.0 BytePlus Mini
|
||||
/// and BytePlus Ultra Mini (which share the same rates — a higher margin than
|
||||
/// the regular Mini).
|
||||
///
|
||||
/// Only 480p and 720p are offered; any other resolution prices at 720p. A
|
||||
/// reference video adds the per-second surcharge. The fractional total is
|
||||
/// rounded UP to a whole cent.
|
||||
pub fn seedance_2p0_byteplus_mini_usd_cents(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
batch_count: u16,
|
||||
has_video_reference: bool,
|
||||
) -> u64 {
|
||||
mini_usd_cents(
|
||||
resolution,
|
||||
duration_seconds,
|
||||
batch_count,
|
||||
has_video_reference,
|
||||
BYTEPLUS_MINI_CENTS_PER_SECOND_480P,
|
||||
BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P,
|
||||
BYTEPLUS_MINI_CENTS_PER_SECOND_720P,
|
||||
BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn mini_usd_cents(
|
||||
resolution: CommonResolution,
|
||||
duration_seconds: u16,
|
||||
batch_count: u16,
|
||||
has_video_reference: bool,
|
||||
cents_per_second_480p: f64,
|
||||
video_reference_surcharge_per_second_480p: f64,
|
||||
cents_per_second_720p: f64,
|
||||
video_reference_surcharge_per_second_720p: f64,
|
||||
) -> u64 {
|
||||
let (base_per_second, video_reference_surcharge_per_second) = match resolution {
|
||||
CommonResolution::FourEightyP => (cents_per_second_480p, video_reference_surcharge_per_second_480p),
|
||||
// Everything else (including 720p and unsupported resolutions) prices at 720p.
|
||||
_ => (cents_per_second_720p, video_reference_surcharge_per_second_720p),
|
||||
};
|
||||
|
||||
let mut cents_per_second = base_per_second;
|
||||
if has_video_reference {
|
||||
cents_per_second += video_reference_surcharge_per_second;
|
||||
}
|
||||
|
||||
(cents_per_second * duration_seconds as f64 * batch_count as f64).ceil() as u64
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -72,4 +175,154 @@ mod tests {
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 4, false), 1732);
|
||||
assert_eq!(seedance_2p0_four_k_usd_cents(5, 2, true), 1038);
|
||||
}
|
||||
|
||||
// ── Seedance 2.0 Mini ──
|
||||
//
|
||||
// Every combination (480p/720p × with/without video reference × 4/5/10/15s)
|
||||
// at batch 1. These are the supplier's per-combo cost plus a 5% margin,
|
||||
// rounded up to whole cents.
|
||||
|
||||
mod mini {
|
||||
use super::*;
|
||||
|
||||
fn cents(res: CommonResolution, dur: u16, has_ref: bool) -> u64 {
|
||||
seedance_2p0_mini_usd_cents(res, dur, 1, has_ref)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 13);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 33);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 49);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 21);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 42);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 62);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 35);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 44);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 87);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 130);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 42);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 52);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 104);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 156);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_reference_always_costs_more() {
|
||||
for res in [CommonResolution::FourEightyP, CommonResolution::SevenTwentyP] {
|
||||
for dur in [4u16, 5, 10, 15] {
|
||||
assert!(
|
||||
cents(res, dur, true) > cents(res, dur, false),
|
||||
"video ref should cost more at {res:?} {dur}s",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_resolution_prices_at_720p() {
|
||||
assert_eq!(
|
||||
seedance_2p0_mini_usd_cents(CommonResolution::TenEightyP, 5, 1, false),
|
||||
seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 1, false),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_scales_the_total() {
|
||||
// Batch is baked in before the single round-up, so batched totals can be
|
||||
// a cent under N× the single price.
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 87);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 173);
|
||||
assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 33);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Seedance 2.0 BytePlus / BytePlus Ultra Mini ──
|
||||
//
|
||||
// Same combinations as the regular Mini, but at a higher margin (6% vs 5%),
|
||||
// so the prices are equal-or-higher than the regular Mini.
|
||||
|
||||
mod byteplus_mini {
|
||||
use super::*;
|
||||
|
||||
fn cents(res: CommonResolution, dur: u16, has_ref: bool) -> u64 {
|
||||
seedance_2p0_byteplus_mini_usd_cents(res, dur, 1, has_ref)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 14);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 33);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_eighty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 17);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 21);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 42);
|
||||
assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 63);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_without_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 35);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 44);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 88);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 131);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seven_twenty_p_with_video_reference() {
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 42);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 53);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 105);
|
||||
assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 158);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn never_cheaper_than_regular_mini() {
|
||||
for res in [CommonResolution::FourEightyP, CommonResolution::SevenTwentyP] {
|
||||
for dur in [4u16, 5, 10, 15] {
|
||||
for has_ref in [false, true] {
|
||||
assert!(
|
||||
seedance_2p0_byteplus_mini_usd_cents(res, dur, 1, has_ref)
|
||||
>= seedance_2p0_mini_usd_cents(res, dur, 1, has_ref),
|
||||
"byteplus mini (6%) should be >= regular mini (5%) at {res:?} {dur}s ref={has_ref}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_resolution_prices_at_720p() {
|
||||
assert_eq!(
|
||||
seedance_2p0_byteplus_mini_usd_cents(CommonResolution::TenEightyP, 5, 1, false),
|
||||
seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 1, false),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_count_scales_the_total() {
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 88);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 175);
|
||||
assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 33);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
pub mod happy_horse_1p0;
|
||||
pub mod seedance_2p0;
|
||||
pub mod seedance_2p0_fast;
|
||||
pub mod seedance_2p0_mini;
|
||||
pub(crate) mod resolve;
|
||||
pub(crate) mod upload;
|
||||
|
||||
+510
@@ -0,0 +1,510 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{
|
||||
KinoviSeedance2p0MiniAspectRatio as KinoviAspectRatio,
|
||||
KinoviSeedance2p0MiniBitrate as KinoviBitrate,
|
||||
KinoviSeedance2p0MiniBatchCount as KinoviBatchCount,
|
||||
KinoviSeedance2p0MiniOutputResolution as KinoviOutputResolution,
|
||||
};
|
||||
|
||||
use crate::api::router_aspect_ratio::RouterAspectRatio;
|
||||
use crate::api::router_bitrate::RouterBitrate;
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
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::kinovi::seedance_2p0_mini::draft::{KinoviSeedance2p0MiniDraftState, KinoviSeedance2p0MiniRemainingItems};
|
||||
use crate::generate::generate_video::video_generation_draft::VideoGenerationDraftRequest;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
|
||||
pub fn build_kinovi_seedance_2p0_mini(builder: GenerateVideoRequestBuilder) -> Result<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
let draft = do_build_kinovi_seedance_2p0_mini(builder)?;
|
||||
Ok(VideoGenerationDraftOrRequest::Draft(VideoGenerationDraftRequest::KinoviSeedance2p0Mini(draft)))
|
||||
}
|
||||
|
||||
fn do_build_kinovi_seedance_2p0_mini(mut builder: GenerateVideoRequestBuilder) -> Result<KinoviSeedance2p0MiniDraftState, ArtcraftRouterError> {
|
||||
let strategy = builder.request_mismatch_mitigation_strategy;
|
||||
|
||||
let aspect_ratio = plan_aspect_ratio(builder.aspect_ratio.take(), strategy)?;
|
||||
let resolution = plan_output_resolution(builder.resolution.take(), strategy)?;
|
||||
let batch_count = plan_batch_count(builder.video_batch_count.take(), strategy)?;
|
||||
let duration_seconds = plan_duration(builder.duration_seconds.take(), strategy)?;
|
||||
let bitrate = plan_bitrate(builder.bitrate.take());
|
||||
let prompt = builder.prompt.take().unwrap_or_default();
|
||||
|
||||
let unhandled_request_state = KinoviSeedance2p0MiniRemainingItems {
|
||||
start_frame: builder.start_frame.take(),
|
||||
end_frame: builder.end_frame.take(),
|
||||
reference_images: builder.reference_images.take(),
|
||||
reference_videos: builder.reference_videos.take(),
|
||||
reference_audio: builder.reference_audio.take(),
|
||||
reference_character_tokens: builder.reference_character_tokens.take(),
|
||||
};
|
||||
|
||||
Ok(KinoviSeedance2p0MiniDraftState {
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
batch_count,
|
||||
duration_seconds,
|
||||
bitrate,
|
||||
prompt,
|
||||
unhandled_request_state: Some(unhandled_request_state),
|
||||
})
|
||||
}
|
||||
|
||||
// ── Plan helpers ──
|
||||
|
||||
// Seedance 2.0 Mini supports all six aspect ratios:
|
||||
// 16:9, 21:9, 9:16, 1:1, 4:3, 3:4. All supported ratios cost the same, so
|
||||
// both upgrade and downgrade pick the nearest match.
|
||||
fn plan_aspect_ratio(
|
||||
aspect_ratio: Option<RouterAspectRatio>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<KinoviAspectRatio, ArtcraftRouterError> {
|
||||
match aspect_ratio {
|
||||
// No preference or auto — default to landscape
|
||||
None
|
||||
| Some(RouterAspectRatio::Auto)
|
||||
| Some(RouterAspectRatio::Auto2k)
|
||||
| Some(RouterAspectRatio::Auto4k) => Ok(KinoviAspectRatio::Landscape16x9),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterAspectRatio::WideSixteenByNine) | Some(RouterAspectRatio::Wide) => {
|
||||
Ok(KinoviAspectRatio::Landscape16x9)
|
||||
}
|
||||
Some(RouterAspectRatio::TallNineBySixteen) | Some(RouterAspectRatio::Tall) => {
|
||||
Ok(KinoviAspectRatio::Portrait9x16)
|
||||
}
|
||||
Some(RouterAspectRatio::Square) | Some(RouterAspectRatio::SquareHd) => {
|
||||
Ok(KinoviAspectRatio::Square1x1)
|
||||
}
|
||||
Some(RouterAspectRatio::WideFourByThree) => Ok(KinoviAspectRatio::Standard4x3),
|
||||
Some(RouterAspectRatio::TallThreeByFour) => Ok(KinoviAspectRatio::Portrait3x4),
|
||||
Some(RouterAspectRatio::WideTwentyOneByNine) => Ok(KinoviAspectRatio::UltraWide21x9),
|
||||
|
||||
// Mismatches — apply strategy
|
||||
Some(unsupported) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "aspect_ratio",
|
||||
value: format!("{:?}", unsupported),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
|
||||
Ok(nearest_aspect_ratio(unsupported))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Pick the nearest supported aspect ratio by AR value (width / height).
|
||||
fn nearest_aspect_ratio(aspect_ratio: RouterAspectRatio) -> KinoviAspectRatio {
|
||||
match aspect_ratio {
|
||||
RouterAspectRatio::WideFiveByFour => KinoviAspectRatio::Standard4x3, // 1.25, nearest 1.33
|
||||
RouterAspectRatio::WideThreeByTwo => KinoviAspectRatio::Standard4x3, // 1.50, nearest 1.33
|
||||
RouterAspectRatio::TallFourByFive => KinoviAspectRatio::Portrait3x4, // 0.80, nearest 0.75
|
||||
RouterAspectRatio::TallTwoByThree => KinoviAspectRatio::Portrait3x4, // 0.67, nearest 0.75
|
||||
RouterAspectRatio::TallNineByTwentyOne => KinoviAspectRatio::Portrait9x16, // 0.43, nearest 0.56
|
||||
_ => KinoviAspectRatio::Square1x1,
|
||||
}
|
||||
}
|
||||
|
||||
// Seedance 2.0 Mini supports output resolutions: 480p and 720p only.
|
||||
// 1080p (and higher) is NOT supported — downgrade to 720p or error based on strategy.
|
||||
fn plan_output_resolution(
|
||||
resolution: Option<RouterResolution>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<KinoviOutputResolution>, ArtcraftRouterError> {
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
|
||||
// Direct mappings
|
||||
Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)),
|
||||
Some(RouterResolution::SevenTwentyP) => Ok(Some(KinoviOutputResolution::SevenTwentyP)),
|
||||
|
||||
// 1080p is not supported for Mini — handle via strategy
|
||||
Some(RouterResolution::TenEightyP) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "resolution",
|
||||
value: format!("{:?}", RouterResolution::TenEightyP),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
|
||||
// 1080p not available — downgrade to 720p (highest supported)
|
||||
Ok(Some(KinoviOutputResolution::SevenTwentyP))
|
||||
}
|
||||
},
|
||||
|
||||
// Other unsupported resolutions
|
||||
Some(unsupported) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "resolution",
|
||||
value: format!("{:?}", unsupported),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
|
||||
Ok(Some(match unsupported {
|
||||
RouterResolution::HalfK => KinoviOutputResolution::FourEightyP,
|
||||
_ => KinoviOutputResolution::SevenTwentyP,
|
||||
}))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Seedance 2.0 Mini supports batch counts of 1–8.
|
||||
fn plan_batch_count(
|
||||
video_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<KinoviBatchCount, ArtcraftRouterError> {
|
||||
let count = video_batch_count.unwrap_or(1);
|
||||
match count {
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1 => Ok(KinoviBatchCount::One),
|
||||
2 => Ok(KinoviBatchCount::Two),
|
||||
3 => Ok(KinoviBatchCount::Three),
|
||||
4 => Ok(KinoviBatchCount::Four),
|
||||
5 => Ok(KinoviBatchCount::Five),
|
||||
6 => Ok(KinoviBatchCount::Six),
|
||||
7 => Ok(KinoviBatchCount::Seven),
|
||||
8 => Ok(KinoviBatchCount::Eight),
|
||||
// Over the maximum of 8.
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "video_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(KinoviBatchCount::Eight),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Seedance 2.0 Mini supports duration of 4–15 seconds.
|
||||
fn plan_duration(
|
||||
duration_seconds: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<u8, ArtcraftRouterError> {
|
||||
const MIN: u16 = 4;
|
||||
const MAX: u16 = 15;
|
||||
const DEFAULT: u8 = 5;
|
||||
match duration_seconds {
|
||||
None => Ok(DEFAULT),
|
||||
Some(d) if d >= MIN && d <= MAX => Ok(d as u8),
|
||||
Some(d) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "duration_seconds",
|
||||
value: format!("{}", d),
|
||||
}))
|
||||
}
|
||||
_ => Ok(d.clamp(MIN, MAX) as u8),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Seedance 2.0 Mini bitrate: "standard" (default, field omitted) or "high".
|
||||
// Bitrate does not affect cost. `Normal` and unset both map to standard (None);
|
||||
// only `High` requests the higher bitrate.
|
||||
fn plan_bitrate(bitrate: Option<RouterBitrate>) -> Option<KinoviBitrate> {
|
||||
match bitrate {
|
||||
Some(RouterBitrate::High) => Some(KinoviBitrate::High),
|
||||
Some(RouterBitrate::Normal) | None => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{
|
||||
KinoviSeedance2p0MiniAspectRatio as KinoviAspectRatio,
|
||||
KinoviSeedance2p0MiniBatchCount as KinoviBatchCount,
|
||||
KinoviSeedance2p0MiniOutputResolution as KinoviOutputResolution,
|
||||
};
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::audio_list_ref::AudioListRef;
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
use crate::api::router_aspect_ratio::RouterAspectRatio;
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::draft::KinoviSeedance2p0MiniDraftState;
|
||||
use crate::generate::generate_video::video_generation_draft::VideoGenerationDraftRequest;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
|
||||
use super::*;
|
||||
|
||||
mod materialized_field_conversions {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn prompt_is_passed_through() {
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(mini_builder()));
|
||||
assert_eq!(draft.prompt, "a cat dancing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prompt_defaults_to_empty() {
|
||||
let builder = GenerateVideoRequestBuilder { prompt: None, ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert_eq!(draft.prompt, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_seconds_converted() {
|
||||
let builder = GenerateVideoRequestBuilder { duration_seconds: Some(10), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert_eq!(draft.duration_seconds, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_defaults_to_5() {
|
||||
let builder = GenerateVideoRequestBuilder { duration_seconds: None, ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert_eq!(draft.duration_seconds, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_clamped_to_max() {
|
||||
let builder = GenerateVideoRequestBuilder { duration_seconds: Some(99), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert_eq!(draft.duration_seconds, 15);
|
||||
}
|
||||
}
|
||||
|
||||
mod batch_count_conversions {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn batch_counts_one_through_eight() {
|
||||
let expected = [
|
||||
(1u16, KinoviBatchCount::One),
|
||||
(2, KinoviBatchCount::Two),
|
||||
(3, KinoviBatchCount::Three),
|
||||
(4, KinoviBatchCount::Four),
|
||||
(5, KinoviBatchCount::Five),
|
||||
(6, KinoviBatchCount::Six),
|
||||
(7, KinoviBatchCount::Seven),
|
||||
(8, KinoviBatchCount::Eight),
|
||||
];
|
||||
for (count, variant) in expected {
|
||||
let builder = GenerateVideoRequestBuilder { video_batch_count: Some(count), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(
|
||||
std::mem::discriminant(&draft.batch_count) == std::mem::discriminant(&variant),
|
||||
"batch {count} mapped wrong",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_over_max_downgrades_to_eight() {
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
video_batch_count: Some(12),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
..mini_builder()
|
||||
};
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.batch_count, KinoviBatchCount::Eight));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_over_max_error_out() {
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
video_batch_count: Some(12),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..mini_builder()
|
||||
};
|
||||
assert!(build_kinovi_seedance_2p0_mini(builder).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_zero_errors() {
|
||||
let builder = GenerateVideoRequestBuilder { video_batch_count: Some(0), ..mini_builder() };
|
||||
assert!(build_kinovi_seedance_2p0_mini(builder).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
mod aspect_ratio_conversions {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_wide() {
|
||||
let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_ultra_wide() {
|
||||
let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::WideTwentyOneByNine), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::UltraWide21x9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_tall() {
|
||||
let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::TallNineBySixteen), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Portrait9x16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_square() {
|
||||
let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::Square), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Square1x1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_four_by_three() {
|
||||
let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::WideFourByThree), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Standard4x3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_three_by_four() {
|
||||
let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::TallThreeByFour), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Portrait3x4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_defaults_to_landscape() {
|
||||
let builder = GenerateVideoRequestBuilder { aspect_ratio: None, ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9));
|
||||
}
|
||||
}
|
||||
|
||||
mod resolution_conversions {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn resolution_480p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: Some(RouterResolution::FourEightyP), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::FourEightyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_720p() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: Some(RouterResolution::SevenTwentyP), ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_1080p_downgrades_to_720p() {
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
resolution: Some(RouterResolution::TenEightyP),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
..mini_builder()
|
||||
};
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_1080p_error_out() {
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
resolution: Some(RouterResolution::TenEightyP),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..mini_builder()
|
||||
};
|
||||
assert!(build_kinovi_seedance_2p0_mini(builder).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_none() {
|
||||
let builder = GenerateVideoRequestBuilder { resolution: None, ..mini_builder() };
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
assert!(draft.resolution.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
mod unhandled_request_state {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn media_refs_placed_in_unhandled() {
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
start_frame: Some(ImageRef::Url("https://example.com/start.jpg".to_string())),
|
||||
end_frame: Some(ImageRef::Url("https://example.com/end.jpg".to_string())),
|
||||
reference_images: Some(ImageListRef::Urls(vec!["https://example.com/ref.jpg".to_string()])),
|
||||
reference_videos: Some(VideoListRef::Urls(vec!["https://example.com/vid.mp4".to_string()])),
|
||||
reference_audio: Some(AudioListRef::Urls(vec!["https://example.com/audio.mp3".to_string()])),
|
||||
reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![
|
||||
CharacterToken::new("char_abc".to_string()),
|
||||
])),
|
||||
..mini_builder()
|
||||
};
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
let remaining = draft.unhandled_request_state.unwrap();
|
||||
assert!(remaining.start_frame.is_some());
|
||||
assert!(remaining.end_frame.is_some());
|
||||
assert!(remaining.reference_images.is_some());
|
||||
assert!(remaining.reference_videos.is_some());
|
||||
assert!(remaining.reference_audio.is_some());
|
||||
assert!(remaining.reference_character_tokens.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_frame_media_token_placed_in_unhandled() {
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new("mf_test123".to_string()))),
|
||||
..mini_builder()
|
||||
};
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder));
|
||||
let remaining = draft.unhandled_request_state.unwrap();
|
||||
assert!(matches!(remaining.start_frame, Some(ImageRef::MediaFileToken(t)) if t.as_str() == "mf_test123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_refs_are_none_in_unhandled() {
|
||||
let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(mini_builder()));
|
||||
let remaining = draft.unhandled_request_state.unwrap();
|
||||
assert!(remaining.start_frame.is_none());
|
||||
assert!(remaining.end_frame.is_none());
|
||||
assert!(remaining.reference_images.is_none());
|
||||
assert!(remaining.reference_videos.is_none());
|
||||
assert!(remaining.reference_audio.is_none());
|
||||
assert!(remaining.reference_character_tokens.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
fn mini_builder() -> GenerateVideoRequestBuilder {
|
||||
GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0Mini,
|
||||
provider: RouterProvider::Seedance2Pro,
|
||||
prompt: Some("a cat dancing".to_string()),
|
||||
duration_seconds: Some(5),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_draft(result: Result<VideoGenerationDraftOrRequest, ArtcraftRouterError>) -> KinoviSeedance2p0MiniDraftState {
|
||||
match result.expect("build should succeed") {
|
||||
VideoGenerationDraftOrRequest::Draft(
|
||||
VideoGenerationDraftRequest::KinoviSeedance2p0Mini(draft)
|
||||
) => draft,
|
||||
_ => panic!("expected KinoviSeedance2p0Mini draft"),
|
||||
}
|
||||
}
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{
|
||||
GenerateSeedance2p0MiniRequest, KinoviSeedance2p0MiniBatchCount,
|
||||
KinoviSeedance2p0MiniOutputResolution,
|
||||
};
|
||||
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::draft::KinoviSeedance2p0MiniDraftState;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::request::KinoviSeedance2p0MiniRequestState;
|
||||
|
||||
pub struct KinoviSeedance2p0MiniCostState {
|
||||
pub resolution: Option<KinoviSeedance2p0MiniOutputResolution>,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: Option<KinoviSeedance2p0MiniBatchCount>,
|
||||
pub has_video_reference: bool,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0MiniCostState {
|
||||
pub fn from_request(request: &KinoviSeedance2p0MiniRequestState) -> Self {
|
||||
Self {
|
||||
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
|
||||
.as_ref()
|
||||
.is_some_and(|urls| !urls.is_empty()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_draft(draft: &KinoviSeedance2p0MiniDraftState) -> Self {
|
||||
let has_video_reference = draft.unhandled_request_state
|
||||
.as_ref()
|
||||
.and_then(|rem| rem.reference_videos.as_ref())
|
||||
.is_some();
|
||||
|
||||
Self {
|
||||
resolution: draft.resolution,
|
||||
duration_seconds: draft.duration_seconds,
|
||||
batch_count: Some(draft.batch_count),
|
||||
has_video_reference,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
let pricing_request = GenerateSeedance2p0MiniRequest {
|
||||
output_resolution: self.resolution,
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: self.batch_count,
|
||||
|
||||
// PRESENCE of reference videos changes the price (per-second
|
||||
// surcharge); the URL contents don't.
|
||||
reference_video_urls: if self.has_video_reference {
|
||||
Some(vec!["pricing-placeholder".to_string()])
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
||||
// No impact on price
|
||||
prompt: String::new(),
|
||||
aspect_ratio: None,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
reference_audio_urls: None,
|
||||
character_ids: None,
|
||||
use_face_blur_hack: None,
|
||||
bitrate: None,
|
||||
};
|
||||
|
||||
let costs = pricing_request.calculate_costs();
|
||||
// Mini credits can be fractional (e.g. 480p odd durations land on 37.5).
|
||||
// The router's credit field is an integer, so round to the nearest credit;
|
||||
// the USD cents (the authoritative charge) are already rounded up.
|
||||
let cost_in_credits = costs.total_cost.kinovi_credits.round() as u64;
|
||||
let cost_in_usd_cents = costs.total_cost.usd_cents_rounded_up;
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits),
|
||||
cost_in_usd_cents: Some(cost_in_usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{
|
||||
KinoviSeedance2p0MiniOutputResolution as KinoviOutputResolution,
|
||||
KinoviSeedance2p0MiniBatchCount as KinoviBatchCount,
|
||||
};
|
||||
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::video_generation_draft::VideoGenerationDraftRequest;
|
||||
use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
|
||||
// ── USD cents: every combination (480p/720p × with/without video ref) ──
|
||||
|
||||
mod pricing_480p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One, false), 13);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One, false), 16);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One, false), 31);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One, false), 47);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One, true), 16);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One, true), 20);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One, true), 40);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One, true), 59);
|
||||
}
|
||||
}
|
||||
|
||||
mod pricing_720p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn without_video_reference() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One, false), 33);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One, false), 42);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One, false), 83);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One, false), 124);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_video_reference() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One, true), 40);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One, true), 50);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One, true), 99);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One, true), 149);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Credits (mini bills fractional credits, rounded for the integer field) ──
|
||||
|
||||
mod credits_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn credits_480p() {
|
||||
// 7.5 credits/s; odd durations are fractional (37.5 → 38, 112.5 → 113).
|
||||
assert_eq!(credits(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One, false), 30);
|
||||
assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One, false), 38);
|
||||
assert_eq!(credits(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One, false), 75);
|
||||
assert_eq!(credits(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One, false), 113);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn credits_720p() {
|
||||
assert_eq!(credits(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One, false), 100);
|
||||
assert_eq!(credits(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One, false), 300);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Relative pricing ──
|
||||
|
||||
mod relative_pricing_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cost_480p_cheaper_than_720p() {
|
||||
let c480 = usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One, false);
|
||||
let c720 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One, false);
|
||||
assert!(c480 < c720, "480p ({c480}) should be cheaper than 720p ({c720})");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_reference_costs_more() {
|
||||
for res in [KinoviOutputResolution::FourEightyP, KinoviOutputResolution::SevenTwentyP] {
|
||||
for dur in [4u8, 5, 10, 15] {
|
||||
let without = usd_cents(res, dur, KinoviBatchCount::One, false);
|
||||
let with = usd_cents(res, dur, KinoviBatchCount::One, true);
|
||||
assert!(with > without, "video ref should cost more at {res:?} {dur}s");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_scales_with_duration_and_batch() {
|
||||
let c4 = usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One, false);
|
||||
let c15 = usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One, false);
|
||||
assert!(c4 < c15);
|
||||
let b1 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One, false);
|
||||
let b4 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four, false);
|
||||
assert!(b1 < b4);
|
||||
}
|
||||
}
|
||||
|
||||
// ── from_request() ──
|
||||
|
||||
mod from_request_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn from_request_720p() {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0MiniCostState::from_request(&req);
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::SevenTwentyP)));
|
||||
assert_eq!(cost.duration_seconds, 5);
|
||||
assert!(!cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(42));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_request_480p_with_video_reference() {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 5, KinoviBatchCount::One, true);
|
||||
let cost = KinoviSeedance2p0MiniCostState::from_request(&req);
|
||||
assert!(cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(20));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_request_none_defaults_to_720p() {
|
||||
let req = make_request_state(None, 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0MiniCostState::from_request(&req);
|
||||
assert!(cost.resolution.is_none());
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(42));
|
||||
}
|
||||
}
|
||||
|
||||
// ── from_draft() ──
|
||||
|
||||
mod from_draft_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn from_draft_480p() {
|
||||
let draft = make_draft(5, 1, Some(RouterResolution::FourEightyP), false);
|
||||
let cost = KinoviSeedance2p0MiniCostState::from_draft(&draft);
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::FourEightyP)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_draft_with_video_reference() {
|
||||
let draft = make_draft(5, 1, Some(RouterResolution::SevenTwentyP), true);
|
||||
let cost = KinoviSeedance2p0MiniCostState::from_draft(&draft);
|
||||
assert!(cost.has_video_reference);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(50));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_draft_batch_8() {
|
||||
// Mini supports up to batch 8: 720p 5s × 8 = 800 credits → 80000/243 → 330¢.
|
||||
let draft = make_draft(5, 8, Some(RouterResolution::SevenTwentyP), false);
|
||||
let cost = KinoviSeedance2p0MiniCostState::from_draft(&draft);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Eight)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(330));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Cross-check: from_draft matches from_request ──
|
||||
|
||||
mod cross_check_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn draft_and_request_produce_same_cost() {
|
||||
let draft = make_draft(10, 2, Some(RouterResolution::FourEightyP), true);
|
||||
let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 10, KinoviBatchCount::Two, true);
|
||||
let draft_cost = KinoviSeedance2p0MiniCostState::from_draft(&draft).estimate_cost();
|
||||
let req_cost = KinoviSeedance2p0MiniCostState::from_request(&req).estimate_cost();
|
||||
assert_eq!(draft_cost.cost_in_usd_cents, req_cost.cost_in_usd_cents);
|
||||
assert_eq!(draft_cost.cost_in_credits, req_cost.cost_in_credits);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
fn usd_cents(
|
||||
resolution: KinoviOutputResolution,
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
has_video_reference: bool,
|
||||
) -> u64 {
|
||||
KinoviSeedance2p0MiniCostState {
|
||||
resolution: Some(resolution),
|
||||
duration_seconds,
|
||||
batch_count: Some(batch_count),
|
||||
has_video_reference,
|
||||
}
|
||||
.estimate_cost()
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn credits(
|
||||
resolution: KinoviOutputResolution,
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
has_video_reference: bool,
|
||||
) -> u64 {
|
||||
KinoviSeedance2p0MiniCostState {
|
||||
resolution: Some(resolution),
|
||||
duration_seconds,
|
||||
batch_count: Some(batch_count),
|
||||
has_video_reference,
|
||||
}
|
||||
.estimate_cost()
|
||||
.cost_in_credits
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Build a draft via the builder to test from_draft().
|
||||
fn make_draft(
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
resolution: Option<RouterResolution>,
|
||||
with_video_ref: bool,
|
||||
) -> KinoviSeedance2p0MiniDraftState {
|
||||
let reference_videos = if with_video_ref {
|
||||
Some(VideoListRef::Urls(vec!["https://example.com/video.mp4".to_string()]))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0Mini,
|
||||
provider: RouterProvider::Seedance2Pro,
|
||||
resolution,
|
||||
reference_videos,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: Some(video_batch_count),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
match builder.build2().expect("build2 should succeed") {
|
||||
VideoGenerationDraftOrRequest::Draft(
|
||||
VideoGenerationDraftRequest::KinoviSeedance2p0Mini(draft)
|
||||
) => draft,
|
||||
_ => panic!("expected KinoviSeedance2p0Mini draft"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a request state for from_request() tests.
|
||||
fn make_request_state(
|
||||
resolution: Option<KinoviOutputResolution>,
|
||||
duration_seconds: u8,
|
||||
batch_count: KinoviBatchCount,
|
||||
with_video_ref: bool,
|
||||
) -> KinoviSeedance2p0MiniRequestState {
|
||||
let reference_video_urls = if with_video_ref {
|
||||
Some(vec!["https://cdn.seedance2-pro.com/video.mp4".to_string()])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
KinoviSeedance2p0MiniRequestState {
|
||||
request: GenerateSeedance2p0MiniRequest {
|
||||
prompt: "test".to_string(),
|
||||
aspect_ratio: None,
|
||||
output_resolution: resolution,
|
||||
duration_seconds,
|
||||
batch_count: Some(batch_count),
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
reference_video_urls,
|
||||
reference_audio_urls: None,
|
||||
character_ids: None,
|
||||
use_face_blur_hack: None,
|
||||
bitrate: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{
|
||||
GenerateSeedance2p0MiniRequest, KinoviSeedance2p0MiniAspectRatio, KinoviSeedance2p0MiniBitrate,
|
||||
KinoviSeedance2p0MiniBatchCount, KinoviSeedance2p0MiniOutputResolution,
|
||||
};
|
||||
|
||||
use crate::api::audio_list_ref::AudioListRef;
|
||||
use crate::api::character_list_ref::CharacterListRef;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_video::providers::kinovi::resolve::{
|
||||
audio_list_ref_into_urls_or_tokens, image_list_ref_into_urls_or_tokens,
|
||||
resolve_and_upload_list, resolve_and_upload_single, resolve_character_tokens,
|
||||
video_list_ref_into_urls_or_tokens,
|
||||
};
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::request::KinoviSeedance2p0MiniRequestState;
|
||||
use crate::generate::generate_video::video_generation_draft_context::VideoGenerationDraftContext;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KinoviSeedance2p0MiniDraftState {
|
||||
pub prompt: String,
|
||||
pub aspect_ratio: KinoviSeedance2p0MiniAspectRatio,
|
||||
pub resolution: Option<KinoviSeedance2p0MiniOutputResolution>,
|
||||
pub duration_seconds: u8,
|
||||
pub batch_count: KinoviSeedance2p0MiniBatchCount,
|
||||
pub bitrate: Option<KinoviSeedance2p0MiniBitrate>,
|
||||
|
||||
pub unhandled_request_state: Option<KinoviSeedance2p0MiniRemainingItems>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KinoviSeedance2p0MiniRemainingItems {
|
||||
pub start_frame: Option<ImageRef>,
|
||||
pub end_frame: Option<ImageRef>,
|
||||
pub reference_images: Option<ImageListRef>,
|
||||
pub reference_videos: Option<VideoListRef>,
|
||||
pub reference_audio: Option<AudioListRef>,
|
||||
pub reference_character_tokens: Option<CharacterListRef>,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0MiniDraftState {
|
||||
pub async fn to_request(
|
||||
&mut self,
|
||||
draft_context: &VideoGenerationDraftContext<'_>,
|
||||
) -> Result<KinoviSeedance2p0MiniRequestState, ArtcraftRouterError> {
|
||||
let client = draft_context.get_seedance2pro_client_ref()?;
|
||||
let session = &client.session;
|
||||
|
||||
let mut start_frame_url = None;
|
||||
let mut end_frame_url = None;
|
||||
let mut reference_image_urls = None;
|
||||
let mut reference_video_urls = None;
|
||||
let mut reference_audio_urls = None;
|
||||
let mut character_ids = None;
|
||||
|
||||
if let Some(remaining) = self.unhandled_request_state.take() {
|
||||
let map = draft_context.media_file_to_artcraft_url_map;
|
||||
|
||||
start_frame_url = resolve_and_upload_single(session, remaining.start_frame, map).await?;
|
||||
end_frame_url = resolve_and_upload_single(session, remaining.end_frame, map).await?;
|
||||
|
||||
reference_image_urls = resolve_and_upload_list(
|
||||
session, remaining.reference_images.map(image_list_ref_into_urls_or_tokens), map,
|
||||
).await?;
|
||||
|
||||
reference_video_urls = resolve_and_upload_list(
|
||||
session, remaining.reference_videos.map(video_list_ref_into_urls_or_tokens), map,
|
||||
).await?;
|
||||
|
||||
reference_audio_urls = resolve_and_upload_list(
|
||||
session, remaining.reference_audio.map(audio_list_ref_into_urls_or_tokens), map,
|
||||
).await?;
|
||||
|
||||
character_ids = resolve_character_tokens(
|
||||
remaining.reference_character_tokens.as_ref(),
|
||||
draft_context,
|
||||
)?;
|
||||
}
|
||||
|
||||
let request = GenerateSeedance2p0MiniRequest {
|
||||
prompt: self.prompt.clone(),
|
||||
aspect_ratio: Some(self.aspect_ratio),
|
||||
output_resolution: self.resolution,
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: Some(self.batch_count),
|
||||
start_frame_url,
|
||||
end_frame_url,
|
||||
reference_image_urls,
|
||||
reference_video_urls,
|
||||
reference_audio_urls,
|
||||
character_ids,
|
||||
use_face_blur_hack: None,
|
||||
bitrate: self.bitrate,
|
||||
};
|
||||
|
||||
Ok(KinoviSeedance2p0MiniRequestState { request })
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
pub mod build;
|
||||
pub mod draft;
|
||||
pub mod request;
|
||||
pub mod cost;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{
|
||||
generate_seedance_2p0_mini, GenerateSeedance2p0MiniArgs, GenerateSeedance2p0MiniRequest,
|
||||
};
|
||||
|
||||
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};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KinoviSeedance2p0MiniRequestState {
|
||||
/// Final materialized request; ready to fire.
|
||||
pub request: GenerateSeedance2p0MiniRequest,
|
||||
}
|
||||
|
||||
impl KinoviSeedance2p0MiniRequestState {
|
||||
pub async fn send(&self, client: &RouterSeedance2ProClient) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let session = &client.session;
|
||||
|
||||
let args = GenerateSeedance2p0MiniArgs {
|
||||
session,
|
||||
host_override: None,
|
||||
request: self.request.clone(),
|
||||
};
|
||||
|
||||
let response = generate_seedance_2p0_mini(args)
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?;
|
||||
|
||||
Ok(GenerateVideoResponse::Seedance2Pro(Seedance2proVideoResponsePayload {
|
||||
order_id: response.order_id,
|
||||
task_id: response.task_id,
|
||||
maybe_order_ids: response.order_ids,
|
||||
maybe_task_ids: response.task_ids,
|
||||
}))
|
||||
}
|
||||
}
|
||||
+9
@@ -7,6 +7,8 @@ use crate::generate::generate_video::providers::kinovi::seedance_2p0::cost::Kino
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0::draft::KinoviSeedance2p0DraftState;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::cost::KinoviSeedance2p0FastCostState;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::draft::KinoviSeedance2p0FastDraftState;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::cost::KinoviSeedance2p0MiniCostState;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::draft::KinoviSeedance2p0MiniDraftState;
|
||||
use crate::generate::generate_video::video_generation_draft_context::VideoGenerationDraftContext;
|
||||
use crate::generate::generate_video::video_generation_request::VideoGenerationRequest;
|
||||
|
||||
@@ -18,6 +20,7 @@ pub enum VideoGenerationDraftRequest {
|
||||
KinoviHappyHorse1p0(KinoviHappyHorse1p0DraftState),
|
||||
KinoviSeedance2p0(KinoviSeedance2p0DraftState),
|
||||
KinoviSeedance2p0Fast(KinoviSeedance2p0FastDraftState),
|
||||
KinoviSeedance2p0Mini(KinoviSeedance2p0MiniDraftState),
|
||||
}
|
||||
|
||||
impl VideoGenerationDraftRequest {
|
||||
@@ -27,6 +30,7 @@ impl VideoGenerationDraftRequest {
|
||||
Self::KinoviHappyHorse1p0(_) => RouterProvider::Seedance2Pro,
|
||||
Self::KinoviSeedance2p0(_) => RouterProvider::Seedance2Pro,
|
||||
Self::KinoviSeedance2p0Fast(_) => RouterProvider::Seedance2Pro,
|
||||
Self::KinoviSeedance2p0Mini(_) => RouterProvider::Seedance2Pro,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +40,7 @@ impl VideoGenerationDraftRequest {
|
||||
VideoGenerationDraftRequest::KinoviHappyHorse1p0(draft) => Ok(KinoviHappyHorse1p0CostState::from_draft(draft).estimate_cost()),
|
||||
VideoGenerationDraftRequest::KinoviSeedance2p0(draft) => Ok(KinoviSeedance2p0CostState::from_draft(draft).estimate_cost()),
|
||||
VideoGenerationDraftRequest::KinoviSeedance2p0Fast(draft) => Ok(KinoviSeedance2p0FastCostState::from_draft(draft).estimate_cost()),
|
||||
VideoGenerationDraftRequest::KinoviSeedance2p0Mini(draft) => Ok(KinoviSeedance2p0MiniCostState::from_draft(draft).estimate_cost()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +60,10 @@ impl VideoGenerationDraftRequest {
|
||||
let result = draft.to_request(&draft_context).await?;
|
||||
Ok(VideoGenerationRequest::KinoviSeedance2p0Fast(result))
|
||||
},
|
||||
VideoGenerationDraftRequest::KinoviSeedance2p0Mini(mut draft) => {
|
||||
let result = draft.to_request(&draft_context).await?;
|
||||
Ok(VideoGenerationRequest::KinoviSeedance2p0Mini(result))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
@@ -47,6 +47,12 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu::cost
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu::request::ArtcraftSeedance2p0BytePlusUltraRequestState;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_fast::cost::ArtcraftSeedance2p0BytePlusUltraFastCostState;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_fast::request::ArtcraftSeedance2p0BytePlusUltraFastRequestState;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_mini::cost::ArtcraftSeedance2p0MiniCostState;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_mini::request::ArtcraftSeedance2p0MiniRequestState;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_mini::cost::ArtcraftSeedance2p0BytePlusMiniCostState;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_mini::request::ArtcraftSeedance2p0BytePlusMiniRequestState;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_mini::cost::ArtcraftSeedance2p0BytePlusUltraMiniCostState;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_mini::request::ArtcraftSeedance2p0BytePlusUltraMiniRequestState;
|
||||
use crate::generate::generate_video::providers::artcraft::sora_2::cost::ArtcraftSora2CostState;
|
||||
use crate::generate::generate_video::providers::artcraft::sora_2::request::ArtcraftSora2RequestState;
|
||||
use crate::generate::generate_video::providers::artcraft::sora_2_pro::cost::ArtcraftSora2ProCostState;
|
||||
@@ -107,6 +113,8 @@ use crate::generate::generate_video::providers::kinovi::seedance_2p0::cost::Kino
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0::request::KinoviSeedance2p0RequestState;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::cost::KinoviSeedance2p0FastCostState;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::request::KinoviSeedance2p0FastRequestState;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::cost::KinoviSeedance2p0MiniCostState;
|
||||
use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::request::KinoviSeedance2p0MiniRequestState;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VideoGenerationRequest {
|
||||
@@ -132,6 +140,9 @@ pub enum VideoGenerationRequest {
|
||||
ArtcraftSeedance2p0BytePlusFast(ArtcraftSeedance2p0BytePlusFastRequestState),
|
||||
ArtcraftSeedance2p0BytePlusUltra(ArtcraftSeedance2p0BytePlusUltraRequestState),
|
||||
ArtcraftSeedance2p0BytePlusUltraFast(ArtcraftSeedance2p0BytePlusUltraFastRequestState),
|
||||
ArtcraftSeedance2p0Mini(ArtcraftSeedance2p0MiniRequestState),
|
||||
ArtcraftSeedance2p0BytePlusMini(ArtcraftSeedance2p0BytePlusMiniRequestState),
|
||||
ArtcraftSeedance2p0BytePlusUltraMini(ArtcraftSeedance2p0BytePlusUltraMiniRequestState),
|
||||
ArtcraftSora2(ArtcraftSora2RequestState),
|
||||
ArtcraftSora2Pro(ArtcraftSora2ProRequestState),
|
||||
ArtcraftVeo2(ArtcraftVeo2RequestState),
|
||||
@@ -162,6 +173,7 @@ pub enum VideoGenerationRequest {
|
||||
KinoviHappyHorse1p0(KinoviHappyHorse1p0RequestState),
|
||||
KinoviSeedance2p0(KinoviSeedance2p0RequestState),
|
||||
KinoviSeedance2p0Fast(KinoviSeedance2p0FastRequestState),
|
||||
KinoviSeedance2p0Mini(KinoviSeedance2p0MiniRequestState),
|
||||
}
|
||||
|
||||
impl VideoGenerationRequest {
|
||||
@@ -190,6 +202,9 @@ impl VideoGenerationRequest {
|
||||
Self::ArtcraftSeedance2p0BytePlusFast(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftSeedance2p0BytePlusUltra(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftSeedance2p0BytePlusUltraFast(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftSeedance2p0Mini(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftSeedance2p0BytePlusMini(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftSeedance2p0BytePlusUltraMini(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftSora2(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftSora2Pro(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftVeo2(_) => RouterProvider::Artcraft,
|
||||
@@ -220,6 +235,7 @@ impl VideoGenerationRequest {
|
||||
Self::KinoviHappyHorse1p0(_) => RouterProvider::Seedance2Pro,
|
||||
Self::KinoviSeedance2p0(_) => RouterProvider::Seedance2Pro,
|
||||
Self::KinoviSeedance2p0Fast(_) => RouterProvider::Seedance2Pro,
|
||||
Self::KinoviSeedance2p0Mini(_) => RouterProvider::Seedance2Pro,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,6 +264,9 @@ impl VideoGenerationRequest {
|
||||
VideoGenerationRequest::ArtcraftSeedance2p0BytePlusFast(request) => Ok(ArtcraftSeedance2p0BytePlusFastCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltra(request) => Ok(ArtcraftSeedance2p0BytePlusUltraCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltraFast(request) => Ok(ArtcraftSeedance2p0BytePlusUltraFastCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::ArtcraftSeedance2p0Mini(request) => Ok(ArtcraftSeedance2p0MiniCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::ArtcraftSeedance2p0BytePlusMini(request) => Ok(ArtcraftSeedance2p0BytePlusMiniCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltraMini(request) => Ok(ArtcraftSeedance2p0BytePlusUltraMiniCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::ArtcraftSora2(request) => Ok(ArtcraftSora2CostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::ArtcraftSora2Pro(request) => Ok(ArtcraftSora2ProCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::ArtcraftVeo2(request) => Ok(ArtcraftVeo2CostState::from_request(request).estimate_cost()),
|
||||
@@ -278,6 +297,7 @@ impl VideoGenerationRequest {
|
||||
VideoGenerationRequest::KinoviHappyHorse1p0(request) => Ok(KinoviHappyHorse1p0CostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::KinoviSeedance2p0(request) => Ok(KinoviSeedance2p0CostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::KinoviSeedance2p0Fast(request) => Ok(KinoviSeedance2p0FastCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::KinoviSeedance2p0Mini(request) => Ok(KinoviSeedance2p0MiniCostState::from_request(request).estimate_cost()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,6 +393,18 @@ impl VideoGenerationRequest {
|
||||
let client_ref = client.get_artcraft_client_ref()?;
|
||||
request.send(client_ref).await
|
||||
},
|
||||
VideoGenerationRequest::ArtcraftSeedance2p0Mini(request) => {
|
||||
let client_ref = client.get_artcraft_client_ref()?;
|
||||
request.send(client_ref).await
|
||||
},
|
||||
VideoGenerationRequest::ArtcraftSeedance2p0BytePlusMini(request) => {
|
||||
let client_ref = client.get_artcraft_client_ref()?;
|
||||
request.send(client_ref).await
|
||||
},
|
||||
VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltraMini(request) => {
|
||||
let client_ref = client.get_artcraft_client_ref()?;
|
||||
request.send(client_ref).await
|
||||
},
|
||||
VideoGenerationRequest::ArtcraftSora2(request) => {
|
||||
let client_ref = client.get_artcraft_client_ref()?;
|
||||
request.send(client_ref).await
|
||||
@@ -493,6 +525,10 @@ impl VideoGenerationRequest {
|
||||
let client_ref = client.get_seedance2pro_client_ref()?;
|
||||
request.send(client_ref).await
|
||||
},
|
||||
VideoGenerationRequest::KinoviSeedance2p0Mini(request) => {
|
||||
let client_ref = client.get_seedance2pro_client_ref()?;
|
||||
request.send(client_ref).await
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -74,6 +74,9 @@ fn video_model_to_common_model_type(model: RouterVideoModel) -> Option<CommonMod
|
||||
RouterVideoModel::Seedance2p0UltraFast => Some(CommonModelType::Seedance2p0UltraFast),
|
||||
RouterVideoModel::Seedance2p0BytePlusUltra => Some(CommonModelType::Seedance2p0BytePlusUltra),
|
||||
RouterVideoModel::Seedance2p0BytePlusUltraFast => Some(CommonModelType::Seedance2p0BytePlusUltraFast),
|
||||
RouterVideoModel::Seedance2p0Mini => Some(CommonModelType::Seedance2p0Mini),
|
||||
RouterVideoModel::Seedance2p0BytePlusMini => Some(CommonModelType::Seedance2p0BytePlusMini),
|
||||
RouterVideoModel::Seedance2p0BytePlusUltraMini => Some(CommonModelType::Seedance2p0BytePlusUltraMini),
|
||||
RouterVideoModel::PreviewModel => Some(CommonModelType::PreviewModel),
|
||||
RouterVideoModel::PreviewModelFast => Some(CommonModelType::PreviewModelFast),
|
||||
RouterVideoModel::GrokImagineVideo => Some(CommonModelType::GrokImagineVideo),
|
||||
|
||||
+27
@@ -198,6 +198,12 @@ pub enum InferenceModelType {
|
||||
Seedance2p0BytePlusUltra,
|
||||
#[serde(rename = "seedance_2p0_bpu_fast")]
|
||||
Seedance2p0BytePlusUltraFast,
|
||||
#[serde(rename = "seedance_2p0_mini")]
|
||||
Seedance2p0Mini,
|
||||
#[serde(rename = "seedance_2p0_bp_mini")]
|
||||
Seedance2p0BytePlusMini,
|
||||
#[serde(rename = "seedance_2p0_bpu_mini")]
|
||||
Seedance2p0BytePlusUltraMini,
|
||||
#[serde(rename = "sora_2")]
|
||||
Sora2,
|
||||
#[serde(rename = "sora_2_pro")]
|
||||
@@ -320,6 +326,9 @@ impl InferenceModelType {
|
||||
Self::Seedance2p0UltraFast => "seedance_2p0_u_fast",
|
||||
Self::Seedance2p0BytePlusUltra => "seedance_2p0_bpu",
|
||||
Self::Seedance2p0BytePlusUltraFast => "seedance_2p0_bpu_fast",
|
||||
Self::Seedance2p0Mini => "seedance_2p0_mini",
|
||||
Self::Seedance2p0BytePlusMini => "seedance_2p0_bp_mini",
|
||||
Self::Seedance2p0BytePlusUltraMini => "seedance_2p0_bpu_mini",
|
||||
Self::Sora2 => "sora_2",
|
||||
Self::Sora2Pro => "sora_2_pro",
|
||||
Self::Veo2 => "veo_2",
|
||||
@@ -422,6 +431,9 @@ impl InferenceModelType {
|
||||
"seedance_2p0_u_fast" => Ok(Self::Seedance2p0UltraFast),
|
||||
"seedance_2p0_bpu" => Ok(Self::Seedance2p0BytePlusUltra),
|
||||
"seedance_2p0_bpu_fast" => Ok(Self::Seedance2p0BytePlusUltraFast),
|
||||
"seedance_2p0_mini" => Ok(Self::Seedance2p0Mini),
|
||||
"seedance_2p0_bp_mini" => Ok(Self::Seedance2p0BytePlusMini),
|
||||
"seedance_2p0_bpu_mini" => Ok(Self::Seedance2p0BytePlusUltraMini),
|
||||
"sora_2" => Ok(Self::Sora2),
|
||||
"sora_2_pro" => Ok(Self::Sora2Pro),
|
||||
"veo_2" => Ok(Self::Veo2),
|
||||
@@ -528,6 +540,9 @@ impl InferenceModelType {
|
||||
Self::Seedance2p0UltraFast,
|
||||
Self::Seedance2p0BytePlusUltra,
|
||||
Self::Seedance2p0BytePlusUltraFast,
|
||||
Self::Seedance2p0Mini,
|
||||
Self::Seedance2p0BytePlusMini,
|
||||
Self::Seedance2p0BytePlusUltraMini,
|
||||
Self::Sora2,
|
||||
Self::Sora2Pro,
|
||||
Self::Veo2,
|
||||
@@ -617,6 +632,9 @@ impl InferenceModelType {
|
||||
CommonModelType::Seedance2p0UltraFast => Self::Seedance2p0UltraFast,
|
||||
CommonModelType::Seedance2p0BytePlusUltra => Self::Seedance2p0BytePlusUltra,
|
||||
CommonModelType::Seedance2p0BytePlusUltraFast => Self::Seedance2p0BytePlusUltraFast,
|
||||
CommonModelType::Seedance2p0Mini => Self::Seedance2p0Mini,
|
||||
CommonModelType::Seedance2p0BytePlusMini => Self::Seedance2p0BytePlusMini,
|
||||
CommonModelType::Seedance2p0BytePlusUltraMini => Self::Seedance2p0BytePlusUltraMini,
|
||||
CommonModelType::Sora2 => Self::Sora2,
|
||||
CommonModelType::Sora2Pro => Self::Sora2Pro,
|
||||
CommonModelType::Veo2 => Self::Veo2,
|
||||
@@ -729,6 +747,9 @@ mod tests {
|
||||
assert_serialization(InferenceModelType::Seedance2p0UltraFast, "seedance_2p0_u_fast");
|
||||
assert_serialization(InferenceModelType::Seedance2p0BytePlusUltra, "seedance_2p0_bpu");
|
||||
assert_serialization(InferenceModelType::Seedance2p0BytePlusUltraFast, "seedance_2p0_bpu_fast");
|
||||
assert_serialization(InferenceModelType::Seedance2p0Mini, "seedance_2p0_mini");
|
||||
assert_serialization(InferenceModelType::Seedance2p0BytePlusMini, "seedance_2p0_bp_mini");
|
||||
assert_serialization(InferenceModelType::Seedance2p0BytePlusUltraMini, "seedance_2p0_bpu_mini");
|
||||
assert_serialization(InferenceModelType::Sora2, "sora_2");
|
||||
assert_serialization(InferenceModelType::Sora2Pro, "sora_2_pro");
|
||||
assert_serialization(InferenceModelType::Veo2, "veo_2");
|
||||
@@ -830,6 +851,9 @@ mod tests {
|
||||
assert_eq!(InferenceModelType::Seedance2p0UltraFast.to_str(), "seedance_2p0_u_fast");
|
||||
assert_eq!(InferenceModelType::Seedance2p0BytePlusUltra.to_str(), "seedance_2p0_bpu");
|
||||
assert_eq!(InferenceModelType::Seedance2p0BytePlusUltraFast.to_str(), "seedance_2p0_bpu_fast");
|
||||
assert_eq!(InferenceModelType::Seedance2p0Mini.to_str(), "seedance_2p0_mini");
|
||||
assert_eq!(InferenceModelType::Seedance2p0BytePlusMini.to_str(), "seedance_2p0_bp_mini");
|
||||
assert_eq!(InferenceModelType::Seedance2p0BytePlusUltraMini.to_str(), "seedance_2p0_bpu_mini");
|
||||
assert_eq!(InferenceModelType::Sora2.to_str(), "sora_2");
|
||||
assert_eq!(InferenceModelType::Sora2Pro.to_str(), "sora_2_pro");
|
||||
assert_eq!(InferenceModelType::Veo2.to_str(), "veo_2");
|
||||
@@ -931,6 +955,9 @@ mod tests {
|
||||
assert_eq!(InferenceModelType::from_str("seedance_2p0_u_fast").unwrap(), InferenceModelType::Seedance2p0UltraFast);
|
||||
assert_eq!(InferenceModelType::from_str("seedance_2p0_bpu").unwrap(), InferenceModelType::Seedance2p0BytePlusUltra);
|
||||
assert_eq!(InferenceModelType::from_str("seedance_2p0_bpu_fast").unwrap(), InferenceModelType::Seedance2p0BytePlusUltraFast);
|
||||
assert_eq!(InferenceModelType::from_str("seedance_2p0_mini").unwrap(), InferenceModelType::Seedance2p0Mini);
|
||||
assert_eq!(InferenceModelType::from_str("seedance_2p0_bp_mini").unwrap(), InferenceModelType::Seedance2p0BytePlusMini);
|
||||
assert_eq!(InferenceModelType::from_str("seedance_2p0_bpu_mini").unwrap(), InferenceModelType::Seedance2p0BytePlusUltraMini);
|
||||
assert_eq!(InferenceModelType::from_str("sora_2").unwrap(), InferenceModelType::Sora2);
|
||||
assert_eq!(InferenceModelType::from_str("sora_2_pro").unwrap(), InferenceModelType::Sora2Pro);
|
||||
assert_eq!(InferenceModelType::from_str("veo_2").unwrap(), InferenceModelType::Veo2);
|
||||
|
||||
@@ -167,6 +167,12 @@ pub enum CommonModelType {
|
||||
Seedance2p0BytePlusUltra,
|
||||
#[serde(rename = "seedance_2p0_bpu_fast")]
|
||||
Seedance2p0BytePlusUltraFast,
|
||||
#[serde(rename = "seedance_2p0_mini")]
|
||||
Seedance2p0Mini,
|
||||
#[serde(rename = "seedance_2p0_bp_mini")]
|
||||
Seedance2p0BytePlusMini,
|
||||
#[serde(rename = "seedance_2p0_bpu_mini")]
|
||||
Seedance2p0BytePlusUltraMini,
|
||||
#[serde(rename = "sora_2")]
|
||||
Sora2,
|
||||
#[serde(rename = "sora_2_pro")]
|
||||
@@ -273,6 +279,9 @@ impl CommonModelType {
|
||||
Self::Seedance2p0UltraFast => "seedance_2p0_u_fast",
|
||||
Self::Seedance2p0BytePlusUltra => "seedance_2p0_bpu",
|
||||
Self::Seedance2p0BytePlusUltraFast => "seedance_2p0_bpu_fast",
|
||||
Self::Seedance2p0Mini => "seedance_2p0_mini",
|
||||
Self::Seedance2p0BytePlusMini => "seedance_2p0_bp_mini",
|
||||
Self::Seedance2p0BytePlusUltraMini => "seedance_2p0_bpu_mini",
|
||||
Self::Sora2 => "sora_2",
|
||||
Self::Sora2Pro => "sora_2_pro",
|
||||
Self::Veo2 => "veo_2",
|
||||
@@ -358,6 +367,9 @@ impl CommonModelType {
|
||||
"seedance_2p0_u_fast" => Ok(Self::Seedance2p0UltraFast),
|
||||
"seedance_2p0_bpu" => Ok(Self::Seedance2p0BytePlusUltra),
|
||||
"seedance_2p0_bpu_fast" => Ok(Self::Seedance2p0BytePlusUltraFast),
|
||||
"seedance_2p0_mini" => Ok(Self::Seedance2p0Mini),
|
||||
"seedance_2p0_bp_mini" => Ok(Self::Seedance2p0BytePlusMini),
|
||||
"seedance_2p0_bpu_mini" => Ok(Self::Seedance2p0BytePlusUltraMini),
|
||||
"sora_2" => Ok(Self::Sora2),
|
||||
"sora_2_pro" => Ok(Self::Sora2Pro),
|
||||
"veo_2" => Ok(Self::Veo2),
|
||||
@@ -447,6 +459,9 @@ impl CommonModelType {
|
||||
Self::Seedance2p0UltraFast,
|
||||
Self::Seedance2p0BytePlusUltra,
|
||||
Self::Seedance2p0BytePlusUltraFast,
|
||||
Self::Seedance2p0Mini,
|
||||
Self::Seedance2p0BytePlusMini,
|
||||
Self::Seedance2p0BytePlusUltraMini,
|
||||
Self::Sora2,
|
||||
Self::Sora2Pro,
|
||||
Self::Veo2,
|
||||
@@ -533,6 +548,9 @@ impl CommonModelType {
|
||||
Self::Seedance2p0UltraFast => CommonModelClass::Video,
|
||||
Self::Seedance2p0BytePlusUltra => CommonModelClass::Video,
|
||||
Self::Seedance2p0BytePlusUltraFast => CommonModelClass::Video,
|
||||
Self::Seedance2p0Mini => CommonModelClass::Video,
|
||||
Self::Seedance2p0BytePlusMini => CommonModelClass::Video,
|
||||
Self::Seedance2p0BytePlusUltraMini => CommonModelClass::Video,
|
||||
Self::Sora2 => CommonModelClass::Video,
|
||||
Self::Sora2Pro => CommonModelClass::Video,
|
||||
Self::Veo2 => CommonModelClass::Video,
|
||||
@@ -628,6 +646,9 @@ mod tests {
|
||||
assert_serialization(CommonModelType::Seedance2p0UltraFast, "seedance_2p0_u_fast");
|
||||
assert_serialization(CommonModelType::Seedance2p0BytePlusUltra, "seedance_2p0_bpu");
|
||||
assert_serialization(CommonModelType::Seedance2p0BytePlusUltraFast, "seedance_2p0_bpu_fast");
|
||||
assert_serialization(CommonModelType::Seedance2p0Mini, "seedance_2p0_mini");
|
||||
assert_serialization(CommonModelType::Seedance2p0BytePlusMini, "seedance_2p0_bp_mini");
|
||||
assert_serialization(CommonModelType::Seedance2p0BytePlusUltraMini, "seedance_2p0_bpu_mini");
|
||||
assert_serialization(CommonModelType::Sora2, "sora_2");
|
||||
assert_serialization(CommonModelType::Sora2Pro, "sora_2_pro");
|
||||
assert_serialization(CommonModelType::Veo2, "veo_2");
|
||||
@@ -710,6 +731,9 @@ mod tests {
|
||||
assert_eq!(CommonModelType::Seedance2p0UltraFast.to_str(), "seedance_2p0_u_fast");
|
||||
assert_eq!(CommonModelType::Seedance2p0BytePlusUltra.to_str(), "seedance_2p0_bpu");
|
||||
assert_eq!(CommonModelType::Seedance2p0BytePlusUltraFast.to_str(), "seedance_2p0_bpu_fast");
|
||||
assert_eq!(CommonModelType::Seedance2p0Mini.to_str(), "seedance_2p0_mini");
|
||||
assert_eq!(CommonModelType::Seedance2p0BytePlusMini.to_str(), "seedance_2p0_bp_mini");
|
||||
assert_eq!(CommonModelType::Seedance2p0BytePlusUltraMini.to_str(), "seedance_2p0_bpu_mini");
|
||||
assert_eq!(CommonModelType::Sora2.to_str(), "sora_2");
|
||||
assert_eq!(CommonModelType::Sora2Pro.to_str(), "sora_2_pro");
|
||||
assert_eq!(CommonModelType::Veo2.to_str(), "veo_2");
|
||||
@@ -792,6 +816,9 @@ mod tests {
|
||||
assert_eq!(CommonModelType::from_str("seedance_2p0_u_fast").unwrap(), CommonModelType::Seedance2p0UltraFast);
|
||||
assert_eq!(CommonModelType::from_str("seedance_2p0_bpu").unwrap(), CommonModelType::Seedance2p0BytePlusUltra);
|
||||
assert_eq!(CommonModelType::from_str("seedance_2p0_bpu_fast").unwrap(), CommonModelType::Seedance2p0BytePlusUltraFast);
|
||||
assert_eq!(CommonModelType::from_str("seedance_2p0_mini").unwrap(), CommonModelType::Seedance2p0Mini);
|
||||
assert_eq!(CommonModelType::from_str("seedance_2p0_bp_mini").unwrap(), CommonModelType::Seedance2p0BytePlusMini);
|
||||
assert_eq!(CommonModelType::from_str("seedance_2p0_bpu_mini").unwrap(), CommonModelType::Seedance2p0BytePlusUltraMini);
|
||||
assert_eq!(CommonModelType::from_str("sora_2").unwrap(), CommonModelType::Sora2);
|
||||
assert_eq!(CommonModelType::from_str("sora_2_pro").unwrap(), CommonModelType::Sora2Pro);
|
||||
assert_eq!(CommonModelType::from_str("veo_2").unwrap(), CommonModelType::Veo2);
|
||||
@@ -815,7 +842,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = CommonModelType::all_variants();
|
||||
assert_eq!(variants.len(), 73);
|
||||
assert_eq!(variants.len(), 76);
|
||||
// Image models
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Flux1Dev));
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Flux1Schnell));
|
||||
@@ -876,6 +903,9 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0UltraFast));
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0BytePlusUltra));
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0BytePlusUltraFast));
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0Mini));
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0BytePlusMini));
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0BytePlusUltraMini));
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Sora2));
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Sora2Pro));
|
||||
assert_eq!(variants.pop_first(), Some(CommonModelType::Veo2));
|
||||
@@ -974,6 +1004,9 @@ mod tests {
|
||||
assert_eq!(CommonModelType::Veo3.get_model_class(), CommonModelClass::Video);
|
||||
assert_eq!(CommonModelType::Sora2.get_model_class(), CommonModelClass::Video);
|
||||
assert_eq!(CommonModelType::Kling3p0Pro.get_model_class(), CommonModelClass::Video);
|
||||
assert_eq!(CommonModelType::Seedance2p0Mini.get_model_class(), CommonModelClass::Video);
|
||||
assert_eq!(CommonModelType::Seedance2p0BytePlusMini.get_model_class(), CommonModelClass::Video);
|
||||
assert_eq!(CommonModelType::Seedance2p0BytePlusUltraMini.get_model_class(), CommonModelClass::Video);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -71,6 +71,15 @@ pub enum CommonVideoModel {
|
||||
#[serde(rename = "seedance_2p0_bpu_fast")]
|
||||
Seedance2p0BytePlusUltraFast,
|
||||
|
||||
#[serde(rename = "seedance_2p0_mini")]
|
||||
Seedance2p0Mini,
|
||||
|
||||
#[serde(rename = "seedance_2p0_bp_mini")]
|
||||
Seedance2p0BytePlusMini,
|
||||
|
||||
#[serde(rename = "seedance_2p0_bpu_mini")]
|
||||
Seedance2p0BytePlusUltraMini,
|
||||
|
||||
#[serde(rename = "sora_2")]
|
||||
Sora2,
|
||||
|
||||
@@ -128,6 +137,9 @@ impl CommonVideoModel {
|
||||
Self::Seedance2p0UltraFast => CommonModelType::Seedance2p0UltraFast,
|
||||
Self::Seedance2p0BytePlusUltra => CommonModelType::Seedance2p0BytePlusUltra,
|
||||
Self::Seedance2p0BytePlusUltraFast => CommonModelType::Seedance2p0BytePlusUltraFast,
|
||||
Self::Seedance2p0Mini => CommonModelType::Seedance2p0Mini,
|
||||
Self::Seedance2p0BytePlusMini => CommonModelType::Seedance2p0BytePlusMini,
|
||||
Self::Seedance2p0BytePlusUltraMini => CommonModelType::Seedance2p0BytePlusUltraMini,
|
||||
Self::Sora2 => CommonModelType::Sora2,
|
||||
Self::Sora2Pro => CommonModelType::Sora2Pro,
|
||||
Self::Veo2 => CommonModelType::Veo2,
|
||||
@@ -170,6 +182,9 @@ mod tests {
|
||||
assert_serialization(CommonVideoModel::Seedance2p0UltraFast, "seedance_2p0_u_fast");
|
||||
assert_serialization(CommonVideoModel::Seedance2p0BytePlusUltra, "seedance_2p0_bpu");
|
||||
assert_serialization(CommonVideoModel::Seedance2p0BytePlusUltraFast, "seedance_2p0_bpu_fast");
|
||||
assert_serialization(CommonVideoModel::Seedance2p0Mini, "seedance_2p0_mini");
|
||||
assert_serialization(CommonVideoModel::Seedance2p0BytePlusMini, "seedance_2p0_bp_mini");
|
||||
assert_serialization(CommonVideoModel::Seedance2p0BytePlusUltraMini, "seedance_2p0_bpu_mini");
|
||||
assert_serialization(CommonVideoModel::Sora2, "sora_2");
|
||||
assert_serialization(CommonVideoModel::Sora2Pro, "sora_2_pro");
|
||||
assert_serialization(CommonVideoModel::Veo2, "veo_2");
|
||||
@@ -205,6 +220,9 @@ mod tests {
|
||||
("seedance_2p0_u_fast", CommonVideoModel::Seedance2p0UltraFast),
|
||||
("seedance_2p0_bpu", CommonVideoModel::Seedance2p0BytePlusUltra),
|
||||
("seedance_2p0_bpu_fast", CommonVideoModel::Seedance2p0BytePlusUltraFast),
|
||||
("seedance_2p0_mini", CommonVideoModel::Seedance2p0Mini),
|
||||
("seedance_2p0_bp_mini", CommonVideoModel::Seedance2p0BytePlusMini),
|
||||
("seedance_2p0_bpu_mini", CommonVideoModel::Seedance2p0BytePlusUltraMini),
|
||||
("sora_2", CommonVideoModel::Sora2),
|
||||
("sora_2_pro", CommonVideoModel::Sora2Pro),
|
||||
("veo_2", CommonVideoModel::Veo2),
|
||||
@@ -247,6 +265,9 @@ mod tests {
|
||||
CommonVideoModel::Seedance2p0UltraFast,
|
||||
CommonVideoModel::Seedance2p0BytePlusUltra,
|
||||
CommonVideoModel::Seedance2p0BytePlusUltraFast,
|
||||
CommonVideoModel::Seedance2p0Mini,
|
||||
CommonVideoModel::Seedance2p0BytePlusMini,
|
||||
CommonVideoModel::Seedance2p0BytePlusUltraMini,
|
||||
CommonVideoModel::Sora2,
|
||||
CommonVideoModel::Sora2Pro,
|
||||
CommonVideoModel::Veo2,
|
||||
@@ -288,6 +309,9 @@ mod tests {
|
||||
(CommonVideoModel::Seedance2p0UltraFast, CommonModelType::Seedance2p0UltraFast),
|
||||
(CommonVideoModel::Seedance2p0BytePlusUltra, CommonModelType::Seedance2p0BytePlusUltra),
|
||||
(CommonVideoModel::Seedance2p0BytePlusUltraFast, CommonModelType::Seedance2p0BytePlusUltraFast),
|
||||
(CommonVideoModel::Seedance2p0Mini, CommonModelType::Seedance2p0Mini),
|
||||
(CommonVideoModel::Seedance2p0BytePlusMini, CommonModelType::Seedance2p0BytePlusMini),
|
||||
(CommonVideoModel::Seedance2p0BytePlusUltraMini, CommonModelType::Seedance2p0BytePlusUltraMini),
|
||||
(CommonVideoModel::Sora2, CommonModelType::Sora2),
|
||||
(CommonVideoModel::Sora2Pro, CommonModelType::Sora2Pro),
|
||||
(CommonVideoModel::Veo2, CommonModelType::Veo2),
|
||||
|
||||
+140
-1
@@ -7,7 +7,8 @@ use enums::common::generation::model_creator::ModelCreator;
|
||||
|
||||
/// The Seedance 2.0 family of video models, in display order:
|
||||
/// Seedance 2.0, Seedance 2.0 Fast, Seedance 2.0 BytePlus, Seedance 2.0 BytePlus
|
||||
/// Fast, Seedance 2.0 BytePlus Ultra, Seedance 2.0 BytePlus Ultra Fast.
|
||||
/// Fast, Seedance 2.0 BytePlus Ultra, Seedance 2.0 BytePlus Ultra Fast,
|
||||
/// Seedance 2.0 Mini, Seedance 2.0 BytePlus Mini, Seedance 2.0 BytePlus Ultra Mini.
|
||||
pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
let mut models = Vec::new();
|
||||
|
||||
@@ -292,5 +293,143 @@ pub fn seedance_2p0_video_models() -> Vec<OmniGenVideoModelDetails> {
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
models.push(OmniGenVideoModelDetails {
|
||||
model: CommonVideoModel::Seedance2p0Mini,
|
||||
model_creator: Some(ModelCreator::Bytedance),
|
||||
full_name: Some("Seedance 2.0 Mini".to_string()),
|
||||
extra_info: Some("The Chinese Volcengine (ByteDance China API platform) version of Seedance 2.0 Mini. This may be better at some characters than the other Seedance models.".to_string()),
|
||||
extra_info_short: Some("Chinese Volcengine Version".to_string()),
|
||||
text_prompt_supported: Some(true),
|
||||
starting_keyframe_supported: Some(true),
|
||||
ending_keyframe_supported: Some(true),
|
||||
image_references_supported: Some(true),
|
||||
image_references_max: Some(9),
|
||||
audio_references_supported: Some(true),
|
||||
audio_references_max: Some(3),
|
||||
audio_references_max_total_duration_seconds: Some(15),
|
||||
video_references_supported: Some(true),
|
||||
video_references_max: Some(3),
|
||||
video_references_max_total_duration_seconds: Some(15),
|
||||
character_references_supported: Some(true),
|
||||
character_references_max: Some(9),
|
||||
aspect_ratio_options: Some(vec![
|
||||
CommonAspectRatio::WideTwentyOneByNine,
|
||||
CommonAspectRatio::WideSixteenByNine,
|
||||
CommonAspectRatio::WideFourByThree,
|
||||
CommonAspectRatio::Square,
|
||||
CommonAspectRatio::TallThreeByFour,
|
||||
CommonAspectRatio::TallNineBySixteen,
|
||||
]),
|
||||
aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
resolution_options: Some(vec![
|
||||
CommonResolution::FourEightyP,
|
||||
CommonResolution::SevenTwentyP,
|
||||
]),
|
||||
resolution_default: Some(CommonResolution::SevenTwentyP),
|
||||
bitrate_options: Some(vec![
|
||||
CommonBitrate::Normal,
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
duration_seconds_default: Some(5),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
models.push(OmniGenVideoModelDetails {
|
||||
model: CommonVideoModel::Seedance2p0BytePlusMini,
|
||||
model_creator: Some(ModelCreator::Bytedance),
|
||||
full_name: Some("Seedance 2.0 Plus Mini".to_string()),
|
||||
extra_info: Some("The Chinese BytePlus (ByteDance's Western API platform) version of Seedance 2.0 Mini. This has fewer restrictions on faces and IP.".to_string()),
|
||||
extra_info_short: Some("BytePlus Version".to_string()),
|
||||
text_prompt_supported: Some(true),
|
||||
starting_keyframe_supported: Some(true),
|
||||
ending_keyframe_supported: Some(true),
|
||||
image_references_supported: Some(true),
|
||||
image_references_max: Some(9),
|
||||
audio_references_supported: Some(true),
|
||||
audio_references_max: Some(3),
|
||||
audio_references_max_total_duration_seconds: Some(15),
|
||||
video_references_supported: Some(true),
|
||||
video_references_max: Some(3),
|
||||
video_references_max_total_duration_seconds: Some(15),
|
||||
character_references_supported: Some(false), // TODO
|
||||
character_references_max: Some(9),
|
||||
aspect_ratio_options: Some(vec![
|
||||
CommonAspectRatio::WideTwentyOneByNine,
|
||||
CommonAspectRatio::WideSixteenByNine,
|
||||
CommonAspectRatio::WideFourByThree,
|
||||
CommonAspectRatio::Square,
|
||||
CommonAspectRatio::TallThreeByFour,
|
||||
CommonAspectRatio::TallNineBySixteen,
|
||||
]),
|
||||
aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
resolution_options: Some(vec![
|
||||
CommonResolution::FourEightyP,
|
||||
CommonResolution::SevenTwentyP,
|
||||
]),
|
||||
resolution_default: Some(CommonResolution::SevenTwentyP),
|
||||
bitrate_options: Some(vec![
|
||||
CommonBitrate::Normal,
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
duration_seconds_default: Some(5),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
models.push(OmniGenVideoModelDetails {
|
||||
model: CommonVideoModel::Seedance2p0BytePlusUltraMini,
|
||||
model_creator: Some(ModelCreator::Bytedance),
|
||||
full_name: Some("Seedance 2.0 Plus Ultra Mini".to_string()),
|
||||
extra_info: Some("This is the same BytePlus version of Seedance 2.0 Mini, but with even fewer restrictions around content. Horror movies, action movie violence, and more is possible.".to_string()),
|
||||
extra_info_short: Some("BytePlus Version".to_string()),
|
||||
text_prompt_supported: Some(true),
|
||||
starting_keyframe_supported: Some(true),
|
||||
ending_keyframe_supported: Some(true),
|
||||
image_references_supported: Some(true),
|
||||
image_references_max: Some(9),
|
||||
audio_references_supported: Some(true),
|
||||
audio_references_max: Some(3),
|
||||
audio_references_max_total_duration_seconds: Some(15),
|
||||
video_references_supported: Some(true),
|
||||
video_references_max: Some(3),
|
||||
video_references_max_total_duration_seconds: Some(15),
|
||||
character_references_supported: Some(false), // TODO
|
||||
character_references_max: Some(9),
|
||||
aspect_ratio_options: Some(vec![
|
||||
CommonAspectRatio::WideTwentyOneByNine,
|
||||
CommonAspectRatio::WideSixteenByNine,
|
||||
CommonAspectRatio::WideFourByThree,
|
||||
CommonAspectRatio::Square,
|
||||
CommonAspectRatio::TallThreeByFour,
|
||||
CommonAspectRatio::TallNineBySixteen,
|
||||
]),
|
||||
aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
resolution_options: Some(vec![
|
||||
CommonResolution::FourEightyP,
|
||||
CommonResolution::SevenTwentyP,
|
||||
]),
|
||||
resolution_default: Some(CommonResolution::SevenTwentyP),
|
||||
bitrate_options: Some(vec![
|
||||
CommonBitrate::Normal,
|
||||
CommonBitrate::High,
|
||||
]),
|
||||
bitrate_default: Some(CommonBitrate::Normal),
|
||||
batch_size_options: Some(vec![1, 2, 4]),
|
||||
batch_size_default: Some(1),
|
||||
duration_seconds_min: Some(4),
|
||||
duration_seconds_max: Some(15),
|
||||
duration_seconds_default: Some(5),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
models
|
||||
}
|
||||
|
||||
+3
@@ -150,6 +150,9 @@ fn map_video_model(model: CommonVideoModel) -> Result<RouterVideoModel, HandlerE
|
||||
CommonVideoModel::Seedance2p0BytePlusFast=> RouterVideoModel::Seedance2p0BytePlusFast,
|
||||
CommonVideoModel::Seedance2p0BytePlusUltra => RouterVideoModel::Seedance2p0BytePlusUltra,
|
||||
CommonVideoModel::Seedance2p0BytePlusUltraFast => RouterVideoModel::Seedance2p0BytePlusUltraFast,
|
||||
CommonVideoModel::Seedance2p0Mini => RouterVideoModel::Seedance2p0Mini,
|
||||
CommonVideoModel::Seedance2p0BytePlusMini => RouterVideoModel::Seedance2p0BytePlusMini,
|
||||
CommonVideoModel::Seedance2p0BytePlusUltraMini => RouterVideoModel::Seedance2p0BytePlusUltraMini,
|
||||
CommonVideoModel::HappyHorse1p0 => RouterVideoModel::HappyHorse1p0,
|
||||
CommonVideoModel::Sora2 => RouterVideoModel::Sora2,
|
||||
CommonVideoModel::Sora2Pro => RouterVideoModel::Sora2Pro,
|
||||
|
||||
+2
@@ -188,9 +188,11 @@ pub async fn omni_api_video_generate_handler(
|
||||
// BytePlus Ultra
|
||||
Some(CommonVideoModel::Seedance2p0BytePlusUltra) => KinoviAccount::BytePlusUltra,
|
||||
Some(CommonVideoModel::Seedance2p0BytePlusUltraFast) => KinoviAccount::BytePlusUltra,
|
||||
Some(CommonVideoModel::Seedance2p0BytePlusUltraMini) => KinoviAccount::BytePlusUltra,
|
||||
// BytePlus
|
||||
Some(CommonVideoModel::Seedance2p0BytePlus) => KinoviAccount::BytePlus,
|
||||
Some(CommonVideoModel::Seedance2p0BytePlusFast) => KinoviAccount::BytePlus,
|
||||
Some(CommonVideoModel::Seedance2p0BytePlusMini) => KinoviAccount::BytePlus,
|
||||
Some(CommonVideoModel::PreviewModel) => KinoviAccount::BytePlus,
|
||||
Some(CommonVideoModel::PreviewModelFast) => KinoviAccount::BytePlus,
|
||||
// Everything else goes through Volcengine
|
||||
|
||||
+2
@@ -178,9 +178,11 @@ pub async fn omni_gen_video_generate_handler(
|
||||
// BytePlus Ultra
|
||||
Some(CommonVideoModel::Seedance2p0BytePlusUltra) => KinoviAccount::BytePlusUltra,
|
||||
Some(CommonVideoModel::Seedance2p0BytePlusUltraFast) => KinoviAccount::BytePlusUltra,
|
||||
Some(CommonVideoModel::Seedance2p0BytePlusUltraMini) => KinoviAccount::BytePlusUltra,
|
||||
// BytePlus
|
||||
Some(CommonVideoModel::Seedance2p0BytePlus) => KinoviAccount::BytePlus,
|
||||
Some(CommonVideoModel::Seedance2p0BytePlusFast) => KinoviAccount::BytePlus,
|
||||
Some(CommonVideoModel::Seedance2p0BytePlusMini) => KinoviAccount::BytePlus,
|
||||
Some(CommonVideoModel::PreviewModel) => KinoviAccount::BytePlus,
|
||||
Some(CommonVideoModel::PreviewModelFast) => KinoviAccount::BytePlus,
|
||||
// Everything else goes through Volcengine
|
||||
|
||||
+3
@@ -67,6 +67,9 @@ pub async fn run_pipeline_v2(args: RunPipelineV2Args<'_>) -> Result<PipelineResu
|
||||
RouterVideoModel::HappyHorse1p0 => RouterProvider::Seedance2Pro,
|
||||
RouterVideoModel::Seedance2p0 => RouterProvider::Seedance2Pro,
|
||||
RouterVideoModel::Seedance2p0Fast => RouterProvider::Seedance2Pro,
|
||||
RouterVideoModel::Seedance2p0Mini => RouterProvider::Seedance2Pro,
|
||||
RouterVideoModel::Seedance2p0BytePlusMini => RouterProvider::Seedance2Pro,
|
||||
RouterVideoModel::Seedance2p0BytePlusUltraMini => RouterProvider::Seedance2Pro,
|
||||
//RouterVideoModel::Seedance2p0Ultra => RouterProvider::GmiCloud,
|
||||
//RouterVideoModel::Seedance2p0UltraFast => RouterProvider::GmiCloud,
|
||||
RouterVideoModel::GrokImagineVideo => RouterProvider::GrokApi,
|
||||
|
||||
@@ -22,7 +22,7 @@ if (import.meta.env.DEV) {
|
||||
// (disabled — it overrides the origin above and points at localhost:12345,
|
||||
// which breaks dev unless a local backend is running. Re-enable only when
|
||||
// testing against a local storyteller-web.)
|
||||
// StorytellerApiHostStore.getInstance().setDevelopment();
|
||||
StorytellerApiHostStore.getInstance().setDevelopment();
|
||||
} catch (e) {
|
||||
console.warn("Failed to set dev API host override", e);
|
||||
}
|
||||
|
||||
@@ -3,16 +3,19 @@
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "../popover"
|
||||
"path": "../modal"
|
||||
},
|
||||
{
|
||||
"path": "../button-icon-select"
|
||||
"path": "../button"
|
||||
},
|
||||
{
|
||||
"path": "../tooltip"
|
||||
},
|
||||
{
|
||||
"path": "../button"
|
||||
"path": "../popover"
|
||||
},
|
||||
{
|
||||
"path": "../button-icon-select"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
|
||||
@@ -26,16 +26,19 @@
|
||||
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../popover/tsconfig.lib.json"
|
||||
"path": "../modal/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../button-icon-select/tsconfig.lib.json"
|
||||
"path": "../button/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../tooltip/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../button/tsconfig.lib.json"
|
||||
"path": "../popover/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../button-icon-select/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+102
-303
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user