From ba28e8631b11b9a024c33a05ff63f17d105c33d6 Mon Sep 17 00:00:00 2001 From: comrender <139063732+comrender@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:23:58 +0200 Subject: [PATCH] Add files via upload Added support for more direct inputs, safety filter reduced by default for NB2, some other minor tweaks. --- nano_seed.py | 172 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 105 insertions(+), 67 deletions(-) diff --git a/nano_seed.py b/nano_seed.py index c634f00..0956a5c 100644 --- a/nano_seed.py +++ b/nano_seed.py @@ -25,56 +25,78 @@ def tensor2pil(image_tensor): return Image.fromarray(image, mode='RGB') # Helper function to convert PIL Image (RGB) back to ComfyUI tensor (B=1, H, W, C) -def pil2tensor(pil_image): - if pil_image is None: - return None - arr = np.array(pil_image).astype(np.float32) / 255.0 - arr = arr[np.newaxis, ...] - return torch.from_numpy(arr) - -# Main node class -class NanoSeedEdit: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "prompt": ("STRING", {"default": "Edit the image according to this prompt.", "multiline": True}), - "model": (["nano_banana", "nano_banana_pro", "nano_banana_2", "grok_imagine_edit", "seedream_4.5", "qwen_edit_plus", "flux_2_edit", "flux_2_pro", "flux_2_flex"],), - "fal_key": ("STRING", {"default": "your_fal_key_here"}), - }, - "optional": { - "image1": ("IMAGE",), - "image2": ("IMAGE",), - "image3": ("IMAGE",), - "image4": ("IMAGE",), - "image5": ("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}), - "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"}), - "enable_web_search": ("BOOLEAN", {"default": False}), - "thinking_level": (["off", "minimal", "high"], {"default": "off"}), - } - } +def pil2tensor(pil_image): + if pil_image is None: + return None + arr = np.array(pil_image).astype(np.float32) / 255.0 + arr = arr[np.newaxis, ...] + return torch.from_numpy(arr) + +def tensor2data_uri(image_tensor): + pil_image = tensor2pil(image_tensor) + if pil_image is None: + return None + + return pil2data_uri(pil_image) + +def pil2data_uri(pil_image): + buffer = BytesIO() + pil_image.save(buffer, format="PNG") + img_str = base64.b64encode(buffer.getvalue()).decode() + return f"data:image/png;base64,{img_str}" + +# Main node class +class NanoSeedEdit: + @classmethod + def INPUT_TYPES(cls): + 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", "qwen_edit_plus", "flux_2_edit", "flux_2_pro", "flux_2_flex"],), + "fal_key": ("STRING", {"default": "your_fal_key_here"}), + }, + "optional": { + "image1": ("IMAGE",), + "image2": ("IMAGE",), + "image3": ("IMAGE",), + "image4": ("IMAGE",), + "image5": ("IMAGE",), + "image6": ("IMAGE",), + "image7": ("IMAGE",), + "image8": ("IMAGE",), + "image9": ("IMAGE",), + "image10": ("IMAGE",), + "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}), + "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"}), + "enable_web_search": ("BOOLEAN", {"default": False}), + "thinking_level": (["off", "minimal", "high"], {"default": "off"}), + } + } RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("edited_image",) - FUNCTION = "edit_image" - CATEGORY = "image/edit" - OUTPUT_NODE = True - - def edit_image(self, prompt, model, fal_key, image1=None, image2=None, image3=None, image4=None, image5=None, - width=0, height=0, num_images=1, seed=0, aspect_ratio="auto", resolution="1K", - enable_web_search=False, thinking_level="off", acceleration="none"): # Hardcoded to none, kept for compatibility - if fal_key == "your_fal_key_here": - raise ValueError("Please set your fal.ai API key in the node.") - - # Collect all non-None images - images = [img for img in [image1, image2, image3, image4, image5] if img is not None] - if not images: - raise ValueError("At least one image input must be connected.") + FUNCTION = "edit_image" + CATEGORY = "image/edit" + OUTPUT_NODE = True + + def edit_image(self, prompt, model, fal_key, image1=None, image2=None, image3=None, image4=None, image5=None, + image6=None, image7=None, image8=None, image9=None, image10=None, mask=None, + width=0, height=0, num_images=1, seed=0, aspect_ratio="auto", resolution="1K", + quality="high", enable_web_search=False, thinking_level="off", acceleration="none"): # Hardcoded to none, kept for compatibility + if fal_key == "your_fal_key_here": + raise ValueError("Please set your fal.ai API key in the node.") + + # Collect all non-None images + image_inputs = [image1, image2, image3, image4, image5, image6, image7, image8, image9, image10] + images = [img for img in image_inputs if img is not None] + if not images: + raise ValueError("At least one image input must be connected.") # Convert each to PIL and encode img_data_uris = [] @@ -89,16 +111,15 @@ class NanoSeedEdit: # For Flux and Qwen, we usually send image_size in payload, but resizing here # ensures consistent aspect ratio calculation before sending if needed. # Nano models ignore this as per original code. - if custom_size: - if model in ["nano_banana", "nano_banana_pro", "nano_banana_2"]: - pass - else: - pil_image = pil_image.resize((width, height), Image.LANCZOS) - - buffer = BytesIO() - pil_image.save(buffer, format="PNG") - img_str = base64.b64encode(buffer.getvalue()).decode() - img_data_uris.append(f"data:image/png;base64,{img_str}") + if custom_size: + if model in ["nano_banana", "nano_banana_pro", "nano_banana_2"]: + pass + else: + pil_image = pil_image.resize((width, height), Image.LANCZOS) + + img_data_uri = pil2data_uri(pil_image) + if img_data_uri is not None: + 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: @@ -135,16 +156,33 @@ class NanoSeedEdit: "seed": seed, "aspect_ratio": aspect_ratio, "resolution": resolution, - "output_format": "png", - "enable_web_search": enable_web_search, - "limit_generations": True, - "sync_mode": True, - } - if thinking_level != "off": - payload["thinking_level"] = thinking_level - elif model == "grok_imagine_edit": - url = "https://fal.run/xai/grok-imagine-image/edit" - payload = { + "output_format": "png", + "enable_web_search": enable_web_search, + "limit_generations": True, + "safety_tolerance": "6", + "sync_mode": True, + } + if thinking_level != "off": + payload["thinking_level"] = thinking_level + elif model == "gpt_image_2_edit": + 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", + "sync_mode": True, + } + if custom_size: + payload["image_size"] = {"width": width, "height": height} + if mask is not None: + mask_data_uri = tensor2data_uri(mask) + if mask_data_uri is not None: + payload["mask_image_url"] = mask_data_uri + elif model == "grok_imagine_edit": + url = "https://fal.run/xai/grok-imagine-image/edit" + payload = { "prompt": prompt, "image_urls": img_data_uris[:3], "num_images": min(num_images, 4),