diff --git a/crates/api_clients/fal_client/src/requests/webhook/video/image/enqueue_pixverse_v5_image_to_video_webhook.rs b/crates/api_clients/fal_client/src/requests/webhook/video/image/enqueue_pixverse_v5_image_to_video_webhook.rs new file mode 100644 index 0000000000..d50f1b1cf4 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/webhook/video/image/enqueue_pixverse_v5_image_to_video_webhook.rs @@ -0,0 +1,152 @@ +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::pixverse::v5::pixverse_v5_image_to_video::{pixverse_v5_image_to_video, PixverseV5ImageToVideoInput}; +use fal::webhook::WebhookResponse; +use reqwest::IntoUrl; + +pub struct EnqueuePixverseV5ImageToVideoArgs<'a, R: IntoUrl> { + // Request required + pub prompt: String, + pub image_url: String, + + // Optional args + pub negative_prompt: Option, + + // NB: eight-second videos do not work with 1080P + pub duration: Option, + pub aspect_ratio: Option, + pub resolution: Option, + pub style: Option, + + // Fulfillment + pub webhook_url: R, + pub api_key: &'a FalApiKey, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueuePixverseV5ImageToVideoDurationSeconds { + Five, + Eight, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueuePixverseV5ImageToVideoResolution { + ThreeSixtyP, + FiveFortyP, + SevenTwentyP, + TenEightyP, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueuePixverseV5ImageToVideoAspectRatio { + Square, + SixteenByNine, + FourByThree, + NineBySixteen, + ThreeByFour, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueuePixverseV5ImageToVideoStyle { + Anime, + Animation3d, + Clay, + Comic, + Cyberpunk, +} + +pub async fn enqueue_pixverse_v5_image_to_video_webhook( + args: EnqueuePixverseV5ImageToVideoArgs<'_, R> +) -> Result { + + let duration = args.duration + .map(|resolution| match resolution { + EnqueuePixverseV5ImageToVideoDurationSeconds::Five => "5", + EnqueuePixverseV5ImageToVideoDurationSeconds::Eight=> "8", + }) + .map(|s| s.to_string()); + + let aspect_ratio = args.aspect_ratio + .map(|aspect_ratio| match aspect_ratio { + EnqueuePixverseV5ImageToVideoAspectRatio::Square => "1:1", + EnqueuePixverseV5ImageToVideoAspectRatio::SixteenByNine => "16:9", + EnqueuePixverseV5ImageToVideoAspectRatio::FourByThree => "4:3", + EnqueuePixverseV5ImageToVideoAspectRatio::NineBySixteen => "9:16", + EnqueuePixverseV5ImageToVideoAspectRatio::ThreeByFour => "3:4", + }) + .map(|s| s.to_string()); + + let resolution = args.resolution + .map(|resolution| match resolution { + EnqueuePixverseV5ImageToVideoResolution::ThreeSixtyP => "360p", + EnqueuePixverseV5ImageToVideoResolution::FiveFortyP => "540p", + EnqueuePixverseV5ImageToVideoResolution::SevenTwentyP => "720p", + EnqueuePixverseV5ImageToVideoResolution::TenEightyP => "1080p", + }) + .map(|s| s.to_string()); + + let style = args.style + .map(|style| match style { + EnqueuePixverseV5ImageToVideoStyle::Anime => "anime", + EnqueuePixverseV5ImageToVideoStyle::Animation3d => "3d_animation", + EnqueuePixverseV5ImageToVideoStyle::Clay => "clay", + EnqueuePixverseV5ImageToVideoStyle::Comic => "comic", + EnqueuePixverseV5ImageToVideoStyle::Cyberpunk => "cyberpunk", + }) + .map(|s| s.to_string()); + + let request = PixverseV5ImageToVideoInput { + prompt: args.prompt, + image_url: args.image_url, + // Optionals + duration, + negative_prompt: args.negative_prompt, + aspect_ratio, + resolution, + style, + // Constant + seed: None, + }; + + let result = pixverse_v5_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::image::enqueue_pixverse_v5_image_to_video_webhook::{enqueue_pixverse_v5_image_to_video_webhook, EnqueuePixverseV5ImageToVideoArgs, EnqueuePixverseV5ImageToVideoAspectRatio, EnqueuePixverseV5ImageToVideoDurationSeconds, EnqueuePixverseV5ImageToVideoResolution, EnqueuePixverseV5ImageToVideoStyle}; + use errors::AnyhowResult; + use std::fs::read_to_string; + use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL; + + #[tokio::test] + #[ignore] + async fn test() -> AnyhowResult<()> { + // XXX: Don't commit secrets! + let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?; + + let api_key = FalApiKey::from_str(&secret); + + let args = EnqueuePixverseV5ImageToVideoArgs { + image_url: TREX_SKELETON_IMAGE_URL.to_string(), + prompt: "the t-rex skeleton gets off the podium and begins walking to the camera. the camera orbits slightly. The t-rex gets close and then bites.".to_string(), + negative_prompt: None, + duration: Some(EnqueuePixverseV5ImageToVideoDurationSeconds::Five), + style: Some(EnqueuePixverseV5ImageToVideoStyle::Cyberpunk), + aspect_ratio: Some(EnqueuePixverseV5ImageToVideoAspectRatio::FourByThree), + resolution: Some(EnqueuePixverseV5ImageToVideoResolution::TenEightyP), + api_key: &api_key, + webhook_url: "https://example.com/webhook", + }; + + let result = enqueue_pixverse_v5_image_to_video_webhook(args).await?; + + Ok(()) + } +} diff --git a/crates/api_clients/fal_client/src/requests/webhook/video/image/mod.rs b/crates/api_clients/fal_client/src/requests/webhook/video/image/mod.rs index dd70b03c59..aac73ccc6a 100644 --- a/crates/api_clients/fal_client/src/requests/webhook/video/image/mod.rs +++ b/crates/api_clients/fal_client/src/requests/webhook/video/image/mod.rs @@ -1,3 +1,4 @@ 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; \ No newline at end of file +pub mod enqueue_kling_v2p6_pro_image_to_video_webhook; +pub mod enqueue_pixverse_v5_image_to_video_webhook; \ No newline at end of file diff --git a/crates/api_clients/fal_client/src/requests/webhook/video/text/enqueue_pixverse_v5_text_to_video_webhook.rs b/crates/api_clients/fal_client/src/requests/webhook/video/text/enqueue_pixverse_v5_text_to_video_webhook.rs new file mode 100644 index 0000000000..e9f0439ef6 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/webhook/video/text/enqueue_pixverse_v5_text_to_video_webhook.rs @@ -0,0 +1,148 @@ +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::pixverse::v5::pixverse_v5_text_to_video::{pixverse_v5_text_to_video, PixverseV5TextToVideoInput}; +use fal::webhook::WebhookResponse; +use reqwest::IntoUrl; + +pub struct EnqueuePixverseV5TextToVideoArgs<'a, R: IntoUrl> { + // Request required + pub prompt: String, + + // Optional args + pub negative_prompt: Option, + + // NB: eight-second videos do not work with 1080P + pub duration: Option, + pub aspect_ratio: Option, + pub resolution: Option, + pub style: Option, + + // Fulfillment + pub webhook_url: R, + pub api_key: &'a FalApiKey, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueuePixverseV5TextToVideoDurationSeconds { + Five, + Eight, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueuePixverseV5TextToVideoResolution { + ThreeSixtyP, + FiveFortyP, + SevenTwentyP, + TenEightyP, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueuePixverseV5TextToVideoAspectRatio { + Square, + SixteenByNine, + FourByThree, + NineBySixteen, + ThreeByFour, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueuePixverseV5TextToVideoStyle { + Anime, + Animation3d, + Clay, + Comic, + Cyberpunk, +} + +pub async fn enqueue_pixverse_v5_text_to_video_webhook( + args: EnqueuePixverseV5TextToVideoArgs<'_, R> +) -> Result { + + let duration = args.duration + .map(|resolution| match resolution { + EnqueuePixverseV5TextToVideoDurationSeconds::Five => "5", + EnqueuePixverseV5TextToVideoDurationSeconds::Eight=> "8", + }) + .map(|s| s.to_string()); + + let aspect_ratio = args.aspect_ratio + .map(|aspect_ratio| match aspect_ratio { + EnqueuePixverseV5TextToVideoAspectRatio::Square => "1:1", + EnqueuePixverseV5TextToVideoAspectRatio::SixteenByNine => "16:9", + EnqueuePixverseV5TextToVideoAspectRatio::FourByThree => "4:3", + EnqueuePixverseV5TextToVideoAspectRatio::NineBySixteen => "9:16", + EnqueuePixverseV5TextToVideoAspectRatio::ThreeByFour => "3:4", + }) + .map(|s| s.to_string()); + + let resolution = args.resolution + .map(|resolution| match resolution { + EnqueuePixverseV5TextToVideoResolution::ThreeSixtyP => "360p", + EnqueuePixverseV5TextToVideoResolution::FiveFortyP => "540p", + EnqueuePixverseV5TextToVideoResolution::SevenTwentyP => "720p", + EnqueuePixverseV5TextToVideoResolution::TenEightyP => "1080p", + }) + .map(|s| s.to_string()); + + let style = args.style + .map(|style| match style { + EnqueuePixverseV5TextToVideoStyle::Anime => "anime", + EnqueuePixverseV5TextToVideoStyle::Animation3d => "3d_animation", + EnqueuePixverseV5TextToVideoStyle::Clay => "clay", + EnqueuePixverseV5TextToVideoStyle::Comic => "comic", + EnqueuePixverseV5TextToVideoStyle::Cyberpunk => "cyberpunk", + }) + .map(|s| s.to_string()); + + let request = PixverseV5TextToVideoInput { + prompt: args.prompt, + // Optionals + duration, + negative_prompt: args.negative_prompt, + aspect_ratio, + resolution, + style, + // Constant + seed: None, + }; + + let result = pixverse_v5_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 crate::creds::fal_api_key::FalApiKey; + use crate::requests::webhook::video::text::enqueue_pixverse_v5_text_to_video_webhook::{enqueue_pixverse_v5_text_to_video_webhook, EnqueuePixverseV5TextToVideoArgs, EnqueuePixverseV5TextToVideoAspectRatio, EnqueuePixverseV5TextToVideoDurationSeconds, EnqueuePixverseV5TextToVideoResolution, EnqueuePixverseV5TextToVideoStyle}; + use errors::AnyhowResult; + use std::fs::read_to_string; + + #[tokio::test] + #[ignore] + async fn test() -> AnyhowResult<()> { + // XXX: Don't commit secrets! + let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?; + + let api_key = FalApiKey::from_str(&secret); + + let args = EnqueuePixverseV5TextToVideoArgs { + prompt: "an angry racoon shakes its fist at the garbage truck as it drives away, the camera orbits the racoon. the racoon sighs".to_string(), + negative_prompt: None, + duration: Some(EnqueuePixverseV5TextToVideoDurationSeconds::Eight), + style: Some(EnqueuePixverseV5TextToVideoStyle::Anime), + aspect_ratio: Some(EnqueuePixverseV5TextToVideoAspectRatio::FourByThree), + resolution: Some(EnqueuePixverseV5TextToVideoResolution::SevenTwentyP), + api_key: &api_key, + webhook_url: "https://example.com/webhook", + }; + + let result = enqueue_pixverse_v5_text_to_video_webhook(args).await?; + + Ok(()) + } +} diff --git a/crates/api_clients/fal_client/src/requests/webhook/video/text/mod.rs b/crates/api_clients/fal_client/src/requests/webhook/video/text/mod.rs index 457b4e2f3f..5ee48f2dfe 100644 --- a/crates/api_clients/fal_client/src/requests/webhook/video/text/mod.rs +++ b/crates/api_clients/fal_client/src/requests/webhook/video/text/mod.rs @@ -1,2 +1,3 @@ pub mod enqueue_kling_v2p5_turbo_pro_text_to_video_webhook; -pub mod enqueue_kling_v2p6_pro_text_to_video_webhook; \ No newline at end of file +pub mod enqueue_kling_v2p6_pro_text_to_video_webhook; +pub mod enqueue_pixverse_v5_text_to_video_webhook; \ No newline at end of file diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/mod.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/mod.rs index 6e8f6f7066..b32a9d21fe 100644 --- a/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/mod.rs +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/mod.rs @@ -12,3 +12,17 @@ ))) )] pub mod v3_5; +#[cfg(any( + feature = "endpoints", + feature = "endpoints_fal-ai", + feature = "endpoints_fal-ai_pixverse" +))] +#[cfg_attr( + docsrs, + doc(cfg(any( + feature = "endpoints", + feature = "endpoints_fal-ai", + feature = "endpoints_fal-ai_pixverse" + ))) +)] +pub mod v5; diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/v5/mod.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/v5/mod.rs new file mode 100644 index 0000000000..fd2e473d3d --- /dev/null +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/v5/mod.rs @@ -0,0 +1,2 @@ +pub mod pixverse_v5_image_to_video; +pub mod pixverse_v5_text_to_video; \ No newline at end of file diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/v5/pixverse_v5_image_to_video.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/v5/pixverse_v5_image_to_video.rs new file mode 100644 index 0000000000..0d3a4e2ac7 --- /dev/null +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/v5/pixverse_v5_image_to_video.rs @@ -0,0 +1,56 @@ +use crate::prelude::{Deserialize, FalRequest, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct PixverseV5ImageToVideoInput { + pub prompt: String, + + /// Starting frame + pub image_url: String, + + /// Resolution + /// Possible enum values: 360p, 540p, 720p, 1080p + /// Default is 720p + #[serde(skip_serializing_if = "Option::is_none")] + pub resolution: Option, + + /// The aspect ratio of the generated video + /// Default value: "16:9" + /// Possible enum values: 16:9, 4:3, 1:1, 3:4, 9:16 + #[serde(skip_serializing_if = "Option::is_none")] + pub aspect_ratio: Option, + + /// Optional negative prompt + #[serde(skip_serializing_if = "Option::is_none")] + pub negative_prompt: Option, + + /// Duration in seconds + /// Options: "5", "8" + #[serde(skip_serializing_if = "Option::is_none")] + pub duration: Option, + + /// Optional style + /// Possible enum values: anime, 3d_animation, clay, comic, cyberpunk + #[serde(skip_serializing_if = "Option::is_none")] + pub style: Option, + + /// Optional seed + #[serde(skip_serializing_if = "Option::is_none")] + pub seed: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PixverseV5ImageToVideoOutput { + 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 pixverse_v5_image_to_video( + params: PixverseV5ImageToVideoInput, +) -> FalRequest { + FalRequest::new("fal-ai/pixverse/v5/image-to-video", params) +} diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/v5/pixverse_v5_text_to_video.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/v5/pixverse_v5_text_to_video.rs new file mode 100644 index 0000000000..92709e53f8 --- /dev/null +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/pixverse/v5/pixverse_v5_text_to_video.rs @@ -0,0 +1,53 @@ +use crate::prelude::{Deserialize, FalRequest, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct PixverseV5TextToVideoInput { + pub prompt: String, + + /// Resolution + /// Possible enum values: 360p, 540p, 720p, 1080p + /// Default is 720p + #[serde(skip_serializing_if = "Option::is_none")] + pub resolution: Option, + + /// The aspect ratio of the generated video + /// Default value: "16:9" + /// Possible enum values: 16:9, 4:3, 1:1, 3:4, 9:16 + #[serde(skip_serializing_if = "Option::is_none")] + pub aspect_ratio: Option, + + /// Optional negative prompt + #[serde(skip_serializing_if = "Option::is_none")] + pub negative_prompt: Option, + + /// Duration in seconds + /// Options: "5", "8" + #[serde(skip_serializing_if = "Option::is_none")] + pub duration: Option, + + /// Optional style + /// Possible enum values: anime, 3d_animation, clay, comic, cyberpunk + #[serde(skip_serializing_if = "Option::is_none")] + pub style: Option, + + /// Optional seed + #[serde(skip_serializing_if = "Option::is_none")] + pub seed: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PixverseV5TextToVideoOutput { + 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 pixverse_v5_text_to_video( + params: PixverseV5TextToVideoInput, +) -> FalRequest { + FalRequest::new("fal-ai/pixverse/v5/text-to-video", params) +}