improve types

This commit is contained in:
Brandon Thomas committed 2026-03-08 14:47:02 -04:00
1 parent ac6a69fa32
commit 75539a2026
12 files changed
+462 -61

No files matched your search

@@ -36,6 +36,7 @@ pub(crate) fn estimate_video_cost_artcraft_kling3p0_pro(
negative_prompt: None,
duration,
aspect_ratio: None,
shot_type: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
@@ -36,6 +36,7 @@ pub(crate) fn estimate_video_cost_artcraft_kling3p0_standard(
negative_prompt: None,
duration,
aspect_ratio: None,
shot_type: None,
webhook_url: "https://example.com",
api_key: &api_key,
};
@@ -30,6 +30,11 @@ pub struct Kling3p0ProImageToVideoInput {
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
/// Shot type for multi-shot video generation.
/// Possible enum values: "customize", "intelligent"
#[serde(skip_serializing_if = "Option::is_none")]
pub shot_type: Option<String>,
/// The CFG (Classifier Free Guidance) scale.
/// Default value: 0.5
#[serde(skip_serializing_if = "Option::is_none")]
@@ -23,6 +23,11 @@ pub struct Kling3p0ProTextToVideoInput {
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
/// Shot type for multi-shot video generation.
/// Possible enum values: "customize", "intelligent"
#[serde(skip_serializing_if = "Option::is_none")]
pub shot_type: Option<String>,
/// The CFG (Classifier Free Guidance) scale.
/// Default value: 0.5
#[serde(skip_serializing_if = "Option::is_none")]
@@ -30,6 +30,11 @@ pub struct Kling3p0StandardImageToVideoInput {
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
/// Shot type for multi-shot video generation.
/// Possible enum values: "customize", "intelligent"
#[serde(skip_serializing_if = "Option::is_none")]
pub shot_type: Option<String>,
/// The CFG (Classifier Free Guidance) scale.
/// Default value: 0.5
#[serde(skip_serializing_if = "Option::is_none")]
@@ -23,6 +23,11 @@ pub struct Kling3p0StandardTextToVideoInput {
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
/// Shot type for multi-shot video generation.
/// Possible enum values: "customize", "intelligent"
#[serde(skip_serializing_if = "Option::is_none")]
pub shot_type: Option<String>,
/// The CFG (Classifier Free Guidance) scale.
/// Default value: 0.5
#[serde(skip_serializing_if = "Option::is_none")]
@@ -2,12 +2,12 @@ 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_3p0_pro_text_to_video_webhook::EnqueueKling3p0ProTextToVideoDuration;
use crate::requests::http::video::http_kling_3p0_pro_image_to_video::{kling_3p0_pro_image_to_video, Kling3p0ProImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueKling3p0ProImageToVideoArgs<'a, R: IntoUrl> {
// Request required
pub prompt: String,
pub image_url: String,
@@ -15,28 +15,91 @@ pub struct EnqueueKling3p0ProImageToVideoArgs<'a, R: IntoUrl> {
pub end_image_url: Option<String>,
pub generate_audio: Option<bool>,
pub negative_prompt: Option<String>,
pub duration: Option<EnqueueKling3p0ProTextToVideoDuration>,
pub duration: Option<EnqueueKling3p0ProImageToVideoDuration>,
pub aspect_ratio: Option<EnqueueKling3p0ProImageToVideoAspectRatio>,
pub shot_type: Option<EnqueueKling3p0ProImageToVideoShotType>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumIter)]
pub enum EnqueueKling3p0ProImageToVideoDuration {
ThreeSeconds,
FourSeconds,
FiveSeconds,
SixSeconds,
SevenSeconds,
EightSeconds,
NineSeconds,
TenSeconds,
ElevenSeconds,
TwelveSeconds,
ThirteenSeconds,
FourteenSeconds,
FifteenSeconds,
}
impl EnqueueKling3p0ProImageToVideoDuration {
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, Eq, PartialEq, strum::EnumIter)]
pub enum EnqueueKling3p0ProImageToVideoAspectRatio {
Square,
SixteenByNine,
NineBySixteen,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumIter)]
pub enum EnqueueKling3p0ProImageToVideoShotType {
Customize,
Intelligent,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKling3p0ProImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Fal Docs:
// For every second of video you generated, you will be charged $0.224 (audio off)
// or $0.336 (audio on),
// if voice control is used while generating audio you will be charged $0.392.
// For example, a 5s video with audio on and voice control will cost $1.96
// Our Docs:
// 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(EnqueueKling3p0ProTextToVideoDuration::FiveSeconds)
.unwrap_or(EnqueueKling3p0ProImageToVideoDuration::FiveSeconds)
.to_seconds();
let rate = if generate_audio { 336u64 } else { 224u64 };
@@ -53,14 +116,30 @@ pub async fn enqueue_kling_3p0_pro_image_to_video_webhook<R: IntoUrl>(
let duration = args.duration
.map(|d| d.to_str().to_string());
let aspect_ratio = args.aspect_ratio
.map(|ar| match ar {
EnqueueKling3p0ProImageToVideoAspectRatio::Square => "1:1",
EnqueueKling3p0ProImageToVideoAspectRatio::SixteenByNine => "16:9",
EnqueueKling3p0ProImageToVideoAspectRatio::NineBySixteen => "9:16",
})
.map(|s| s.to_string());
let shot_type = args.shot_type
.map(|st| match st {
EnqueueKling3p0ProImageToVideoShotType::Customize => "customize",
EnqueueKling3p0ProImageToVideoShotType::Intelligent => "intelligent",
})
.map(|s| s.to_string());
let request = Kling3p0ProImageToVideoInput {
prompt: args.prompt,
image_url: args.image_url,
end_image_url: args.end_image_url,
aspect_ratio: None,
aspect_ratio,
generate_audio: args.generate_audio,
duration,
negative_prompt: args.negative_prompt,
shot_type,
cfg_scale: None,
};
@@ -92,7 +171,9 @@ mod tests {
end_image_url: None,
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKling3p0ProTextToVideoDuration::FiveSeconds),
duration: Some(EnqueueKling3p0ProImageToVideoDuration::FiveSeconds),
aspect_ratio: None,
shot_type: None,
webhook_url: "https://example.com/webhook",
api_key: &api_key,
};
@@ -102,22 +183,22 @@ mod tests {
assert_eq!(args.calculate_cost_in_cents(), 112);
// 10s: (224 * 10 + 9) / 10 = 2249 / 10 = 224
args.duration = Some(EnqueueKling3p0ProTextToVideoDuration::TenSeconds);
args.duration = Some(EnqueueKling3p0ProImageToVideoDuration::TenSeconds);
assert_eq!(args.calculate_cost_in_cents(), 224);
// 15s: (224 * 15 + 9) / 10 = 3369 / 10 = 336
args.duration = Some(EnqueueKling3p0ProTextToVideoDuration::FifteenSeconds);
args.duration = Some(EnqueueKling3p0ProImageToVideoDuration::FifteenSeconds);
assert_eq!(args.calculate_cost_in_cents(), 336);
// Audio on: $0.336/sec
args.generate_audio = Some(true);
// 5s: (336 * 5 + 9) / 10 = 1689 / 10 = 168
args.duration = Some(EnqueueKling3p0ProTextToVideoDuration::FiveSeconds);
args.duration = Some(EnqueueKling3p0ProImageToVideoDuration::FiveSeconds);
assert_eq!(args.calculate_cost_in_cents(), 168);
// 10s: (336 * 10 + 9) / 10 = 3369 / 10 = 336
args.duration = Some(EnqueueKling3p0ProTextToVideoDuration::TenSeconds);
args.duration = Some(EnqueueKling3p0ProImageToVideoDuration::TenSeconds);
assert_eq!(args.calculate_cost_in_cents(), 336);
}
@@ -130,10 +211,12 @@ mod tests {
let args = EnqueueKling3p0ProImageToVideoArgs {
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
prompt: "the t-rex skeleton gets off the podium and begins walking to the camera, then bites".to_string(),
duration: Some(EnqueueKling3p0ProTextToVideoDuration::FiveSeconds),
duration: Some(EnqueueKling3p0ProImageToVideoDuration::FiveSeconds),
aspect_ratio: Some(EnqueueKling3p0ProImageToVideoAspectRatio::SixteenByNine),
generate_audio: Some(true),
negative_prompt: None,
end_image_url: None,
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -144,21 +227,77 @@ mod tests {
Ok(())
}
#[tokio::test]
#[ignore] // manually run — fires a real API request per variant (expensive)
async fn test_all_aspect_ratios() -> AnyhowResult<()> {
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
for ar in EnqueueKling3p0ProImageToVideoAspectRatio::iter() {
println!("--- aspect ratio: {:?} ---", ar);
let args = EnqueueKling3p0ProImageToVideoArgs {
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
prompt: "the skeleton comes alive and roars at the camera".to_string(),
duration: Some(EnqueueKling3p0ProImageToVideoDuration::ThreeSeconds),
aspect_ratio: Some(ar),
generate_audio: Some(false),
negative_prompt: None,
end_image_url: None,
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_kling_3p0_pro_image_to_video_webhook(args).await?;
println!("result: {:?}", result);
}
Ok(())
}
#[tokio::test]
#[ignore] // manually run — fires a real API request per variant (expensive)
async fn test_all_durations() -> AnyhowResult<()> {
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
for dur in EnqueueKling3p0ProTextToVideoDuration::iter() {
for dur in EnqueueKling3p0ProImageToVideoDuration::iter() {
println!("--- duration: {:?} ---", dur);
let args = EnqueueKling3p0ProImageToVideoArgs {
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
prompt: "the skeleton slowly turns its head and roars".to_string(),
duration: Some(dur),
aspect_ratio: Some(EnqueueKling3p0ProImageToVideoAspectRatio::SixteenByNine),
generate_audio: Some(false),
negative_prompt: None,
end_image_url: None,
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_kling_3p0_pro_image_to_video_webhook(args).await?;
println!("result: {:?}", result);
}
Ok(())
}
#[tokio::test]
#[ignore] // manually run — fires a real API request per variant (expensive)
async fn test_all_shot_types() -> AnyhowResult<()> {
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
for st in EnqueueKling3p0ProImageToVideoShotType::iter() {
println!("--- shot type: {:?} ---", st);
let args = EnqueueKling3p0ProImageToVideoArgs {
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
prompt: "the skeleton lurches forward and snaps its jaws".to_string(),
duration: Some(EnqueueKling3p0ProImageToVideoDuration::FiveSeconds),
aspect_ratio: Some(EnqueueKling3p0ProImageToVideoAspectRatio::SixteenByNine),
generate_audio: Some(true),
negative_prompt: None,
end_image_url: None,
shot_type: Some(st),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -2,12 +2,12 @@ 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_3p0_standard_text_to_video_webhook::EnqueueKling3p0StandardTextToVideoDuration;
use crate::requests::http::video::http_kling_3p0_standard_image_to_video::{kling_3p0_standard_image_to_video, Kling3p0StandardImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueKling3p0StandardImageToVideoArgs<'a, R: IntoUrl> {
// Request required
pub prompt: String,
pub image_url: String,
@@ -15,13 +15,83 @@ pub struct EnqueueKling3p0StandardImageToVideoArgs<'a, R: IntoUrl> {
pub end_image_url: Option<String>,
pub generate_audio: Option<bool>,
pub negative_prompt: Option<String>,
pub duration: Option<EnqueueKling3p0StandardTextToVideoDuration>,
pub duration: Option<EnqueueKling3p0StandardImageToVideoDuration>,
pub aspect_ratio: Option<EnqueueKling3p0StandardImageToVideoAspectRatio>,
pub shot_type: Option<EnqueueKling3p0StandardImageToVideoShotType>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumIter)]
pub enum EnqueueKling3p0StandardImageToVideoDuration {
ThreeSeconds,
FourSeconds,
FiveSeconds,
SixSeconds,
SevenSeconds,
EightSeconds,
NineSeconds,
TenSeconds,
ElevenSeconds,
TwelveSeconds,
ThirteenSeconds,
FourteenSeconds,
FifteenSeconds,
}
impl EnqueueKling3p0StandardImageToVideoDuration {
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, Eq, PartialEq, strum::EnumIter)]
pub enum EnqueueKling3p0StandardImageToVideoAspectRatio {
Square,
SixteenByNine,
NineBySixteen,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumIter)]
pub enum EnqueueKling3p0StandardImageToVideoShotType {
Customize,
Intelligent,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKling3p0StandardImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Same pricing as text-to-video for Kling 3.0 Standard:
@@ -29,7 +99,7 @@ impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKling3p0StandardImageToVid
// Audio on: $0.252/second
let generate_audio = self.generate_audio.unwrap_or(true);
let duration_secs = self.duration
.unwrap_or(EnqueueKling3p0StandardTextToVideoDuration::FiveSeconds)
.unwrap_or(EnqueueKling3p0StandardImageToVideoDuration::FiveSeconds)
.to_seconds();
let rate = if generate_audio { 252u64 } else { 168u64 };
@@ -46,14 +116,30 @@ pub async fn enqueue_kling_3p0_standard_image_to_video_webhook<R: IntoUrl>(
let duration = args.duration
.map(|d| d.to_str().to_string());
let aspect_ratio = args.aspect_ratio
.map(|ar| match ar {
EnqueueKling3p0StandardImageToVideoAspectRatio::Square => "1:1",
EnqueueKling3p0StandardImageToVideoAspectRatio::SixteenByNine => "16:9",
EnqueueKling3p0StandardImageToVideoAspectRatio::NineBySixteen => "9:16",
})
.map(|s| s.to_string());
let shot_type = args.shot_type
.map(|st| match st {
EnqueueKling3p0StandardImageToVideoShotType::Customize => "customize",
EnqueueKling3p0StandardImageToVideoShotType::Intelligent => "intelligent",
})
.map(|s| s.to_string());
let request = Kling3p0StandardImageToVideoInput {
prompt: args.prompt,
image_url: args.image_url,
end_image_url: args.end_image_url,
aspect_ratio: None, // Not applicable for image-to-video
aspect_ratio,
generate_audio: args.generate_audio,
duration,
negative_prompt: args.negative_prompt,
shot_type,
cfg_scale: None,
};
@@ -85,7 +171,9 @@ mod tests {
end_image_url: None,
generate_audio: Some(false),
negative_prompt: None,
duration: Some(EnqueueKling3p0StandardTextToVideoDuration::FiveSeconds),
duration: Some(EnqueueKling3p0StandardImageToVideoDuration::FiveSeconds),
aspect_ratio: None,
shot_type: None,
webhook_url: "https://example.com/webhook",
api_key: &api_key,
};
@@ -95,22 +183,22 @@ mod tests {
assert_eq!(args.calculate_cost_in_cents(), 84);
// 10s: (168 * 10 + 9) / 10 = 1689 / 10 = 168
args.duration = Some(EnqueueKling3p0StandardTextToVideoDuration::TenSeconds);
args.duration = Some(EnqueueKling3p0StandardImageToVideoDuration::TenSeconds);
assert_eq!(args.calculate_cost_in_cents(), 168);
// 15s: (168 * 15 + 9) / 10 = 2529 / 10 = 252
args.duration = Some(EnqueueKling3p0StandardTextToVideoDuration::FifteenSeconds);
args.duration = Some(EnqueueKling3p0StandardImageToVideoDuration::FifteenSeconds);
assert_eq!(args.calculate_cost_in_cents(), 252);
// Audio on: $0.252/sec
args.generate_audio = Some(true);
// 5s: (252 * 5 + 9) / 10 = 1269 / 10 = 126
args.duration = Some(EnqueueKling3p0StandardTextToVideoDuration::FiveSeconds);
args.duration = Some(EnqueueKling3p0StandardImageToVideoDuration::FiveSeconds);
assert_eq!(args.calculate_cost_in_cents(), 126);
// 10s: (252 * 10 + 9) / 10 = 2529 / 10 = 252
args.duration = Some(EnqueueKling3p0StandardTextToVideoDuration::TenSeconds);
args.duration = Some(EnqueueKling3p0StandardImageToVideoDuration::TenSeconds);
assert_eq!(args.calculate_cost_in_cents(), 252);
}
@@ -123,10 +211,12 @@ mod tests {
let args = EnqueueKling3p0StandardImageToVideoArgs {
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
prompt: "the t-rex skeleton gets off the podium and begins walking toward the camera".to_string(),
duration: Some(EnqueueKling3p0StandardTextToVideoDuration::FiveSeconds),
duration: Some(EnqueueKling3p0StandardImageToVideoDuration::FiveSeconds),
aspect_ratio: Some(EnqueueKling3p0StandardImageToVideoAspectRatio::SixteenByNine),
generate_audio: Some(true),
negative_prompt: None,
end_image_url: None,
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -137,21 +227,77 @@ mod tests {
Ok(())
}
#[tokio::test]
#[ignore] // manually run — fires a real API request per variant (expensive)
async fn test_all_aspect_ratios() -> AnyhowResult<()> {
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
for ar in EnqueueKling3p0StandardImageToVideoAspectRatio::iter() {
println!("--- aspect ratio: {:?} ---", ar);
let args = EnqueueKling3p0StandardImageToVideoArgs {
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
prompt: "the skeleton comes alive and roars at the camera".to_string(),
duration: Some(EnqueueKling3p0StandardImageToVideoDuration::ThreeSeconds),
aspect_ratio: Some(ar),
generate_audio: Some(false),
negative_prompt: None,
end_image_url: None,
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_kling_3p0_standard_image_to_video_webhook(args).await?;
println!("result: {:?}", result);
}
Ok(())
}
#[tokio::test]
#[ignore] // manually run — fires a real API request per variant (expensive)
async fn test_all_durations() -> AnyhowResult<()> {
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
for dur in EnqueueKling3p0StandardTextToVideoDuration::iter() {
for dur in EnqueueKling3p0StandardImageToVideoDuration::iter() {
println!("--- duration: {:?} ---", dur);
let args = EnqueueKling3p0StandardImageToVideoArgs {
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
prompt: "the skeleton slowly turns its head and opens its jaw".to_string(),
duration: Some(dur),
aspect_ratio: Some(EnqueueKling3p0StandardImageToVideoAspectRatio::SixteenByNine),
generate_audio: Some(false),
negative_prompt: None,
end_image_url: None,
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_kling_3p0_standard_image_to_video_webhook(args).await?;
println!("result: {:?}", result);
}
Ok(())
}
#[tokio::test]
#[ignore] // manually run — fires a real API request per variant (expensive)
async fn test_all_shot_types() -> AnyhowResult<()> {
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
for st in EnqueueKling3p0StandardImageToVideoShotType::iter() {
println!("--- shot type: {:?} ---", st);
let args = EnqueueKling3p0StandardImageToVideoArgs {
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
prompt: "the skeleton lurches forward dramatically".to_string(),
duration: Some(EnqueueKling3p0StandardImageToVideoDuration::FiveSeconds),
aspect_ratio: Some(EnqueueKling3p0StandardImageToVideoAspectRatio::SixteenByNine),
generate_audio: Some(true),
negative_prompt: None,
end_image_url: None,
shot_type: Some(st),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -14,6 +14,7 @@ pub struct EnqueueKling3p0ProTextToVideoArgs<'a, R: IntoUrl> {
pub negative_prompt: Option<String>,
pub duration: Option<EnqueueKling3p0ProTextToVideoDuration>,
pub aspect_ratio: Option<EnqueueKling3p0ProTextToVideoAspectRatio>,
pub shot_type: Option<EnqueueKling3p0ProTextToVideoShotType>,
// Fulfillment
pub webhook_url: R,
@@ -82,6 +83,12 @@ pub enum EnqueueKling3p0ProTextToVideoAspectRatio {
NineBySixteen,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumIter)]
pub enum EnqueueKling3p0ProTextToVideoShotType {
Customize,
Intelligent,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKling3p0ProTextToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Kling 3.0 Pro pricing:
@@ -114,12 +121,20 @@ pub async fn enqueue_kling_3p0_pro_text_to_video_webhook<R: IntoUrl>(
})
.map(|s| s.to_string());
let shot_type = args.shot_type
.map(|st| match st {
EnqueueKling3p0ProTextToVideoShotType::Customize => "customize",
EnqueueKling3p0ProTextToVideoShotType::Intelligent => "intelligent",
})
.map(|s| s.to_string());
let request = Kling3p0ProTextToVideoInput {
prompt: args.prompt,
generate_audio: args.generate_audio,
duration,
aspect_ratio,
negative_prompt: args.negative_prompt,
shot_type,
cfg_scale: None,
};
@@ -150,6 +165,7 @@ mod tests {
negative_prompt: None,
duration: Some(EnqueueKling3p0ProTextToVideoDuration::FiveSeconds),
aspect_ratio: None,
shot_type: None,
webhook_url: "https://example.com/webhook",
api_key: &api_key,
};
@@ -198,6 +214,7 @@ mod tests {
negative_prompt: None,
duration: Some(EnqueueKling3p0ProTextToVideoDuration::FiveSeconds),
aspect_ratio: Some(EnqueueKling3p0ProTextToVideoAspectRatio::SixteenByNine),
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -222,6 +239,7 @@ mod tests {
negative_prompt: None,
duration: Some(EnqueueKling3p0ProTextToVideoDuration::ThreeSeconds),
aspect_ratio: Some(ar),
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -246,6 +264,32 @@ mod tests {
negative_prompt: None,
duration: Some(dur),
aspect_ratio: Some(EnqueueKling3p0ProTextToVideoAspectRatio::SixteenByNine),
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_kling_3p0_pro_text_to_video_webhook(args).await?;
println!("result: {:?}", result);
}
Ok(())
}
#[tokio::test]
#[ignore] // manually run — fires a real API request per variant (expensive)
async fn test_all_shot_types() -> AnyhowResult<()> {
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
for st in EnqueueKling3p0ProTextToVideoShotType::iter() {
println!("--- shot type: {:?} ---", st);
let args = EnqueueKling3p0ProTextToVideoArgs {
prompt: "an eagle soars over a snow-capped mountain range".to_string(),
generate_audio: Some(true),
negative_prompt: None,
duration: Some(EnqueueKling3p0ProTextToVideoDuration::FiveSeconds),
aspect_ratio: Some(EnqueueKling3p0ProTextToVideoAspectRatio::SixteenByNine),
shot_type: Some(st),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -14,6 +14,7 @@ pub struct EnqueueKling3p0StandardTextToVideoArgs<'a, R: IntoUrl> {
pub negative_prompt: Option<String>,
pub duration: Option<EnqueueKling3p0StandardTextToVideoDuration>,
pub aspect_ratio: Option<EnqueueKling3p0StandardTextToVideoAspectRatio>,
pub shot_type: Option<EnqueueKling3p0StandardTextToVideoShotType>,
// Fulfillment
pub webhook_url: R,
@@ -82,6 +83,12 @@ pub enum EnqueueKling3p0StandardTextToVideoAspectRatio {
NineBySixteen,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::EnumIter)]
pub enum EnqueueKling3p0StandardTextToVideoShotType {
Customize,
Intelligent,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKling3p0StandardTextToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Kling 3.0 Standard pricing:
@@ -115,12 +122,20 @@ pub async fn enqueue_kling_3p0_standard_text_to_video_webhook<R: IntoUrl>(
})
.map(|s| s.to_string());
let shot_type = args.shot_type
.map(|st| match st {
EnqueueKling3p0StandardTextToVideoShotType::Customize => "customize",
EnqueueKling3p0StandardTextToVideoShotType::Intelligent => "intelligent",
})
.map(|s| s.to_string());
let request = Kling3p0StandardTextToVideoInput {
prompt: args.prompt,
generate_audio: args.generate_audio,
duration,
aspect_ratio,
negative_prompt: args.negative_prompt,
shot_type,
cfg_scale: None,
};
@@ -151,6 +166,7 @@ mod tests {
negative_prompt: None,
duration: Some(EnqueueKling3p0StandardTextToVideoDuration::FiveSeconds),
aspect_ratio: None,
shot_type: None,
webhook_url: "https://example.com/webhook",
api_key: &api_key,
};
@@ -199,6 +215,7 @@ mod tests {
negative_prompt: None,
duration: Some(EnqueueKling3p0StandardTextToVideoDuration::FiveSeconds),
aspect_ratio: Some(EnqueueKling3p0StandardTextToVideoAspectRatio::SixteenByNine),
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -223,6 +240,7 @@ mod tests {
negative_prompt: None,
duration: Some(EnqueueKling3p0StandardTextToVideoDuration::ThreeSeconds),
aspect_ratio: Some(ar),
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -247,6 +265,32 @@ mod tests {
negative_prompt: None,
duration: Some(dur),
aspect_ratio: Some(EnqueueKling3p0StandardTextToVideoAspectRatio::SixteenByNine),
shot_type: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_kling_3p0_standard_text_to_video_webhook(args).await?;
println!("result: {:?}", result);
}
Ok(())
}
#[tokio::test]
#[ignore] // manually run — fires a real API request per variant (expensive)
async fn test_all_shot_types() -> AnyhowResult<()> {
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
for st in EnqueueKling3p0StandardTextToVideoShotType::iter() {
println!("--- shot type: {:?} ---", st);
let args = EnqueueKling3p0StandardTextToVideoArgs {
prompt: "a bird takes flight from a tree branch".to_string(),
generate_audio: Some(true),
negative_prompt: None,
duration: Some(EnqueueKling3p0StandardTextToVideoDuration::FiveSeconds),
aspect_ratio: Some(EnqueueKling3p0StandardTextToVideoAspectRatio::SixteenByNine),
shot_type: Some(st),
api_key: &api_key,
webhook_url: "https://example.com/webhook",
};
@@ -21,7 +21,7 @@ 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_3p0_pro_image_to_video_webhook::{enqueue_kling_3p0_pro_image_to_video_webhook, EnqueueKling3p0ProImageToVideoArgs};
use fal_client::requests::webhook::video::image::enqueue_kling_3p0_pro_image_to_video_webhook::{enqueue_kling_3p0_pro_image_to_video_webhook, EnqueueKling3p0ProImageToVideoArgs, EnqueueKling3p0ProImageToVideoDuration};
use fal_client::requests::webhook::video::text::enqueue_kling_3p0_pro_text_to_video_webhook::{enqueue_kling_3p0_pro_text_to_video_webhook, EnqueueKling3p0ProTextToVideoArgs, EnqueueKling3p0ProTextToVideoAspectRatio, EnqueueKling3p0ProTextToVideoDuration};
use http_server_common::request::get_request_ip::get_request_ip;
use log::{error, info, warn};
@@ -40,22 +40,22 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
fn map_duration_i2v(duration: Option<Kling3p0ProMultiFunctionVideoGenDuration>) -> EnqueueKling3p0ProTextToVideoDuration {
fn map_duration_i2v(duration: Option<Kling3p0ProMultiFunctionVideoGenDuration>) -> EnqueueKling3p0ProImageToVideoDuration {
match duration {
Some(Kling3p0ProMultiFunctionVideoGenDuration::ThreeSeconds) => EnqueueKling3p0ProTextToVideoDuration::ThreeSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FourSeconds) => EnqueueKling3p0ProTextToVideoDuration::FourSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FiveSeconds) => EnqueueKling3p0ProTextToVideoDuration::FiveSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::SixSeconds) => EnqueueKling3p0ProTextToVideoDuration::SixSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::SevenSeconds) => EnqueueKling3p0ProTextToVideoDuration::SevenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::EightSeconds) => EnqueueKling3p0ProTextToVideoDuration::EightSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::NineSeconds) => EnqueueKling3p0ProTextToVideoDuration::NineSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::TenSeconds) => EnqueueKling3p0ProTextToVideoDuration::TenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::ElevenSeconds) => EnqueueKling3p0ProTextToVideoDuration::ElevenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::TwelveSeconds) => EnqueueKling3p0ProTextToVideoDuration::TwelveSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::ThirteenSeconds) => EnqueueKling3p0ProTextToVideoDuration::ThirteenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FourteenSeconds) => EnqueueKling3p0ProTextToVideoDuration::FourteenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FifteenSeconds) => EnqueueKling3p0ProTextToVideoDuration::FifteenSeconds,
None => EnqueueKling3p0ProTextToVideoDuration::FiveSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::ThreeSeconds) => EnqueueKling3p0ProImageToVideoDuration::ThreeSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FourSeconds) => EnqueueKling3p0ProImageToVideoDuration::FourSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FiveSeconds) => EnqueueKling3p0ProImageToVideoDuration::FiveSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::SixSeconds) => EnqueueKling3p0ProImageToVideoDuration::SixSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::SevenSeconds) => EnqueueKling3p0ProImageToVideoDuration::SevenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::EightSeconds) => EnqueueKling3p0ProImageToVideoDuration::EightSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::NineSeconds) => EnqueueKling3p0ProImageToVideoDuration::NineSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::TenSeconds) => EnqueueKling3p0ProImageToVideoDuration::TenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::ElevenSeconds) => EnqueueKling3p0ProImageToVideoDuration::ElevenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::TwelveSeconds) => EnqueueKling3p0ProImageToVideoDuration::TwelveSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::ThirteenSeconds) => EnqueueKling3p0ProImageToVideoDuration::ThirteenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FourteenSeconds) => EnqueueKling3p0ProImageToVideoDuration::FourteenSeconds,
Some(Kling3p0ProMultiFunctionVideoGenDuration::FifteenSeconds) => EnqueueKling3p0ProImageToVideoDuration::FifteenSeconds,
None => EnqueueKling3p0ProImageToVideoDuration::FiveSeconds,
}
}
@@ -206,6 +206,8 @@ pub async fn kling_3p0_pro_multi_function_video_gen_handler(
generate_audio: request.generate_audio,
negative_prompt: request.negative_prompt.clone(),
duration: Some(duration),
aspect_ratio: None,
shot_type: None,
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
@@ -241,6 +243,7 @@ pub async fn kling_3p0_pro_multi_function_video_gen_handler(
generate_audio: request.generate_audio,
duration: Some(duration),
aspect_ratio: Some(aspect_ratio),
shot_type: None,
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
@@ -21,7 +21,7 @@ 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_3p0_standard_image_to_video_webhook::{enqueue_kling_3p0_standard_image_to_video_webhook, EnqueueKling3p0StandardImageToVideoArgs};
use fal_client::requests::webhook::video::image::enqueue_kling_3p0_standard_image_to_video_webhook::{enqueue_kling_3p0_standard_image_to_video_webhook, EnqueueKling3p0StandardImageToVideoArgs, EnqueueKling3p0StandardImageToVideoDuration};
use fal_client::requests::webhook::video::text::enqueue_kling_3p0_standard_text_to_video_webhook::{enqueue_kling_3p0_standard_text_to_video_webhook, EnqueueKling3p0StandardTextToVideoArgs, EnqueueKling3p0StandardTextToVideoAspectRatio, EnqueueKling3p0StandardTextToVideoDuration};
use http_server_common::request::get_request_ip::get_request_ip;
use log::{error, info, warn};
@@ -40,22 +40,22 @@ use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
fn map_duration_i2v(duration: Option<Kling3p0StandardMultiFunctionVideoGenDuration>) -> EnqueueKling3p0StandardTextToVideoDuration {
fn map_duration_i2v(duration: Option<Kling3p0StandardMultiFunctionVideoGenDuration>) -> EnqueueKling3p0StandardImageToVideoDuration {
match duration {
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ThreeSeconds) => EnqueueKling3p0StandardTextToVideoDuration::ThreeSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FourSeconds) => EnqueueKling3p0StandardTextToVideoDuration::FourSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FiveSeconds) => EnqueueKling3p0StandardTextToVideoDuration::FiveSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::SixSeconds) => EnqueueKling3p0StandardTextToVideoDuration::SixSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::SevenSeconds) => EnqueueKling3p0StandardTextToVideoDuration::SevenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::EightSeconds) => EnqueueKling3p0StandardTextToVideoDuration::EightSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::NineSeconds) => EnqueueKling3p0StandardTextToVideoDuration::NineSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::TenSeconds) => EnqueueKling3p0StandardTextToVideoDuration::TenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ElevenSeconds) => EnqueueKling3p0StandardTextToVideoDuration::ElevenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::TwelveSeconds) => EnqueueKling3p0StandardTextToVideoDuration::TwelveSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ThirteenSeconds) => EnqueueKling3p0StandardTextToVideoDuration::ThirteenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FourteenSeconds) => EnqueueKling3p0StandardTextToVideoDuration::FourteenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FifteenSeconds) => EnqueueKling3p0StandardTextToVideoDuration::FifteenSeconds,
None => EnqueueKling3p0StandardTextToVideoDuration::FiveSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ThreeSeconds) => EnqueueKling3p0StandardImageToVideoDuration::ThreeSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FourSeconds) => EnqueueKling3p0StandardImageToVideoDuration::FourSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FiveSeconds) => EnqueueKling3p0StandardImageToVideoDuration::FiveSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::SixSeconds) => EnqueueKling3p0StandardImageToVideoDuration::SixSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::SevenSeconds) => EnqueueKling3p0StandardImageToVideoDuration::SevenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::EightSeconds) => EnqueueKling3p0StandardImageToVideoDuration::EightSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::NineSeconds) => EnqueueKling3p0StandardImageToVideoDuration::NineSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::TenSeconds) => EnqueueKling3p0StandardImageToVideoDuration::TenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ElevenSeconds) => EnqueueKling3p0StandardImageToVideoDuration::ElevenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::TwelveSeconds) => EnqueueKling3p0StandardImageToVideoDuration::TwelveSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::ThirteenSeconds) => EnqueueKling3p0StandardImageToVideoDuration::ThirteenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FourteenSeconds) => EnqueueKling3p0StandardImageToVideoDuration::FourteenSeconds,
Some(Kling3p0StandardMultiFunctionVideoGenDuration::FifteenSeconds) => EnqueueKling3p0StandardImageToVideoDuration::FifteenSeconds,
None => EnqueueKling3p0StandardImageToVideoDuration::FiveSeconds,
}
}
@@ -206,6 +206,8 @@ pub async fn kling_3p0_standard_multi_function_video_gen_handler(
generate_audio: request.generate_audio,
negative_prompt: request.negative_prompt.clone(),
duration: Some(duration),
aspect_ratio: None,
shot_type: None,
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
@@ -241,6 +243,7 @@ pub async fn kling_3p0_standard_multi_function_video_gen_handler(
generate_audio: request.generate_audio,
duration: Some(duration),
aspect_ratio: Some(aspect_ratio),
shot_type: None,
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};