mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
kling 2.1 pro
This commit is contained in:
1 parent
9cb6f3a60c
commit
bee9756af8
6 files changed
+383
-1
No files matched your search
+98
@@ -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;
|
||||
+241
@@ -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;
|
||||
Reference in new issue
Block a user