kling 2.1 pro

This commit is contained in:
Brandon Thomas committed 2025-06-15 11:38:41 -04:00
1 parent 9cb6f3a60c
commit bee9756af8
6 files changed
+383 -1

No files matched your search

@@ -0,0 +1,98 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::kling_video::v2_1::pro::image_to_video::{image_to_video, ProImageToVideoRequest};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct Kling21ProArgs<'a, U: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub webhook_url: V,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
pub duration: Kling21ProDuration,
pub aspect_ratio: Kling21ProAspectRatio,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling21ProDuration {
Default,
FiveSeconds,
TenSeconds,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling21ProAspectRatio {
Square, // 1:1
WideSixteenNine, // 16:9
TallNineSixteen, // 9:16
}
pub async fn enqueue_kling_21_pro_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
args: Kling21ProArgs<'_, U, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Kling21ProDuration::Default => None,
Kling21ProDuration::FiveSeconds => Some("5".to_string()), // Gross...
Kling21ProDuration::TenSeconds => Some("10".to_string()),
};
let aspect_ratio = match args.aspect_ratio {
Kling21ProAspectRatio::Square => Some("1:1".to_string()),
Kling21ProAspectRatio::WideSixteenNine => Some("16:9".to_string()),
Kling21ProAspectRatio::TallNineSixteen => Some("9:16".to_string()),
};
let image_url = args.image_url.as_str().to_string();
let request = ProImageToVideoRequest {
image_url,
prompt: args.prompt.to_string(),
aspect_ratio,
duration,
// Maybe expose these later
cfg_scale: None,
negative_prompt: None,
tail_image_url: None,
};
let result = 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 crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::enqueue_kling_20_pro_image_to_video_webhook::{enqueue_kling_21_pro_image_to_video_webhook, Kling21ProArgs, Kling21ProAspectRatio, Kling21ProDuration};
use errors::AnyhowResult;
use std::fs::read_to_string;
#[tokio::test]
#[ignore]
async fn test_Kling21_video() -> AnyhowResult<()> {
let image_url = "https://cdn-2.fakeyou.com/media/3/4/h/f/s/34hfsmt8e38rvne6mwa4pwbxr6292sgy/image_34hfsmt8e38rvne6mwa4pwbxr6292sgy.png";
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Kling21ProArgs {
image_url: image_url,
prompt: "a shot of the mountains, the camera rotates around to show the mountain range",
api_key: &api_key,
duration: Kling21ProDuration::Default,
aspect_ratio: Kling21ProAspectRatio::WideSixteenNine,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_kling_21_pro_image_to_video_webhook(args).await?;
Ok(())
}
}
@@ -1 +1,2 @@
pub mod enqueue_kling_16_pro_image_to_video_webhook;
pub mod enqueue_kling_16_pro_image_to_video_webhook;
pub mod enqueue_kling_20_pro_image_to_video_webhook;
@@ -40,3 +40,17 @@ pub mod v1_5;
)))
)]
pub mod v1_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 v2_1;
@@ -0,0 +1,14 @@
#[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 pro;
@@ -0,0 +1,241 @@
#[allow(unused_imports)]
use crate::prelude::*;
#[allow(unused_imports)]
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
use std::collections::HashMap;
// #[derive(Debug, Serialize, Deserialize, Default)]
// pub struct CameraControl {
// /// The type of camera movement
// /// "horizontal"
// pub movement_type: String,
// /// The value of the camera movement
// /// 10
// pub movement_value: i64,
// }
// #[derive(Debug, Serialize, Deserialize, Default)]
// pub struct DynamicMask {
// /// URL of the image for Dynamic Brush Application Area (Mask image created by users using the motion brush)
// /// "https://h2.inkwai.com/bs2/upload-ylab-stunt/ai_portal/1732888130/WU8spl23dA/dynamic_mask_1.png"
// pub mask_url: String,
// /// List of trajectories
// /// [{"x":279,"y":219},{"x":417,"y":65}]
// #[serde(skip_serializing_if = "Option::is_none")]
// pub trajectories: Option<Vec<Option<Trajectory>>>,
// }
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct File {
/// The mime type of the file.
/// "image/png"
#[serde(skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
/// File data
#[serde(skip_serializing_if = "Option::is_none")]
pub file_data: Option<String>,
/// The name of the file. It will be auto-generated if not provided.
/// "z9RV14K95DvU.png"
#[serde(skip_serializing_if = "Option::is_none")]
pub file_name: Option<String>,
/// The size of the file in bytes.
/// 4404019
#[serde(skip_serializing_if = "Option::is_none")]
pub file_size: Option<i64>,
/// The URL where the file can be downloaded from.
pub url: String,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct HTTPValidationError {
#[serde(skip_serializing_if = "Option::is_none")]
pub detail: Option<Vec<Option<ValidationError>>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct I2VOutput {
/// The generated video
/// {"url":"https://v2.fal.media/files/36087878b0c1435bb75c19b64b7db178_output.mp4"}
pub video: File,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ImageToVideoRequest {
/// The aspect ratio of the generated video frame
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// The CFG (Classifier Free Guidance) scale is a measure of how close you want
/// the model to stick to your prompt.
#[serde(skip_serializing_if = "Option::is_none")]
pub cfg_scale: Option<f64>,
/// The duration of the generated video in seconds
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
pub image_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub negative_prompt: Option<String>,
pub prompt: String,
}
// #[derive(Debug, Serialize, Deserialize, Default)]
// pub struct KlingV1I2VOutput {
// /// The generated video
// /// {"url":"https://v3.fal.media/files/kangaroo/VUmAU3JvzS7mxwdgSU9zj_output.mp4"}
// pub video: File,
// }
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ProImageToVideoRequest {
/// The aspect ratio of the generated video frame
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// The CFG (Classifier Free Guidance) scale is a measure of how close you want
/// the model to stick to your prompt.
#[serde(skip_serializing_if = "Option::is_none")]
pub cfg_scale: Option<f64>,
/// The duration of the generated video in seconds
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,
pub image_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub negative_prompt: Option<String>,
pub prompt: String,
/// URL of the image to be used for the end of the video
#[serde(skip_serializing_if = "Option::is_none")]
pub tail_image_url: Option<String>,
}
// #[derive(Debug, Serialize, Deserialize, Default)]
// pub struct T2VOutput {
// /// The generated video
// /// {"url":"https://v2.fal.media/files/fb33a862b94d4d7195e610e4cbc5d392_output.mp4"}
// pub video: File,
// }
//
// #[derive(Debug, Serialize, Deserialize, Default)]
// pub struct TextToVideoRequest {
// /// The aspect ratio of the generated video frame
// #[serde(skip_serializing_if = "Option::is_none")]
// pub aspect_ratio: Option<String>,
// /// The CFG (Classifier Free Guidance) scale is a measure of how close you want
// /// the model to stick to your prompt.
// #[serde(skip_serializing_if = "Option::is_none")]
// pub cfg_scale: Option<f64>,
// /// The duration of the generated video in seconds
// #[serde(skip_serializing_if = "Option::is_none")]
// pub duration: Option<String>,
// #[serde(skip_serializing_if = "Option::is_none")]
// pub negative_prompt: Option<String>,
// pub prompt: String,
// }
//
// #[derive(Debug, Serialize, Deserialize, Default)]
// pub struct Trajectory {
// /// X coordinate of the motion trajectory
// /// 279
// pub x: i64,
// /// Y coordinate of the motion trajectory
// /// 219
// pub y: i64,
// }
//
// #[derive(Debug, Serialize, Deserialize, Default)]
// pub struct V1ImageToVideoRequest {
// /// The aspect ratio of the generated video frame
// #[serde(skip_serializing_if = "Option::is_none")]
// pub aspect_ratio: Option<String>,
// /// The CFG (Classifier Free Guidance) scale is a measure of how close you want
// /// the model to stick to your prompt.
// #[serde(skip_serializing_if = "Option::is_none")]
// pub cfg_scale: Option<f64>,
// /// The duration of the generated video in seconds
// #[serde(skip_serializing_if = "Option::is_none")]
// pub duration: Option<String>,
// /// List of dynamic masks
// #[serde(skip_serializing_if = "Option::is_none")]
// pub dynamic_masks: Option<Vec<Option<DynamicMask>>>,
// /// URL of the image to be used for the video
// /// "https://h2.inkwai.com/bs2/upload-ylab-stunt/se/ai_portal_queue_mmu_image_upscale_aiweb/3214b798-e1b4-4b00-b7af-72b5b0417420_raw_image_0.jpg"
// pub image_url: String,
// #[serde(skip_serializing_if = "Option::is_none")]
// pub negative_prompt: Option<String>,
// /// The prompt for the video
// /// "The astronaut stood up and walked away"
// pub prompt: String,
// /// URL of the image for Static Brush Application Area (Mask image created by users using the motion brush)
// /// "https://h2.inkwai.com/bs2/upload-ylab-stunt/ai_portal/1732888177/cOLNrShrSO/static_mask.png"
// #[serde(skip_serializing_if = "Option::is_none")]
// pub static_mask_url: Option<String>,
// /// URL of the image to be used for the end of the video
// #[serde(skip_serializing_if = "Option::is_none")]
// pub tail_image_url: Option<String>,
// }
//
// #[derive(Debug, Serialize, Deserialize, Default)]
// pub struct V1TextToVideoRequest {
// /// Advanced Camera control parameters
// #[serde(skip_serializing_if = "Option::is_none")]
// pub advanced_camera_control: Option<Option<CameraControl>>,
// /// The aspect ratio of the generated video frame
// #[serde(skip_serializing_if = "Option::is_none")]
// pub aspect_ratio: Option<String>,
// /// Camera control parameters
// #[serde(skip_serializing_if = "Option::is_none")]
// pub camera_control: Option<String>,
// /// The CFG (Classifier Free Guidance) scale is a measure of how close you want
// /// the model to stick to your prompt.
// #[serde(skip_serializing_if = "Option::is_none")]
// pub cfg_scale: Option<f64>,
// /// The duration of the generated video in seconds
// #[serde(skip_serializing_if = "Option::is_none")]
// pub duration: Option<String>,
// #[serde(skip_serializing_if = "Option::is_none")]
// pub negative_prompt: Option<String>,
// pub prompt: String,
// }
//
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ValidationError {
pub loc: Vec<serde_json::Value>,
pub msg: String,
#[serde(rename = "type")]
pub ty: String,
}
//
// #[derive(Debug, Serialize, Deserialize, Default)]
// pub struct VideoEffectsOutput {
// /// The generated video
// /// {"content_type":"video/mp4","file_name":"output.mp4","url":"https://storage.googleapis.com/falserverless/kling/kling_ex.mp4.mp4"}
// pub video: File,
// }
//
// #[derive(Debug, Serialize, Deserialize, Default)]
// pub struct VideoEffectsRequest {
// /// The duration of the generated video in seconds
// #[serde(skip_serializing_if = "Option::is_none")]
// pub duration: Option<String>,
// /// The effect scene to use for the video generation
// /// "hug"
// pub effect_scene: String,
// /// URL of the image to be used for the squish and expansion video
// /// "https://h2.inkwai.com/bs2/upload-ylab-stunt/se/ai_portal_queue_mmu_image_upscale_aiweb/3214b798-e1b4-4b00-b7af-72b5b0417420_raw_image_0.jpg"
// pub image_url: String,
// /// URL of images to be used for hug, kiss or heart_gesture video.
// /// ["https://storage.googleapis.com/falserverless/juggernaut_examples/VHXMavzPyI27zi6JseyL4.png","https://storage.googleapis.com/falserverless/juggernaut_examples/QEW5VrzccxGva7mPfEXjf.png"]
// #[serde(skip_serializing_if = "Option::is_none")]
// pub input_image_urls: Option<Vec<Option<String>>>,
// }
/// Kling 1.0
///
/// Category: text-to-video
/// Machine Type: A100
///
///
/// Kling 1.6 (pro) Image to Video API.
pub fn image_to_video(
params: ProImageToVideoRequest,
) -> FalRequest<ProImageToVideoRequest, I2VOutput> {
FalRequest::new("fal-ai/kling-video/v2.1/pro/image-to-video", params)
}
@@ -0,0 +1,14 @@
#[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 image_to_video;