mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-25 19:29:29 -04:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
962abee112 | ||
|
|
8507b1ac64 | ||
|
|
9c5ae7038d | ||
|
|
c6ca35119b | ||
|
|
bb4975bbf6 | ||
|
|
1a98d8e9c3 |
+10
-2
@@ -9,7 +9,10 @@ use enums::common::generation_provider::GenerationProvider;
|
||||
pub const ESTIMATE_VIDEO_COST_PATH: &str = "/v1/generate/cost_estimate/video";
|
||||
|
||||
/// Request body for the video cost estimate endpoint.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
///
|
||||
/// Every model cares about its own set of parameters and not all
|
||||
/// parameters may be relevant for every model.
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
pub struct EstimateVideoCostRequest {
|
||||
/// The video model to estimate costs for.
|
||||
pub model: CommonVideoModel,
|
||||
@@ -34,10 +37,13 @@ pub struct EstimateVideoCostRequest {
|
||||
|
||||
/// Number of videos to generate in parallel.
|
||||
pub video_batch_count: Option<u16>,
|
||||
|
||||
/// Whether to generate audio for the video.
|
||||
pub generate_audio: Option<bool>,
|
||||
}
|
||||
|
||||
/// Describes the type of video generation being requested.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum GenerationMode {
|
||||
TextToVideo,
|
||||
@@ -99,6 +105,7 @@ mod tests {
|
||||
resolution: None,
|
||||
duration_seconds: None,
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
};
|
||||
let serialized = serde_json::to_string(&request).unwrap();
|
||||
assert!(serialized.contains("\"generation_mode\":{\"type\":\"text_to_video\"}"));
|
||||
@@ -115,6 +122,7 @@ mod tests {
|
||||
resolution: None,
|
||||
duration_seconds: None,
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
};
|
||||
let serialized = serde_json::to_string(&request).unwrap();
|
||||
assert!(serialized.contains("\"generation_mode\":{\"type\":\"reference_image_to_video\",\"count\":1}"));
|
||||
|
||||
+4
-1
@@ -6,7 +6,7 @@ use utoipa::ToSchema;
|
||||
pub const SEEDANCE_1P5_PRO_MULTI_FUNCTION_VIDEO_GEN_PATH: &str = "/v1/generate/video/multi_function/seedance_1p5_pro";
|
||||
|
||||
/// Both text-to-video and image-to-video in one request.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
pub struct Seedance1p5ProMultiFunctionVideoGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
@@ -38,6 +38,9 @@ pub struct Seedance1p5ProMultiFunctionVideoGenRequest {
|
||||
|
||||
/// Optional.
|
||||
pub aspect_ratio: Option<Seedance1p5ProMultiFunctionVideoGenAspectRatio>,
|
||||
|
||||
/// Whether to generate audio.
|
||||
pub generate_audio: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema, Copy, Clone, Debug)]
|
||||
|
||||
+3
-1
@@ -32,7 +32,7 @@ pub(crate) fn estimate_video_cost_artcraft_kling3p0_pro(
|
||||
let api_key = fal_client::creds::fal_api_key::FalApiKey::from_str("");
|
||||
let args = EnqueueKling3p0ProTextToVideoArgs {
|
||||
prompt: String::new(),
|
||||
generate_audio: None,
|
||||
generate_audio: plan.generate_audio,
|
||||
negative_prompt: None,
|
||||
duration,
|
||||
aspect_ratio: None,
|
||||
@@ -65,6 +65,7 @@ mod tests {
|
||||
model: CommonVideoModel::Kling3p0Pro,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
negative_prompt: None,
|
||||
start_frame: None,
|
||||
end_frame: None,
|
||||
reference_images: None,
|
||||
@@ -72,6 +73,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
+3
-1
@@ -32,7 +32,7 @@ pub(crate) fn estimate_video_cost_artcraft_kling3p0_standard(
|
||||
let api_key = fal_client::creds::fal_api_key::FalApiKey::from_str("");
|
||||
let args = EnqueueKling3p0StandardTextToVideoArgs {
|
||||
prompt: String::new(),
|
||||
generate_audio: None,
|
||||
generate_audio: plan.generate_audio,
|
||||
negative_prompt: None,
|
||||
duration,
|
||||
aspect_ratio: None,
|
||||
@@ -65,6 +65,7 @@ mod tests {
|
||||
model: CommonVideoModel::Kling3p0Standard,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
negative_prompt: None,
|
||||
start_frame: None,
|
||||
end_frame: None,
|
||||
reference_images: None,
|
||||
@@ -72,6 +73,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
+3
@@ -39,6 +39,7 @@ pub(crate) fn estimate_video_cost_artcraft_seedance1p5_pro(
|
||||
duration,
|
||||
resolution,
|
||||
aspect_ratio: None,
|
||||
generate_audio: plan.generate_audio,
|
||||
webhook_url: "https://example.com",
|
||||
api_key: &api_key,
|
||||
};
|
||||
@@ -67,6 +68,7 @@ mod tests {
|
||||
model: CommonVideoModel::Seedance1p5Pro,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
negative_prompt: None,
|
||||
start_frame: None,
|
||||
end_frame: None,
|
||||
reference_images: None,
|
||||
@@ -74,6 +76,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
+2
@@ -57,6 +57,7 @@ mod tests {
|
||||
model: CommonVideoModel::Seedance2p0,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
negative_prompt: None,
|
||||
start_frame: None,
|
||||
end_frame: None,
|
||||
reference_images: None,
|
||||
@@ -64,6 +65,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: Some(video_batch_count),
|
||||
generate_audio: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
+2
-2
@@ -15,11 +15,11 @@ pub async fn execute_artcraft_kling3p0_pro(
|
||||
let request = Kling3p0ProMultiFunctionVideoGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
negative_prompt: None,
|
||||
negative_prompt: plan.negative_prompt.map(|s| s.to_string()),
|
||||
image_media_token: plan.start_frame.map(|t| t.to_owned()),
|
||||
end_image_media_token: plan.end_frame.map(|t| t.to_owned()),
|
||||
duration: plan.duration,
|
||||
generate_audio: None,
|
||||
generate_audio: plan.generate_audio,
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -15,11 +15,11 @@ pub async fn execute_artcraft_kling3p0_standard(
|
||||
let request = Kling3p0StandardMultiFunctionVideoGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
negative_prompt: None,
|
||||
negative_prompt: plan.negative_prompt.map(|s| s.to_string()),
|
||||
image_media_token: plan.start_frame.map(|t| t.to_owned()),
|
||||
end_image_media_token: plan.end_frame.map(|t| t.to_owned()),
|
||||
duration: plan.duration,
|
||||
generate_audio: None,
|
||||
generate_audio: plan.generate_audio,
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
};
|
||||
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ pub async fn execute_artcraft_seedance1p5_pro(
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
duration: plan.duration,
|
||||
resolution: plan.resolution,
|
||||
generate_audio: plan.generate_audio,
|
||||
};
|
||||
|
||||
let response = seedance_1p5_pro_multi_function_video_gen(
|
||||
|
||||
+7
-5
@@ -22,8 +22,8 @@ pub struct GenerateVideoRequest<'a> {
|
||||
/// The prompt for the video generation
|
||||
pub prompt: Option<&'a str>,
|
||||
|
||||
// /// Some models support negative prompts
|
||||
// pub negative_prompt: Option<String>,
|
||||
/// Some models support negative prompts
|
||||
pub negative_prompt: Option<&'a str>,
|
||||
|
||||
/// Starting keyframe (optional).
|
||||
pub start_frame: Option<ImageRef<'a>>,
|
||||
@@ -46,9 +46,11 @@ pub struct GenerateVideoRequest<'a> {
|
||||
/// How many videos to generate.
|
||||
pub video_batch_count: Option<u16>,
|
||||
|
||||
// /// Whether to turn on/off audio.
|
||||
// /// Not all models support audio, not all models have a choice.
|
||||
// pub generate_audio: Option<bool>,
|
||||
/// Whether to turn on/off audio.
|
||||
/// Not all models support audio, not all models have a choice.
|
||||
/// Some models will default this to true, others will default it to false,
|
||||
/// so it's best to be explicit.
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
/// If the request is a mismatch with the (model/provider), how to mitigate it.
|
||||
pub request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy,
|
||||
|
||||
+6
@@ -13,10 +13,12 @@ use tokens::tokens::media_files::MediaFileToken;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlanArtcraftKling3p0Pro<'a> {
|
||||
pub prompt: Option<&'a str>,
|
||||
pub negative_prompt: Option<&'a str>,
|
||||
pub start_frame: Option<&'a MediaFileToken>,
|
||||
pub end_frame: Option<&'a MediaFileToken>,
|
||||
pub aspect_ratio: Option<Kling3p0ProMultiFunctionVideoGenAspectRatio>,
|
||||
pub duration: Option<Kling3p0ProMultiFunctionVideoGenDuration>,
|
||||
pub generate_audio: Option<bool>,
|
||||
pub idempotency_token: String,
|
||||
}
|
||||
|
||||
@@ -33,10 +35,12 @@ pub fn plan_generate_video_artcraft_kling3p0_pro<'a>(
|
||||
|
||||
Ok(PlanArtcraftKling3p0Pro {
|
||||
prompt: request.prompt,
|
||||
negative_prompt: request.negative_prompt,
|
||||
start_frame,
|
||||
end_frame,
|
||||
aspect_ratio,
|
||||
duration,
|
||||
generate_audio: request.generate_audio,
|
||||
idempotency_token: request.get_or_generate_idempotency_token(),
|
||||
})
|
||||
}
|
||||
@@ -169,6 +173,7 @@ mod tests {
|
||||
model: CommonVideoModel::Kling3p0Pro,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: Some("a cat in space"),
|
||||
negative_prompt: None,
|
||||
start_frame: None,
|
||||
end_frame: None,
|
||||
reference_images: None,
|
||||
@@ -176,6 +181,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
duration_seconds: None,
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
idempotency_token: None,
|
||||
}
|
||||
|
||||
+6
@@ -13,10 +13,12 @@ use tokens::tokens::media_files::MediaFileToken;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlanArtcraftKling3p0Standard<'a> {
|
||||
pub prompt: Option<&'a str>,
|
||||
pub negative_prompt: Option<&'a str>,
|
||||
pub start_frame: Option<&'a MediaFileToken>,
|
||||
pub end_frame: Option<&'a MediaFileToken>,
|
||||
pub aspect_ratio: Option<Kling3p0StandardMultiFunctionVideoGenAspectRatio>,
|
||||
pub duration: Option<Kling3p0StandardMultiFunctionVideoGenDuration>,
|
||||
pub generate_audio: Option<bool>,
|
||||
pub idempotency_token: String,
|
||||
}
|
||||
|
||||
@@ -33,10 +35,12 @@ pub fn plan_generate_video_artcraft_kling3p0_standard<'a>(
|
||||
|
||||
Ok(PlanArtcraftKling3p0Standard {
|
||||
prompt: request.prompt,
|
||||
negative_prompt: request.negative_prompt,
|
||||
start_frame,
|
||||
end_frame,
|
||||
aspect_ratio,
|
||||
duration,
|
||||
generate_audio: request.generate_audio,
|
||||
idempotency_token: request.get_or_generate_idempotency_token(),
|
||||
})
|
||||
}
|
||||
@@ -169,6 +173,7 @@ mod tests {
|
||||
model: CommonVideoModel::Kling3p0Standard,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: Some("a cat in space"),
|
||||
negative_prompt: None,
|
||||
start_frame: None,
|
||||
end_frame: None,
|
||||
reference_images: None,
|
||||
@@ -176,6 +181,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
duration_seconds: None,
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
idempotency_token: None,
|
||||
}
|
||||
|
||||
+4
@@ -19,6 +19,7 @@ pub struct PlanArtcraftSeedance1p5Pro<'a> {
|
||||
pub aspect_ratio: Option<Seedance1p5ProMultiFunctionVideoGenAspectRatio>,
|
||||
pub duration: Option<Seedance1p5ProMultiFunctionVideoGenDuration>,
|
||||
pub resolution: Option<Seedance1p5ProMultiFunctionVideoGenResolution>,
|
||||
pub generate_audio: Option<bool>,
|
||||
pub idempotency_token: String,
|
||||
}
|
||||
|
||||
@@ -41,6 +42,7 @@ pub fn plan_generate_video_artcraft_seedance1p5_pro<'a>(
|
||||
aspect_ratio,
|
||||
duration,
|
||||
resolution,
|
||||
generate_audio: request.generate_audio,
|
||||
idempotency_token: request.get_or_generate_idempotency_token(),
|
||||
})
|
||||
}
|
||||
@@ -184,6 +186,7 @@ mod tests {
|
||||
model: CommonVideoModel::Seedance1p5Pro,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: Some("a cat in space"),
|
||||
negative_prompt: None,
|
||||
start_frame: None,
|
||||
end_frame: None,
|
||||
reference_images: None,
|
||||
@@ -191,6 +194,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
duration_seconds: None,
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
idempotency_token: None,
|
||||
}
|
||||
|
||||
@@ -214,6 +214,7 @@ pub fn base_video_request() -> GenerateVideoRequest<'static> {
|
||||
model: CommonVideoModel::Seedance2p0,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: Some("a cat in space"),
|
||||
negative_prompt: None,
|
||||
start_frame: None,
|
||||
end_frame: None,
|
||||
reference_images: None,
|
||||
@@ -221,6 +222,7 @@ pub fn base_video_request() -> GenerateVideoRequest<'static> {
|
||||
aspect_ratio: None,
|
||||
duration_seconds: None,
|
||||
video_batch_count: None,
|
||||
generate_audio: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
idempotency_token: None,
|
||||
}
|
||||
|
||||
+49
-3
@@ -16,6 +16,7 @@ pub struct EnqueueSeedance1p5ProImageToVideoArgs<'a, R: IntoUrl> {
|
||||
pub resolution: Option<EnqueueSeedance1p5ProImageToVideoResolution>,
|
||||
pub duration: Option<EnqueueSeedance1p5ProImageToVideoDuration>,
|
||||
pub aspect_ratio: Option<EnqueueSeedance1p5ProImageToVideoAspectRatio>,
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
@@ -62,10 +63,13 @@ impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSeedance1p5ProImageToVideo
|
||||
let resolution = self.resolution.unwrap_or(EnqueueSeedance1p5ProImageToVideoResolution::SevenTwentyP);
|
||||
let duration = self.duration.unwrap_or(EnqueueSeedance1p5ProImageToVideoDuration::FiveSeconds);
|
||||
|
||||
let audio = self.generate_audio.unwrap_or(true);
|
||||
let dollars_per_million_tokens = if audio { 2.4 } else { 1.2 };
|
||||
|
||||
if resolution == EnqueueSeedance1p5ProImageToVideoResolution::SevenTwentyP
|
||||
&& duration == EnqueueSeedance1p5ProImageToVideoDuration::FiveSeconds
|
||||
{
|
||||
return 26;
|
||||
return if audio { 26 } else { 13 };
|
||||
}
|
||||
|
||||
// TODO: Only correct for some aspect ratios for now.
|
||||
@@ -92,7 +96,7 @@ impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSeedance1p5ProImageToVideo
|
||||
let tokens = (height as f64) * (width as f64) * FPS * duration_secs;
|
||||
let tokens = tokens / 1024.0;
|
||||
|
||||
let cost = tokens * 2.4 / 1_000_000.0;
|
||||
let cost = tokens * dollars_per_million_tokens / 1_000_000.0;
|
||||
let cost = cost * 100.0; // Dollars to cents.
|
||||
let cost = cost.ceil();
|
||||
|
||||
@@ -150,7 +154,7 @@ pub async fn enqueue_seedance_1p5_pro_image_to_video_webhook<R: IntoUrl>(
|
||||
camera_fixed: None,
|
||||
seed: None,
|
||||
enable_safety_checker: Some(false),
|
||||
generate_audio: Some(true),
|
||||
generate_audio: Some(args.generate_audio.unwrap_or(true)),
|
||||
};
|
||||
|
||||
let result = seedance_1p5_pro_image_to_video(request)
|
||||
@@ -183,6 +187,7 @@ mod tests {
|
||||
duration: Some(EnqueueSeedance1p5ProImageToVideoDuration::FiveSeconds),
|
||||
resolution: Some(EnqueueSeedance1p5ProImageToVideoResolution::SevenTwentyP),
|
||||
aspect_ratio: None,
|
||||
generate_audio: None,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
@@ -207,6 +212,43 @@ mod tests {
|
||||
assert_eq!(cost, 146);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cost_audio_off() {
|
||||
let api_key = FalApiKey::from_str("");
|
||||
|
||||
let mut args = EnqueueSeedance1p5ProImageToVideoArgs {
|
||||
prompt: String::new(),
|
||||
image_url: String::new(),
|
||||
end_image_url: None,
|
||||
api_key: &api_key,
|
||||
duration: Some(EnqueueSeedance1p5ProImageToVideoDuration::FiveSeconds),
|
||||
resolution: Some(EnqueueSeedance1p5ProImageToVideoResolution::SevenTwentyP),
|
||||
aspect_ratio: None,
|
||||
generate_audio: Some(false),
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
// 720p 5s without audio = half of 26
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
assert_eq!(cost, 13);
|
||||
|
||||
// Calculated values — half of audio-on costs (ceil)
|
||||
args.duration = Some(EnqueueSeedance1p5ProImageToVideoDuration::TenSeconds);
|
||||
args.resolution = Some(EnqueueSeedance1p5ProImageToVideoResolution::SevenTwentyP);
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
assert_eq!(cost, 33);
|
||||
|
||||
args.duration = Some(EnqueueSeedance1p5ProImageToVideoDuration::FiveSeconds);
|
||||
args.resolution = Some(EnqueueSeedance1p5ProImageToVideoResolution::TenEightyP);
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
assert_eq!(cost, 37);
|
||||
|
||||
args.duration = Some(EnqueueSeedance1p5ProImageToVideoDuration::TenSeconds);
|
||||
args.resolution = Some(EnqueueSeedance1p5ProImageToVideoResolution::TenEightyP);
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
assert_eq!(cost, 73);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
@@ -220,6 +262,7 @@ mod tests {
|
||||
aspect_ratio: Some(EnqueueSeedance1p5ProImageToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueSeedance1p5ProImageToVideoResolution::SevenTwentyP),
|
||||
end_image_url: None,
|
||||
generate_audio: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
@@ -245,6 +288,7 @@ mod tests {
|
||||
aspect_ratio: Some(ar),
|
||||
resolution: None,
|
||||
end_image_url: None,
|
||||
generate_audio: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
@@ -270,6 +314,7 @@ mod tests {
|
||||
aspect_ratio: Some(EnqueueSeedance1p5ProImageToVideoAspectRatio::SixteenByNine),
|
||||
resolution: None,
|
||||
end_image_url: None,
|
||||
generate_audio: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
@@ -295,6 +340,7 @@ mod tests {
|
||||
aspect_ratio: Some(EnqueueSeedance1p5ProImageToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(res),
|
||||
end_image_url: None,
|
||||
generate_audio: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
+49
-3
@@ -14,6 +14,7 @@ pub struct EnqueueSeedance1p5ProTextToVideoArgs<'a, R: IntoUrl> {
|
||||
pub resolution: Option<EnqueueSeedance1p5ProTextToVideoResolution>,
|
||||
pub duration: Option<EnqueueSeedance1p5ProTextToVideoDuration>,
|
||||
pub aspect_ratio: Option<EnqueueSeedance1p5ProTextToVideoAspectRatio>,
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
@@ -53,17 +54,22 @@ pub enum EnqueueSeedance1p5ProTextToVideoAspectRatio {
|
||||
|
||||
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSeedance1p5ProTextToVideoArgs<'_, U> {
|
||||
fn calculate_cost_in_cents(&self) -> UsdCents {
|
||||
// Fal:
|
||||
// "Each 720p 5 second video with audio costs roughly $0.26.
|
||||
// For other resolutions, 1 million video tokens with audio costs $2.4.
|
||||
// Without audio, the price is 1.2 per million tokens.
|
||||
// tokens(video) = (height x width x FPS x duration) / 1024."
|
||||
|
||||
let resolution = self.resolution.unwrap_or(EnqueueSeedance1p5ProTextToVideoResolution::SevenTwentyP);
|
||||
let duration = self.duration.unwrap_or(EnqueueSeedance1p5ProTextToVideoDuration::FiveSeconds);
|
||||
|
||||
let audio = self.generate_audio.unwrap_or(true);
|
||||
let dollars_per_million_tokens = if audio { 2.4 } else { 1.2 };
|
||||
|
||||
if resolution == EnqueueSeedance1p5ProTextToVideoResolution::SevenTwentyP
|
||||
&& duration == EnqueueSeedance1p5ProTextToVideoDuration::FiveSeconds
|
||||
{
|
||||
return 26;
|
||||
return if audio { 26 } else { 13 };
|
||||
}
|
||||
|
||||
// TODO: Only correct for some aspect ratios for now.
|
||||
@@ -90,7 +96,7 @@ impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSeedance1p5ProTextToVideoA
|
||||
let tokens = (height as f64) * (width as f64) * FPS * duration_secs;
|
||||
let tokens = tokens / 1024.0;
|
||||
|
||||
let cost = tokens * 2.4 / 1_000_000.0;
|
||||
let cost = tokens * dollars_per_million_tokens / 1_000_000.0;
|
||||
let cost = cost * 100.0; // Dollars to cents.
|
||||
let cost = cost.ceil();
|
||||
|
||||
@@ -146,7 +152,7 @@ pub async fn enqueue_seedance_1p5_pro_text_to_video_webhook<R: IntoUrl>(
|
||||
camera_fixed: None,
|
||||
seed: None,
|
||||
enable_safety_checker: Some(false),
|
||||
generate_audio: Some(true),
|
||||
generate_audio: Some(args.generate_audio.unwrap_or(true)),
|
||||
};
|
||||
|
||||
let result = seedance_1p5_pro_text_to_video(request)
|
||||
@@ -176,6 +182,7 @@ mod tests {
|
||||
duration: Some(EnqueueSeedance1p5ProTextToVideoDuration::FiveSeconds),
|
||||
resolution: Some(EnqueueSeedance1p5ProTextToVideoResolution::SevenTwentyP),
|
||||
aspect_ratio: None,
|
||||
generate_audio: None,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
@@ -200,6 +207,41 @@ mod tests {
|
||||
assert_eq!(cost, 146);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cost_audio_off() {
|
||||
let api_key = FalApiKey::from_str("");
|
||||
|
||||
let mut args = EnqueueSeedance1p5ProTextToVideoArgs {
|
||||
prompt: String::new(),
|
||||
api_key: &api_key,
|
||||
duration: Some(EnqueueSeedance1p5ProTextToVideoDuration::FiveSeconds),
|
||||
resolution: Some(EnqueueSeedance1p5ProTextToVideoResolution::SevenTwentyP),
|
||||
aspect_ratio: None,
|
||||
generate_audio: Some(false),
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
// 720p 5s without audio = half of 26
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
assert_eq!(cost, 13);
|
||||
|
||||
// Calculated values — half of audio-on costs (ceil)
|
||||
args.duration = Some(EnqueueSeedance1p5ProTextToVideoDuration::TenSeconds);
|
||||
args.resolution = Some(EnqueueSeedance1p5ProTextToVideoResolution::SevenTwentyP);
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
assert_eq!(cost, 33);
|
||||
|
||||
args.duration = Some(EnqueueSeedance1p5ProTextToVideoDuration::FiveSeconds);
|
||||
args.resolution = Some(EnqueueSeedance1p5ProTextToVideoResolution::TenEightyP);
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
assert_eq!(cost, 37);
|
||||
|
||||
args.duration = Some(EnqueueSeedance1p5ProTextToVideoDuration::TenSeconds);
|
||||
args.resolution = Some(EnqueueSeedance1p5ProTextToVideoResolution::TenEightyP);
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
assert_eq!(cost, 73);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
@@ -211,6 +253,7 @@ mod tests {
|
||||
duration: Some(EnqueueSeedance1p5ProTextToVideoDuration::FiveSeconds),
|
||||
aspect_ratio: Some(EnqueueSeedance1p5ProTextToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueSeedance1p5ProTextToVideoResolution::SevenTwentyP),
|
||||
generate_audio: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
@@ -234,6 +277,7 @@ mod tests {
|
||||
duration: Some(EnqueueSeedance1p5ProTextToVideoDuration::FourSeconds),
|
||||
aspect_ratio: Some(ar),
|
||||
resolution: None,
|
||||
generate_audio: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
@@ -257,6 +301,7 @@ mod tests {
|
||||
duration: Some(dur),
|
||||
aspect_ratio: Some(EnqueueSeedance1p5ProTextToVideoAspectRatio::SixteenByNine),
|
||||
resolution: None,
|
||||
generate_audio: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
@@ -280,6 +325,7 @@ mod tests {
|
||||
duration: Some(EnqueueSeedance1p5ProTextToVideoDuration::FourSeconds),
|
||||
aspect_ratio: Some(EnqueueSeedance1p5ProTextToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(res),
|
||||
generate_audio: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
+1
-2
@@ -7,8 +7,8 @@ use artcraft_api_defs::generate::cost_estimate::estimate_video_cost::{
|
||||
EstimateVideoCostError, EstimateVideoCostErrorType, EstimateVideoCostRequest,
|
||||
EstimateVideoCostResponse,
|
||||
};
|
||||
use log::info;
|
||||
use artcraft_client::endpoints::generate::cost_estimate::video::estimate_video_cost::estimate_video_cost;
|
||||
use log::debug;
|
||||
use tauri::State;
|
||||
|
||||
impl SerializeMarker for EstimateVideoCostResponse {}
|
||||
@@ -18,7 +18,6 @@ pub async fn estimate_video_cost_command(
|
||||
request: EstimateVideoCostRequest,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
) -> ResponseOrError<EstimateVideoCostResponse, EstimateVideoCostError> {
|
||||
info!("estimate_video_cost_command called");
|
||||
|
||||
let result = estimate_video_cost(
|
||||
&app_env_configs.storyteller_host,
|
||||
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use artcraft_router::api::common_video_model::CommonVideoModel;
|
||||
use artcraft_router::api::image_ref::ImageRef;
|
||||
use artcraft_router::api::provider::Provider;
|
||||
use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use artcraft_router::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use artcraft_router::client::router_client::RouterClient;
|
||||
use artcraft_router::generate::generate_video::generate_video_request::GenerateVideoRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use log::{error, info};
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
|
||||
pub(super) async fn handle_artcraft_seedance_1p5_pro(
|
||||
request: &EnqueueImageToVideoRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
creds: &StorytellerCredentialSet,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let client = RouterClient::Artcraft(RouterArtcraftClient::new(
|
||||
app_env_configs.storyteller_host.clone(),
|
||||
creds.clone(),
|
||||
));
|
||||
|
||||
let start_frame = request.image_media_token.as_ref().map(ImageRef::MediaFileToken);
|
||||
let end_frame = request.end_frame_image_media_token.as_ref().map(ImageRef::MediaFileToken);
|
||||
|
||||
let router_request = GenerateVideoRequest {
|
||||
model: CommonVideoModel::Seedance1p5Pro,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: request.prompt.as_deref(),
|
||||
start_frame,
|
||||
end_frame,
|
||||
reference_images: None,
|
||||
resolution: None,
|
||||
aspect_ratio: request.aspect_ratio,
|
||||
duration_seconds: request.duration_seconds,
|
||||
video_batch_count: request.video_batch_count,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
let plan = router_request.build()?;
|
||||
|
||||
info!("Video Generation Plan: {:?}", plan);
|
||||
|
||||
let response = match plan.generate_video(&client).await {
|
||||
Ok(resp) => {
|
||||
info!("Successfully enqueued.");
|
||||
resp
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
let job_id = response.get_artcraft_payload()
|
||||
.map(|p| p.inference_job_token.to_string())
|
||||
.ok_or(GenerateError::ResponseHadNoJobTokens)?;
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::VideoGeneration,
|
||||
model: Some(GenerationModel::Seedance1p5Pro),
|
||||
provider: GenerationProvider::Artcraft,
|
||||
provider_job_id: Some(job_id),
|
||||
})
|
||||
}
|
||||
+2
@@ -34,6 +34,7 @@ pub(super) async fn handle_artcraft_seedance_2p0(
|
||||
model: CommonVideoModel::Seedance2p0,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: request.prompt.as_deref(),
|
||||
negative_prompt: None,
|
||||
start_frame,
|
||||
end_frame,
|
||||
reference_images,
|
||||
@@ -41,6 +42,7 @@ pub(super) async fn handle_artcraft_seedance_2p0(
|
||||
aspect_ratio: request.aspect_ratio,
|
||||
duration_seconds: request.duration_seconds,
|
||||
video_batch_count: request.video_batch_count,
|
||||
generate_audio: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
+1
-2
@@ -9,7 +9,6 @@ use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_vi
|
||||
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_seedance_1_lite::handle_artcraft_seedance_1_lite;
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use artcraft_router::api::common_video_model::CommonVideoModel;
|
||||
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_seedance_1p5_pro::handle_artcraft_seedance_1p5_pro;
|
||||
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_seedance_2p0::handle_artcraft_seedance_2p0;
|
||||
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_sora2::handle_artcraft_sora2;
|
||||
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_sora2_pro::handle_artcraft_sora2_pro;
|
||||
@@ -46,7 +45,7 @@ pub async fn handle_video_artcraft(
|
||||
Some(VideoModel::Kling3p0Standard) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Kling3p0Standard, GenerationModel::Kling3p0Standard).await,
|
||||
Some(VideoModel::Kling3p0Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Kling3p0Pro, GenerationModel::Kling3p0Pro).await,
|
||||
Some(VideoModel::Seedance10Lite) => handle_artcraft_seedance_1_lite(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Seedance1p5Pro) => handle_artcraft_seedance_1p5_pro(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Seedance1p5Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance1p5Pro, GenerationModel::Seedance1p5Pro).await,
|
||||
Some(VideoModel::Seedance2p0) => handle_artcraft_seedance_2p0(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Sora2) => handle_artcraft_sora2(request, app_env_configs, &creds).await,
|
||||
Some(VideoModel::Sora2Pro) => handle_artcraft_sora2_pro(request, app_env_configs, &creds).await,
|
||||
|
||||
+2
@@ -34,6 +34,7 @@ pub(super) async fn handle_artcraft_video_via_router(
|
||||
model,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: request.prompt.as_deref(),
|
||||
negative_prompt: None,
|
||||
start_frame,
|
||||
end_frame,
|
||||
reference_images: None,
|
||||
@@ -41,6 +42,7 @@ pub(super) async fn handle_artcraft_video_via_router(
|
||||
aspect_ratio: request.aspect_ratio,
|
||||
duration_seconds: request.duration_seconds,
|
||||
video_batch_count: request.video_batch_count,
|
||||
generate_audio: request.generate_audio,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@ mod handle_artcraft_kling_2p5_turbo_pro;
|
||||
mod handle_artcraft_kling_2p6_pro;
|
||||
mod handle_artcraft_video_via_router;
|
||||
mod handle_artcraft_seedance_1_lite;
|
||||
mod handle_artcraft_seedance_1p5_pro;
|
||||
mod handle_artcraft_seedance_2p0;
|
||||
mod handle_artcraft_sora2;
|
||||
mod handle_artcraft_sora2_pro;
|
||||
|
||||
+2
@@ -42,6 +42,7 @@ pub async fn estimate_video_cost_handler(
|
||||
model: router_model,
|
||||
provider: router_provider,
|
||||
prompt: None,
|
||||
negative_prompt: None,
|
||||
start_frame: None,
|
||||
end_frame: None,
|
||||
reference_images: None,
|
||||
@@ -49,6 +50,7 @@ pub async fn estimate_video_cost_handler(
|
||||
aspect_ratio: router_aspect_ratio,
|
||||
duration_seconds: request.duration_seconds,
|
||||
video_batch_count: request.video_batch_count,
|
||||
generate_audio: request.generate_audio,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
+5
-2
@@ -200,6 +200,9 @@ pub async fn kling_3p0_pro_multi_function_video_gen_handler(
|
||||
|
||||
let apriori_job_token = InferenceJobToken::generate();
|
||||
|
||||
// Most people will want audio
|
||||
let generate_audio = request.generate_audio.unwrap_or(true);
|
||||
|
||||
let fal_result;
|
||||
|
||||
if let Some(start_frame_url) = maybe_start_frame_image_url {
|
||||
@@ -212,7 +215,7 @@ pub async fn kling_3p0_pro_multi_function_video_gen_handler(
|
||||
prompt: request.prompt.as_deref().unwrap_or("").to_string(),
|
||||
image_url: start_frame_url,
|
||||
end_image_url: maybe_end_frame_image_url,
|
||||
generate_audio: request.generate_audio,
|
||||
generate_audio: Some(generate_audio),
|
||||
negative_prompt: request.negative_prompt.clone(),
|
||||
duration: Some(duration),
|
||||
aspect_ratio,
|
||||
@@ -249,7 +252,7 @@ pub async fn kling_3p0_pro_multi_function_video_gen_handler(
|
||||
let args = EnqueueKling3p0ProTextToVideoArgs {
|
||||
prompt: request.prompt.as_deref().unwrap_or("").to_string(),
|
||||
negative_prompt: request.negative_prompt.clone(),
|
||||
generate_audio: request.generate_audio,
|
||||
generate_audio: Some(generate_audio),
|
||||
duration: Some(duration),
|
||||
aspect_ratio: Some(aspect_ratio),
|
||||
shot_type: None,
|
||||
|
||||
+5
-2
@@ -200,6 +200,9 @@ pub async fn kling_3p0_standard_multi_function_video_gen_handler(
|
||||
|
||||
let apriori_job_token = InferenceJobToken::generate();
|
||||
|
||||
// Most people will want audio
|
||||
let generate_audio = request.generate_audio.unwrap_or(true);
|
||||
|
||||
let fal_result;
|
||||
|
||||
if let Some(start_frame_url) = maybe_start_frame_image_url {
|
||||
@@ -212,7 +215,7 @@ pub async fn kling_3p0_standard_multi_function_video_gen_handler(
|
||||
prompt: request.prompt.as_deref().unwrap_or("").to_string(),
|
||||
image_url: start_frame_url,
|
||||
end_image_url: maybe_end_frame_image_url,
|
||||
generate_audio: request.generate_audio,
|
||||
generate_audio: Some(generate_audio),
|
||||
negative_prompt: request.negative_prompt.clone(),
|
||||
duration: Some(duration),
|
||||
aspect_ratio,
|
||||
@@ -249,7 +252,7 @@ pub async fn kling_3p0_standard_multi_function_video_gen_handler(
|
||||
let args = EnqueueKling3p0StandardTextToVideoArgs {
|
||||
prompt: request.prompt.as_deref().unwrap_or("").to_string(),
|
||||
negative_prompt: request.negative_prompt.clone(),
|
||||
generate_audio: request.generate_audio,
|
||||
generate_audio: Some(generate_audio),
|
||||
duration: Some(duration),
|
||||
aspect_ratio: Some(aspect_ratio),
|
||||
shot_type: None,
|
||||
|
||||
+7
@@ -120,6 +120,8 @@ pub async fn seedance_1p5_pro_multi_function_video_gen_handler(
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<Seedance1p5ProMultiFunctionVideoGenResponse>, CommonWebError> {
|
||||
|
||||
info!("Request: {:?}", request);
|
||||
|
||||
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
@@ -204,6 +206,9 @@ pub async fn seedance_1p5_pro_multi_function_video_gen_handler(
|
||||
|
||||
let apriori_job_token = InferenceJobToken::generate();
|
||||
|
||||
// Most people will want audio
|
||||
let generate_audio = request.generate_audio.unwrap_or(true);
|
||||
|
||||
let fal_result;
|
||||
|
||||
if let Some(image_url) = maybe_image_url {
|
||||
@@ -220,6 +225,7 @@ pub async fn seedance_1p5_pro_multi_function_video_gen_handler(
|
||||
duration: Some(duration),
|
||||
aspect_ratio: Some(aspect_ratio),
|
||||
resolution: Some(resolution),
|
||||
generate_audio: Some(generate_audio),
|
||||
webhook_url: &server_state.fal.webhook_url,
|
||||
api_key: &server_state.fal.api_key,
|
||||
};
|
||||
@@ -254,6 +260,7 @@ pub async fn seedance_1p5_pro_multi_function_video_gen_handler(
|
||||
duration: Some(duration),
|
||||
aspect_ratio: Some(aspect_ratio),
|
||||
resolution: Some(resolution),
|
||||
generate_audio: Some(generate_audio),
|
||||
webhook_url: &server_state.fal.webhook_url,
|
||||
api_key: &server_state.fal.api_key,
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ export function useVideoCostEstimate(
|
||||
const inputMode = usePromptVideoStore((s) => s.inputMode);
|
||||
const referenceImages = usePromptVideoStore((s) => s.referenceImages);
|
||||
const endFrameImage = usePromptVideoStore((s) => s.endFrameImage);
|
||||
const generateWithSound = usePromptVideoStore((s) => s.generateWithSound);
|
||||
|
||||
useEffect(() => {
|
||||
if (activePage !== ModelPage.ImageToVideo || !selectedModel) {
|
||||
@@ -65,6 +66,7 @@ export function useVideoCostEstimate(
|
||||
generation_mode: generationMode,
|
||||
aspect_ratio: commonAspectRatio ?? undefined,
|
||||
duration_seconds: duration ?? undefined,
|
||||
generate_audio: generateWithSound,
|
||||
})
|
||||
.then((result) => {
|
||||
if (isEstimateVideoCostSuccess(result)) {
|
||||
@@ -89,6 +91,7 @@ export function useVideoCostEstimate(
|
||||
inputMode,
|
||||
referenceImages.length,
|
||||
!!endFrameImage,
|
||||
generateWithSound,
|
||||
]);
|
||||
|
||||
return { isLoading };
|
||||
|
||||
@@ -144,7 +144,7 @@ export const VIDEO_MODELS: VideoModel[] = [
|
||||
durationOptions: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
defaultDuration: 5,
|
||||
progressBarTime: 100000,
|
||||
generateWithSound: false,
|
||||
generateWithSound: true,
|
||||
}),
|
||||
new VideoModel({
|
||||
id: "kling_3p0_pro",
|
||||
@@ -179,7 +179,7 @@ export const VIDEO_MODELS: VideoModel[] = [
|
||||
durationOptions: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
defaultDuration: 5,
|
||||
progressBarTime: 100000,
|
||||
generateWithSound: false,
|
||||
generateWithSound: true,
|
||||
}),
|
||||
new VideoModel({
|
||||
id: "seedance_1_0_lite",
|
||||
@@ -244,6 +244,7 @@ export const VIDEO_MODELS: VideoModel[] = [
|
||||
resolutionOptions: ["480p", "720p", "1080p"],
|
||||
defaultResolution: "720p",
|
||||
supportsSystemPrompt: false,
|
||||
generateWithSound: true,
|
||||
progressBarTime: 100000,
|
||||
}),
|
||||
new VideoModel({
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface EstimateVideoCostRequest {
|
||||
resolution?: CommonVideoResolution;
|
||||
duration_seconds?: number;
|
||||
video_batch_count?: number;
|
||||
generate_audio?: boolean;
|
||||
}
|
||||
|
||||
export interface EstimateVideoCostPayload {
|
||||
|
||||
Reference in New Issue
Block a user