mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
Kling 3.0
This commit is contained in:
1 parent
db24eff600
commit
3023ade0cc
52 files changed
+2943
-14
No files matched your search
+66
@@ -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,
|
||||
}
|
||||
+66
@@ -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;
|
||||
|
||||
+18
@@ -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?)
|
||||
}
|
||||
+18
@@ -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
@@ -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,
|
||||
|
||||
|
||||
+95
@@ -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);
|
||||
}
|
||||
}
|
||||
+95
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
@@ -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;
|
||||
+39
@@ -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,
|
||||
}))
|
||||
}
|
||||
+39
@@ -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,
|
||||
}))
|
||||
}
|
||||
+2
@@ -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;
|
||||
+8
@@ -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)
|
||||
}
|
||||
|
||||
+2
@@ -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;
|
||||
+266
@@ -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))
|
||||
));
|
||||
}
|
||||
}
|
||||
+266
@@ -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))
|
||||
));
|
||||
}
|
||||
}
|
||||
+22
@@ -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)
|
||||
}
|
||||
|
||||
+105
@@ -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);
|
||||
}
|
||||
}
|
||||
+104
@@ -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;
|
||||
|
||||
+201
@@ -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);
|
||||
}
|
||||
}
|
||||
+218
@@ -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;
|
||||
|
||||
+71
@@ -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),
|
||||
})
|
||||
}
|
||||
+71
@@ -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),
|
||||
})
|
||||
}
|
||||
+4
@@ -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;
|
||||
|
||||
+6
@@ -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,
|
||||
|
||||
+2
@@ -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,
|
||||
|
||||
+377
@@ -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,
|
||||
}))
|
||||
}
|
||||
+377
@@ -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,
|
||||
}))
|
||||
}
|
||||
+2
@@ -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;
|
||||
|
||||
+10
@@ -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;
|
||||
+53
@@ -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)
|
||||
}
|
||||
+46
@@ -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)
|
||||
}
|
||||
Vendored
+53
@@ -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)
|
||||
}
|
||||
Vendored
+46
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user