From fa9e95187f5c50b445b1deec70184cf19b10fc24 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 9 Mar 2026 15:36:59 -0400 Subject: [PATCH] endpoint bindings --- .../http_qwen_edit_2511_edit_image_angle.rs | 88 ++++++++++ .../fal_client/src/requests/http/image/mod.rs | 1 + ...qwen_edit_2511_edit_image_angle_webhook.rs | 150 ++++++++++++++++++ .../src/requests/webhook/image/angle/mod.rs | 1 + .../src/requests/webhook/image/mod.rs | 1 + 5 files changed, 241 insertions(+) create mode 100644 crates/api_clients/fal_client/src/requests/http/image/http_qwen_edit_2511_edit_image_angle.rs create mode 100644 crates/api_clients/fal_client/src/requests/webhook/image/angle/enqueue_qwen_edit_2511_edit_image_angle_webhook.rs create mode 100644 crates/api_clients/fal_client/src/requests/webhook/image/angle/mod.rs diff --git a/crates/api_clients/fal_client/src/requests/http/image/http_qwen_edit_2511_edit_image_angle.rs b/crates/api_clients/fal_client/src/requests/http/image/http_qwen_edit_2511_edit_image_angle.rs new file mode 100644 index 0000000000..91d579a990 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/http/image/http_qwen_edit_2511_edit_image_angle.rs @@ -0,0 +1,88 @@ +use fal::request::FalRequest; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct QwenEdit2511EditImageAngleInput { + /// The URL of the image to adjust camera angle for. + pub image_urls: Vec, + + /// Horizontal rotation angle around the object in degrees. 0°=front view. + /// Default: 0 + #[serde(skip_serializing_if = "Option::is_none")] + pub horizontal_angle: Option, + + /// Vertical camera angle in degrees. -30°=low-angle shot, 30°=high-angle shot. + /// Default: 0 + #[serde(skip_serializing_if = "Option::is_none")] + pub vertical_angle: Option, + + /// Camera zoom/distance. 0=wide shot, 5=medium shot, 10=close-up. + /// Default: 5 + #[serde(skip_serializing_if = "Option::is_none")] + pub zoom: Option, + + /// Additional text to append to the automatically generated prompt. + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_prompt: Option, + + /// The scale factor for the LoRA model. Controls the strength of the effect. + /// Default: 1 + #[serde(skip_serializing_if = "Option::is_none")] + pub lora_scale: Option, + + /// The size of the generated image. + /// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9 + #[serde(skip_serializing_if = "Option::is_none")] + pub image_size: Option, + + /// The CFG (Classifier Free Guidance) scale. + /// Default: 4.5 + #[serde(skip_serializing_if = "Option::is_none")] + pub guidance_scale: Option, + + /// The number of inference steps to perform. + /// Default: 28 + #[serde(skip_serializing_if = "Option::is_none")] + pub num_inference_steps: Option, + + /// The negative prompt for the generation. + #[serde(skip_serializing_if = "Option::is_none")] + pub negative_prompt: Option, + + /// Random seed for reproducibility. + #[serde(skip_serializing_if = "Option::is_none")] + pub seed: Option, + + /// Number of images to generate. + /// Default: 1 + #[serde(skip_serializing_if = "Option::is_none")] + pub num_images: Option, + + /// Whether to enable the safety checker. + /// Default: true + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_safety_checker: Option, + + /// Output format: png, jpeg, webp. + /// Default: "png" + #[serde(skip_serializing_if = "Option::is_none")] + pub output_format: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct QwenEdit2511EditImageAngleFile { + pub url: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct QwenEdit2511EditImageAngleOutput { + pub images: Vec, + pub seed: u64, + pub prompt: Option, +} + +pub fn http_qwen_edit_2511_edit_image_angle( + params: QwenEdit2511EditImageAngleInput, +) -> FalRequest { + FalRequest::new("fal-ai/qwen-image-edit-2511-multiple-angles", params) +} diff --git a/crates/api_clients/fal_client/src/requests/http/image/mod.rs b/crates/api_clients/fal_client/src/requests/http/image/mod.rs index cdb5f07873..22c484f3d2 100644 --- a/crates/api_clients/fal_client/src/requests/http/image/mod.rs +++ b/crates/api_clients/fal_client/src/requests/http/image/mod.rs @@ -1,4 +1,5 @@ pub mod http_nano_banana_2_edit_image; pub mod http_nano_banana_2_text_to_image; +pub mod http_qwen_edit_2511_edit_image_angle; pub mod http_seedream_5_edit_image; pub mod http_seedream_5_text_to_image; diff --git a/crates/api_clients/fal_client/src/requests/webhook/image/angle/enqueue_qwen_edit_2511_edit_image_angle_webhook.rs b/crates/api_clients/fal_client/src/requests/webhook/image/angle/enqueue_qwen_edit_2511_edit_image_angle_webhook.rs new file mode 100644 index 0000000000..9cdb0e2c83 --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/webhook/image/angle/enqueue_qwen_edit_2511_edit_image_angle_webhook.rs @@ -0,0 +1,150 @@ +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::http::image::http_qwen_edit_2511_edit_image_angle::{http_qwen_edit_2511_edit_image_angle, QwenEdit2511EditImageAngleInput}; +use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents}; +use fal::webhook::WebhookResponse; +use reqwest::IntoUrl; + +pub struct EnqueueQwenEdit2511EditImageAngleArgs<'a, R: IntoUrl> { + // Request required + pub image_urls: Vec, + + // Camera parameters + pub horizontal_angle: Option, + pub vertical_angle: Option, + pub zoom: Option, + + // Optional args + pub additional_prompt: Option<&'a str>, + pub num_images: Option, + pub image_size: Option, + pub lora_scale: Option, + pub guidance_scale: Option, + pub num_inference_steps: Option, + + // Fulfillment + pub webhook_url: R, + pub api_key: &'a FalApiKey, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueueQwenEdit2511AngleNumImages { + One, + Two, + Three, + Four, +} + +#[derive(Copy, Clone, Debug)] +pub enum EnqueueQwenEdit2511AngleImageSize { + Square, + SquareHd, + PortraitFourThree, + PortraitSixteenNine, + LandscapeFourThree, + LandscapeSixteenNine, +} + +impl FalRequestCostCalculator for EnqueueQwenEdit2511EditImageAngleArgs<'_, U> { + fn calculate_cost_in_cents(&self) -> UsdCents { + // Pricing: $0.035 per megapixel. + // For a 1024x1024 image (~1 MP), that's ~4 cents per image. + let unit_cost = 4; + let cost = match self.num_images { + None => unit_cost, + Some(EnqueueQwenEdit2511AngleNumImages::One) => unit_cost, + Some(EnqueueQwenEdit2511AngleNumImages::Two) => unit_cost * 2, + Some(EnqueueQwenEdit2511AngleNumImages::Three) => unit_cost * 3, + Some(EnqueueQwenEdit2511AngleNumImages::Four) => unit_cost * 4, + }; + cost as UsdCents + } +} + +pub async fn enqueue_qwen_edit_2511_edit_image_angle_webhook( + args: EnqueueQwenEdit2511EditImageAngleArgs<'_, R> +) -> Result { + + let num_images = args.num_images + .map(|n| match n { + EnqueueQwenEdit2511AngleNumImages::One => 1, + EnqueueQwenEdit2511AngleNumImages::Two => 2, + EnqueueQwenEdit2511AngleNumImages::Three => 3, + EnqueueQwenEdit2511AngleNumImages::Four => 4, + }); + + let image_size = args.image_size + .map(|s| match s { + EnqueueQwenEdit2511AngleImageSize::Square => "square", + EnqueueQwenEdit2511AngleImageSize::SquareHd => "square_hd", + EnqueueQwenEdit2511AngleImageSize::PortraitFourThree => "portrait_4_3", + EnqueueQwenEdit2511AngleImageSize::PortraitSixteenNine => "portrait_16_9", + EnqueueQwenEdit2511AngleImageSize::LandscapeFourThree => "landscape_4_3", + EnqueueQwenEdit2511AngleImageSize::LandscapeSixteenNine => "landscape_16_9", + }) + .map(|s| s.to_string()); + + let request = QwenEdit2511EditImageAngleInput { + image_urls: args.image_urls, + horizontal_angle: args.horizontal_angle, + vertical_angle: args.vertical_angle, + zoom: args.zoom, + additional_prompt: args.additional_prompt.map(|s| s.to_string()), + lora_scale: args.lora_scale, + image_size, + guidance_scale: args.guidance_scale, + num_inference_steps: args.num_inference_steps, + num_images, + // Constants + enable_safety_checker: Some(false), + output_format: Some("png".to_string()), + // Unused + negative_prompt: None, + seed: None, + }; + + let result = http_qwen_edit_2511_edit_image_angle(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::image::angle::enqueue_qwen_edit_2511_edit_image_angle_webhook::{enqueue_qwen_edit_2511_edit_image_angle_webhook, EnqueueQwenEdit2511EditImageAngleArgs, EnqueueQwenEdit2511AngleNumImages, EnqueueQwenEdit2511AngleImageSize}; + use errors::AnyhowResult; + use std::fs::read_to_string; + use test_data::web::image_urls::GHOST_IMAGE_URL; + + #[tokio::test] + #[ignore] + async fn test() -> AnyhowResult<()> { + // 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 = EnqueueQwenEdit2511EditImageAngleArgs { + image_urls: vec![GHOST_IMAGE_URL.to_string()], + horizontal_angle: Some(45.0), + vertical_angle: Some(15.0), + zoom: Some(5.0), + additional_prompt: Some("cinematic lighting"), + num_images: Some(EnqueueQwenEdit2511AngleNumImages::One), + image_size: Some(EnqueueQwenEdit2511AngleImageSize::SquareHd), + lora_scale: None, + guidance_scale: None, + num_inference_steps: None, + api_key: &api_key, + webhook_url: "https://example.com/webhook", + }; + + let _result = enqueue_qwen_edit_2511_edit_image_angle_webhook(args).await?; + + Ok(()) + } +} diff --git a/crates/api_clients/fal_client/src/requests/webhook/image/angle/mod.rs b/crates/api_clients/fal_client/src/requests/webhook/image/angle/mod.rs new file mode 100644 index 0000000000..2980e5bc0b --- /dev/null +++ b/crates/api_clients/fal_client/src/requests/webhook/image/angle/mod.rs @@ -0,0 +1 @@ +pub mod enqueue_qwen_edit_2511_edit_image_angle_webhook; diff --git a/crates/api_clients/fal_client/src/requests/webhook/image/mod.rs b/crates/api_clients/fal_client/src/requests/webhook/image/mod.rs index 37b6108e73..8578d164d9 100644 --- a/crates/api_clients/fal_client/src/requests/webhook/image/mod.rs +++ b/crates/api_clients/fal_client/src/requests/webhook/image/mod.rs @@ -1,3 +1,4 @@ +pub mod angle; pub mod edit; pub mod infill; pub mod remove_background_rembg_webhook;