Kling 3.0

This commit is contained in:
Brandon Thomas committed 2026-03-08 13:13:07 -04:00
1 parent db24eff600
commit 3023ade0cc
52 files changed
+2943 -14

No files matched your search

@@ -0,0 +1,66 @@
use serde::{Deserialize, Serialize};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
pub const KLING_3P0_PRO_MULTI_FUNCTION_VIDEO_PATH: &str = "/v1/generate/video/multi_function/kling_3p0_pro";
/// Both text-to-video and image-to-video in one request.
#[derive(Serialize, Deserialize, ToSchema)]
pub struct Kling3p0ProMultiFunctionVideoGenRequest {
/// Idempotency token to prevent duplicate requests.
pub uuid_idempotency_token: String,
/// Text prompt.
pub prompt: Option<String>,
/// Optional negative prompt.
pub negative_prompt: Option<String>,
/// If present, we're doing image-to-video. If absent, text-to-video.
pub image_media_token: Option<MediaFileToken>,
/// Optional end frame image.
pub end_image_media_token: Option<MediaFileToken>,
/// Duration of the video (3-15 seconds).
pub duration: Option<Kling3p0ProMultiFunctionVideoGenDuration>,
/// Whether to generate audio.
pub generate_audio: Option<bool>,
/// Aspect ratio (only for text-to-video).
pub aspect_ratio: Option<Kling3p0ProMultiFunctionVideoGenAspectRatio>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Kling3p0ProMultiFunctionVideoGenDuration {
ThreeSeconds,
FourSeconds,
FiveSeconds,
SixSeconds,
SevenSeconds,
EightSeconds,
NineSeconds,
TenSeconds,
ElevenSeconds,
TwelveSeconds,
ThirteenSeconds,
FourteenSeconds,
FifteenSeconds,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Kling3p0ProMultiFunctionVideoGenAspectRatio {
Square,
SixteenByNine,
NineBySixteen,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct Kling3p0ProMultiFunctionVideoGenResponse {
pub success: bool,
pub inference_job_token: InferenceJobToken,
}
@@ -0,0 +1,66 @@
use serde::{Deserialize, Serialize};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
pub const KLING_3P0_STANDARD_MULTI_FUNCTION_VIDEO_PATH: &str = "/v1/generate/video/multi_function/kling_3p0_standard";
/// Both text-to-video and image-to-video in one request.
#[derive(Serialize, Deserialize, ToSchema)]
pub struct Kling3p0StandardMultiFunctionVideoGenRequest {
/// Idempotency token to prevent duplicate requests.
pub uuid_idempotency_token: String,
/// Text prompt.
pub prompt: Option<String>,
/// Optional negative prompt.
pub negative_prompt: Option<String>,
/// If present, we're doing image-to-video. If absent, text-to-video.
pub image_media_token: Option<MediaFileToken>,
/// Optional end frame image.
pub end_image_media_token: Option<MediaFileToken>,
/// Duration of the video (3-15 seconds).
pub duration: Option<Kling3p0StandardMultiFunctionVideoGenDuration>,
/// Whether to generate audio.
pub generate_audio: Option<bool>,
/// Aspect ratio (only for text-to-video).
pub aspect_ratio: Option<Kling3p0StandardMultiFunctionVideoGenAspectRatio>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Kling3p0StandardMultiFunctionVideoGenDuration {
ThreeSeconds,
FourSeconds,
FiveSeconds,
SixSeconds,
SevenSeconds,
EightSeconds,
NineSeconds,
TenSeconds,
ElevenSeconds,
TwelveSeconds,
ThirteenSeconds,
FourteenSeconds,
FifteenSeconds,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Kling3p0StandardMultiFunctionVideoGenAspectRatio {
Square,
SixteenByNine,
NineBySixteen,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct Kling3p0StandardMultiFunctionVideoGenResponse {
pub success: bool,
pub inference_job_token: InferenceJobToken,
}
@@ -1,5 +1,7 @@
pub mod kling_2_5_turbo_multi_function_video_gen;
pub mod kling_2_6_multi_function_video_gen;
pub mod kling_3p0_pro_multi_function_video_gen;
pub mod kling_3p0_standard_multi_function_video_gen;
pub mod seedance_1p5_pro_multi_function_video_gen;
pub mod seedance_2p0_multi_function_video_gen;
pub mod sora_2_multi_function_video_gen;
@@ -0,0 +1,18 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::{Kling3p0ProMultiFunctionVideoGenRequest, Kling3p0ProMultiFunctionVideoGenResponse, KLING_3P0_PRO_MULTI_FUNCTION_VIDEO_PATH};
pub async fn kling_3p0_pro_multi_function_video_gen(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: Kling3p0ProMultiFunctionVideoGenRequest,
) -> Result<Kling3p0ProMultiFunctionVideoGenResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
KLING_3P0_PRO_MULTI_FUNCTION_VIDEO_PATH,
maybe_creds,
request,
).await?)
}
@@ -0,0 +1,18 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::{Kling3p0StandardMultiFunctionVideoGenRequest, Kling3p0StandardMultiFunctionVideoGenResponse, KLING_3P0_STANDARD_MULTI_FUNCTION_VIDEO_PATH};
pub async fn kling_3p0_standard_multi_function_video_gen(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: Kling3p0StandardMultiFunctionVideoGenRequest,
) -> Result<Kling3p0StandardMultiFunctionVideoGenResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
KLING_3P0_STANDARD_MULTI_FUNCTION_VIDEO_PATH,
maybe_creds,
request,
).await?)
}
@@ -2,6 +2,8 @@ pub mod kling_2p5_turbo_pro_multi_function_video_gen;
pub mod seedance_1p5_pro_multi_function_video_gen;
pub mod seedance_2p0_multi_function_video_gen;
pub mod kling_2p6_pro_multi_function_video_gen;
pub mod kling_3p0_pro_multi_function_video_gen;
pub mod kling_3p0_standard_multi_function_video_gen;
pub mod sora_2_multi_function_video_gen;
pub mod sora_2_pro_multi_function_video_gen;
pub mod veo_3p1_fast_multi_function_video_gen;
@@ -23,6 +23,12 @@ pub enum CommonVideoModel {
#[serde(rename = "kling_2p6_pro")]
Kling2p6Pro,
#[serde(rename = "kling_3p0_standard")]
Kling3p0Standard,
#[serde(rename = "kling_3p0_pro")]
Kling3p0Pro,
#[serde(rename = "seedance_1p0_lite")]
Seedance10Lite,
@@ -0,0 +1,95 @@
use artcraft_api_defs::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::Kling3p0ProMultiFunctionVideoGenDuration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_kling_v3_pro_text_to_video_webhook::{
EnqueueKlingV3ProTextToVideoArgs,
EnqueueKlingV3ProTextToVideoDuration,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_pro::PlanArtcraftKling3p0Pro;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling3p0_pro(
plan: &PlanArtcraftKling3p0Pro<'_>,
) -> VideoGenerationCostEstimate {
let duration = plan.duration
.map(|d| match d {
Kling3p0ProMultiFunctionVideoGenDuration::ThreeSeconds => EnqueueKlingV3ProTextToVideoDuration::ThreeSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::FourSeconds => EnqueueKlingV3ProTextToVideoDuration::FourSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::FiveSeconds => EnqueueKlingV3ProTextToVideoDuration::FiveSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::SixSeconds => EnqueueKlingV3ProTextToVideoDuration::SixSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::SevenSeconds => EnqueueKlingV3ProTextToVideoDuration::SevenSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::EightSeconds => EnqueueKlingV3ProTextToVideoDuration::EightSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::NineSeconds => EnqueueKlingV3ProTextToVideoDuration::NineSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::TenSeconds => EnqueueKlingV3ProTextToVideoDuration::TenSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::ElevenSeconds => EnqueueKlingV3ProTextToVideoDuration::ElevenSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::TwelveSeconds => EnqueueKlingV3ProTextToVideoDuration::TwelveSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::ThirteenSeconds => EnqueueKlingV3ProTextToVideoDuration::ThirteenSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::FourteenSeconds => EnqueueKlingV3ProTextToVideoDuration::FourteenSeconds,
Kling3p0ProMultiFunctionVideoGenDuration::FifteenSeconds => EnqueueKlingV3ProTextToVideoDuration::FifteenSeconds,
});
// Use text-to-video cost calculator (same pricing for both modes)
let api_key = fal_client::creds::fal_api_key::FalApiKey::from_str("");
let args = EnqueueKlingV3ProTextToVideoArgs {
prompt: String::new(),
generate_audio: None,
negative_prompt: None,
duration,
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
#[cfg(test)]
mod tests {
use crate::api::common_video_model::CommonVideoModel;
use crate::api::provider::Provider;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::generate::generate_video::generate_video_request::GenerateVideoRequest;
fn estimate_usd_cents(duration_seconds: u16) -> u64 {
let request = GenerateVideoRequest {
model: CommonVideoModel::Kling3p0Pro,
provider: Provider::Artcraft,
prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
resolution: None,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
#[test]
fn test_estimate_cost_usd_cents() {
// Kling 3.0 Pro: $0.336/sec (audio on, the default when None)
// Formula: (336 * duration_secs + 9) / 10 → ceiling division
// 5s: (336 * 5 + 9) / 10 = 1689 / 10 = 168
assert_eq!(estimate_usd_cents(5), 168);
// 10s: (336 * 10 + 9) / 10 = 3369 / 10 = 336
assert_eq!(estimate_usd_cents(10), 336);
// 15s: (336 * 15 + 9) / 10 = 5049 / 10 = 504
assert_eq!(estimate_usd_cents(15), 504);
}
}
@@ -0,0 +1,95 @@
use artcraft_api_defs::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::Kling3p0StandardMultiFunctionVideoGenDuration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::text::enqueue_kling_v3_standard_text_to_video_webhook::{
EnqueueKlingV3StandardTextToVideoArgs,
EnqueueKlingV3StandardTextToVideoDuration,
};
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_standard::PlanArtcraftKling3p0Standard;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
pub(crate) fn estimate_video_cost_artcraft_kling3p0_standard(
plan: &PlanArtcraftKling3p0Standard<'_>,
) -> VideoGenerationCostEstimate {
let duration = plan.duration
.map(|d| match d {
Kling3p0StandardMultiFunctionVideoGenDuration::ThreeSeconds => EnqueueKlingV3StandardTextToVideoDuration::ThreeSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::FourSeconds => EnqueueKlingV3StandardTextToVideoDuration::FourSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::FiveSeconds => EnqueueKlingV3StandardTextToVideoDuration::FiveSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::SixSeconds => EnqueueKlingV3StandardTextToVideoDuration::SixSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::SevenSeconds => EnqueueKlingV3StandardTextToVideoDuration::SevenSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::EightSeconds => EnqueueKlingV3StandardTextToVideoDuration::EightSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::NineSeconds => EnqueueKlingV3StandardTextToVideoDuration::NineSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::TenSeconds => EnqueueKlingV3StandardTextToVideoDuration::TenSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::ElevenSeconds => EnqueueKlingV3StandardTextToVideoDuration::ElevenSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::TwelveSeconds => EnqueueKlingV3StandardTextToVideoDuration::TwelveSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::ThirteenSeconds => EnqueueKlingV3StandardTextToVideoDuration::ThirteenSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::FourteenSeconds => EnqueueKlingV3StandardTextToVideoDuration::FourteenSeconds,
Kling3p0StandardMultiFunctionVideoGenDuration::FifteenSeconds => EnqueueKlingV3StandardTextToVideoDuration::FifteenSeconds,
});
// Use text-to-video cost calculator (same pricing for both modes)
let api_key = fal_client::creds::fal_api_key::FalApiKey::from_str("");
let args = EnqueueKlingV3StandardTextToVideoArgs {
prompt: String::new(),
generate_audio: None,
negative_prompt: None,
duration,
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
let cost_in_usd_cents = args.calculate_cost_in_cents();
VideoGenerationCostEstimate {
cost_in_credits: Some(cost_in_usd_cents),
cost_in_usd_cents: Some(cost_in_usd_cents),
is_free: false,
is_unlimited: false,
is_rate_limited: false,
has_watermark: false,
}
}
#[cfg(test)]
mod tests {
use crate::api::common_video_model::CommonVideoModel;
use crate::api::provider::Provider;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::generate::generate_video::generate_video_request::GenerateVideoRequest;
fn estimate_usd_cents(duration_seconds: u16) -> u64 {
let request = GenerateVideoRequest {
model: CommonVideoModel::Kling3p0Standard,
provider: Provider::Artcraft,
prompt: None,
start_frame: None,
end_frame: None,
reference_images: None,
resolution: None,
aspect_ratio: None,
duration_seconds: Some(duration_seconds),
video_batch_count: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
};
request.build()
.expect("build should succeed")
.estimate_costs()
.cost_in_usd_cents
.expect("cost_in_usd_cents should be present")
}
#[test]
fn test_estimate_cost_usd_cents() {
// Kling 3.0 Standard: $0.252/sec (audio on, the default when None)
// Formula: (252 * duration_secs + 9) / 10 → ceiling division
// 5s: (252 * 5 + 9) / 10 = 1269 / 10 = 126
assert_eq!(estimate_usd_cents(5), 126);
// 10s: (252 * 10 + 9) / 10 = 2529 / 10 = 252
assert_eq!(estimate_usd_cents(10), 252);
// 15s: (252 * 15 + 9) / 10 = 3789 / 10 = 378
assert_eq!(estimate_usd_cents(15), 378);
}
}
@@ -1,2 +1,4 @@
pub mod estimate_video_cost_artcraft_kling3p0_pro;
pub mod estimate_video_cost_artcraft_kling3p0_standard;
pub mod estimate_video_cost_artcraft_seedance1p5_pro;
pub mod estimate_video_cost_artcraft_seedance2p0;
@@ -0,0 +1,39 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_pro::PlanArtcraftKling3p0Pro;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use artcraft_api_defs::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::Kling3p0ProMultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::kling_3p0_pro_multi_function_video_gen;
pub async fn execute_artcraft_kling3p0_pro(
plan: &PlanArtcraftKling3p0Pro<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling3p0ProMultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
negative_prompt: None,
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,
aspect_ratio: plan.aspect_ratio,
};
let response = kling_3p0_pro_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -0,0 +1,39 @@
use crate::client::router_artcraft_client::RouterArtcraftClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::provider_error::ProviderError;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_standard::PlanArtcraftKling3p0Standard;
use crate::generate::generate_video::generate_video_response::{
ArtcraftVideoResponsePayload, GenerateVideoResponse,
};
use artcraft_api_defs::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::Kling3p0StandardMultiFunctionVideoGenRequest;
use artcraft_client::endpoints::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::kling_3p0_standard_multi_function_video_gen;
pub async fn execute_artcraft_kling3p0_standard(
plan: &PlanArtcraftKling3p0Standard<'_>,
artcraft_client: &RouterArtcraftClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
let request = Kling3p0StandardMultiFunctionVideoGenRequest {
uuid_idempotency_token: plan.idempotency_token.clone(),
prompt: plan.prompt.map(|p| p.to_string()),
negative_prompt: None,
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,
aspect_ratio: plan.aspect_ratio,
};
let response = kling_3p0_standard_multi_function_video_gen(
&artcraft_client.api_host,
Some(&artcraft_client.credentials),
request,
)
.await
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
let all_tokens = vec![response.inference_job_token.clone()];
Ok(GenerateVideoResponse::Artcraft(ArtcraftVideoResponsePayload {
inference_job_token: response.inference_job_token,
all_inference_job_tokens: all_tokens,
}))
}
@@ -1,2 +1,4 @@
pub mod generate_video_artcraft_kling3p0_pro;
pub mod generate_video_artcraft_kling3p0_standard;
pub mod generate_video_artcraft_seedance1p5_pro;
pub mod generate_video_artcraft_seedance2p0;
@@ -6,6 +6,8 @@ use crate::api::image_ref::ImageRef;
use crate::api::provider::Provider;
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_pro::plan_generate_video_artcraft_kling3p0_pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_standard::plan_generate_video_artcraft_kling3p0_standard;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance1p5_pro::plan_generate_video_artcraft_seedance1p5_pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::plan_generate_video_artcraft_seedance2p0;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
@@ -61,6 +63,12 @@ impl<'a> GenerateVideoRequest<'a> {
pub fn build(&self) -> Result<VideoGenerationPlan<'_>, ArtcraftRouterError> {
match self.provider {
Provider::Artcraft => match self.model {
CommonVideoModel::Kling3p0Pro => {
plan_generate_video_artcraft_kling3p0_pro(self).map(VideoGenerationPlan::ArtcraftKling3p0Pro)
}
CommonVideoModel::Kling3p0Standard => {
plan_generate_video_artcraft_kling3p0_standard(self).map(VideoGenerationPlan::ArtcraftKling3p0Standard)
}
CommonVideoModel::Seedance1p5Pro => {
plan_generate_video_artcraft_seedance1p5_pro(self).map(VideoGenerationPlan::ArtcraftSeedance1p5Pro)
}
@@ -1,2 +1,4 @@
pub mod plan_generate_video_artcraft_kling3p0_pro;
pub mod plan_generate_video_artcraft_kling3p0_standard;
pub mod plan_generate_video_artcraft_seedance1p5_pro;
pub mod plan_generate_video_artcraft_seedance2p0;
@@ -0,0 +1,266 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::image_ref::ImageRef;
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::GenerateVideoRequest;
use artcraft_api_defs::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::{
Kling3p0ProMultiFunctionVideoGenAspectRatio,
Kling3p0ProMultiFunctionVideoGenDuration,
};
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftKling3p0Pro<'a> {
pub 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 idempotency_token: String,
}
pub fn plan_generate_video_artcraft_kling3p0_pro<'a>(
request: &'a GenerateVideoRequest<'a>,
) -> Result<PlanArtcraftKling3p0Pro<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let start_frame = resolve_image_ref(request.start_frame)?;
let end_frame = resolve_image_ref(request.end_frame)?;
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
let duration = plan_duration(request.duration_seconds, strategy)?;
Ok(PlanArtcraftKling3p0Pro {
prompt: request.prompt,
start_frame,
end_frame,
aspect_ratio,
duration,
idempotency_token: request.get_or_generate_idempotency_token(),
})
}
fn resolve_image_ref<'a>(
image_ref: Option<ImageRef<'a>>,
) -> Result<Option<&'a MediaFileToken>, ArtcraftRouterError> {
match image_ref {
None => Ok(None),
Some(ImageRef::MediaFileToken(t)) => Ok(Some(t)),
Some(ImageRef::Url(_)) => {
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
}
}
}
// Kling 3.0 Pro supports: Square (1:1), SixteenByNine (16:9), NineBySixteen (9:16)
fn plan_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Kling3p0ProMultiFunctionVideoGenAspectRatio>, ArtcraftRouterError> {
match aspect_ratio {
None => Ok(None),
// Direct mappings
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => {
Ok(Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::SixteenByNine))
}
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => {
Ok(Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::NineBySixteen))
}
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => {
Ok(Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::Square))
}
// Auto defaults to 16:9
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => {
Ok(Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::SixteenByNine))
}
// Mismatches
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
RequestMismatchMitigationStrategy::PayMoreUpgrade
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
Ok(Some(nearest_aspect_ratio(unsupported)))
}
},
}
}
fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> Kling3p0ProMultiFunctionVideoGenAspectRatio {
match aspect_ratio {
// Wide-ish → 16:9
CommonAspectRatio::WideFourByThree
| CommonAspectRatio::WideFiveByFour
| CommonAspectRatio::WideThreeByTwo
| CommonAspectRatio::WideTwentyOneByNine => Kling3p0ProMultiFunctionVideoGenAspectRatio::SixteenByNine,
// Tall-ish → 9:16
CommonAspectRatio::TallThreeByFour
| CommonAspectRatio::TallFourByFive
| CommonAspectRatio::TallTwoByThree
| CommonAspectRatio::TallNineByTwentyOne => Kling3p0ProMultiFunctionVideoGenAspectRatio::NineBySixteen,
// Fallback
_ => Kling3p0ProMultiFunctionVideoGenAspectRatio::Square,
}
}
// Kling 3.0 Pro supports durations of 3–15 seconds.
fn plan_duration(
duration_seconds: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Kling3p0ProMultiFunctionVideoGenDuration>, ArtcraftRouterError> {
const MIN: u16 = 3;
const MAX: u16 = 15;
match duration_seconds {
None => Ok(None),
Some(d) if d >= MIN && d <= MAX => Ok(Some(seconds_to_duration(d))),
Some(d) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "duration_seconds",
value: format!("{}", d),
}))
}
_ => Ok(Some(seconds_to_duration(d.clamp(MIN, MAX)))),
},
}
}
fn seconds_to_duration(seconds: u16) -> Kling3p0ProMultiFunctionVideoGenDuration {
match seconds {
3 => Kling3p0ProMultiFunctionVideoGenDuration::ThreeSeconds,
4 => Kling3p0ProMultiFunctionVideoGenDuration::FourSeconds,
5 => Kling3p0ProMultiFunctionVideoGenDuration::FiveSeconds,
6 => Kling3p0ProMultiFunctionVideoGenDuration::SixSeconds,
7 => Kling3p0ProMultiFunctionVideoGenDuration::SevenSeconds,
8 => Kling3p0ProMultiFunctionVideoGenDuration::EightSeconds,
9 => Kling3p0ProMultiFunctionVideoGenDuration::NineSeconds,
10 => Kling3p0ProMultiFunctionVideoGenDuration::TenSeconds,
11 => Kling3p0ProMultiFunctionVideoGenDuration::ElevenSeconds,
12 => Kling3p0ProMultiFunctionVideoGenDuration::TwelveSeconds,
13 => Kling3p0ProMultiFunctionVideoGenDuration::ThirteenSeconds,
14 => Kling3p0ProMultiFunctionVideoGenDuration::FourteenSeconds,
_ => Kling3p0ProMultiFunctionVideoGenDuration::FifteenSeconds,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::common_video_model::CommonVideoModel;
use crate::api::image_ref::ImageRef;
use crate::api::provider::Provider;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_video::generate_video_request::GenerateVideoRequest;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
fn base_request() -> GenerateVideoRequest<'static> {
GenerateVideoRequest {
model: CommonVideoModel::Kling3p0Pro,
provider: Provider::Artcraft,
prompt: Some("a cat in space"),
start_frame: None,
end_frame: None,
reference_images: None,
resolution: None,
aspect_ratio: None,
duration_seconds: None,
video_batch_count: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
}
}
#[test]
fn aspect_ratio_direct_16x9() {
let request = GenerateVideoRequest {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Pro(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::SixteenByNine)));
}
#[test]
fn aspect_ratio_direct_9x16() {
let request = GenerateVideoRequest {
aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Pro(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::NineBySixteen)));
}
#[test]
fn aspect_ratio_direct_square() {
let request = GenerateVideoRequest {
aspect_ratio: Some(CommonAspectRatio::Square),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Pro(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::Square)));
}
#[test]
fn aspect_ratio_error_out_on_unsupported() {
let request = GenerateVideoRequest {
aspect_ratio: Some(CommonAspectRatio::WideThreeByTwo),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
));
}
#[test]
fn duration_valid_range() {
for d in 3..=15 {
let request = GenerateVideoRequest {
duration_seconds: Some(d),
..base_request()
};
let result = request.build();
assert!(result.is_ok(), "duration {} should be valid", d);
}
}
#[test]
fn duration_out_of_range_error_out() {
let request = GenerateVideoRequest {
duration_seconds: Some(16),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
));
}
#[test]
fn url_image_ref_returns_error() {
let request = GenerateVideoRequest {
start_frame: Some(ImageRef::Url("https://example.com/image.jpg")),
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
));
}
}
@@ -0,0 +1,266 @@
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::image_ref::ImageRef;
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::GenerateVideoRequest;
use artcraft_api_defs::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::{
Kling3p0StandardMultiFunctionVideoGenAspectRatio,
Kling3p0StandardMultiFunctionVideoGenDuration,
};
use tokens::tokens::media_files::MediaFileToken;
#[derive(Debug, Clone)]
pub struct PlanArtcraftKling3p0Standard<'a> {
pub 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 idempotency_token: String,
}
pub fn plan_generate_video_artcraft_kling3p0_standard<'a>(
request: &'a GenerateVideoRequest<'a>,
) -> Result<PlanArtcraftKling3p0Standard<'a>, ArtcraftRouterError> {
let strategy = request.request_mismatch_mitigation_strategy;
let start_frame = resolve_image_ref(request.start_frame)?;
let end_frame = resolve_image_ref(request.end_frame)?;
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
let duration = plan_duration(request.duration_seconds, strategy)?;
Ok(PlanArtcraftKling3p0Standard {
prompt: request.prompt,
start_frame,
end_frame,
aspect_ratio,
duration,
idempotency_token: request.get_or_generate_idempotency_token(),
})
}
fn resolve_image_ref<'a>(
image_ref: Option<ImageRef<'a>>,
) -> Result<Option<&'a MediaFileToken>, ArtcraftRouterError> {
match image_ref {
None => Ok(None),
Some(ImageRef::MediaFileToken(t)) => Ok(Some(t)),
Some(ImageRef::Url(_)) => {
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
}
}
}
// Kling 3.0 Standard supports: Square (1:1), SixteenByNine (16:9), NineBySixteen (9:16)
fn plan_aspect_ratio(
aspect_ratio: Option<CommonAspectRatio>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Kling3p0StandardMultiFunctionVideoGenAspectRatio>, ArtcraftRouterError> {
match aspect_ratio {
None => Ok(None),
// Direct mappings
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => {
Ok(Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::SixteenByNine))
}
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => {
Ok(Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::NineBySixteen))
}
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => {
Ok(Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::Square))
}
// Auto defaults to 16:9
Some(CommonAspectRatio::Auto)
| Some(CommonAspectRatio::Auto2k)
| Some(CommonAspectRatio::Auto4k) => {
Ok(Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::SixteenByNine))
}
// Mismatches
Some(unsupported) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "aspect_ratio",
value: format!("{:?}", unsupported),
}))
}
RequestMismatchMitigationStrategy::PayMoreUpgrade
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
Ok(Some(nearest_aspect_ratio(unsupported)))
}
},
}
}
fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> Kling3p0StandardMultiFunctionVideoGenAspectRatio {
match aspect_ratio {
// Wide-ish → 16:9
CommonAspectRatio::WideFourByThree
| CommonAspectRatio::WideFiveByFour
| CommonAspectRatio::WideThreeByTwo
| CommonAspectRatio::WideTwentyOneByNine => Kling3p0StandardMultiFunctionVideoGenAspectRatio::SixteenByNine,
// Tall-ish → 9:16
CommonAspectRatio::TallThreeByFour
| CommonAspectRatio::TallFourByFive
| CommonAspectRatio::TallTwoByThree
| CommonAspectRatio::TallNineByTwentyOne => Kling3p0StandardMultiFunctionVideoGenAspectRatio::NineBySixteen,
// Fallback
_ => Kling3p0StandardMultiFunctionVideoGenAspectRatio::Square,
}
}
// Kling 3.0 Standard supports durations of 3–15 seconds.
fn plan_duration(
duration_seconds: Option<u16>,
strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Kling3p0StandardMultiFunctionVideoGenDuration>, ArtcraftRouterError> {
const MIN: u16 = 3;
const MAX: u16 = 15;
match duration_seconds {
None => Ok(None),
Some(d) if d >= MIN && d <= MAX => Ok(Some(seconds_to_duration(d))),
Some(d) => match strategy {
RequestMismatchMitigationStrategy::ErrorOut => {
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
field: "duration_seconds",
value: format!("{}", d),
}))
}
_ => Ok(Some(seconds_to_duration(d.clamp(MIN, MAX)))),
},
}
}
fn seconds_to_duration(seconds: u16) -> Kling3p0StandardMultiFunctionVideoGenDuration {
match seconds {
3 => Kling3p0StandardMultiFunctionVideoGenDuration::ThreeSeconds,
4 => Kling3p0StandardMultiFunctionVideoGenDuration::FourSeconds,
5 => Kling3p0StandardMultiFunctionVideoGenDuration::FiveSeconds,
6 => Kling3p0StandardMultiFunctionVideoGenDuration::SixSeconds,
7 => Kling3p0StandardMultiFunctionVideoGenDuration::SevenSeconds,
8 => Kling3p0StandardMultiFunctionVideoGenDuration::EightSeconds,
9 => Kling3p0StandardMultiFunctionVideoGenDuration::NineSeconds,
10 => Kling3p0StandardMultiFunctionVideoGenDuration::TenSeconds,
11 => Kling3p0StandardMultiFunctionVideoGenDuration::ElevenSeconds,
12 => Kling3p0StandardMultiFunctionVideoGenDuration::TwelveSeconds,
13 => Kling3p0StandardMultiFunctionVideoGenDuration::ThirteenSeconds,
14 => Kling3p0StandardMultiFunctionVideoGenDuration::FourteenSeconds,
_ => Kling3p0StandardMultiFunctionVideoGenDuration::FifteenSeconds,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::common_aspect_ratio::CommonAspectRatio;
use crate::api::common_video_model::CommonVideoModel;
use crate::api::image_ref::ImageRef;
use crate::api::provider::Provider;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::errors::client_error::ClientError;
use crate::generate::generate_video::generate_video_request::GenerateVideoRequest;
use crate::generate::generate_video::video_generation_plan::VideoGenerationPlan;
fn base_request() -> GenerateVideoRequest<'static> {
GenerateVideoRequest {
model: CommonVideoModel::Kling3p0Standard,
provider: Provider::Artcraft,
prompt: Some("a cat in space"),
start_frame: None,
end_frame: None,
reference_images: None,
resolution: None,
aspect_ratio: None,
duration_seconds: None,
video_batch_count: None,
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
idempotency_token: None,
}
}
#[test]
fn aspect_ratio_direct_16x9() {
let request = GenerateVideoRequest {
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Standard(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::SixteenByNine)));
}
#[test]
fn aspect_ratio_direct_9x16() {
let request = GenerateVideoRequest {
aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Standard(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::NineBySixteen)));
}
#[test]
fn aspect_ratio_direct_square() {
let request = GenerateVideoRequest {
aspect_ratio: Some(CommonAspectRatio::Square),
..base_request()
};
let VideoGenerationPlan::ArtcraftKling3p0Standard(plan) = request.build().unwrap() else { panic!("wrong variant") };
assert!(matches!(plan.aspect_ratio, Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::Square)));
}
#[test]
fn aspect_ratio_error_out_on_unsupported() {
let request = GenerateVideoRequest {
aspect_ratio: Some(CommonAspectRatio::WideThreeByTwo),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
));
}
#[test]
fn duration_valid_range() {
for d in 3..=15 {
let request = GenerateVideoRequest {
duration_seconds: Some(d),
..base_request()
};
let result = request.build();
assert!(result.is_ok(), "duration {} should be valid", d);
}
}
#[test]
fn duration_out_of_range_error_out() {
let request = GenerateVideoRequest {
duration_seconds: Some(16),
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
));
}
#[test]
fn url_image_ref_returns_error() {
let request = GenerateVideoRequest {
start_frame: Some(ImageRef::Url("https://example.com/image.jpg")),
..base_request()
};
let result = request.build();
assert!(matches!(
result,
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
));
}
}
@@ -1,16 +1,24 @@
use crate::client::router_client::RouterClient;
use crate::errors::artcraft_router_error::ArtcraftRouterError;
use crate::generate::generate_video::cost::artcraft::estimate_video_cost_artcraft_kling3p0_pro::estimate_video_cost_artcraft_kling3p0_pro;
use crate::generate::generate_video::cost::artcraft::estimate_video_cost_artcraft_kling3p0_standard::estimate_video_cost_artcraft_kling3p0_standard;
use crate::generate::generate_video::cost::artcraft::estimate_video_cost_artcraft_seedance1p5_pro::estimate_video_cost_artcraft_seedance1p5_pro;
use crate::generate::generate_video::cost::artcraft::estimate_video_cost_artcraft_seedance2p0::estimate_video_cost_artcraft_seedance2p0;
use crate::generate::generate_video::execute::artcraft::generate_video_artcraft_kling3p0_pro::execute_artcraft_kling3p0_pro;
use crate::generate::generate_video::execute::artcraft::generate_video_artcraft_kling3p0_standard::execute_artcraft_kling3p0_standard;
use crate::generate::generate_video::execute::artcraft::generate_video_artcraft_seedance1p5_pro::execute_artcraft_seedance1p5_pro;
use crate::generate::generate_video::execute::artcraft::generate_video_artcraft_seedance2p0::execute_artcraft_seedance2p0;
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_pro::PlanArtcraftKling3p0Pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_kling3p0_standard::PlanArtcraftKling3p0Standard;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance1p5_pro::PlanArtcraftSeedance1p5Pro;
use crate::generate::generate_video::plan::artcraft::plan_generate_video_artcraft_seedance2p0::PlanArtcraftSeedance2p0;
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
#[derive(Debug)]
pub enum VideoGenerationPlan<'a> {
ArtcraftKling3p0Pro(PlanArtcraftKling3p0Pro<'a>),
ArtcraftKling3p0Standard(PlanArtcraftKling3p0Standard<'a>),
ArtcraftSeedance1p5Pro(PlanArtcraftSeedance1p5Pro<'a>),
ArtcraftSeedance2p0(PlanArtcraftSeedance2p0<'a>),
}
@@ -21,6 +29,14 @@ impl<'a> VideoGenerationPlan<'a> {
client: &RouterClient,
) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
match self {
VideoGenerationPlan::ArtcraftKling3p0Pro(plan) => {
let artcraft_client = client.get_artcraft_client_ref()?;
execute_artcraft_kling3p0_pro(plan, artcraft_client).await
}
VideoGenerationPlan::ArtcraftKling3p0Standard(plan) => {
let artcraft_client = client.get_artcraft_client_ref()?;
execute_artcraft_kling3p0_standard(plan, artcraft_client).await
}
VideoGenerationPlan::ArtcraftSeedance1p5Pro(plan) => {
let artcraft_client = client.get_artcraft_client_ref()?;
execute_artcraft_seedance1p5_pro(plan, artcraft_client).await
@@ -34,6 +50,12 @@ impl<'a> VideoGenerationPlan<'a> {
pub fn estimate_costs(&self) -> VideoGenerationCostEstimate {
match self {
VideoGenerationPlan::ArtcraftKling3p0Pro(plan) => {
estimate_video_cost_artcraft_kling3p0_pro(plan)
}
VideoGenerationPlan::ArtcraftKling3p0Standard(plan) => {
estimate_video_cost_artcraft_kling3p0_standard(plan)
}
VideoGenerationPlan::ArtcraftSeedance1p5Pro(plan) => {
estimate_video_cost_artcraft_seedance1p5_pro(plan)
}
@@ -0,0 +1,105 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use crate::requests::webhook::video::text::enqueue_kling_v3_pro_text_to_video_webhook::EnqueueKlingV3ProTextToVideoDuration;
use fal::endpoints::fal_ai::kling_video::v3::kling_v3_pro_image_to_video::{kling_v3_pro_image_to_video, KlingV3ProImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueKlingV3ProImageToVideoArgs<'a, R: IntoUrl> {
pub prompt: String,
pub image_url: String,
// Optional args
pub end_image_url: Option<String>,
pub generate_audio: Option<bool>,
pub negative_prompt: Option<String>,
pub duration: Option<EnqueueKlingV3ProTextToVideoDuration>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKlingV3ProImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Same pricing as text-to-video for Kling 3.0 Pro:
// Audio off: $0.224/second
// Audio on: $0.336/second
let generate_audio = self.generate_audio.unwrap_or(true);
let duration_secs = self.duration
.unwrap_or(EnqueueKlingV3ProTextToVideoDuration::FiveSeconds)
.to_seconds();
let rate = if generate_audio { 336u64 } else { 224u64 };
(rate * duration_secs + 9) / 10
}
}
/// Kling 3.0 Pro Image-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v3/pro/image-to-video
pub async fn enqueue_kling_v3_pro_image_to_video_webhook<R: IntoUrl>(
args: EnqueueKlingV3ProImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = args.duration
.map(|d| d.to_str().to_string());
let request = KlingV3ProImageToVideoInput {
prompt: args.prompt,
image_url: args.image_url,
end_image_url: args.end_image_url,
aspect_ratio: None,
generate_audio: args.generate_audio,
duration,
negative_prompt: args.negative_prompt,
cfg_scale: None,
};
let result = kling_v3_pro_image_to_video(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cost_pro_i2v_audio_off_5s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3ProImageToVideoArgs {
prompt: String::new(),
image_url: String::new(),
end_image_url: None,
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKlingV3ProTextToVideoDuration::FiveSeconds),
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.224 * 5 = $1.12 = 112 cents
assert_eq!(args.calculate_cost_in_cents(), 112);
}
#[test]
fn test_cost_pro_i2v_audio_on_10s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3ProImageToVideoArgs {
prompt: String::new(),
image_url: String::new(),
end_image_url: None,
generate_audio: Some(true),
negative_prompt: None,
duration: Some(EnqueueKlingV3ProTextToVideoDuration::TenSeconds),
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.336 * 10 = $3.36 = 336 cents
assert_eq!(args.calculate_cost_in_cents(), 336);
}
}
@@ -0,0 +1,104 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use crate::requests::webhook::video::text::enqueue_kling_v3_standard_text_to_video_webhook::EnqueueKlingV3StandardTextToVideoDuration;
use fal::endpoints::fal_ai::kling_video::v3::kling_v3_standard_image_to_video::{kling_v3_standard_image_to_video, KlingV3StandardImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueKlingV3StandardImageToVideoArgs<'a, R: IntoUrl> {
pub prompt: String,
pub image_url: String,
// Optional args
pub end_image_url: Option<String>,
pub generate_audio: Option<bool>,
pub negative_prompt: Option<String>,
pub duration: Option<EnqueueKlingV3StandardTextToVideoDuration>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKlingV3StandardImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Same pricing as text-to-video for Kling 3.0 Standard:
// Audio off: $0.168/second
// Audio on: $0.252/second
let generate_audio = self.generate_audio.unwrap_or(true);
let duration_secs = self.duration
.unwrap_or(EnqueueKlingV3StandardTextToVideoDuration::FiveSeconds)
.to_seconds();
let rate = if generate_audio { 252u64 } else { 168u64 };
(rate * duration_secs + 9) / 10
}
}
/// Kling 3.0 Standard Image-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v3/standard/image-to-video
pub async fn enqueue_kling_v3_standard_image_to_video_webhook<R: IntoUrl>(
args: EnqueueKlingV3StandardImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = args.duration
.map(|d| d.to_str().to_string());
let request = KlingV3StandardImageToVideoInput {
prompt: args.prompt,
image_url: args.image_url,
end_image_url: args.end_image_url,
aspect_ratio: None, // Not applicable for image-to-video
generate_audio: args.generate_audio,
duration,
negative_prompt: args.negative_prompt,
cfg_scale: None,
};
let result = kling_v3_standard_image_to_video(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cost_standard_i2v_audio_off_5s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3StandardImageToVideoArgs {
prompt: String::new(),
image_url: String::new(),
end_image_url: None,
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKlingV3StandardTextToVideoDuration::FiveSeconds),
webhook_url: "https://example.com",
api_key: &api_key,
};
assert_eq!(args.calculate_cost_in_cents(), 84);
}
#[test]
fn test_cost_standard_i2v_audio_on_10s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3StandardImageToVideoArgs {
prompt: String::new(),
image_url: String::new(),
end_image_url: None,
generate_audio: Some(true),
negative_prompt: None,
duration: Some(EnqueueKlingV3StandardTextToVideoDuration::TenSeconds),
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.252 * 10 = $2.52 = 252 cents
assert_eq!(args.calculate_cost_in_cents(), 252);
}
}
@@ -6,6 +6,8 @@ pub mod enqueue_kling_v2p1_pro_image_to_video_webhook;
pub mod enqueue_kling_v2p5_turbo_pro_image_to_video_webhook;
pub mod enqueue_kling_v2p5_turbo_standard_image_to_video_webhook;
pub mod enqueue_kling_v2p6_pro_image_to_video_webhook;
pub mod enqueue_kling_v3_pro_image_to_video_webhook;
pub mod enqueue_kling_v3_standard_image_to_video_webhook;
pub mod enqueue_pixverse_v5_image_to_video_webhook;
pub mod enqueue_seedance_1_lite_image_to_video_webhook;
pub mod enqueue_seedance_1_pro_image_to_video_webhook;
@@ -0,0 +1,201 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::kling_video::v3::kling_v3_pro_text_to_video::{kling_v3_pro_text_to_video, KlingV3ProTextToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueKlingV3ProTextToVideoArgs<'a, R: IntoUrl> {
pub prompt: String,
// Optional args
pub generate_audio: Option<bool>,
pub negative_prompt: Option<String>,
pub duration: Option<EnqueueKlingV3ProTextToVideoDuration>,
pub aspect_ratio: Option<EnqueueKlingV3ProTextToVideoAspectRatio>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueKlingV3ProTextToVideoDuration {
ThreeSeconds,
FourSeconds,
FiveSeconds,
SixSeconds,
SevenSeconds,
EightSeconds,
NineSeconds,
TenSeconds,
ElevenSeconds,
TwelveSeconds,
ThirteenSeconds,
FourteenSeconds,
FifteenSeconds,
}
impl EnqueueKlingV3ProTextToVideoDuration {
pub fn to_seconds(&self) -> u64 {
match self {
Self::ThreeSeconds => 3,
Self::FourSeconds => 4,
Self::FiveSeconds => 5,
Self::SixSeconds => 6,
Self::SevenSeconds => 7,
Self::EightSeconds => 8,
Self::NineSeconds => 9,
Self::TenSeconds => 10,
Self::ElevenSeconds => 11,
Self::TwelveSeconds => 12,
Self::ThirteenSeconds => 13,
Self::FourteenSeconds => 14,
Self::FifteenSeconds => 15,
}
}
pub fn to_str(&self) -> &'static str {
match self {
Self::ThreeSeconds => "3",
Self::FourSeconds => "4",
Self::FiveSeconds => "5",
Self::SixSeconds => "6",
Self::SevenSeconds => "7",
Self::EightSeconds => "8",
Self::NineSeconds => "9",
Self::TenSeconds => "10",
Self::ElevenSeconds => "11",
Self::TwelveSeconds => "12",
Self::ThirteenSeconds => "13",
Self::FourteenSeconds => "14",
Self::FifteenSeconds => "15",
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueKlingV3ProTextToVideoAspectRatio {
Square,
SixteenByNine,
NineBySixteen,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKlingV3ProTextToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Kling 3.0 Pro pricing:
// Audio off: $0.224/second
// Audio on: $0.336/second
let generate_audio = self.generate_audio.unwrap_or(true);
let duration_secs = self.duration
.unwrap_or(EnqueueKlingV3ProTextToVideoDuration::FiveSeconds)
.to_seconds();
let rate = if generate_audio { 336u64 } else { 224u64 };
(rate * duration_secs + 9) / 10
}
}
/// Kling 3.0 Pro Text-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v3/pro/text-to-video
pub async fn enqueue_kling_v3_pro_text_to_video_webhook<R: IntoUrl>(
args: EnqueueKlingV3ProTextToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = args.duration
.map(|d| d.to_str().to_string());
let aspect_ratio = args.aspect_ratio
.map(|aspect| match aspect {
EnqueueKlingV3ProTextToVideoAspectRatio::Square => "1:1",
EnqueueKlingV3ProTextToVideoAspectRatio::SixteenByNine => "16:9",
EnqueueKlingV3ProTextToVideoAspectRatio::NineBySixteen => "9:16",
})
.map(|s| s.to_string());
let request = KlingV3ProTextToVideoInput {
prompt: args.prompt,
generate_audio: args.generate_audio,
duration,
aspect_ratio,
negative_prompt: args.negative_prompt,
cfg_scale: None,
};
let result = kling_v3_pro_text_to_video(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cost_pro_audio_off_5s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3ProTextToVideoArgs {
prompt: String::new(),
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKlingV3ProTextToVideoDuration::FiveSeconds),
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.224 * 5 = $1.12 = 112 cents
assert_eq!(args.calculate_cost_in_cents(), 112);
}
#[test]
fn test_cost_pro_audio_on_5s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3ProTextToVideoArgs {
prompt: String::new(),
generate_audio: Some(true),
negative_prompt: None,
duration: Some(EnqueueKlingV3ProTextToVideoDuration::FiveSeconds),
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.336 * 5 = $1.68 = 168 cents
assert_eq!(args.calculate_cost_in_cents(), 168);
}
#[test]
fn test_cost_pro_audio_off_10s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3ProTextToVideoArgs {
prompt: String::new(),
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKlingV3ProTextToVideoDuration::TenSeconds),
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.224 * 10 = $2.24 = 224 cents
assert_eq!(args.calculate_cost_in_cents(), 224);
}
#[test]
fn test_cost_pro_audio_off_15s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3ProTextToVideoArgs {
prompt: String::new(),
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKlingV3ProTextToVideoDuration::FifteenSeconds),
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.224 * 15 = $3.36 = 336 cents
assert_eq!(args.calculate_cost_in_cents(), 336);
}
}
@@ -0,0 +1,218 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::kling_video::v3::kling_v3_standard_text_to_video::{kling_v3_standard_text_to_video, KlingV3StandardTextToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueKlingV3StandardTextToVideoArgs<'a, R: IntoUrl> {
pub prompt: String,
// Optional args
pub generate_audio: Option<bool>,
pub negative_prompt: Option<String>,
pub duration: Option<EnqueueKlingV3StandardTextToVideoDuration>,
pub aspect_ratio: Option<EnqueueKlingV3StandardTextToVideoAspectRatio>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueKlingV3StandardTextToVideoDuration {
ThreeSeconds,
FourSeconds,
FiveSeconds,
SixSeconds,
SevenSeconds,
EightSeconds,
NineSeconds,
TenSeconds,
ElevenSeconds,
TwelveSeconds,
ThirteenSeconds,
FourteenSeconds,
FifteenSeconds,
}
impl EnqueueKlingV3StandardTextToVideoDuration {
pub fn to_seconds(&self) -> u64 {
match self {
Self::ThreeSeconds => 3,
Self::FourSeconds => 4,
Self::FiveSeconds => 5,
Self::SixSeconds => 6,
Self::SevenSeconds => 7,
Self::EightSeconds => 8,
Self::NineSeconds => 9,
Self::TenSeconds => 10,
Self::ElevenSeconds => 11,
Self::TwelveSeconds => 12,
Self::ThirteenSeconds => 13,
Self::FourteenSeconds => 14,
Self::FifteenSeconds => 15,
}
}
pub fn to_str(&self) -> &'static str {
match self {
Self::ThreeSeconds => "3",
Self::FourSeconds => "4",
Self::FiveSeconds => "5",
Self::SixSeconds => "6",
Self::SevenSeconds => "7",
Self::EightSeconds => "8",
Self::NineSeconds => "9",
Self::TenSeconds => "10",
Self::ElevenSeconds => "11",
Self::TwelveSeconds => "12",
Self::ThirteenSeconds => "13",
Self::FourteenSeconds => "14",
Self::FifteenSeconds => "15",
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueKlingV3StandardTextToVideoAspectRatio {
Square,
SixteenByNine,
NineBySixteen,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKlingV3StandardTextToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Kling 3.0 Standard pricing:
// Audio off: $0.168/second
// Audio on: $0.252/second
let generate_audio = self.generate_audio.unwrap_or(true);
let duration_secs = self.duration
.unwrap_or(EnqueueKlingV3StandardTextToVideoDuration::FiveSeconds)
.to_seconds();
// Rate in tenths-of-cents per second
let rate = if generate_audio { 252u64 } else { 168u64 };
(rate * duration_secs + 9) / 10
}
}
/// Kling 3.0 Standard Text-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v3/standard/text-to-video
pub async fn enqueue_kling_v3_standard_text_to_video_webhook<R: IntoUrl>(
args: EnqueueKlingV3StandardTextToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = args.duration
.map(|d| d.to_str().to_string());
let aspect_ratio = args.aspect_ratio
.map(|aspect| match aspect {
EnqueueKlingV3StandardTextToVideoAspectRatio::Square => "1:1",
EnqueueKlingV3StandardTextToVideoAspectRatio::SixteenByNine => "16:9",
EnqueueKlingV3StandardTextToVideoAspectRatio::NineBySixteen => "9:16",
})
.map(|s| s.to_string());
let request = KlingV3StandardTextToVideoInput {
prompt: args.prompt,
generate_audio: args.generate_audio,
duration,
aspect_ratio,
negative_prompt: args.negative_prompt,
cfg_scale: None,
};
let result = kling_v3_standard_text_to_video(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cost_standard_audio_off() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3StandardTextToVideoArgs {
prompt: String::new(),
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKlingV3StandardTextToVideoDuration::FiveSeconds),
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.168 * 5 = $0.84 = 84 cents
assert_eq!(args.calculate_cost_in_cents(), 84);
}
#[test]
fn test_cost_standard_audio_on() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3StandardTextToVideoArgs {
prompt: String::new(),
generate_audio: Some(true),
negative_prompt: None,
duration: Some(EnqueueKlingV3StandardTextToVideoDuration::FiveSeconds),
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.252 * 5 = $1.26 = 126 cents
assert_eq!(args.calculate_cost_in_cents(), 126);
}
#[test]
fn test_cost_standard_audio_off_10s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3StandardTextToVideoArgs {
prompt: String::new(),
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKlingV3StandardTextToVideoDuration::TenSeconds),
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.168 * 10 = $1.68 = 168 cents
assert_eq!(args.calculate_cost_in_cents(), 168);
}
#[test]
fn test_cost_standard_audio_off_3s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3StandardTextToVideoArgs {
prompt: String::new(),
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKlingV3StandardTextToVideoDuration::ThreeSeconds),
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.168 * 3 = $0.504 = ceil(50.4) = 51 cents
assert_eq!(args.calculate_cost_in_cents(), 51);
}
#[test]
fn test_cost_standard_audio_off_15s() {
let api_key = FalApiKey::from_str("");
let args = EnqueueKlingV3StandardTextToVideoArgs {
prompt: String::new(),
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKlingV3StandardTextToVideoDuration::FifteenSeconds),
aspect_ratio: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
// $0.168 * 15 = $2.52 = 252 cents
assert_eq!(args.calculate_cost_in_cents(), 252);
}
}
@@ -1,6 +1,8 @@
pub mod enqueue_hailuo_v2p3_pro_text_to_video_webhook;
pub mod enqueue_kling_v2p5_turbo_pro_text_to_video_webhook;
pub mod enqueue_kling_v2p6_pro_text_to_video_webhook;
pub mod enqueue_kling_v3_pro_text_to_video_webhook;
pub mod enqueue_kling_v3_standard_text_to_video_webhook;
pub mod enqueue_pixverse_v5_text_to_video_webhook;
pub mod enqueue_seedance_1p5_pro_text_to_video_webhook;
pub mod enqueue_sora_2_pro_text_to_video_webhook;
@@ -0,0 +1,71 @@
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_kling_3p0_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::Kling3p0Pro,
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::Kling3p0Pro),
provider: GenerationProvider::Artcraft,
provider_job_id: Some(job_id),
})
}
@@ -0,0 +1,71 @@
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_kling_3p0_standard(
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::Kling3p0Standard,
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::Kling3p0Standard),
provider: GenerationProvider::Artcraft,
provider_job_id: Some(job_id),
})
}
@@ -5,6 +5,8 @@ use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kl
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kling_2p1_pro::handle_artcraft_kling_2p1_pro;
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kling_2p5_turbo_pro::handle_artcraft_kling_2p5_turbo_pro;
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kling_2p6_pro::handle_artcraft_kling_2p6_pro;
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kling_3p0_pro::handle_artcraft_kling_3p0_pro;
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kling_3p0_standard::handle_artcraft_kling_3p0_standard;
use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_seedance_1_lite::handle_artcraft_seedance_1_lite;
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;
@@ -40,6 +42,8 @@ pub async fn handle_video_artcraft(
Some(VideoModel::Kling21Master) => handle_artcraft_kling_2p1_master(request, app_env_configs, &creds).await,
Some(VideoModel::Kling2p5TurboPro) => handle_artcraft_kling_2p5_turbo_pro(request, app_env_configs, &creds).await,
Some(VideoModel::Kling2p6Pro) => handle_artcraft_kling_2p6_pro(request, app_env_configs, &creds).await,
Some(VideoModel::Kling3p0Standard) => handle_artcraft_kling_3p0_standard(request, app_env_configs, &creds).await,
Some(VideoModel::Kling3p0Pro) => handle_artcraft_kling_3p0_pro(request, app_env_configs, &creds).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::Seedance2p0) => handle_artcraft_seedance_2p0(request, app_env_configs, &creds).await,
@@ -4,6 +4,8 @@ mod handle_artcraft_kling_2p1_master;
mod handle_artcraft_kling_2p1_pro;
mod handle_artcraft_kling_2p5_turbo_pro;
mod handle_artcraft_kling_2p6_pro;
mod handle_artcraft_kling_3p0_pro;
mod handle_artcraft_kling_3p0_standard;
mod handle_artcraft_seedance_1_lite;
mod handle_artcraft_seedance_1p5_pro;
mod handle_artcraft_seedance_2p0;
@@ -54,6 +54,12 @@ pub enum VideoModel {
#[serde(rename = "kling_2p6_pro")]
Kling2p6Pro,
#[serde(rename = "kling_3p0_standard")]
Kling3p0Standard,
#[serde(rename = "kling_3p0_pro")]
Kling3p0Pro,
#[serde(rename = "seedance_1.0_lite")]
Seedance10Lite,
@@ -83,6 +83,8 @@ impl TaskEnqueueSuccess{
Some(GenerationModel::Kling21Master) => Some(TaskModelType::Kling21Master),
Some(GenerationModel::Kling2p5TurboPro) => Some(TaskModelType::Kling2p5TurboPro),
Some(GenerationModel::Kling2p6Pro) => Some(TaskModelType::Kling2p6Pro),
Some(GenerationModel::Kling3p0Standard) => Some(TaskModelType::Kling3p0Standard),
Some(GenerationModel::Kling3p0Pro) => Some(TaskModelType::Kling3p0Pro),
Some(GenerationModel::Seedance10Lite) => Some(TaskModelType::Seedance10Lite),
Some(GenerationModel::Seedance1p5Pro) => Some(TaskModelType::Seedance1p5Pro),
Some(GenerationModel::Seedance2p0) => Some(TaskModelType::Seedance2p0),
@@ -80,6 +80,10 @@ pub enum GenerationModel {
Kling2p5TurboPro,
#[serde(rename = "kling_2p6_pro")]
Kling2p6Pro,
#[serde(rename = "kling_3p0_standard")]
Kling3p0Standard,
#[serde(rename = "kling_3p0_pro")]
Kling3p0Pro,
#[serde(rename = "recraft_3")]
Recraft3,
#[serde(rename = "sora")]
@@ -23,6 +23,12 @@ pub enum CommonVideoModel {
#[serde(rename = "kling_2p6_pro")]
Kling2p6Pro,
#[serde(rename = "kling_3p0_standard")]
Kling3p0Standard,
#[serde(rename = "kling_3p0_pro")]
Kling3p0Pro,
#[serde(rename = "seedance_1p0_lite")]
Seedance10Lite,
@@ -93,6 +93,10 @@ pub enum ModelType {
Kling2p5TurboPro,
#[serde(rename = "kling_2p6_pro")]
Kling2p6Pro,
#[serde(rename = "kling_3p0_standard")]
Kling3p0Standard,
#[serde(rename = "kling_3p0_pro")]
Kling3p0Pro,
#[serde(rename = "seedance_1p0_lite")]
Seedance10Lite,
#[serde(rename = "seedance_1p0_pro")]
@@ -171,6 +175,8 @@ impl ModelType {
Self::Kling21Master => "kling_2p1_master",
Self::Kling2p5TurboPro => "kling_2p5_turbo_pro",
Self::Kling2p6Pro => "kling_2p6_pro",
Self::Kling3p0Standard => "kling_3p0_standard",
Self::Kling3p0Pro => "kling_3p0_pro",
Self::Seedance10Lite => "seedance_1p0_lite",
Self::Seedance10Pro => "seedance_1p0_pro",
Self::Seedance1p5Pro => "seedance_1p5_pro",
@@ -229,6 +235,8 @@ impl ModelType {
"kling_2p1_master" => Ok(Self::Kling21Master),
"kling_2p5_turbo_pro" => Ok(Self::Kling2p5TurboPro),
"kling_2p6_pro" => Ok(Self::Kling2p6Pro),
"kling_3p0_standard" => Ok(Self::Kling3p0Standard),
"kling_3p0_pro" => Ok(Self::Kling3p0Pro),
"seedance_1p0_lite" => Ok(Self::Seedance10Lite),
"seedance_1p0_pro" => Ok(Self::Seedance10Pro),
"seedance_1p5_pro" => Ok(Self::Seedance1p5Pro),
@@ -291,6 +299,8 @@ impl ModelType {
Self::Kling21Master,
Self::Kling2p5TurboPro,
Self::Kling2p6Pro,
Self::Kling3p0Standard,
Self::Kling3p0Pro,
Self::Seedance10Lite,
Self::Seedance10Pro,
Self::Seedance1p5Pro,
@@ -357,6 +367,8 @@ mod tests {
assert_serialization(ModelType::Kling21Master, "kling_2p1_master");
assert_serialization(ModelType::Kling2p5TurboPro, "kling_2p5_turbo_pro");
assert_serialization(ModelType::Kling2p6Pro, "kling_2p6_pro");
assert_serialization(ModelType::Kling3p0Standard, "kling_3p0_standard");
assert_serialization(ModelType::Kling3p0Pro, "kling_3p0_pro");
assert_serialization(ModelType::Seedance10Lite, "seedance_1p0_lite");
assert_serialization(ModelType::Seedance10Pro, "seedance_1p0_pro");
assert_serialization(ModelType::Seedance1p5Pro, "seedance_1p5_pro");
@@ -413,6 +425,8 @@ mod tests {
assert_eq!(ModelType::Kling21Master.to_str(), "kling_2p1_master");
assert_eq!(ModelType::Kling2p5TurboPro.to_str(), "kling_2p5_turbo_pro");
assert_eq!(ModelType::Kling2p6Pro.to_str(), "kling_2p6_pro");
assert_eq!(ModelType::Kling3p0Standard.to_str(), "kling_3p0_standard");
assert_eq!(ModelType::Kling3p0Pro.to_str(), "kling_3p0_pro");
assert_eq!(ModelType::Seedance10Lite.to_str(), "seedance_1p0_lite");
assert_eq!(ModelType::Seedance10Pro.to_str(), "seedance_1p0_pro");
assert_eq!(ModelType::Seedance1p5Pro.to_str(), "seedance_1p5_pro");
@@ -469,6 +483,8 @@ mod tests {
assert_eq!(ModelType::from_str("kling_2p1_master").unwrap(), ModelType::Kling21Master);
assert_eq!(ModelType::from_str("kling_2p5_turbo_pro").unwrap(), ModelType::Kling2p5TurboPro);
assert_eq!(ModelType::from_str("kling_2p6_pro").unwrap(), ModelType::Kling2p6Pro);
assert_eq!(ModelType::from_str("kling_3p0_standard").unwrap(), ModelType::Kling3p0Standard);
assert_eq!(ModelType::from_str("kling_3p0_pro").unwrap(), ModelType::Kling3p0Pro);
assert_eq!(ModelType::from_str("seedance_1p0_lite").unwrap(), ModelType::Seedance10Lite);
assert_eq!(ModelType::from_str("seedance_1p0_pro").unwrap(), ModelType::Seedance10Pro);
assert_eq!(ModelType::from_str("seedance_1p5_pro").unwrap(), ModelType::Seedance1p5Pro);
@@ -490,7 +506,7 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = ModelType::all_variants();
assert_eq!(variants.len(), 48);
assert_eq!(variants.len(), 50);
// Image models
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Dev));
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Schnell));
@@ -527,6 +543,8 @@ mod tests {
assert_eq!(variants.pop_first(), Some(ModelType::Kling21Master));
assert_eq!(variants.pop_first(), Some(ModelType::Kling2p5TurboPro));
assert_eq!(variants.pop_first(), Some(ModelType::Kling2p6Pro));
assert_eq!(variants.pop_first(), Some(ModelType::Kling3p0Standard));
assert_eq!(variants.pop_first(), Some(ModelType::Kling3p0Pro));
assert_eq!(variants.pop_first(), Some(ModelType::Seedance10Lite));
assert_eq!(variants.pop_first(), Some(ModelType::Seedance10Pro));
assert_eq!(variants.pop_first(), Some(ModelType::Seedance1p5Pro));
@@ -67,6 +67,10 @@ pub enum TaskModelType {
Kling2p5TurboPro,
#[serde(rename = "kling_2p6_pro")]
Kling2p6Pro,
#[serde(rename = "kling_3p0_standard")]
Kling3p0Standard,
#[serde(rename = "kling_3p0_pro")]
Kling3p0Pro,
#[serde(rename = "seedance_1.0_lite")]
Seedance10Lite,
#[serde(rename = "seedance_1p5_pro")]
@@ -135,6 +139,8 @@ impl TaskModelType {
Self::Kling21Master => "kling_2.1_master",
Self::Kling2p5TurboPro => "kling_2p5_turbo_pro",
Self::Kling2p6Pro => "kling_2p6_pro",
Self::Kling3p0Standard => "kling_3p0_standard",
Self::Kling3p0Pro => "kling_3p0_pro",
Self::Seedance10Lite => "seedance_1.0_lite",
Self::Seedance1p5Pro => "seedance_1p5_pro",
Self::Seedance2p0 => "seedance_2p0",
@@ -182,6 +188,8 @@ impl TaskModelType {
"kling_2.1_master" => Ok(Self::Kling21Master),
"kling_2p5_turbo_pro" => Ok(Self::Kling2p5TurboPro),
"kling_2p6_pro" => Ok(Self::Kling2p6Pro),
"kling_3p0_standard" => Ok(Self::Kling3p0Standard),
"kling_3p0_pro" => Ok(Self::Kling3p0Pro),
"seedance_1.0_lite" => Ok(Self::Seedance10Lite),
"seedance_1p5_pro" => Ok(Self::Seedance1p5Pro),
"seedance_2p0" => Ok(Self::Seedance2p0),
@@ -232,6 +240,8 @@ impl TaskModelType {
Self::Kling21Master,
Self::Kling2p5TurboPro,
Self::Kling2p6Pro,
Self::Kling3p0Standard,
Self::Kling3p0Pro,
Self::Seedance10Lite,
Self::Seedance1p5Pro,
Self::Seedance2p0,
@@ -289,6 +299,8 @@ mod tests {
assert_serialization(TaskModelType::Kling21Master, "kling_2.1_master");
assert_serialization(TaskModelType::Kling2p5TurboPro, "kling_2p5_turbo_pro");
assert_serialization(TaskModelType::Kling2p6Pro, "kling_2p6_pro");
assert_serialization(TaskModelType::Kling3p0Standard, "kling_3p0_standard");
assert_serialization(TaskModelType::Kling3p0Pro, "kling_3p0_pro");
assert_serialization(TaskModelType::Seedance10Lite, "seedance_1.0_lite");
assert_serialization(TaskModelType::Seedance1p5Pro, "seedance_1p5_pro");
assert_serialization(TaskModelType::Seedance2p0, "seedance_2p0");
@@ -335,6 +347,8 @@ mod tests {
assert_eq!(TaskModelType::Kling21Master.to_str(), "kling_2.1_master");
assert_eq!(TaskModelType::Kling2p5TurboPro.to_str(), "kling_2p5_turbo_pro");
assert_eq!(TaskModelType::Kling2p6Pro.to_str(), "kling_2p6_pro");
assert_eq!(TaskModelType::Kling3p0Standard.to_str(), "kling_3p0_standard");
assert_eq!(TaskModelType::Kling3p0Pro.to_str(), "kling_3p0_pro");
assert_eq!(TaskModelType::Seedance10Lite.to_str(), "seedance_1.0_lite");
assert_eq!(TaskModelType::Seedance1p5Pro.to_str(), "seedance_1p5_pro");
assert_eq!(TaskModelType::Seedance2p0.to_str(), "seedance_2p0");
@@ -381,6 +395,8 @@ mod tests {
assert_eq!(TaskModelType::from_str("kling_2.1_master").unwrap(), TaskModelType::Kling21Master);
assert_eq!(TaskModelType::from_str("kling_2p5_turbo_pro").unwrap(), TaskModelType::Kling2p5TurboPro);
assert_eq!(TaskModelType::from_str("kling_2p6_pro").unwrap(), TaskModelType::Kling2p6Pro);
assert_eq!(TaskModelType::from_str("kling_3p0_standard").unwrap(), TaskModelType::Kling3p0Standard);
assert_eq!(TaskModelType::from_str("kling_3p0_pro").unwrap(), TaskModelType::Kling3p0Pro);
assert_eq!(TaskModelType::from_str("seedance_1.0_lite").unwrap(), TaskModelType::Seedance10Lite);
assert_eq!(TaskModelType::from_str("seedance_1p5_pro").unwrap(), TaskModelType::Seedance1p5Pro);
assert_eq!(TaskModelType::from_str("seedance_2p0").unwrap(), TaskModelType::Seedance2p0);
@@ -412,7 +428,7 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = TaskModelType::all_variants();
assert_eq!(variants.len(), 39);
assert_eq!(variants.len(), 41);
// Image models
assert_eq!(variants.pop_first(), Some(TaskModelType::Flux1Dev));
assert_eq!(variants.pop_first(), Some(TaskModelType::Flux1Schnell));
@@ -440,6 +456,8 @@ mod tests {
assert_eq!(variants.pop_first(), Some(TaskModelType::Kling21Master));
assert_eq!(variants.pop_first(), Some(TaskModelType::Kling2p5TurboPro));
assert_eq!(variants.pop_first(), Some(TaskModelType::Kling2p6Pro));
assert_eq!(variants.pop_first(), Some(TaskModelType::Kling3p0Standard));
assert_eq!(variants.pop_first(), Some(TaskModelType::Kling3p0Pro));
assert_eq!(variants.pop_first(), Some(TaskModelType::Seedance10Lite));
assert_eq!(variants.pop_first(), Some(TaskModelType::Seedance1p5Pro));
assert_eq!(variants.pop_first(), Some(TaskModelType::Seedance2p0));
@@ -251,6 +251,8 @@ use artcraft_api_defs::generate::video::generate_veo_3_fast_image_to_video::*;
// Video multi-function
use artcraft_api_defs::generate::video::multi_function::kling_2_5_turbo_multi_function_video_gen::*;
use artcraft_api_defs::generate::video::multi_function::kling_2_6_multi_function_video_gen::*;
use artcraft_api_defs::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::*;
use artcraft_api_defs::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::*;
use artcraft_api_defs::generate::video::multi_function::seedance_2p0_multi_function_video_gen::*;
use artcraft_api_defs::generate::video::multi_function::seedance_1p5_pro_multi_function_video_gen::*;
use artcraft_api_defs::generate::video::multi_function::sora_2_multi_function_video_gen::*;
@@ -427,6 +429,8 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
// Generate Video (Multi-Function)
crate::http_server::endpoints::generate::video::multi_function::kling_2p5_turbo_pro_multi_function_video_gen_handler::kling_2p5_turbo_pro_multi_function_video_gen_handler,
crate::http_server::endpoints::generate::video::multi_function::kling_2p6_pro_multi_function_video_gen_handler::kling_2p6_pro_multi_function_video_gen_handler,
crate::http_server::endpoints::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen_handler::kling_3p0_pro_multi_function_video_gen_handler,
crate::http_server::endpoints::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen_handler::kling_3p0_standard_multi_function_video_gen_handler,
crate::http_server::endpoints::generate::video::multi_function::seedance_1p5_pro_multi_function_video_gen_handler::seedance_1p5_pro_multi_function_video_gen_handler,
crate::http_server::endpoints::generate::video::multi_function::seedance_2p0_multi_function_video_gen_handler::seedance_2p0_multi_function_video_gen_handler,
crate::http_server::endpoints::generate::video::multi_function::sora_2_multi_function_video_gen_handler::sora_2_multi_function_video_gen_handler,
@@ -1025,6 +1029,14 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
Kling2p5TurboProMultiFunctionVideoGenResponse,
Kling2p6ProMultiFunctionVideoGenRequest,
Kling2p6ProMultiFunctionVideoGenResponse,
Kling3p0ProMultiFunctionVideoGenRequest,
Kling3p0ProMultiFunctionVideoGenResponse,
Kling3p0ProMultiFunctionVideoGenDuration,
Kling3p0ProMultiFunctionVideoGenAspectRatio,
Kling3p0StandardMultiFunctionVideoGenRequest,
Kling3p0StandardMultiFunctionVideoGenResponse,
Kling3p0StandardMultiFunctionVideoGenDuration,
Kling3p0StandardMultiFunctionVideoGenAspectRatio,
Seedance2p0MultiFunctionVideoGenRequest,
Seedance2p0MultiFunctionVideoGenResponse,
Sora2MultiFunctionVideoGenRequest,
@@ -130,6 +130,8 @@ fn map_video_model(model: CommonVideoModel) -> Result<RouterVideoModel, HandlerE
CommonVideoModel::Kling21Master => RouterVideoModel::Kling21Master,
CommonVideoModel::Kling2p5TurboPro => RouterVideoModel::Kling2p5TurboPro,
CommonVideoModel::Kling2p6Pro => RouterVideoModel::Kling2p6Pro,
CommonVideoModel::Kling3p0Standard => RouterVideoModel::Kling3p0Standard,
CommonVideoModel::Kling3p0Pro => RouterVideoModel::Kling3p0Pro,
CommonVideoModel::Seedance10Lite => RouterVideoModel::Seedance10Lite,
CommonVideoModel::Seedance1p5Pro => RouterVideoModel::Seedance1p5Pro,
CommonVideoModel::Seedance2p0 => RouterVideoModel::Seedance2p0,
@@ -0,0 +1,377 @@
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
use crate::util::lookup::lookup_image_urls_as_map::lookup_image_urls_as_map;
use actix_web::web::Json;
use actix_web::{web, HttpRequest};
use artcraft_api_defs::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen::{Kling3p0ProMultiFunctionVideoGenAspectRatio, Kling3p0ProMultiFunctionVideoGenDuration, Kling3p0ProMultiFunctionVideoGenRequest, Kling3p0ProMultiFunctionVideoGenResponse};
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
use enums::by_table::prompts::prompt_type::PromptType;
use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_kling_v3_pro_image_to_video_webhook::{enqueue_kling_v3_pro_image_to_video_webhook, EnqueueKlingV3ProImageToVideoArgs};
use fal_client::requests::webhook::video::text::enqueue_kling_v3_pro_text_to_video_webhook::{enqueue_kling_v3_pro_text_to_video_webhook, EnqueueKlingV3ProTextToVideoArgs, EnqueueKlingV3ProTextToVideoAspectRatio, EnqueueKlingV3ProTextToVideoDuration};
use http_server_common::request::get_request_ip::get_request_ip;
use log::{error, info, warn};
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::InsertGenericInferenceForFalArgs;
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use mysql_queries::queries::wallets::spend::wallet_spend_error::WalletSpendError;
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
fn map_duration_i2v(duration: Option<Kling3p0ProMultiFunctionVideoGenDuration>) -> EnqueueKlingV3ProTextToVideoDuration {
match duration {
Some(Kling3p0ProMultiFunctionVideoGenDuration::ThreeSeconds) => EnqueueKlingV3ProTextToVideoDuration::ThreeSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FourSeconds) => EnqueueKlingV3ProTextToVideoDuration::FourSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FiveSeconds) => EnqueueKlingV3ProTextToVideoDuration::FiveSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::SixSeconds) => EnqueueKlingV3ProTextToVideoDuration::SixSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::SevenSeconds) => EnqueueKlingV3ProTextToVideoDuration::SevenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::EightSeconds) => EnqueueKlingV3ProTextToVideoDuration::EightSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::NineSeconds) => EnqueueKlingV3ProTextToVideoDuration::NineSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::TenSeconds) => EnqueueKlingV3ProTextToVideoDuration::TenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::ElevenSeconds) => EnqueueKlingV3ProTextToVideoDuration::ElevenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::TwelveSeconds) => EnqueueKlingV3ProTextToVideoDuration::TwelveSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::ThirteenSeconds) => EnqueueKlingV3ProTextToVideoDuration::ThirteenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FourteenSeconds) => EnqueueKlingV3ProTextToVideoDuration::FourteenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FifteenSeconds) => EnqueueKlingV3ProTextToVideoDuration::FifteenSeconds,
None => EnqueueKlingV3ProTextToVideoDuration::FiveSeconds,
}
}
fn map_duration_t2v(duration: Option<Kling3p0ProMultiFunctionVideoGenDuration>) -> EnqueueKlingV3ProTextToVideoDuration {
match duration {
Some(Kling3p0ProMultiFunctionVideoGenDuration::ThreeSeconds) => EnqueueKlingV3ProTextToVideoDuration::ThreeSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FourSeconds) => EnqueueKlingV3ProTextToVideoDuration::FourSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FiveSeconds) => EnqueueKlingV3ProTextToVideoDuration::FiveSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::SixSeconds) => EnqueueKlingV3ProTextToVideoDuration::SixSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::SevenSeconds) => EnqueueKlingV3ProTextToVideoDuration::SevenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::EightSeconds) => EnqueueKlingV3ProTextToVideoDuration::EightSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::NineSeconds) => EnqueueKlingV3ProTextToVideoDuration::NineSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::TenSeconds) => EnqueueKlingV3ProTextToVideoDuration::TenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::ElevenSeconds) => EnqueueKlingV3ProTextToVideoDuration::ElevenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::TwelveSeconds) => EnqueueKlingV3ProTextToVideoDuration::TwelveSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::ThirteenSeconds) => EnqueueKlingV3ProTextToVideoDuration::ThirteenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FourteenSeconds) => EnqueueKlingV3ProTextToVideoDuration::FourteenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FifteenSeconds) => EnqueueKlingV3ProTextToVideoDuration::FifteenSeconds,
None => EnqueueKlingV3ProTextToVideoDuration::FiveSeconds,
}
}
fn map_aspect_ratio_t2v(aspect_ratio: Option<Kling3p0ProMultiFunctionVideoGenAspectRatio>) -> EnqueueKlingV3ProTextToVideoAspectRatio {
match aspect_ratio {
Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::Square) => EnqueueKlingV3ProTextToVideoAspectRatio::Square,
Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::SixteenByNine) => EnqueueKlingV3ProTextToVideoAspectRatio::SixteenByNine,
Some(Kling3p0ProMultiFunctionVideoGenAspectRatio::NineBySixteen) => EnqueueKlingV3ProTextToVideoAspectRatio::NineBySixteen,
None => EnqueueKlingV3ProTextToVideoAspectRatio::Square,
}
}
/// Kling 3.0 Pro Multi-Function (text and image to video)
#[utoipa::path(
post,
tag = "Generate Video (Multi-Function)",
path = "/v1/generate/video/multi_function/kling_3p0_pro",
responses(
(status = 200, description = "Success", body = Kling3p0ProMultiFunctionVideoGenResponse),
),
params(
("request" = Kling3p0ProMultiFunctionVideoGenRequest, description = "Payload for Request"),
)
)]
pub async fn kling_3p0_pro_multi_function_video_gen_handler(
http_request: HttpRequest,
request: Json<Kling3p0ProMultiFunctionVideoGenRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<Kling3p0ProMultiFunctionVideoGenResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
let maybe_user_session = server_state
.session_checker
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
CommonWebError::ServerError
})?;
let maybe_avt_token = server_state
.avt_cookie_manager
.get_avt_token_from_request(&http_request);
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
}
let mut query_media_tokens = None;
if let Some(start_frame_token) = request.image_media_token.as_ref() {
let mut tokens = Vec::new();
tokens.push(start_frame_token.to_owned());
if let Some(end_frame_token) = request.end_image_media_token.as_ref() {
tokens.push(end_frame_token.to_owned());
}
query_media_tokens = Some(tokens);
}
let image_urls_by_token = match query_media_tokens {
None => HashMap::new(),
Some(media_tokens) => {
info!("Looking up image media tokens: {:?}", media_tokens);
lookup_image_urls_as_map(
&http_request,
&mut mysql_connection,
server_state.server_environment,
&media_tokens,
).await?
}
};
let maybe_start_frame_image_url = match request.image_media_token.as_ref() {
None => None,
Some(token) => match image_urls_by_token.get(token) {
Some(url) => Some(url.to_string()),
None => {
return Err(CommonWebError::BadInputWithSimpleMessage("Media for start frame not found.".to_string()));
}
}
};
let maybe_end_frame_image_url = match request.end_image_media_token.as_ref() {
None => None,
Some(token) => match image_urls_by_token.get(token) {
Some(url) => Some(url.to_string()),
None => {
return Err(CommonWebError::BadInputWithSimpleMessage("Media for end frame not found.".to_string()));
}
}
};
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
.await
.map_err(|err| {
error!("Error inserting idempotency token: {:?}", err);
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
})?;
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(start_frame_url) = maybe_start_frame_image_url {
info!("image-to-video case");
let duration = map_duration_i2v(request.duration);
let args = EnqueueKlingV3ProImageToVideoArgs {
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,
negative_prompt: request.negative_prompt.clone(),
duration: Some(duration),
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_kling_v3_pro_image_to_video_webhook(args)
.await
.map_err(|err| {
warn!("Error calling enqueue_kling_v3_pro_image_to_video_webhook: {:?}", err);
CommonWebError::ServerError
})?;
} else {
info!("text-to-video case");
let duration = map_duration_t2v(request.duration);
let aspect_ratio = map_aspect_ratio_t2v(request.aspect_ratio);
let args = EnqueueKlingV3ProTextToVideoArgs {
prompt: request.prompt.as_deref().unwrap_or("").to_string(),
negative_prompt: request.negative_prompt.clone(),
generate_audio: request.generate_audio,
duration: Some(duration),
aspect_ratio: Some(aspect_ratio),
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet...");
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_kling_v3_pro_text_to_video_webhook(args)
.await
.map_err(|err| {
warn!("Error calling enqueue_kling_v3_pro_text_to_video_webhook: {:?}", err);
CommonWebError::ServerError
})?;
}
let external_job_id = fal_result.request_id
.ok_or_else(|| {
warn!("Fal request_id is None");
CommonWebError::ServerError
})?;
info!("Fal request_id: {}", external_job_id);
let ip_address = get_request_ip(&http_request);
let mut transaction = mysql_connection
.begin()
.await
.map_err(|err| {
error!("Error starting MySQL transaction: {:?}", err);
CommonWebError::ServerError
})?;
// NB: Don't fail the job if the query fails.
let prompt_result = insert_prompt(InsertPromptArgs {
maybe_apriori_prompt_token: None,
prompt_type: PromptType::ArtcraftApp,
maybe_creator_user_token: maybe_user_session
.as_ref()
.map(|s| &s.user_token),
maybe_model_type: Some(ModelType::Kling3p0Pro),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
maybe_negative_prompt: None,
maybe_other_args: None,
creator_ip_address: &ip_address,
mysql_executor: &mut *transaction,
phantom: Default::default(),
}).await;
let prompt_token = match prompt_result {
Ok(token) => Some(token),
Err(err) => {
warn!("Error inserting prompt: {:?}", err);
None // Don't fail the job if the prompt insertion fails.
}
};
if let Some(token) = prompt_token.as_ref() {
let mut context_items = Vec::with_capacity(2);
if let Some(media_token) = &request.image_media_token {
context_items.push(PromptContextItem {
media_token: media_token.clone(),
context_semantic_type: PromptContextSemanticType::VidStartFrame,
});
}
if let Some(media_token) = &request.end_image_media_token {
context_items.push(PromptContextItem {
media_token: media_token.clone(),
context_semantic_type: PromptContextSemanticType::VidEndFrame,
});
}
if !context_items.is_empty() {
let result = insert_batch_prompt_context_items(InsertBatchArgs {
prompt_token: token.clone(),
items: context_items,
transaction: &mut transaction,
}).await;
if let Err(err) = result {
// NB: Fail open.
warn!("Error inserting batch prompt context items: {:?}", err);
}
}
}
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
maybe_inference_args: None,
maybe_prompt_token: prompt_token.as_ref(),
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_executor: &mut *transaction,
starting_job_status_override: None,
phantom: Default::default(),
}).await;
let job_token = match db_result {
Ok(token) => token,
Err(err) => {
warn!("Error inserting generic inference job for FAL queue: {:?}", err);
return Err(CommonWebError::ServerError);
}
};
let _r = transaction
.commit()
.await
.map_err(|err| {
error!("Error committing MySQL transaction: {:?}", err);
CommonWebError::ServerError
})?;
Ok(Json(Kling3p0ProMultiFunctionVideoGenResponse {
success: true,
inference_job_token: job_token,
}))
}
@@ -0,0 +1,377 @@
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
use crate::util::lookup::lookup_image_urls_as_map::lookup_image_urls_as_map;
use actix_web::web::Json;
use actix_web::{web, HttpRequest};
use artcraft_api_defs::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen::{Kling3p0StandardMultiFunctionVideoGenAspectRatio, Kling3p0StandardMultiFunctionVideoGenDuration, Kling3p0StandardMultiFunctionVideoGenRequest, Kling3p0StandardMultiFunctionVideoGenResponse};
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
use enums::by_table::prompts::prompt_type::PromptType;
use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_kling_v3_standard_image_to_video_webhook::{enqueue_kling_v3_standard_image_to_video_webhook, EnqueueKlingV3StandardImageToVideoArgs};
use fal_client::requests::webhook::video::text::enqueue_kling_v3_standard_text_to_video_webhook::{enqueue_kling_v3_standard_text_to_video_webhook, EnqueueKlingV3StandardTextToVideoArgs, EnqueueKlingV3StandardTextToVideoAspectRatio, EnqueueKlingV3StandardTextToVideoDuration};
use http_server_common::request::get_request_ip::get_request_ip;
use log::{error, info, warn};
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::InsertGenericInferenceForFalArgs;
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use mysql_queries::queries::wallets::spend::wallet_spend_error::WalletSpendError;
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
fn map_duration_i2v(duration: Option<Kling3p0StandardMultiFunctionVideoGenDuration>) -> EnqueueKlingV3StandardTextToVideoDuration {
match duration {
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ThreeSeconds) => EnqueueKlingV3StandardTextToVideoDuration::ThreeSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FourSeconds) => EnqueueKlingV3StandardTextToVideoDuration::FourSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FiveSeconds) => EnqueueKlingV3StandardTextToVideoDuration::FiveSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::SixSeconds) => EnqueueKlingV3StandardTextToVideoDuration::SixSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::SevenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::SevenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::EightSeconds) => EnqueueKlingV3StandardTextToVideoDuration::EightSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::NineSeconds) => EnqueueKlingV3StandardTextToVideoDuration::NineSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::TenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::TenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ElevenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::ElevenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::TwelveSeconds) => EnqueueKlingV3StandardTextToVideoDuration::TwelveSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ThirteenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::ThirteenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FourteenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::FourteenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FifteenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::FifteenSeconds,
None => EnqueueKlingV3StandardTextToVideoDuration::FiveSeconds,
}
}
fn map_duration_t2v(duration: Option<Kling3p0StandardMultiFunctionVideoGenDuration>) -> EnqueueKlingV3StandardTextToVideoDuration {
match duration {
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ThreeSeconds) => EnqueueKlingV3StandardTextToVideoDuration::ThreeSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FourSeconds) => EnqueueKlingV3StandardTextToVideoDuration::FourSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FiveSeconds) => EnqueueKlingV3StandardTextToVideoDuration::FiveSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::SixSeconds) => EnqueueKlingV3StandardTextToVideoDuration::SixSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::SevenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::SevenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::EightSeconds) => EnqueueKlingV3StandardTextToVideoDuration::EightSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::NineSeconds) => EnqueueKlingV3StandardTextToVideoDuration::NineSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::TenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::TenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ElevenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::ElevenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::TwelveSeconds) => EnqueueKlingV3StandardTextToVideoDuration::TwelveSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ThirteenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::ThirteenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FourteenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::FourteenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FifteenSeconds) => EnqueueKlingV3StandardTextToVideoDuration::FifteenSeconds,
None => EnqueueKlingV3StandardTextToVideoDuration::FiveSeconds,
}
}
fn map_aspect_ratio_t2v(aspect_ratio: Option<Kling3p0StandardMultiFunctionVideoGenAspectRatio>) -> EnqueueKlingV3StandardTextToVideoAspectRatio {
match aspect_ratio {
Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::Square) => EnqueueKlingV3StandardTextToVideoAspectRatio::Square,
Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::SixteenByNine) => EnqueueKlingV3StandardTextToVideoAspectRatio::SixteenByNine,
Some(Kling3p0StandardMultiFunctionVideoGenAspectRatio::NineBySixteen) => EnqueueKlingV3StandardTextToVideoAspectRatio::NineBySixteen,
None => EnqueueKlingV3StandardTextToVideoAspectRatio::Square,
}
}
/// Kling 3.0 Standard Multi-Function (text and image to video)
#[utoipa::path(
post,
tag = "Generate Video (Multi-Function)",
path = "/v1/generate/video/multi_function/kling_3p0_standard",
responses(
(status = 200, description = "Success", body = Kling3p0StandardMultiFunctionVideoGenResponse),
),
params(
("request" = Kling3p0StandardMultiFunctionVideoGenRequest, description = "Payload for Request"),
)
)]
pub async fn kling_3p0_standard_multi_function_video_gen_handler(
http_request: HttpRequest,
request: Json<Kling3p0StandardMultiFunctionVideoGenRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<Kling3p0StandardMultiFunctionVideoGenResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
let maybe_user_session = server_state
.session_checker
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
CommonWebError::ServerError
})?;
let maybe_avt_token = server_state
.avt_cookie_manager
.get_avt_token_from_request(&http_request);
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
}
let mut query_media_tokens = None;
if let Some(start_frame_token) = request.image_media_token.as_ref() {
let mut tokens = Vec::new();
tokens.push(start_frame_token.to_owned());
if let Some(end_frame_token) = request.end_image_media_token.as_ref() {
tokens.push(end_frame_token.to_owned());
}
query_media_tokens = Some(tokens);
}
let image_urls_by_token = match query_media_tokens {
None => HashMap::new(),
Some(media_tokens) => {
info!("Looking up image media tokens: {:?}", media_tokens);
lookup_image_urls_as_map(
&http_request,
&mut mysql_connection,
server_state.server_environment,
&media_tokens,
).await?
}
};
let maybe_start_frame_image_url = match request.image_media_token.as_ref() {
None => None,
Some(token) => match image_urls_by_token.get(token) {
Some(url) => Some(url.to_string()),
None => {
return Err(CommonWebError::BadInputWithSimpleMessage("Media for start frame not found.".to_string()));
}
}
};
let maybe_end_frame_image_url = match request.end_image_media_token.as_ref() {
None => None,
Some(token) => match image_urls_by_token.get(token) {
Some(url) => Some(url.to_string()),
None => {
return Err(CommonWebError::BadInputWithSimpleMessage("Media for end frame not found.".to_string()));
}
}
};
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
.await
.map_err(|err| {
error!("Error inserting idempotency token: {:?}", err);
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
})?;
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(start_frame_url) = maybe_start_frame_image_url {
info!("image-to-video case");
let duration = map_duration_i2v(request.duration);
let args = EnqueueKlingV3StandardImageToVideoArgs {
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,
negative_prompt: request.negative_prompt.clone(),
duration: Some(duration),
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_kling_v3_standard_image_to_video_webhook(args)
.await
.map_err(|err| {
warn!("Error calling enqueue_kling_v3_standard_image_to_video_webhook: {:?}", err);
CommonWebError::ServerError
})?;
} else {
info!("text-to-video case");
let duration = map_duration_t2v(request.duration);
let aspect_ratio = map_aspect_ratio_t2v(request.aspect_ratio);
let args = EnqueueKlingV3StandardTextToVideoArgs {
prompt: request.prompt.as_deref().unwrap_or("").to_string(),
negative_prompt: request.negative_prompt.clone(),
generate_audio: request.generate_audio,
duration: Some(duration),
aspect_ratio: Some(aspect_ratio),
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet...");
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_kling_v3_standard_text_to_video_webhook(args)
.await
.map_err(|err| {
warn!("Error calling enqueue_kling_v3_standard_text_to_video_webhook: {:?}", err);
CommonWebError::ServerError
})?;
}
let external_job_id = fal_result.request_id
.ok_or_else(|| {
warn!("Fal request_id is None");
CommonWebError::ServerError
})?;
info!("Fal request_id: {}", external_job_id);
let ip_address = get_request_ip(&http_request);
let mut transaction = mysql_connection
.begin()
.await
.map_err(|err| {
error!("Error starting MySQL transaction: {:?}", err);
CommonWebError::ServerError
})?;
// NB: Don't fail the job if the query fails.
let prompt_result = insert_prompt(InsertPromptArgs {
maybe_apriori_prompt_token: None,
prompt_type: PromptType::ArtcraftApp,
maybe_creator_user_token: maybe_user_session
.as_ref()
.map(|s| &s.user_token),
maybe_model_type: Some(ModelType::Kling3p0Standard),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
maybe_negative_prompt: None,
maybe_other_args: None,
creator_ip_address: &ip_address,
mysql_executor: &mut *transaction,
phantom: Default::default(),
}).await;
let prompt_token = match prompt_result {
Ok(token) => Some(token),
Err(err) => {
warn!("Error inserting prompt: {:?}", err);
None // Don't fail the job if the prompt insertion fails.
}
};
if let Some(token) = prompt_token.as_ref() {
let mut context_items = Vec::with_capacity(2);
if let Some(media_token) = &request.image_media_token {
context_items.push(PromptContextItem {
media_token: media_token.clone(),
context_semantic_type: PromptContextSemanticType::VidStartFrame,
});
}
if let Some(media_token) = &request.end_image_media_token {
context_items.push(PromptContextItem {
media_token: media_token.clone(),
context_semantic_type: PromptContextSemanticType::VidEndFrame,
});
}
if !context_items.is_empty() {
let result = insert_batch_prompt_context_items(InsertBatchArgs {
prompt_token: token.clone(),
items: context_items,
transaction: &mut transaction,
}).await;
if let Err(err) = result {
// NB: Fail open.
warn!("Error inserting batch prompt context items: {:?}", err);
}
}
}
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
maybe_inference_args: None,
maybe_prompt_token: prompt_token.as_ref(),
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
mysql_executor: &mut *transaction,
starting_job_status_override: None,
phantom: Default::default(),
}).await;
let job_token = match db_result {
Ok(token) => token,
Err(err) => {
warn!("Error inserting generic inference job for FAL queue: {:?}", err);
return Err(CommonWebError::ServerError);
}
};
let _r = transaction
.commit()
.await
.map_err(|err| {
error!("Error committing MySQL transaction: {:?}", err);
CommonWebError::ServerError
})?;
Ok(Json(Kling3p0StandardMultiFunctionVideoGenResponse {
success: true,
inference_job_token: job_token,
}))
}
@@ -1,5 +1,7 @@
pub mod kling_2p5_turbo_pro_multi_function_video_gen_handler;
pub mod kling_2p6_pro_multi_function_video_gen_handler;
pub mod kling_3p0_pro_multi_function_video_gen_handler;
pub mod kling_3p0_standard_multi_function_video_gen_handler;
pub mod seedance_1p5_pro_multi_function_video_gen_handler;
pub mod seedance_2p0_multi_function_video_gen_handler;
pub mod sora_2_multi_function_video_gen_handler;
@@ -33,6 +33,8 @@ use crate::http_server::endpoints::generate::video::generate_veo_3_fast_image_to
use crate::http_server::endpoints::generate::video::generate_veo_3_image_to_video_handler::generate_veo_3_image_to_video_handler;
use crate::http_server::endpoints::generate::video::multi_function::kling_2p5_turbo_pro_multi_function_video_gen_handler::kling_2p5_turbo_pro_multi_function_video_gen_handler;
use crate::http_server::endpoints::generate::video::multi_function::kling_2p6_pro_multi_function_video_gen_handler::kling_2p6_pro_multi_function_video_gen_handler;
use crate::http_server::endpoints::generate::video::multi_function::kling_3p0_pro_multi_function_video_gen_handler::kling_3p0_pro_multi_function_video_gen_handler;
use crate::http_server::endpoints::generate::video::multi_function::kling_3p0_standard_multi_function_video_gen_handler::kling_3p0_standard_multi_function_video_gen_handler;
use crate::http_server::endpoints::generate::video::multi_function::seedance_1p5_pro_multi_function_video_gen_handler::seedance_1p5_pro_multi_function_video_gen_handler;
use crate::http_server::endpoints::generate::video::multi_function::seedance_2p0_multi_function_video_gen_handler::seedance_2p0_multi_function_video_gen_handler;
use crate::http_server::endpoints::generate::video::multi_function::sora_2_multi_function_video_gen_handler::sora_2_multi_function_video_gen_handler;
@@ -164,6 +166,14 @@ where
.route(web::post().to(kling_2p6_pro_multi_function_video_gen_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
.service(web::resource("/kling_3p0_standard")
.route(web::post().to(kling_3p0_standard_multi_function_video_gen_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
.service(web::resource("/kling_3p0_pro")
.route(web::post().to(kling_3p0_pro_multi_function_video_gen_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
.service(web::resource("/seedance_1p5_pro")
.route(web::post().to(seedance_1p5_pro_multi_function_video_gen_handler))
.route(web::head().to(|| HttpResponse::Ok()))
@@ -82,3 +82,17 @@ pub mod v2_5;
)))
)]
pub mod v2_6;
#[cfg(any(
feature = "endpoints",
feature = "endpoints_fal-ai",
feature = "endpoints_fal-ai_kling-video"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "endpoints",
feature = "endpoints_fal-ai",
feature = "endpoints_fal-ai_kling-video"
)))
)]
pub mod v3;
@@ -0,0 +1,53 @@
use crate::prelude::{Deserialize, FalRequest, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct KlingV3ProImageToVideoInput {
pub prompt: String,
/// Starting frame image URL
pub image_url: String,
/// Optional end frame image URL
#[serde(skip_serializing_if = "Option::is_none")]
pub end_image_url: Option<String>,
/// Aspect ratio
/// Possible enum values: "16:9", "9:16", "1:1"
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// Generate audio
#[serde(skip_serializing_if = "Option::is_none")]
pub generate_audio: Option<bool>,
/// Optional negative prompt
#[serde(skip_serializing_if = "Option::is_none")]
pub negative_prompt: Option<String>,
/// Duration in seconds
/// Options: "3" through "15"
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
/// The CFG (Classifier Free Guidance) scale.
/// Default value: 0.5
#[serde(skip_serializing_if = "Option::is_none")]
pub cfg_scale: Option<f32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KlingV3ProImageToVideoOutput {
pub video: VideoFile,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct VideoFile {
/// The URL where the file can be downloaded from.
pub url: String,
}
pub fn kling_v3_pro_image_to_video(
params: KlingV3ProImageToVideoInput,
) -> FalRequest<KlingV3ProImageToVideoInput, KlingV3ProImageToVideoOutput> {
FalRequest::new("fal-ai/kling-video/v3/pro/image-to-video", params)
}
@@ -0,0 +1,46 @@
use crate::prelude::{Deserialize, FalRequest, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct KlingV3ProTextToVideoInput {
pub prompt: String,
/// Aspect ratio
/// Possible enum values: "16:9", "9:16", "1:1"
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// Generate audio
#[serde(skip_serializing_if = "Option::is_none")]
pub generate_audio: Option<bool>,
/// Optional negative prompt
#[serde(skip_serializing_if = "Option::is_none")]
pub negative_prompt: Option<String>,
/// Duration in seconds
/// Options: "3" through "15"
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
/// The CFG (Classifier Free Guidance) scale.
/// Default value: 0.5
#[serde(skip_serializing_if = "Option::is_none")]
pub cfg_scale: Option<f32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KlingV3ProTextToVideoOutput {
pub video: VideoFile,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct VideoFile {
/// The URL where the file can be downloaded from.
pub url: String,
}
pub fn kling_v3_pro_text_to_video(
params: KlingV3ProTextToVideoInput,
) -> FalRequest<KlingV3ProTextToVideoInput, KlingV3ProTextToVideoOutput> {
FalRequest::new("fal-ai/kling-video/v3/pro/text-to-video", params)
}
@@ -0,0 +1,53 @@
use crate::prelude::{Deserialize, FalRequest, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct KlingV3StandardImageToVideoInput {
pub prompt: String,
/// Starting frame image URL
pub image_url: String,
/// Optional end frame image URL
#[serde(skip_serializing_if = "Option::is_none")]
pub end_image_url: Option<String>,
/// Aspect ratio
/// Possible enum values: "16:9", "9:16", "1:1"
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// Generate audio
#[serde(skip_serializing_if = "Option::is_none")]
pub generate_audio: Option<bool>,
/// Optional negative prompt
#[serde(skip_serializing_if = "Option::is_none")]
pub negative_prompt: Option<String>,
/// Duration in seconds
/// Options: "3" through "15"
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
/// The CFG (Classifier Free Guidance) scale.
/// Default value: 0.5
#[serde(skip_serializing_if = "Option::is_none")]
pub cfg_scale: Option<f32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KlingV3StandardImageToVideoOutput {
pub video: VideoFile,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct VideoFile {
/// The URL where the file can be downloaded from.
pub url: String,
}
pub fn kling_v3_standard_image_to_video(
params: KlingV3StandardImageToVideoInput,
) -> FalRequest<KlingV3StandardImageToVideoInput, KlingV3StandardImageToVideoOutput> {
FalRequest::new("fal-ai/kling-video/v3/standard/image-to-video", params)
}
@@ -0,0 +1,46 @@
use crate::prelude::{Deserialize, FalRequest, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct KlingV3StandardTextToVideoInput {
pub prompt: String,
/// Aspect ratio
/// Possible enum values: "16:9", "9:16", "1:1"
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// Generate audio
#[serde(skip_serializing_if = "Option::is_none")]
pub generate_audio: Option<bool>,
/// Optional negative prompt
#[serde(skip_serializing_if = "Option::is_none")]
pub negative_prompt: Option<String>,
/// Duration in seconds
/// Options: "3" through "15"
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
/// The CFG (Classifier Free Guidance) scale.
/// Default value: 0.5
#[serde(skip_serializing_if = "Option::is_none")]
pub cfg_scale: Option<f32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KlingV3StandardTextToVideoOutput {
pub video: VideoFile,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct VideoFile {
/// The URL where the file can be downloaded from.
pub url: String,
}
pub fn kling_v3_standard_text_to_video(
params: KlingV3StandardTextToVideoInput,
) -> FalRequest<KlingV3StandardTextToVideoInput, KlingV3StandardTextToVideoOutput> {
FalRequest::new("fal-ai/kling-video/v3/standard/text-to-video", params)
}
@@ -0,0 +1,4 @@
pub mod kling_v3_pro_image_to_video;
pub mod kling_v3_pro_text_to_video;
pub mod kling_v3_standard_image_to_video;
pub mod kling_v3_standard_text_to_video;
@@ -38,10 +38,7 @@
],
"references": [
{
"path": "../../libs/components/tab-selector/tsconfig.lib.json"
},
{
"path": "../../libs/state/subscription/tsconfig.lib.json"
"path": "../../libs/components/viewer-3d/tsconfig.lib.json"
},
{
"path": "../../libs/model-list/tsconfig.lib.json"
@@ -58,14 +55,20 @@
{
"path": "../../libs/components/loading-spinner/tsconfig.lib.json"
},
{
"path": "../../libs/components/tab-selector/tsconfig.lib.json"
},
{
"path": "../../libs/state/subscription/tsconfig.lib.json"
},
{
"path": "../../libs/markdown-content/tsconfig.lib.json"
},
{
"path": "../../libs/api/tsconfig.lib.json"
"path": "../../libs/components/button/tsconfig.lib.json"
},
{
"path": "../../libs/components/button/tsconfig.lib.json"
"path": "../../libs/api/tsconfig.lib.json"
}
]
}
+9 -6
View File
@@ -3,10 +3,7 @@
"include": [],
"references": [
{
"path": "../../libs/components/tab-selector"
},
{
"path": "../../libs/state/subscription"
"path": "../../libs/components/viewer-3d"
},
{
"path": "../../libs/model-list"
@@ -23,14 +20,20 @@
{
"path": "../../libs/components/loading-spinner"
},
{
"path": "../../libs/components/tab-selector"
},
{
"path": "../../libs/state/subscription"
},
{
"path": "../../libs/markdown-content"
},
{
"path": "../../libs/api"
"path": "../../libs/components/button"
},
{
"path": "../../libs/components/button"
"path": "../../libs/api"
},
{
"path": "./tsconfig.app.json"
@@ -9,6 +9,8 @@ export enum CommonVideoModel {
Kling21Master = "kling_2p1_master",
Kling2p5TurboPro = "kling_2p5_turbo_pro",
Kling2p6Pro = "kling_2p6_pro",
Kling3p0Standard = "kling_3p0_standard",
Kling3p0Pro = "kling_3p0_pro",
Seedance10Lite = "seedance_1p0_lite",
Seedance1p5Pro = "seedance_1p5_pro",
Seedance2p0 = "seedance_2p0",
@@ -18,8 +18,14 @@ export function videoModelToCommonVideoModel(
return CommonVideoModel.Kling2p5TurboPro;
case "kling_2p6_pro":
return CommonVideoModel.Kling2p6Pro;
case "kling_3p0_standard":
return CommonVideoModel.Kling3p0Standard;
case "kling_3p0_pro":
return CommonVideoModel.Kling3p0Pro;
case "seedance_1.0_lite":
return CommonVideoModel.Seedance10Lite;
case "seedance_1p5_pro":
return CommonVideoModel.Seedance1p5Pro;
case "seedance_2p0":
return CommonVideoModel.Seedance2p0;
case "sora_2":
@@ -248,6 +248,9 @@ export function CostBreakdownModal({ activeTabId }: CostBreakdownModalProps) {
"seedream_4",
"seedream_4p5",
"seedream_5_lite",
"kling_3p0_standard",
"kling_3p0_pro",
"seedance_1p5_pro",
"seedance_2p0",
]);
const hasCostData =
@@ -111,6 +111,74 @@ export const VIDEO_MODELS: VideoModel[] = [
progressBarTime: 100000,
generateWithSound: false,
}),
new VideoModel({
id: "kling_3p0_standard",
tauriId: "kling_3p0_standard",
fullName: "Kling 3.0 Standard",
category: "video",
creator: ModelCreator.Kling,
selectorName: "Kling 3.0 Standard",
selectorDescription: "Standard quality video model",
selectorBadges: ["2 min."],
startFrame: true,
endFrame: true,
requiresImage: false,
sizeOptions: [
{
tauriValue: "wide_sixteen_by_nine",
textLabel: "16:9",
icon: SizeIconOption.Landscape16x9,
},
{
tauriValue: "square",
textLabel: "1:1",
icon: SizeIconOption.Square,
},
{
tauriValue: "tall_nine_by_sixteen",
textLabel: "9:16",
icon: SizeIconOption.Portrait9x16,
},
],
durationOptions: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
defaultDuration: 5,
progressBarTime: 100000,
generateWithSound: false,
}),
new VideoModel({
id: "kling_3p0_pro",
tauriId: "kling_3p0_pro",
fullName: "Kling 3.0 Pro",
category: "video",
creator: ModelCreator.Kling,
selectorName: "Kling 3.0 Pro",
selectorDescription: "High quality video model",
selectorBadges: ["2 min."],
startFrame: true,
endFrame: true,
requiresImage: false,
sizeOptions: [
{
tauriValue: "wide_sixteen_by_nine",
textLabel: "16:9",
icon: SizeIconOption.Landscape16x9,
},
{
tauriValue: "square",
textLabel: "1:1",
icon: SizeIconOption.Square,
},
{
tauriValue: "tall_nine_by_sixteen",
textLabel: "9:16",
icon: SizeIconOption.Portrait9x16,
},
],
durationOptions: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
defaultDuration: 5,
progressBarTime: 100000,
generateWithSound: false,
}),
new VideoModel({
id: "seedance_1_0_lite",
tauriId: "seedance_1.0_lite",