Add GPT Image 2.5 edit models
This commit is contained in:
@@ -21,6 +21,8 @@ A Custom Node that integrates [Fal.ai's](https://fal.ai) powerful image editing
|
||||
| **Nano Banana Pro** | `nano_banana_pro` | Enhanced quality version of Nano Banana. |
|
||||
| **Nano Banana 2** | `nano_banana_2` | New generation model with web search and thinking options. |
|
||||
| **GPT Image 2 Edit** | `gpt_image_2_edit` | OpenAI image editing with quality and mask controls. |
|
||||
| **GPT Image 2.5 Flare Edit** | `gpt_image_2_5_flare_edit` | Precise GPT Image 2.5 editing with high-resolution, background, quality, format, compression, and mask controls. |
|
||||
| **GPT Image 2.5 Sunburst Edit** | `gpt_image_2_5_sunburst_edit` | Precision-focused GPT Image 2.5 editing with the same extended controls. |
|
||||
| **Grok Imagine Edit** | `grok_imagine_edit` | xAI image edit model, up to 3 input images. |
|
||||
| **Seedream 4.5** | `seedream_4.5` | ByteDance's model. Optimized for high-res editing. |
|
||||
| **Seedream 5 Pro Edit** | `seedream_5` | ByteDance Seedream 5.0 Pro region-precise editing. |
|
||||
@@ -58,55 +60,67 @@ You must have a **Fal.ai API Key** to use this node.
|
||||
* **model**: Select the specific AI model to use.
|
||||
* **fal_key**: Optional UI fallback for your API key when `FAL_KEY` is not set.
|
||||
* **image1 - image10**: Connect the images you want to edit.
|
||||
* **mask**: Optional GPT Image 2 edit mask. Other models ignore it.
|
||||
* **mask**: Optional GPT Image 2 or GPT Image 2.5 edit mask. Other models ignore it.
|
||||
|
||||
### Settings
|
||||
* **width / height**: Output resolution.
|
||||
* *Note:* `nano_banana` models ignore this and use `aspect_ratio`.
|
||||
* *Note:* `seedream_4.5` requires high resolutions (see constraints below).
|
||||
* **num_images**: How many variations to generate (Max 6).
|
||||
* **num_images**: How many variations to generate. GPT Image 2.5 supports up to 10; existing model-specific caps remain enforced.
|
||||
* **num_inference_steps**: Number of inference steps for supported models.
|
||||
* **seed**: Seed for reproducibility.
|
||||
* **aspect_ratio**: Set target aspect ratio (e.g., 16:9, 1:1).
|
||||
* **quality**: GPT quality. GPT Image 2 supports `auto` through `high`; GPT Image 2.5 also supports `xhigh` and `max`.
|
||||
* **enable_web_search**: Enables web search context for supported models.
|
||||
* **thinking_level**: `off`, `minimal`, or `high` for supported models.
|
||||
* **concurrent_runs**: Number of independent fal.ai queue jobs to submit in parallel from this node. This is separate from workflow-level concurrency, which automatically overlaps independent NanoSeed nodes. Each run requests its own `num_images`; outputs are returned in run order. Models with an explicit seed increment it for each additional run.
|
||||
* **queue_timeout**: Maximum wait per queue job in seconds. Timed-out jobs receive a best-effort cancellation request.
|
||||
* **gpt_background**: `auto`, `transparent`, or `opaque` for GPT Image models.
|
||||
* **gpt_image_size**: GPT output-size preset, including 1080p, 1440p, and 3840x2160. Non-zero `width` and `height` override this preset.
|
||||
* **gpt_output_format**: `png`, `jpeg`, or `webp` for GPT Image models.
|
||||
* **gpt_output_compression**: Optional GPT Image 2.5 JPEG/WebP compression from 0-100. Leave at `-1` to use the API default.
|
||||
|
||||
### ⚠️ Model Specific Constraints
|
||||
|
||||
Different models have different requirements implemented in the node:
|
||||
|
||||
1. **Flux 2 Edit (`flux_2_edit`)**:
|
||||
1. **GPT Image 2.5 Flare / Sunburst**:
|
||||
* Supports all 10 NanoSeed reference-image sockets and up to **10 output images**.
|
||||
* Supports `auto`, `low`, `medium`, `high`, `xhigh`, and `max` quality. Higher levels increase cost and latency.
|
||||
* Supports automatic, transparent, or opaque backgrounds and PNG, JPEG, or WebP output.
|
||||
* Includes 1080p, 1440p, and **3840x2160** presets. Custom non-zero `width` and `height` take priority.
|
||||
* Supports the shared GPT `mask` socket.
|
||||
|
||||
2. **Flux 2 Edit (`flux_2_edit`)**:
|
||||
* **Single Image Only:** Do not connect `image2` through `image5`.
|
||||
* **Resolution:** Width and Height must be between **512px and 2048px**.
|
||||
|
||||
2. **Flux 2 Klein 9B Edit (`flux_2_klein_9b_edit`)**:
|
||||
3. **Flux 2 Klein 9B Edit (`flux_2_klein_9b_edit`)**:
|
||||
* Supports up to **4 input images**.
|
||||
* `num_inference_steps` is limited to **4-8**.
|
||||
|
||||
3. **Seedream 4.5 (`seedream_4.5`)**:
|
||||
4. **Seedream 4.5 (`seedream_4.5`)**:
|
||||
* **High Res Only:** Width and Height must be between **1920px and 4096px**.
|
||||
* **Pixel Count:** Total pixels (W x H) must be between ~3.6MP and 16.7MP.
|
||||
* **Batch Limit:** The sum of input images and output `num_images` cannot exceed 15.
|
||||
|
||||
4. **Seedream 5 Pro Edit (`seedream_5`)**:
|
||||
5. **Seedream 5 Pro Edit (`seedream_5`)**:
|
||||
* Supports up to **10 input images**.
|
||||
* If `aspect_ratio` is not `auto`, the selected aspect ratio is prioritized and converted to Seedream's `image_size`.
|
||||
* If `aspect_ratio` is `auto`, the selected `resolution` is mapped to Seedream's automatic size presets.
|
||||
* Pro supports `auto_1K` and `auto_2K`; `4K` falls back to Pro's highest automatic preset.
|
||||
|
||||
5. **Seedream 5 Lite Edit (`seedream_5_lite`)**:
|
||||
6. **Seedream 5 Lite Edit (`seedream_5_lite`)**:
|
||||
* Supports up to **10 input images**.
|
||||
* Uses the same aspect-ratio-first behavior as Seedream 5 Pro.
|
||||
* Supports Lite automatic presets up to `auto_4K`.
|
||||
|
||||
6. **Nano Banana / Pro / 2**:
|
||||
7. **Nano Banana / Pro / 2**:
|
||||
* These models rely on `aspect_ratio` and `resolution` settings rather than custom width/height integers.
|
||||
* `nano_banana_2` supports `0.5K`, `1K`, `2K`, and `4K` resolution presets.
|
||||
* `nano_banana_2` also supports extended aspect ratios: `4:1`, `1:4`, `8:1`, and `1:8`.
|
||||
|
||||
7. **Grok Imagine Edit (`grok_imagine_edit`)**:
|
||||
8. **Grok Imagine Edit (`grok_imagine_edit`)**:
|
||||
* Supports up to **3 input images**.
|
||||
* Uses up to **4 output images**.
|
||||
|
||||
|
||||
+64
-9
@@ -16,6 +16,30 @@ FAL_HTTP_TIMEOUT_SECONDS = 60
|
||||
FAL_QUEUE_POLL_INTERVAL_SECONDS = 1.0
|
||||
FAL_MAX_CONCURRENT_RUNS = 8
|
||||
|
||||
GPT_IMAGE_25_EDIT_URLS = {
|
||||
"gpt_image_2_5_flare_edit": "https://fal.run/openai/gpt-image-2.5/flare/edit",
|
||||
"gpt_image_2_5_sunburst_edit": "https://fal.run/openai/gpt-image-2.5/sunburst/edit",
|
||||
}
|
||||
|
||||
GPT_IMAGE_SIZE_OPTIONS = [
|
||||
"auto",
|
||||
"square_hd",
|
||||
"square",
|
||||
"portrait_4_3",
|
||||
"portrait_16_9",
|
||||
"landscape_4_3",
|
||||
"landscape_16_9",
|
||||
"1920x1080",
|
||||
"2560x1440",
|
||||
"3840x2160",
|
||||
]
|
||||
|
||||
GPT_IMAGE_SIZE_DIMENSIONS = {
|
||||
"1920x1080": {"width": 1920, "height": 1080},
|
||||
"2560x1440": {"width": 2560, "height": 1440},
|
||||
"3840x2160": {"width": 3840, "height": 2160},
|
||||
}
|
||||
|
||||
SEEDREAM_5_AUTO_IMAGE_SIZE_BY_RESOLUTION = {
|
||||
"seedream_5": {
|
||||
"0.5K": "auto_1K",
|
||||
@@ -152,6 +176,12 @@ def _prepare_image_data_uris(images, resize_to=None):
|
||||
return data_uris
|
||||
|
||||
|
||||
def _resolve_gpt_image_size(width, height, image_size):
|
||||
if width > 0 and height > 0:
|
||||
return {"width": width, "height": height}
|
||||
return GPT_IMAGE_SIZE_DIMENSIONS.get(image_size, image_size)
|
||||
|
||||
|
||||
def _safe_error_detail(detail):
|
||||
detail = re.sub(
|
||||
r"data:[^,\s\"']+,[A-Za-z0-9+/=]+",
|
||||
@@ -370,7 +400,7 @@ class NanoSeedEdit:
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {"default": "Edit the image according to this prompt.", "multiline": True}),
|
||||
"model": (["nano_banana", "nano_banana_pro", "nano_banana_2", "gpt_image_2_edit", "grok_imagine_edit", "seedream_4.5", "seedream_5", "seedream_5_lite", "qwen_edit_plus", "flux_2_edit", "flux_2_pro", "flux_2_flex", "flux_2_klein_9b_edit"],),
|
||||
"model": (["nano_banana", "nano_banana_pro", "nano_banana_2", "gpt_image_2_edit", "gpt_image_2_5_flare_edit", "gpt_image_2_5_sunburst_edit", "grok_imagine_edit", "seedream_4.5", "seedream_5", "seedream_5_lite", "qwen_edit_plus", "flux_2_edit", "flux_2_pro", "flux_2_flex", "flux_2_klein_9b_edit"],),
|
||||
"fal_key": ("STRING", {"default": "", "multiline": False}),
|
||||
},
|
||||
"optional": {
|
||||
@@ -387,16 +417,20 @@ class NanoSeedEdit:
|
||||
"mask": ("IMAGE",),
|
||||
"width": ("INT", {"default": 0, "min": 0, "max": 4096, "display": "number"}),
|
||||
"height": ("INT", {"default": 0, "min": 0, "max": 4096, "display": "number"}),
|
||||
"num_images": ("INT", {"default": 1, "min": 1, "max": 6}),
|
||||
"num_images": ("INT", {"default": 1, "min": 1, "max": 10}),
|
||||
"num_inference_steps": ("INT", {"default": 28, "min": 1, "max": 100}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 2**32 - 1}),
|
||||
"aspect_ratio": (["auto", "21:9", "16:9", "3:2", "4:3", "5:4", "1:1", "4:5", "3:4", "2:3", "9:16", "4:1", "1:4", "8:1", "1:8"], {"default": "auto"}),
|
||||
"resolution": (["0.5K", "1K", "2K", "4K"], {"default": "1K"}),
|
||||
"quality": (["low", "medium", "high"], {"default": "high"}),
|
||||
"quality": (["auto", "low", "medium", "high", "xhigh", "max"], {"default": "high"}),
|
||||
"enable_web_search": ("BOOLEAN", {"default": False}),
|
||||
"thinking_level": (["off", "minimal", "high"], {"default": "off"}),
|
||||
"concurrent_runs": ("INT", {"default": 1, "min": 1, "max": 8, "step": 1}),
|
||||
"queue_timeout": ("INT", {"default": 900, "min": 60, "max": 3600, "step": 30}),
|
||||
"gpt_background": (["auto", "transparent", "opaque"], {"default": "auto"}),
|
||||
"gpt_image_size": (GPT_IMAGE_SIZE_OPTIONS, {"default": "auto"}),
|
||||
"gpt_output_format": (["png", "jpeg", "webp"], {"default": "png"}),
|
||||
"gpt_output_compression": ("INT", {"default": -1, "min": -1, "max": 100, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +444,8 @@ class NanoSeedEdit:
|
||||
image6=None, image7=None, image8=None, image9=None, image10=None, mask=None,
|
||||
width=0, height=0, num_images=1, num_inference_steps=28, seed=0, aspect_ratio="auto", resolution="1K",
|
||||
quality="high", enable_web_search=False, thinking_level="off", acceleration="none",
|
||||
concurrent_runs=1, queue_timeout=900): # Hardcoded to none, kept for compatibility
|
||||
concurrent_runs=1, queue_timeout=900, gpt_background="auto", gpt_image_size="auto",
|
||||
gpt_output_format="png", gpt_output_compression=-1): # Hardcoded to none, kept for compatibility
|
||||
env_fal_key = (os.environ.get("FAL_KEY") or "").strip()
|
||||
ui_fal_key = (fal_key or "").strip()
|
||||
if ui_fal_key == "your_fal_key_here":
|
||||
@@ -444,7 +479,7 @@ class NanoSeedEdit:
|
||||
)
|
||||
|
||||
# Enforce limits (Updated: Removed Flux 2 single image limit)
|
||||
if model in ["seedream_4.5", "seedream_5", "seedream_5_pro", "seedream_5_lite"] and len(img_data_uris) + num_images > 15:
|
||||
if model in ["seedream_4.5", "seedream_5", "seedream_5_pro", "seedream_5_lite"] and len(img_data_uris) + min(num_images, 6) > 15:
|
||||
raise ValueError("Seedream: Total inputs + outputs must <=15.")
|
||||
|
||||
# Model-specific payloads
|
||||
@@ -487,17 +522,37 @@ class NanoSeedEdit:
|
||||
if thinking_level != "off":
|
||||
payload["thinking_level"] = thinking_level
|
||||
elif model == "gpt_image_2_edit":
|
||||
if quality in {"xhigh", "max"}:
|
||||
raise ValueError("GPT Image 2 quality supports auto, low, medium, or high.")
|
||||
url = "https://fal.run/openai/gpt-image-2/edit"
|
||||
payload = {
|
||||
"prompt": prompt,
|
||||
"image_urls": img_data_uris,
|
||||
"quality": quality,
|
||||
"num_images": num_images,
|
||||
"output_format": "png",
|
||||
"background": gpt_background,
|
||||
"image_size": _resolve_gpt_image_size(width, height, gpt_image_size),
|
||||
"num_images": min(num_images, 4),
|
||||
"output_format": gpt_output_format,
|
||||
"sync_mode": False,
|
||||
}
|
||||
if custom_size:
|
||||
payload["image_size"] = {"width": width, "height": height}
|
||||
if mask is not None:
|
||||
mask_data_uri = await asyncio.to_thread(tensor2data_uri, mask)
|
||||
if mask_data_uri is not None:
|
||||
payload["mask_url"] = mask_data_uri
|
||||
elif model in GPT_IMAGE_25_EDIT_URLS:
|
||||
url = GPT_IMAGE_25_EDIT_URLS[model]
|
||||
payload = {
|
||||
"prompt": prompt,
|
||||
"image_urls": img_data_uris,
|
||||
"quality": quality,
|
||||
"background": gpt_background,
|
||||
"image_size": _resolve_gpt_image_size(width, height, gpt_image_size),
|
||||
"num_images": num_images,
|
||||
"output_format": gpt_output_format,
|
||||
"sync_mode": False,
|
||||
}
|
||||
if gpt_output_format in {"jpeg", "webp"} and gpt_output_compression >= 0:
|
||||
payload["output_compression"] = gpt_output_compression
|
||||
if mask is not None:
|
||||
mask_data_uri = await asyncio.to_thread(tensor2data_uri, mask)
|
||||
if mask_data_uri is not None:
|
||||
|
||||
Reference in New Issue
Block a user