endpoint bindings

This commit is contained in:
Brandon Thomas committed 2026-03-09 15:36:59 -04:00
1 parent cfa14769cd
commit fa9e95187f
5 files changed
+241

No files matched your search

@@ -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<String>,
/// Horizontal rotation angle around the object in degrees. 0°=front view.
/// Default: 0
#[serde(skip_serializing_if = "Option::is_none")]
pub horizontal_angle: Option<f64>,
/// 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<f64>,
/// Camera zoom/distance. 0=wide shot, 5=medium shot, 10=close-up.
/// Default: 5
#[serde(skip_serializing_if = "Option::is_none")]
pub zoom: Option<f64>,
/// Additional text to append to the automatically generated prompt.
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_prompt: Option<String>,
/// 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<f64>,
/// 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<String>,
/// The CFG (Classifier Free Guidance) scale.
/// Default: 4.5
#[serde(skip_serializing_if = "Option::is_none")]
pub guidance_scale: Option<f64>,
/// The number of inference steps to perform.
/// Default: 28
#[serde(skip_serializing_if = "Option::is_none")]
pub num_inference_steps: Option<u32>,
/// The negative prompt for the generation.
#[serde(skip_serializing_if = "Option::is_none")]
pub negative_prompt: Option<String>,
/// Random seed for reproducibility.
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
/// Number of images to generate.
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// Whether to enable the safety checker.
/// Default: true
#[serde(skip_serializing_if = "Option::is_none")]
pub enable_safety_checker: Option<bool>,
/// Output format: png, jpeg, webp.
/// Default: "png"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QwenEdit2511EditImageAngleFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QwenEdit2511EditImageAngleOutput {
pub images: Vec<QwenEdit2511EditImageAngleFile>,
pub seed: u64,
pub prompt: Option<String>,
}
pub fn http_qwen_edit_2511_edit_image_angle(
params: QwenEdit2511EditImageAngleInput,
) -> FalRequest<QwenEdit2511EditImageAngleInput, QwenEdit2511EditImageAngleOutput> {
FalRequest::new("fal-ai/qwen-image-edit-2511-multiple-angles", params)
}
@@ -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;
@@ -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<String>,
// Camera parameters
pub horizontal_angle: Option<f64>,
pub vertical_angle: Option<f64>,
pub zoom: Option<f64>,
// Optional args
pub additional_prompt: Option<&'a str>,
pub num_images: Option<EnqueueQwenEdit2511AngleNumImages>,
pub image_size: Option<EnqueueQwenEdit2511AngleImageSize>,
pub lora_scale: Option<f64>,
pub guidance_scale: Option<f64>,
pub num_inference_steps: Option<u32>,
// 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 <U: IntoUrl> 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<R: IntoUrl>(
args: EnqueueQwenEdit2511EditImageAngleArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
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(())
}
}
@@ -0,0 +1 @@
pub mod enqueue_qwen_edit_2511_edit_image_angle_webhook;
@@ -1,3 +1,4 @@
pub mod angle;
pub mod edit;
pub mod infill;
pub mod remove_background_rembg_webhook;