api bindings

This commit is contained in:
Brandon Thomas committed 2026-04-21 21:37:35 -04:00
1 parent cf1f4eb8c8
commit 26db6cdf32
8 files changed
+475

No files matched your search

@@ -0,0 +1,47 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct GptImage2EditImageInput {
pub prompt: String,
pub image_urls: Vec<String>,
/// The URL of the mask image to use for the generation. This indicates what part of the image to edit.
#[serde(skip_serializing_if = "Option::is_none")]
pub mask_url: Option<String>,
/// Possible enum values: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9, auto
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// "low", "medium", "high"
#[serde(skip_serializing_if = "Option::is_none")]
pub quality: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// "jpeg", "png", "webp"
/// Default: "png"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GptImage2EditImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GptImage2EditImageOutput {
pub images: Vec<GptImage2EditImageFile>,
}
pub fn gpt_image_2_edit_image(
params: GptImage2EditImageInput,
) -> FalRequest<GptImage2EditImageInput, GptImage2EditImageOutput> {
FalRequest::new("fal-ai/gpt-image-2/edit", params)
}
@@ -5,6 +5,7 @@ pub mod http_gemini_25_flash_edit;
pub mod http_gpt_image_1_edit_image;
pub mod http_gpt_image_1_non_byok_edit_image;
pub mod http_gpt_image_1p5_edit_image;
pub mod http_gpt_image_2_edit_image;
pub mod http_nano_banana_2_edit_image;
pub mod http_nano_banana_pro_edit_image;
pub mod http_qwen_image_edit;
@@ -0,0 +1,41 @@
use crate::requests::api::fal_request::FalRequest;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct GptImage2TextToImageInput {
pub prompt: String,
/// "1024x768", "1024x1024", "1024x1536", "1920x1080", "2560x1440", "3840x2160"
#[serde(skip_serializing_if = "Option::is_none")]
pub image_size: Option<String>,
/// "low", "medium", "high"
#[serde(skip_serializing_if = "Option::is_none")]
pub quality: Option<String>,
/// 1 - 4
/// Default: 1
#[serde(skip_serializing_if = "Option::is_none")]
pub num_images: Option<u8>,
/// "jpeg", "png", "webp"
/// Default: "png"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_format: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GptImage2TextToImageFile {
pub url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GptImage2TextToImageOutput {
pub images: Vec<GptImage2TextToImageFile>,
}
pub fn gpt_image_2_text_to_image(
params: GptImage2TextToImageInput,
) -> FalRequest<GptImage2TextToImageInput, GptImage2TextToImageOutput> {
FalRequest::new("fal-ai/gpt-image-2", params)
}
@@ -6,6 +6,7 @@ pub mod http_gemini_25_flash_text_to_image;
pub mod http_gpt_image_1_non_byok_text_to_image;
pub mod http_gpt_image_1_text_to_image;
pub mod http_gpt_image_1p5_text_to_image;
pub mod http_gpt_image_2_text_to_image;
pub mod http_nano_banana_2_text_to_image;
pub mod http_nano_banana_pro_text_to_image;
pub mod http_seedream_4_text_to_image;
@@ -0,0 +1,197 @@
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::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use crate::requests::http::image::edit::http_gpt_image_2_edit_image::{gpt_image_2_edit_image, GptImage2EditImageInput};
use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueGptImage2EditImageArgs<'a, R: IntoUrl> {
// Request required
pub prompt: &'a str,
pub image_urls: Vec<String>,
pub num_images: EnqueueGptImage2EditImageNumImages,
// Optional args
pub mask_url: Option<String>,
pub image_size: Option<EnqueueGptImage2EditImageSize>,
pub quality: Option<EnqueueGptImage2EditImageQuality>,
pub output_format: Option<EnqueueGptImage2EditImageOutputFormat>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueGptImage2EditImageNumImages {
One,
Two,
Three,
Four,
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueGptImage2EditImageSize {
/// 1024x768
Landscape4x3,
/// 1024x1024
Square,
/// 1024x1536
Portrait,
/// 1920x1080
Landscape16x9,
/// 2560x1440
Qhd,
/// 3840x2160
Uhd4k,
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueGptImage2EditImageQuality {
Low,
Medium,
High,
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueGptImage2EditImageOutputFormat {
Jpeg,
Png,
Webp,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueGptImage2EditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Cost table (per image):
// 1024x768: low=$0.01, medium=$0.04, high=$0.15
// 1024x1024: low=$0.01, medium=$0.06, high=$0.22
// 1024x1536: low=$0.01, medium=$0.05, high=$0.17
// 1920x1080: low=$0.01, medium=$0.04, high=$0.16
// 2560x1440: low=$0.01, medium=$0.06, high=$0.23
// 3840x2160: low=$0.02, medium=$0.11, high=$0.41
let use_quality = self.quality.unwrap_or(EnqueueGptImage2EditImageQuality::High);
let use_size = self.image_size.unwrap_or(EnqueueGptImage2EditImageSize::Square);
let base_cost = match (use_quality, use_size) {
(EnqueueGptImage2EditImageQuality::Low, EnqueueGptImage2EditImageSize::Uhd4k) => 2,
(EnqueueGptImage2EditImageQuality::Low, _) => 1,
(EnqueueGptImage2EditImageQuality::Medium, EnqueueGptImage2EditImageSize::Landscape4x3) => 4,
(EnqueueGptImage2EditImageQuality::Medium, EnqueueGptImage2EditImageSize::Square) => 6,
(EnqueueGptImage2EditImageQuality::Medium, EnqueueGptImage2EditImageSize::Portrait) => 5,
(EnqueueGptImage2EditImageQuality::Medium, EnqueueGptImage2EditImageSize::Landscape16x9) => 4,
(EnqueueGptImage2EditImageQuality::Medium, EnqueueGptImage2EditImageSize::Qhd) => 6,
(EnqueueGptImage2EditImageQuality::Medium, EnqueueGptImage2EditImageSize::Uhd4k) => 11,
(EnqueueGptImage2EditImageQuality::High, EnqueueGptImage2EditImageSize::Landscape4x3) => 15,
(EnqueueGptImage2EditImageQuality::High, EnqueueGptImage2EditImageSize::Square) => 22,
(EnqueueGptImage2EditImageQuality::High, EnqueueGptImage2EditImageSize::Portrait) => 17,
(EnqueueGptImage2EditImageQuality::High, EnqueueGptImage2EditImageSize::Landscape16x9) => 16,
(EnqueueGptImage2EditImageQuality::High, EnqueueGptImage2EditImageSize::Qhd) => 23,
(EnqueueGptImage2EditImageQuality::High, EnqueueGptImage2EditImageSize::Uhd4k) => 41,
};
let cost = match self.num_images {
EnqueueGptImage2EditImageNumImages::One => base_cost,
EnqueueGptImage2EditImageNumImages::Two => base_cost * 2,
EnqueueGptImage2EditImageNumImages::Three => base_cost * 3,
EnqueueGptImage2EditImageNumImages::Four => base_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_gpt_image_2_edit_image_webhook<R: IntoUrl>(
args: EnqueueGptImage2EditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let num_images = match args.num_images {
EnqueueGptImage2EditImageNumImages::One => 1,
EnqueueGptImage2EditImageNumImages::Two => 2,
EnqueueGptImage2EditImageNumImages::Three => 3,
EnqueueGptImage2EditImageNumImages::Four => 4,
};
let image_size = args.image_size
.map(|s| match s {
EnqueueGptImage2EditImageSize::Landscape4x3 => "1024x768",
EnqueueGptImage2EditImageSize::Square => "1024x1024",
EnqueueGptImage2EditImageSize::Portrait => "1024x1536",
EnqueueGptImage2EditImageSize::Landscape16x9 => "1920x1080",
EnqueueGptImage2EditImageSize::Qhd => "2560x1440",
EnqueueGptImage2EditImageSize::Uhd4k => "3840x2160",
})
.map(|resolution| resolution.to_string());
let quality = args.quality
.map(|s| match s {
EnqueueGptImage2EditImageQuality::Low => "low",
EnqueueGptImage2EditImageQuality::Medium => "medium",
EnqueueGptImage2EditImageQuality::High => "high",
})
.map(|quality| quality.to_string());
let output_format = args.output_format
.map(|s| match s {
EnqueueGptImage2EditImageOutputFormat::Jpeg => "jpeg",
EnqueueGptImage2EditImageOutputFormat::Png => "png",
EnqueueGptImage2EditImageOutputFormat::Webp => "webp",
})
.map(|format| format.to_string())
.unwrap_or_else(|| "png".to_string());
let request = GptImage2EditImageInput {
prompt: args.prompt.to_string(),
image_urls: args.image_urls,
num_images: Some(num_images),
output_format: Some(output_format),
// Optionals
mask_url: args.mask_url,
image_size,
quality,
};
let result = gpt_image_2_edit_image(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::edit::enqueue_gpt_image_2_edit_image_webhook::{enqueue_gpt_image_2_edit_image_webhook, EnqueueGptImage2EditImageArgs, EnqueueGptImage2EditImageNumImages};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, TREX_SKELETON_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 = EnqueueGptImage2EditImageArgs {
image_urls: vec![
GHOST_IMAGE_URL.to_string(),
TREX_SKELETON_IMAGE_URL.to_string(),
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
],
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly",
num_images: EnqueueGptImage2EditImageNumImages::Two,
mask_url: None,
image_size: None,
quality: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
output_format: None,
};
let result = enqueue_gpt_image_2_edit_image_webhook(args).await?;
Ok(())
}
}
@@ -9,6 +9,7 @@ pub mod enqueue_gemini_25_flash_edit_webhook;
pub mod enqueue_gpt_image_1_byok_edit_image_webhook;
pub mod enqueue_gpt_image_1_edit_image_webhook;
pub mod enqueue_gpt_image_1p5_edit_image_webhook;
pub mod enqueue_gpt_image_2_edit_image_webhook;
pub mod enqueue_nano_banana_pro_edit_image_webhook;
pub mod enqueue_qwen_image_edit_webhook;
pub mod enqueue_seededit_v3_edit_webhook;
@@ -0,0 +1,186 @@
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::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use crate::requests::http::image::text::http_gpt_image_2_text_to_image::{gpt_image_2_text_to_image, GptImage2TextToImageInput};
use crate::requests::api::webhook_response::WebhookResponse;
use reqwest::IntoUrl;
pub struct EnqueueGptImage2TextToImageArgs<'a, R: IntoUrl> {
// Request required
pub prompt: &'a str,
pub num_images: EnqueueGptImage2TextToImageNumImages,
// Optional args
pub image_size: Option<EnqueueGptImage2TextToImageSize>,
pub quality: Option<EnqueueGptImage2TextToImageQuality>,
pub output_format: Option<EnqueueGptImage2TextToImageOutputFormat>,
// Fulfillment
pub webhook_url: R,
pub api_key: &'a FalApiKey,
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueGptImage2TextToImageNumImages {
One,
Two,
Three,
Four,
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueGptImage2TextToImageSize {
/// 1024x768
Landscape4x3,
/// 1024x1024
Square,
/// 1024x1536
Portrait,
/// 1920x1080
Landscape16x9,
/// 2560x1440
Qhd,
/// 3840x2160
Uhd4k,
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueGptImage2TextToImageQuality {
Low,
Medium,
High,
}
#[derive(Copy, Clone, Debug)]
pub enum EnqueueGptImage2TextToImageOutputFormat {
Jpeg,
Png,
Webp,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueGptImage2TextToImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Cost table (per image):
// 1024x768: low=$0.01, medium=$0.04, high=$0.15
// 1024x1024: low=$0.01, medium=$0.06, high=$0.22
// 1024x1536: low=$0.01, medium=$0.05, high=$0.17
// 1920x1080: low=$0.01, medium=$0.04, high=$0.16
// 2560x1440: low=$0.01, medium=$0.06, high=$0.23
// 3840x2160: low=$0.02, medium=$0.11, high=$0.41
let use_quality = self.quality.unwrap_or(EnqueueGptImage2TextToImageQuality::High);
let use_size = self.image_size.unwrap_or(EnqueueGptImage2TextToImageSize::Square);
let base_cost = match (use_quality, use_size) {
(EnqueueGptImage2TextToImageQuality::Low, EnqueueGptImage2TextToImageSize::Uhd4k) => 2,
(EnqueueGptImage2TextToImageQuality::Low, _) => 1,
(EnqueueGptImage2TextToImageQuality::Medium, EnqueueGptImage2TextToImageSize::Landscape4x3) => 4,
(EnqueueGptImage2TextToImageQuality::Medium, EnqueueGptImage2TextToImageSize::Square) => 6,
(EnqueueGptImage2TextToImageQuality::Medium, EnqueueGptImage2TextToImageSize::Portrait) => 5,
(EnqueueGptImage2TextToImageQuality::Medium, EnqueueGptImage2TextToImageSize::Landscape16x9) => 4,
(EnqueueGptImage2TextToImageQuality::Medium, EnqueueGptImage2TextToImageSize::Qhd) => 6,
(EnqueueGptImage2TextToImageQuality::Medium, EnqueueGptImage2TextToImageSize::Uhd4k) => 11,
(EnqueueGptImage2TextToImageQuality::High, EnqueueGptImage2TextToImageSize::Landscape4x3) => 15,
(EnqueueGptImage2TextToImageQuality::High, EnqueueGptImage2TextToImageSize::Square) => 22,
(EnqueueGptImage2TextToImageQuality::High, EnqueueGptImage2TextToImageSize::Portrait) => 17,
(EnqueueGptImage2TextToImageQuality::High, EnqueueGptImage2TextToImageSize::Landscape16x9) => 16,
(EnqueueGptImage2TextToImageQuality::High, EnqueueGptImage2TextToImageSize::Qhd) => 23,
(EnqueueGptImage2TextToImageQuality::High, EnqueueGptImage2TextToImageSize::Uhd4k) => 41,
};
let cost = match self.num_images {
EnqueueGptImage2TextToImageNumImages::One => base_cost,
EnqueueGptImage2TextToImageNumImages::Two => base_cost * 2,
EnqueueGptImage2TextToImageNumImages::Three => base_cost * 3,
EnqueueGptImage2TextToImageNumImages::Four => base_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_gpt_image_2_text_to_image_webhook<R: IntoUrl>(
args: EnqueueGptImage2TextToImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
let num_images = match args.num_images {
EnqueueGptImage2TextToImageNumImages::One => 1,
EnqueueGptImage2TextToImageNumImages::Two => 2,
EnqueueGptImage2TextToImageNumImages::Three => 3,
EnqueueGptImage2TextToImageNumImages::Four => 4,
};
let image_size = args.image_size
.map(|s| match s {
EnqueueGptImage2TextToImageSize::Landscape4x3 => "1024x768",
EnqueueGptImage2TextToImageSize::Square => "1024x1024",
EnqueueGptImage2TextToImageSize::Portrait => "1024x1536",
EnqueueGptImage2TextToImageSize::Landscape16x9 => "1920x1080",
EnqueueGptImage2TextToImageSize::Qhd => "2560x1440",
EnqueueGptImage2TextToImageSize::Uhd4k => "3840x2160",
})
.map(|resolution| resolution.to_string());
let quality = args.quality
.map(|s| match s {
EnqueueGptImage2TextToImageQuality::Low => "low",
EnqueueGptImage2TextToImageQuality::Medium => "medium",
EnqueueGptImage2TextToImageQuality::High => "high",
})
.map(|quality| quality.to_string());
let output_format = args.output_format
.map(|s| match s {
EnqueueGptImage2TextToImageOutputFormat::Jpeg => "jpeg",
EnqueueGptImage2TextToImageOutputFormat::Png => "png",
EnqueueGptImage2TextToImageOutputFormat::Webp => "webp",
})
.map(|format| format.to_string())
.unwrap_or_else(|| "png".to_string());
let request = GptImage2TextToImageInput {
prompt: args.prompt.to_string(),
num_images: Some(num_images),
output_format: Some(output_format),
// Optionals
image_size,
quality,
};
let result = gpt_image_2_text_to_image(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::text::enqueue_gpt_image_2_text_to_image_webhook::{enqueue_gpt_image_2_text_to_image_webhook, EnqueueGptImage2TextToImageArgs, EnqueueGptImage2TextToImageNumImages};
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("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = EnqueueGptImage2TextToImageArgs {
prompt: "an anime girl riding on the back of a t-rex",
num_images: EnqueueGptImage2TextToImageNumImages::Two,
image_size: None,
quality: None,
api_key: &api_key,
webhook_url: "https://example.com/webhook",
output_format: None,
};
let result = enqueue_gpt_image_2_text_to_image_webhook(args).await?;
Ok(())
}
}
@@ -10,4 +10,5 @@ pub mod enqueue_gemini_25_flash_text_to_image_webhook;
pub mod enqueue_gpt_image_1_byok_text_to_image_webhook;
pub mod enqueue_gpt_image_1_text_to_image_webhook;
pub mod enqueue_gpt_image_1p5_text_to_image_webhook;
pub mod enqueue_gpt_image_2_text_to_image_webhook;
pub mod enqueue_nano_banana_pro_text_to_image_webhook;