diff --git a/README.md b/README.md index b3da3ec..4194a4f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ A Custom Node that integrates [Fal.ai's](https://fal.ai) powerful image editing | **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. | +| **Seedream 5 Lite Edit** | `seedream_5_lite` | ByteDance Seedream 5.0 Lite editing. | | **Qwen Edit Plus** | `qwen_edit_plus` | Powerful instruction-based editing with guidance control. | | **Flux 2 (Standard, Pro, Flex)** | `flux_2_edit` | (BFL) High-fidelity editing capabilities. | | **Flux 2 Klein 9B Edit** | `flux_2_klein_9b_edit` | Fast FLUX.2 Klein 9B image editing model. | @@ -85,15 +86,21 @@ Different models have different requirements implemented in the node: 4. **Seedream 5 Pro Edit (`seedream_5`)**: * Supports up to **10 input images**. - * Uses `auto_2K` sizing unless custom width and height are provided. - * Custom total pixels must be between **1024x1024** and **2048x2048**. + * 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. **Nano Banana / Pro / 2**: +5. **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**: * 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`. -6. **Grok Imagine Edit (`grok_imagine_edit`)**: +7. **Grok Imagine Edit (`grok_imagine_edit`)**: * Supports up to **3 input images**. * Uses up to **4 output images**. diff --git a/nano_seed.py b/nano_seed.py index f57901f..551d234 100644 --- a/nano_seed.py +++ b/nano_seed.py @@ -1,12 +1,95 @@ import torch import requests import base64 -import numpy as np -from io import BytesIO -from PIL import Image - -# Helper function to convert ComfyUI tensor (B=1, H, W, C) to PIL Image (RGB) -def tensor2pil(image_tensor): +import numpy as np +from io import BytesIO +from PIL import Image + +SEEDREAM_5_AUTO_IMAGE_SIZE_BY_RESOLUTION = { + "seedream_5": { + "0.5K": "auto_1K", + "1K": "auto_1K", + "2K": "auto_2K", + "4K": "auto_2K", + }, + "seedream_5_pro": { + "0.5K": "auto_1K", + "1K": "auto_1K", + "2K": "auto_2K", + "4K": "auto_2K", + }, + "seedream_5_lite": { + "0.5K": "auto_2K", + "1K": "auto_2K", + "2K": "auto_2K", + "4K": "auto_4K", + }, +} + +SEEDREAM_5_RESOLUTION_PIXELS = { + "seedream_5": { + "0.5K": 1024 * 1024, + "1K": 1024 * 1024, + "2K": 2048 * 2048, + "4K": 2048 * 2048, + }, + "seedream_5_pro": { + "0.5K": 1024 * 1024, + "1K": 1024 * 1024, + "2K": 2048 * 2048, + "4K": 2048 * 2048, + }, + "seedream_5_lite": { + "0.5K": 2560 * 1440, + "1K": 2560 * 1440, + "2K": 2048 * 2048, + "4K": 4096 * 4096, + }, +} + + +def _round_to_multiple(value, multiple=8): + return max(multiple, int(round(value / multiple)) * multiple) + + +def seedream_5_image_size(aspect_ratio, resolution, model="seedream_5"): + auto_sizes = SEEDREAM_5_AUTO_IMAGE_SIZE_BY_RESOLUTION.get( + model, + SEEDREAM_5_AUTO_IMAGE_SIZE_BY_RESOLUTION["seedream_5"], + ) + resolution_pixels = SEEDREAM_5_RESOLUTION_PIXELS.get( + model, + SEEDREAM_5_RESOLUTION_PIXELS["seedream_5"], + ) + + if aspect_ratio == "auto": + return auto_sizes.get(resolution, auto_sizes["2K"]) + + width_ratio, height_ratio = [int(part) for part in aspect_ratio.split(":", 1)] + ratio = width_ratio / height_ratio + target_pixels = resolution_pixels.get(resolution, resolution_pixels["2K"]) + + width = (target_pixels * ratio) ** 0.5 + height = target_pixels / width + + max_dimension = 4096 + if width > max_dimension: + scale = max_dimension / width + width *= scale + height *= scale + if height > max_dimension: + scale = max_dimension / height + width *= scale + height *= scale + + return { + "width": _round_to_multiple(width), + "height": _round_to_multiple(height), + } + + +# Helper function to convert ComfyUI tensor (B=1, H, W, C) to PIL Image (RGB) +def tensor2pil(image_tensor): if image_tensor is None or image_tensor.shape[0] == 0: return None i = 255. * image_tensor[0].cpu().numpy() # (H, W, C) @@ -52,7 +135,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", "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", "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": "your_fal_key_here"}), }, "optional": { @@ -123,8 +206,8 @@ class NanoSeedEdit: img_data_uris.append(img_data_uri) # Enforce limits (Updated: Removed Flux 2 single image limit) - if model == "seedream_4.5" and len(img_data_uris) + num_images > 15: - raise ValueError("Seedream 4.5: Total inputs + outputs must <=15.") + if model in ["seedream_4.5", "seedream_5", "seedream_5_pro", "seedream_5_lite"] and len(img_data_uris) + num_images > 15: + raise ValueError("Seedream: Total inputs + outputs must <=15.") # Model-specific payloads if model == "nano_banana": @@ -207,26 +290,23 @@ class NanoSeedEdit: if not (3686400 <= area <= 16777216): raise ValueError(f"Seedream 4.5: Image area must be 3,686,400-16,777,216px. Got {area}.") payload["image_size"] = {"width": width, "height": height} - elif model == "seedream_5": - url = "https://fal.run/bytedance/seedream/v5/pro/edit" + elif model in ["seedream_5", "seedream_5_pro", "seedream_5_lite"]: + if model in ["seedream_5", "seedream_5_pro"]: + url = "https://fal.run/bytedance/seedream/v5/pro/edit" + else: + url = "https://fal.run/fal-ai/bytedance/seedream/v5/lite/edit" payload = { "prompt": prompt, "image_urls": img_data_uris[:10], + "image_size": seedream_5_image_size(aspect_ratio, resolution, model), "num_images": min(num_images, 6), - "output_format": "png", "enable_safety_checker": False, "sync_mode": True, } - if custom_size: - area = width * height - aspect = width / height - if not (1048576 <= area <= 4194304): - raise ValueError(f"Seedream 5: Image area must be 1,048,576-4,194,304px. Got {area}.") - if not (1 / 16 <= aspect <= 16): - raise ValueError(f"Seedream 5: Aspect ratio must be between 1:16 and 16:1. Got {width}:{height}.") - payload["image_size"] = {"width": width, "height": height} + if model in ["seedream_5", "seedream_5_pro"]: + payload["output_format"] = "png" else: - payload["image_size"] = "auto_2K" + payload["max_images"] = 1 elif model == "qwen_edit_plus": url = "https://fal.run/fal-ai/qwen-image-edit-plus" payload = {