Auto-generated nodes for the entire live fal catalog (1,391 models → 1,482 total nodes) built at startup from a committed registry, plus a full rewrite of the core layer. All 87 v1 node keys, inputs and outputs are preserved — existing workflows load and run unchanged. New: - data/fal_registry.json + scripts/build_registry.py: distills fal's catalog and per-endpoint OpenAPI schemas into a deterministic registry - nodes/dynamic/: schema→node factory (typed widgets, tooltips, pricing in node help, IMAGE/VIDEO/AUDIO auto-upload, native VIDEO/AUDIO outputs, seed -1=random, force_rerun), plus a generic Fal Any Endpoint node for arbitrary endpoints - nodes/utils/: rewritten core — FalApiError surfacing fal's real error payloads, queue progress logs, ComfyUI cancel support, retries and timeouts everywhere, parallel uploads/downloads, structured logging - tests/ (45 tests incl. a legacy-key snapshot lock), CI lint+test workflow, weekly registry-refresh workflow that opens a PR on changes - README regenerated from the registry (scripts/build_readme.py) Fixed: - GPTImage2 / GPTImage2Edit were defined but never registered - Topaz video upscale sent a nonexistent API field (desired_increase) - SeedVR video upscaler had its error handling commented out + wrong model label; ProRes enum value mismatch translated at argument level - 10x `is ""` comparisons, bare excepts, missing HTTP timeouts, temp-file leaks, fps=0 crash in LoadVideoURL, duplicate enum entry - opencv-python & friends missing from pyproject (Registry installs) - API failures now raise visible errors instead of silently returning blank images or "Error:" strings that downstream nodes tried to load BREAKING (behavioral only): failed API calls raise instead of returning blank/black images or error strings. Node signatures are unchanged.
2540 lines
106 KiB
Python
Executable File
2540 lines
106 KiB
Python
Executable File
from .fal_utils import ApiHandler, ImageUtils, ResultProcessor, logger
|
|
|
|
|
|
class Sana:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "square_hd", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": (
|
|
"INT",
|
|
{"default": 3840, "min": 512, "max": 4096, "step": 16, "tooltip": "Output width in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"height": (
|
|
"INT",
|
|
{"default": 2160, "min": 512, "max": 4096, "step": 16, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"num_inference_steps": ("INT", {"default": 18, "min": 1, "max": 50, "tooltip": "Number of denoising steps; more steps can improve quality but take longer"}),
|
|
"guidance_scale": (
|
|
"FLOAT",
|
|
{"default": 5.0, "min": 1.0, "max": 20.0, "step": 0.1, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
},
|
|
"optional": {
|
|
"negative_prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "What to avoid in the generated image"}),
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
"enable_safety_checker": ("BOOLEAN", {"default": True, "tooltip": "Run the safety checker on generated images"}),
|
|
"output_format": (["png", "jpeg"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_inference_steps,
|
|
guidance_scale,
|
|
num_images,
|
|
negative_prompt="",
|
|
seed=-1,
|
|
enable_safety_checker=True,
|
|
output_format="png",
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"negative_prompt": negative_prompt,
|
|
"num_inference_steps": num_inference_steps,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"enable_safety_checker": enable_safety_checker,
|
|
"output_format": output_format,
|
|
}
|
|
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/sana", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Sana", e)
|
|
|
|
|
|
class Recraft:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "square_hd", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": ("INT", {"default": 512, "min": 512, "max": 2048, "step": 16, "tooltip": "Output width in pixels (used when image_size is 'custom')"}),
|
|
"height": (
|
|
"INT",
|
|
{"default": 512, "min": 512, "max": 2048, "step": 16, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"style": (
|
|
[
|
|
"any",
|
|
"realistic_image",
|
|
"digital_illustration",
|
|
"vector_illustration",
|
|
"realistic_image/b_and_w",
|
|
"realistic_image/hard_flash",
|
|
"realistic_image/hdr",
|
|
"realistic_image/natural_light",
|
|
"realistic_image/studio_portrait",
|
|
"realistic_image/enterprise",
|
|
"realistic_image/motion_blur",
|
|
"digital_illustration/pixel_art",
|
|
"digital_illustration/hand_drawn",
|
|
"digital_illustration/grain",
|
|
"digital_illustration/infantile_sketch",
|
|
"digital_illustration/2d_art_poster",
|
|
"digital_illustration/handmade_3d",
|
|
"digital_illustration/hand_drawn_outline",
|
|
"digital_illustration/engraving_color",
|
|
"digital_illustration/2d_art_poster_2",
|
|
"vector_illustration/engraving",
|
|
"vector_illustration/line_art",
|
|
"vector_illustration/line_circuit",
|
|
"vector_illustration/linocut",
|
|
],
|
|
{"default": "realistic_image", "tooltip": "Recraft generation style (realistic photo, digital illustration, vector art, ...)"},
|
|
),
|
|
},
|
|
"optional": {
|
|
"style_id": ("STRING", {"default": "", "tooltip": "ID of a custom Recraft style; overrides the preset style when set"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(self, prompt, image_size, width, height, style, style_id=""):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"style": style,
|
|
}
|
|
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
|
|
if style_id:
|
|
arguments["style_id"] = style_id
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/recraft-v3", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Recraft", e)
|
|
|
|
|
|
class HidreamFull:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "landscape_4_3", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": (
|
|
"INT",
|
|
{"default": 1024, "min": 512, "max": 1440, "step": 32, "tooltip": "Output width in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"height": (
|
|
"INT",
|
|
{"default": 768, "min": 512, "max": 1440, "step": 32, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"num_inference_steps": ("INT", {"default": 28, "min": 1, "max": 100, "tooltip": "Number of denoising steps; more steps can improve quality but take longer"}),
|
|
"guidance_scale": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 20.0, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 10, "tooltip": "Number of images to generate per run"}),
|
|
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2", "tooltip": "Content moderation strictness (1 = strictest, 6 = most permissive)"}),
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_inference_steps,
|
|
guidance_scale,
|
|
num_images,
|
|
safety_tolerance,
|
|
seed=-1,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_inference_steps": num_inference_steps,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"safety_tolerance": safety_tolerance,
|
|
}
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(
|
|
"fal-ai/hidream-i1-full", arguments
|
|
)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Hidream Full", e)
|
|
|
|
|
|
class Ideogramv3:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "landscape_4_3", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": (
|
|
"INT",
|
|
{"default": 1024, "min": 512, "max": 1440, "step": 32, "tooltip": "Output width in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"height": (
|
|
"INT",
|
|
{"default": 768, "min": 512, "max": 1440, "step": 32, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"num_inference_steps": ("INT", {"default": 28, "min": 1, "max": 100, "tooltip": "Number of denoising steps; more steps can improve quality but take longer"}),
|
|
"guidance_scale": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 20.0, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 10, "tooltip": "Number of images to generate per run"}),
|
|
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2", "tooltip": "Content moderation strictness (1 = strictest, 6 = most permissive)"}),
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_inference_steps,
|
|
guidance_scale,
|
|
num_images,
|
|
safety_tolerance,
|
|
seed=-1,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_inference_steps": num_inference_steps,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"safety_tolerance": safety_tolerance,
|
|
}
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/ideogram/v3", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Ideogramv3", e)
|
|
|
|
|
|
class FluxPro:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "landscape_4_3", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": (
|
|
"INT",
|
|
{"default": 1024, "min": 512, "max": 1440, "step": 32, "tooltip": "Output width in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"height": (
|
|
"INT",
|
|
{"default": 768, "min": 512, "max": 1440, "step": 32, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"num_inference_steps": ("INT", {"default": 28, "min": 1, "max": 100, "tooltip": "Number of denoising steps; more steps can improve quality but take longer"}),
|
|
"guidance_scale": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 20.0, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 10, "tooltip": "Number of images to generate per run"}),
|
|
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2", "tooltip": "Content moderation strictness (1 = strictest, 6 = most permissive)"}),
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_inference_steps,
|
|
guidance_scale,
|
|
num_images,
|
|
safety_tolerance,
|
|
seed=-1,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_inference_steps": num_inference_steps,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"safety_tolerance": safety_tolerance,
|
|
}
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/flux-pro", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("FluxPro", e)
|
|
|
|
|
|
class FluxDev:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "landscape_4_3", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": (
|
|
"INT",
|
|
{"default": 1024, "min": 512, "max": 1536, "step": 16, "tooltip": "Output width in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"height": (
|
|
"INT",
|
|
{"default": 768, "min": 512, "max": 1536, "step": 16, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"num_inference_steps": ("INT", {"default": 28, "min": 1, "max": 100, "tooltip": "Number of denoising steps; more steps can improve quality but take longer"}),
|
|
"guidance_scale": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 20.0, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 10, "tooltip": "Number of images to generate per run"}),
|
|
"enable_safety_checker": ("BOOLEAN", {"default": True, "tooltip": "Run the safety checker on generated images"}),
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_inference_steps,
|
|
guidance_scale,
|
|
num_images,
|
|
enable_safety_checker,
|
|
seed=-1,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_inference_steps": num_inference_steps,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"enable_safety_checker": enable_safety_checker,
|
|
}
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/flux/dev", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("FluxDev", e)
|
|
|
|
|
|
class FluxSchnell:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "landscape_4_3", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": (
|
|
"INT",
|
|
{"default": 1024, "min": 512, "max": 1536, "step": 32, "tooltip": "Output width in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"height": (
|
|
"INT",
|
|
{"default": 768, "min": 512, "max": 1536, "step": 32, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"num_inference_steps": ("INT", {"default": 4, "min": 1, "max": 100, "tooltip": "Number of denoising steps; more steps can improve quality but take longer"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 10, "tooltip": "Number of images to generate per run"}),
|
|
"enable_safety_checker": ("BOOLEAN", {"default": True, "tooltip": "Run the safety checker on generated images"}),
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_inference_steps,
|
|
num_images,
|
|
enable_safety_checker,
|
|
seed=-1,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_inference_steps": num_inference_steps,
|
|
"num_images": num_images,
|
|
"enable_safety_checker": enable_safety_checker,
|
|
}
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/flux/schnell", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("FluxSchnell", e)
|
|
|
|
|
|
class FluxPro11:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "landscape_4_3", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": (
|
|
"INT",
|
|
{"default": 1024, "min": 512, "max": 1440, "step": 32, "tooltip": "Output width in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"height": (
|
|
"INT",
|
|
{"default": 768, "min": 512, "max": 1440, "step": 32, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 10, "tooltip": "Number of images to generate per run"}),
|
|
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2", "tooltip": "Content moderation strictness (1 = strictest, 6 = most permissive)"}),
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_images,
|
|
safety_tolerance,
|
|
seed=-1,
|
|
sync_mode=False,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_images": num_images,
|
|
"safety_tolerance": safety_tolerance,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/flux-pro/v1.1", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("FluxPro 1.1", e)
|
|
|
|
|
|
class FluxPro1Fill:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 10, "tooltip": "Number of images to generate per run"}),
|
|
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2", "tooltip": "Content moderation strictness (1 = strictest, 6 = most permissive)"}),
|
|
"output_format": (["png", "jpeg"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
|
|
},
|
|
"optional": {
|
|
"image": ("IMAGE", {"default": None, "tooltip": "Input image to edit or transform"}),
|
|
"mask_image": ("IMAGE", {"default": None, "tooltip": "Inpainting mask: white areas are regenerated, black areas are preserved"}),
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
"enhance_prompt": ("BOOLEAN", {"default": True, "tooltip": "Let the model automatically enhance and expand the prompt"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image = None,
|
|
mask_image = None,
|
|
output_format = "png",
|
|
num_images = 1,
|
|
safety_tolerance = "2",
|
|
seed=0,
|
|
sync_mode=False,
|
|
enhance_prompt=True,
|
|
):
|
|
if image is None or mask_image is None:
|
|
return ApiHandler.handle_image_generation_error(
|
|
"FluxPro 1/FILL", "Both image and mask_image inputs are required."
|
|
)
|
|
image_url = ImageUtils.upload_image(image)
|
|
mask_url = ImageUtils.upload_image(mask_image)
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_images": num_images,
|
|
"sync_mode": sync_mode,
|
|
"output_format": output_format,
|
|
"safety_tolerance": safety_tolerance,
|
|
"image_url": image_url,
|
|
"mask_url": mask_url,
|
|
"enhance_prompt": enhance_prompt,
|
|
}
|
|
if seed != 0:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/flux-pro/v1/fill", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("FluxPro 1/FILL", e)
|
|
|
|
|
|
class FluxUltra:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"aspect_ratio": (
|
|
["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "9:21"],
|
|
{"default": "16:9", "tooltip": "Aspect ratio of the generated image"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 1, "tooltip": "Number of images to generate per run"}),
|
|
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2", "tooltip": "Content moderation strictness (1 = strictest, 6 = most permissive)"}),
|
|
"enable_safety_checker": ("BOOLEAN", {"default": True, "tooltip": "Run the safety checker on generated images"}),
|
|
"raw": ("BOOLEAN", {"default": False, "tooltip": "Generate less processed, more natural-looking images (Flux Ultra raw mode)"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
aspect_ratio,
|
|
num_images,
|
|
safety_tolerance,
|
|
enable_safety_checker,
|
|
raw,
|
|
sync_mode,
|
|
seed=-1,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"aspect_ratio": aspect_ratio,
|
|
"num_images": num_images,
|
|
"safety_tolerance": safety_tolerance,
|
|
"enable_safety_checker": enable_safety_checker,
|
|
"raw": raw,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(
|
|
"fal-ai/flux-pro/v1.1-ultra", arguments
|
|
)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("FluxUltra", e)
|
|
|
|
|
|
class FluxLora:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "landscape_4_3", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": (
|
|
"INT",
|
|
{"default": 1024, "min": 512, "max": 1536, "step": 16, "tooltip": "Output width in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"height": (
|
|
"INT",
|
|
{"default": 768, "min": 512, "max": 1536, "step": 16, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"num_inference_steps": ("INT", {"default": 28, "min": 1, "max": 50, "tooltip": "Number of denoising steps; more steps can improve quality but take longer"}),
|
|
"guidance_scale": (
|
|
"FLOAT",
|
|
{"default": 3.0, "min": 0.0, "max": 20.0, "step": 0.1, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"enable_safety_checker": ("BOOLEAN", {"default": True, "tooltip": "Run the safety checker on generated images"}),
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
"lora_path_1": ("STRING", {"default": "", "tooltip": "URL or path of LoRA weights file #1 to apply"}),
|
|
"lora_scale_1": (
|
|
"FLOAT",
|
|
{"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.05, "tooltip": "Strength of LoRA #1 (1.0 = full effect)"},
|
|
),
|
|
"lora_path_2": ("STRING", {"default": "", "tooltip": "URL or path of LoRA weights file #2 to apply"}),
|
|
"lora_scale_2": (
|
|
"FLOAT",
|
|
{"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.05, "tooltip": "Strength of LoRA #2 (1.0 = full effect)"},
|
|
),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_inference_steps,
|
|
guidance_scale,
|
|
num_images,
|
|
enable_safety_checker,
|
|
seed=-1,
|
|
lora_path_1="",
|
|
lora_scale_1=1.0,
|
|
lora_path_2="",
|
|
lora_scale_2=1.0,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_inference_steps": num_inference_steps,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"enable_safety_checker": enable_safety_checker,
|
|
}
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
# Add LoRAs
|
|
loras = []
|
|
if lora_path_1:
|
|
loras.append({"path": lora_path_1, "scale": lora_scale_1})
|
|
if lora_path_2:
|
|
loras.append({"path": lora_path_2, "scale": lora_scale_2})
|
|
if loras:
|
|
arguments["loras"] = loras
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/flux-lora", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("FluxLora", e)
|
|
|
|
|
|
class FluxGeneral:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "landscape_4_3", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": (
|
|
"INT",
|
|
{"default": 1024, "min": 512, "max": 1536, "step": 16, "tooltip": "Output width in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"height": (
|
|
"INT",
|
|
{"default": 768, "min": 512, "max": 1536, "step": 16, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"num_inference_steps": ("INT", {"default": 28, "min": 1, "max": 50, "tooltip": "Number of denoising steps; more steps can improve quality but take longer"}),
|
|
"guidance_scale": (
|
|
"FLOAT",
|
|
{"default": 3.0, "min": 0.0, "max": 20.0, "step": 0.1, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"},
|
|
),
|
|
"real_cfg_scale": (
|
|
"FLOAT",
|
|
{"default": 3.3, "min": 0.0, "max": 5.0, "step": 0.1, "tooltip": "Classical CFG scale used when use_real_cfg is enabled"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"enable_safety_checker": ("BOOLEAN", {"default": False, "tooltip": "Run the safety checker on generated images"}),
|
|
"use_real_cfg": ("BOOLEAN", {"default": False, "tooltip": "Use classical CFG instead of Flux's distilled guidance"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
"ip_adapter_scale": (
|
|
"FLOAT",
|
|
{"default": 0.6, "min": 0.0, "max": 1.0, "step": 0.1, "tooltip": "Strength of the IP-Adapter image conditioning"},
|
|
),
|
|
"controlnet_conditioning_scale": (
|
|
"FLOAT",
|
|
{"default": 0.6, "min": 0.0, "max": 1.0, "step": 0.1, "tooltip": "Strength of the ControlNet conditioning"},
|
|
),
|
|
"ip_adapters": (
|
|
["None", "XLabs-AI/flux-ip-adapter"],
|
|
{"default": "None", "tooltip": "IP-Adapter model for image-prompt conditioning"},
|
|
),
|
|
"controlnets": (
|
|
[
|
|
"None",
|
|
"XLabs-AI/flux-controlnet-depth-v3",
|
|
"Shakker-Labs/FLUX.1-dev-ControlNet-Depth",
|
|
"jasperai/Flux.1-dev-Controlnet-Depth",
|
|
"jasperai/Flux.1-dev-Controlnet-Surface-Normals",
|
|
"XLabs-AI/flux-controlnet-canny-v3",
|
|
"InstantX/FLUX.1-dev-Controlnet-Canny",
|
|
"jasperai/Flux.1-dev-Controlnet-Upscaler",
|
|
"promeai/FLUX.1-controlnet-lineart-promeai",
|
|
],
|
|
{"default": "None", "tooltip": "ControlNet model used to guide generation"},
|
|
),
|
|
"controlnet_unions": (
|
|
[
|
|
"None",
|
|
"Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro",
|
|
"InstantX/FLUX.1-dev-Controlnet-Union",
|
|
],
|
|
{"default": "None", "tooltip": "ControlNet Union model used to guide generation"},
|
|
),
|
|
"controlnet_union_control_mode": (
|
|
["canny", "tile", "depth", "blur", "pose", "gray", "low_quality"],
|
|
{"default": "canny", "tooltip": "Control mode for the ControlNet Union model (canny, depth, pose, ...)"},
|
|
),
|
|
"control_image": ("IMAGE", {"tooltip": "Control image for the selected ControlNet"}),
|
|
"control_mask": ("MASK", {"tooltip": "Mask limiting where the ControlNet conditioning is applied"}),
|
|
"ip_adapter_image": ("IMAGE", {"tooltip": "Reference image for the IP-Adapter"}),
|
|
"ip_adapter_mask": ("MASK", {"tooltip": "Mask limiting where the IP-Adapter conditioning is applied"}),
|
|
"lora_path_1": ("STRING", {"default": "", "tooltip": "URL or path of LoRA weights file #1 to apply"}),
|
|
"lora_scale_1": (
|
|
"FLOAT",
|
|
{"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.05, "tooltip": "Strength of LoRA #1 (1.0 = full effect)"},
|
|
),
|
|
"lora_path_2": ("STRING", {"default": "", "tooltip": "URL or path of LoRA weights file #2 to apply"}),
|
|
"lora_scale_2": (
|
|
"FLOAT",
|
|
{"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.05, "tooltip": "Strength of LoRA #2 (1.0 = full effect)"},
|
|
),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_inference_steps,
|
|
guidance_scale,
|
|
real_cfg_scale,
|
|
num_images,
|
|
enable_safety_checker,
|
|
use_real_cfg,
|
|
sync_mode,
|
|
seed=-1,
|
|
lora_path_1="",
|
|
lora_scale_1=1.0,
|
|
lora_path_2="",
|
|
lora_scale_2=1.0,
|
|
ip_adapter_scale=0.6,
|
|
controlnet_conditioning_scale=0.6,
|
|
controlnet_union_control_mode="canny",
|
|
ip_adapters="None",
|
|
controlnets="None",
|
|
controlnet_unions="None",
|
|
control_image=None,
|
|
control_mask=None,
|
|
ip_adapter_image=None,
|
|
ip_adapter_mask=None,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_inference_steps": num_inference_steps,
|
|
"guidance_scale": guidance_scale,
|
|
"real_cfg_scale": real_cfg_scale,
|
|
"num_images": num_images,
|
|
"enable_safety_checker": enable_safety_checker,
|
|
"use_real_cfg": use_real_cfg,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
# Add ip_adapters if selected
|
|
if ip_adapters != "None":
|
|
arguments["ip_adapters"] = [
|
|
{
|
|
"path": ip_adapters,
|
|
"image_encoder_path": "openai/clip-vit-large-patch14",
|
|
"scale": ip_adapter_scale,
|
|
}
|
|
]
|
|
|
|
# Controlnet mapping
|
|
controlnet_mapping = {
|
|
"XLabs-AI/flux-controlnet-depth-v3": "https://huggingface.co/XLabs-AI/flux-controlnet-depth-v3/resolve/main/flux-depth-controlnet-v3.safetensors",
|
|
"Shakker-Labs/FLUX.1-dev-ControlNet-Depth": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-ControlNet-Depth/resolve/main/diffusion_pytorch_model.safetensors",
|
|
"jasperai/Flux.1-dev-Controlnet-Depth": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Depth/resolve/main/diffusion_pytorch_model.safetensors",
|
|
"jasperai/Flux.1-dev-Controlnet-Surface-Normals": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Surface-Normals/resolve/main/diffusion_pytorch_model.safetensors",
|
|
"XLabs-AI/flux-controlnet-canny-v3": "https://huggingface.co/XLabs-AI/flux-controlnet-canny-v3/resolve/main/flux-canny-controlnet-v3.safetensors",
|
|
"InstantX/FLUX.1-dev-Controlnet-Canny": "https://huggingface.co/InstantX/FLUX.1-dev-Controlnet-Canny/resolve/main/diffusion_pytorch_model.safetensors",
|
|
"jasperai/Flux.1-dev-Controlnet-Upscaler": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Upscaler/resolve/main/diffusion_pytorch_model.safetensors",
|
|
"promeai/FLUX.1-controlnet-lineart-promeai": "https://huggingface.co/promeai/FLUX.1-controlnet-lineart-promeai/resolve/main/diffusion_pytorch_model.safetensors",
|
|
}
|
|
|
|
# Add controlnets if selected
|
|
if controlnets != "None":
|
|
controlnet_path = controlnet_mapping.get(controlnets, controlnets)
|
|
arguments["controlnets"] = [
|
|
{
|
|
"path": controlnet_path,
|
|
"conditioning_scale": controlnet_conditioning_scale,
|
|
}
|
|
]
|
|
|
|
# Add controlnet_unions if selected
|
|
if controlnet_unions != "None":
|
|
arguments["controlnet_unions"] = [
|
|
{
|
|
"path": controlnet_unions,
|
|
"controls": [
|
|
{
|
|
"control_mode": controlnet_union_control_mode,
|
|
}
|
|
],
|
|
}
|
|
]
|
|
|
|
# Handle controlnets
|
|
if controlnets != "None" and control_image is not None:
|
|
control_image_url = ImageUtils.upload_image(control_image)
|
|
if control_image_url:
|
|
controlnet_path = controlnet_mapping.get(controlnets, controlnets)
|
|
arguments["controlnets"] = [
|
|
{
|
|
"path": controlnet_path,
|
|
"conditioning_scale": controlnet_conditioning_scale,
|
|
"control_image_url": control_image_url,
|
|
}
|
|
]
|
|
if control_mask is not None:
|
|
mask_image = ImageUtils.mask_to_image(control_mask)
|
|
mask_image_url = ImageUtils.upload_image(mask_image)
|
|
if mask_image_url:
|
|
arguments["controlnets"][0]["mask_image_url"] = mask_image_url
|
|
|
|
# Handle controlnet_unions
|
|
if controlnet_unions != "None" and control_image is not None:
|
|
control_image_url = ImageUtils.upload_image(control_image)
|
|
if control_image_url:
|
|
arguments["controlnet_unions"] = [
|
|
{
|
|
"path": controlnet_unions,
|
|
"controls": [
|
|
{
|
|
"control_mode": controlnet_union_control_mode,
|
|
"control_image_url": control_image_url,
|
|
}
|
|
],
|
|
}
|
|
]
|
|
if control_mask is not None:
|
|
mask_image = ImageUtils.mask_to_image(control_mask)
|
|
mask_image_url = ImageUtils.upload_image(mask_image)
|
|
if mask_image_url:
|
|
arguments["controlnet_unions"][0]["controls"][0][
|
|
"mask_image_url"
|
|
] = mask_image_url
|
|
|
|
# Handle ip_adapters
|
|
if ip_adapters != "None" and ip_adapter_image is not None:
|
|
ip_adapter_image_url = ImageUtils.upload_image(ip_adapter_image)
|
|
if ip_adapter_image_url:
|
|
ip_adapter_path = (
|
|
"https://huggingface.co/XLabs-AI/flux-ip-adapter/resolve/main/flux-ip-adapter.safetensors?download=true"
|
|
if ip_adapters == "XLabs-AI/flux-ip-adapter"
|
|
else ip_adapters
|
|
)
|
|
arguments["ip_adapters"] = [
|
|
{
|
|
"path": ip_adapter_path,
|
|
"image_encoder_path": "openai/clip-vit-large-patch14",
|
|
"image_url": ip_adapter_image_url,
|
|
"scale": ip_adapter_scale,
|
|
}
|
|
]
|
|
if ip_adapter_mask is not None:
|
|
mask_image = ImageUtils.mask_to_image(ip_adapter_mask)
|
|
mask_image_url = ImageUtils.upload_image(mask_image)
|
|
if mask_image_url:
|
|
arguments["ip_adapters"][0]["mask_image_url"] = mask_image_url
|
|
|
|
# Add LoRAs if provided
|
|
loras = []
|
|
if lora_path_1:
|
|
loras.append({"path": lora_path_1, "scale": lora_scale_1})
|
|
if lora_path_2:
|
|
loras.append({"path": lora_path_2, "scale": lora_scale_2})
|
|
if loras:
|
|
arguments["loras"] = loras
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/flux-general", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("FluxGeneral", e)
|
|
|
|
|
|
class FluxProKontext:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image": ("IMAGE", {"tooltip": "Input image to edit or transform"}),
|
|
},
|
|
"optional": {
|
|
"aspect_ratio": (
|
|
[
|
|
None,
|
|
"21:9",
|
|
"16:9",
|
|
"4:3",
|
|
"3:2",
|
|
"1:1",
|
|
"2:3",
|
|
"3:4",
|
|
"9:16",
|
|
"9:21",
|
|
],
|
|
{"default": None, "tooltip": "Aspect ratio of the generated image"},
|
|
),
|
|
"max_quality": ("BOOLEAN", {"default": False, "tooltip": "Use the higher-quality 'Max' endpoint (better results, higher cost)"}),
|
|
"guidance_scale": (
|
|
"FLOAT",
|
|
{"default": 3.5, "min": 1.0, "max": 20.0, "step": 0.1, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2", "tooltip": "Content moderation strictness (1 = strictest, 6 = most permissive)"}),
|
|
"output_format": (["jpeg", "png"], {"default": "jpeg", "tooltip": "File format of the generated image"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 2**32 - 1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image,
|
|
aspect_ratio="1:1",
|
|
max_quality=False,
|
|
guidance_scale=3.5,
|
|
num_images=1,
|
|
safety_tolerance="2",
|
|
output_format="jpeg",
|
|
sync_mode=False,
|
|
seed=0,
|
|
):
|
|
# Upload the input image to get URL (raises on failure)
|
|
image_url = ImageUtils.upload_image(image)
|
|
|
|
# Dynamic endpoint selection based on max_quality toggle
|
|
endpoint = (
|
|
"fal-ai/flux-pro/kontext/max" if max_quality else "fal-ai/flux-pro/kontext"
|
|
)
|
|
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_url": image_url,
|
|
"aspect_ratio": aspect_ratio,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"safety_tolerance": safety_tolerance,
|
|
"output_format": output_format,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
if seed > 0:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(endpoint, arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
model_name = "Flux Pro Kontext Max" if max_quality else "Flux Pro Kontext"
|
|
return ApiHandler.handle_image_generation_error(model_name, e)
|
|
|
|
|
|
class FluxProKontextMulti:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_1": ("IMAGE", {"tooltip": "Input reference image 1"}),
|
|
"image_2": ("IMAGE", {"tooltip": "Input reference image 2"}),
|
|
},
|
|
"optional": {
|
|
"image_3": ("IMAGE", {"tooltip": "Input reference image 3"}),
|
|
"image_4": ("IMAGE", {"tooltip": "Input reference image 4"}),
|
|
"aspect_ratio": (
|
|
[
|
|
None,
|
|
"21:9",
|
|
"16:9",
|
|
"4:3",
|
|
"3:2",
|
|
"1:1",
|
|
"2:3",
|
|
"3:4",
|
|
"9:16",
|
|
"9:21",
|
|
],
|
|
{"default": None, "tooltip": "Aspect ratio of the generated image"},
|
|
),
|
|
"max_quality": ("BOOLEAN", {"default": False, "tooltip": "Use the higher-quality 'Max' endpoint (better results, higher cost)"}),
|
|
"guidance_scale": (
|
|
"FLOAT",
|
|
{"default": 3.5, "min": 1.0, "max": 20.0, "step": 0.1, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2", "tooltip": "Content moderation strictness (1 = strictest, 6 = most permissive)"}),
|
|
"output_format": (["jpeg", "png"], {"default": "jpeg", "tooltip": "File format of the generated image"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 2**32 - 1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_1,
|
|
image_2,
|
|
image_3=None,
|
|
image_4=None,
|
|
aspect_ratio="1:1",
|
|
max_quality=False,
|
|
guidance_scale=3.5,
|
|
num_images=1,
|
|
safety_tolerance="2",
|
|
output_format="jpeg",
|
|
sync_mode=False,
|
|
seed=0,
|
|
):
|
|
# Upload all provided images (upload raises on failure)
|
|
image_urls = [
|
|
ImageUtils.upload_image(img)
|
|
for img in [image_1, image_2, image_3, image_4]
|
|
if img is not None
|
|
]
|
|
|
|
if len(image_urls) < 2:
|
|
model_name = (
|
|
"Flux Pro Kontext Max Multi"
|
|
if max_quality
|
|
else "Flux Pro Kontext Multi"
|
|
)
|
|
return ApiHandler.handle_image_generation_error(
|
|
model_name, "At least 2 images are required."
|
|
)
|
|
|
|
# Dynamic endpoint selection based on max_quality toggle
|
|
endpoint = (
|
|
"fal-ai/flux-pro/kontext/max/multi"
|
|
if max_quality
|
|
else "fal-ai/flux-pro/kontext/multi"
|
|
)
|
|
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_urls": image_urls,
|
|
"aspect_ratio": aspect_ratio,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"safety_tolerance": safety_tolerance,
|
|
"output_format": output_format,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
if seed > 0:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(endpoint, arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
model_name = (
|
|
"Flux Pro Kontext Max Multi"
|
|
if max_quality
|
|
else "Flux Pro Kontext Multi"
|
|
)
|
|
return ApiHandler.handle_image_generation_error(model_name, e)
|
|
|
|
|
|
class FluxProKontextTextToImage:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
},
|
|
"optional": {
|
|
"aspect_ratio": (
|
|
["21:9", "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "9:21"],
|
|
{"default": "1:1", "tooltip": "Aspect ratio of the generated image"},
|
|
),
|
|
"max_quality": ("BOOLEAN", {"default": False, "tooltip": "Use the higher-quality 'Max' endpoint (better results, higher cost)"}),
|
|
"guidance_scale": (
|
|
"FLOAT",
|
|
{"default": 3.5, "min": 1.0, "max": 20.0, "step": 0.1, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2", "tooltip": "Content moderation strictness (1 = strictest, 6 = most permissive)"}),
|
|
"output_format": (["jpeg", "png"], {"default": "jpeg", "tooltip": "File format of the generated image"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 2**32 - 1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
aspect_ratio="1:1",
|
|
max_quality=False,
|
|
guidance_scale=3.5,
|
|
num_images=1,
|
|
safety_tolerance="2",
|
|
output_format="jpeg",
|
|
sync_mode=False,
|
|
seed=0,
|
|
):
|
|
# Dynamic endpoint selection based on max_quality toggle
|
|
endpoint = (
|
|
"fal-ai/flux-pro/kontext/max/text-to-image"
|
|
if max_quality
|
|
else "fal-ai/flux-pro/kontext/text-to-image"
|
|
)
|
|
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"aspect_ratio": aspect_ratio,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"safety_tolerance": safety_tolerance,
|
|
"output_format": output_format,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
if seed > 0:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(endpoint, arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
model_name = (
|
|
"Flux Pro Kontext Max Text-to-Image"
|
|
if max_quality
|
|
else "Flux Pro Kontext Text-to-Image"
|
|
)
|
|
return ApiHandler.handle_image_generation_error(model_name, e)
|
|
|
|
|
|
class Imagen4PreviewNode:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
}
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(
|
|
"fal-ai/imagen4/preview", arguments
|
|
)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Imagen4 Preview", e)
|
|
|
|
|
|
class QwenImageEdit:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image": ("IMAGE", {"tooltip": "Input image to edit or transform"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "square_hd", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": (
|
|
"INT",
|
|
{"default": 512, "min": 128, "max": 2048, "step": 8, "tooltip": "Output width in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"height": (
|
|
"INT",
|
|
{"default": 512, "min": 128, "max": 2048, "step": 8, "tooltip": "Output height in pixels (used when image_size is 'custom')"},
|
|
),
|
|
"num_inference_steps": ("INT", {"default": 30, "min": 1, "max": 50, "tooltip": "Number of denoising steps; more steps can improve quality but take longer"}),
|
|
"guidance_scale": (
|
|
"FLOAT",
|
|
{"default": 4.0, "min": 1.0, "max": 20.0, "step": 0.1, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"enable_safety_checker": ("BOOLEAN", {"default": True, "tooltip": "Run the safety checker on generated images"}),
|
|
"output_format": (["png", "jpeg"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
"acceleration": (["none", "regular", "high"], {"default": "none", "tooltip": "Inference acceleration level; higher is faster with a possible quality trade-off"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
"optional": {
|
|
"negative_prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "What to avoid in the generated image"}),
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "edit_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def edit_image(
|
|
self,
|
|
prompt,
|
|
image,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_inference_steps,
|
|
guidance_scale,
|
|
num_images,
|
|
enable_safety_checker,
|
|
output_format,
|
|
acceleration,
|
|
sync_mode,
|
|
negative_prompt="",
|
|
seed=-1,
|
|
):
|
|
model_name = "Qwen Image Edit"
|
|
image_url = ImageUtils.upload_image(image)
|
|
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_url": image_url,
|
|
"num_inference_steps": num_inference_steps,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"enable_safety_checker": enable_safety_checker,
|
|
"output_format": output_format,
|
|
"acceleration": acceleration,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
|
|
if negative_prompt:
|
|
arguments["negative_prompt"] = negative_prompt
|
|
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/qwen-image-edit", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error(model_name, e)
|
|
|
|
|
|
class QwenImageEditPlusLoRA:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"images": ("IMAGE", {"default": None, "multiple": True, "tooltip": "Batched input images; every image in the batch is uploaded as a reference"})
|
|
},
|
|
"optional": {
|
|
"image_size": (
|
|
[
|
|
"custom",
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
],
|
|
{"default": "landscape_16_9", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"custom_width": ("INT", {"default": 1920, "min": 128, "max": 2048, "step": 8, "tooltip": "Output width in pixels (used when image_size is 'custom')"}),
|
|
"custom_height": ("INT", {"default": 1080, "min": 128, "max": 2048, "step": 8, "tooltip": "Output height in pixels (used when image_size is 'custom')"}),
|
|
"num_inference_steps": ("INT", {"default": 28, "min": 2, "max": 50, "tooltip": "Number of denoising steps; more steps can improve quality but take longer"}),
|
|
"guidance_scale": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 20.0, "step": 0.1, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"}),
|
|
"negative_prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "What to avoid in the generated image"}),
|
|
"seed": ("INT", {"default": -1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"enable_safety_checker": ("BOOLEAN", {"default": True, "tooltip": "Run the safety checker on generated images"}),
|
|
"output_format": (["png", "jpeg"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
"lora_path_1": ("STRING", {"default": "", "tooltip": "URL or path of LoRA weights file #1 to apply"}),
|
|
"lora_scale_1": (
|
|
"FLOAT",
|
|
{"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.05, "tooltip": "Strength of LoRA #1 (1.0 = full effect)"},
|
|
),
|
|
"lora_path_2": ("STRING", {"default": "", "tooltip": "URL or path of LoRA weights file #2 to apply"}),
|
|
"lora_scale_2": (
|
|
"FLOAT",
|
|
{"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.05, "tooltip": "Strength of LoRA #2 (1.0 = full effect)"},
|
|
),
|
|
"lora_path_3": ("STRING", {"default": "", "tooltip": "URL or path of LoRA weights file #3 to apply"}),
|
|
"lora_scale_3": (
|
|
"FLOAT",
|
|
{"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.05, "tooltip": "Strength of LoRA #3 (1.0 = full effect)"},
|
|
),
|
|
"lora_path_4": ("STRING", {"default": "", "tooltip": "URL or path of LoRA weights file #4 to apply"}),
|
|
"lora_scale_4": (
|
|
"FLOAT",
|
|
{"default": 1.0, "min": 0.0, "max": 2.0, "step": 0.05, "tooltip": "Strength of LoRA #4 (1.0 = full effect)"},
|
|
),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "edit_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def edit_image(
|
|
self,
|
|
prompt,
|
|
images=None,
|
|
image_size="square_hd",
|
|
custom_width=1024,
|
|
custom_height=1024,
|
|
num_inference_steps=28,
|
|
guidance_scale=4.0,
|
|
negative_prompt="",
|
|
seed=-1,
|
|
num_images=1,
|
|
enable_safety_checker=True,
|
|
output_format="png",
|
|
lora_path_1="",
|
|
lora_scale_1=1.0,
|
|
lora_path_2="",
|
|
lora_scale_2=1.0,
|
|
lora_path_3="",
|
|
lora_scale_3=1.0,
|
|
lora_path_4="",
|
|
lora_scale_4=1.0,
|
|
):
|
|
model_name = "Qwen Image Edit Plus LoRA"
|
|
|
|
# Handle multiple images
|
|
image_urls = ImageUtils.prepare_images(images)
|
|
if not image_urls:
|
|
return ApiHandler.handle_image_generation_error(
|
|
model_name, "No images provided."
|
|
)
|
|
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_urls": image_urls,
|
|
"num_inference_steps": num_inference_steps,
|
|
"guidance_scale": guidance_scale,
|
|
"num_images": num_images,
|
|
"enable_safety_checker": enable_safety_checker,
|
|
"output_format": output_format,
|
|
}
|
|
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": custom_width, "height": custom_height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
|
|
if negative_prompt:
|
|
arguments["negative_prompt"] = negative_prompt
|
|
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
# Add LoRAs if provided (maximum 4)
|
|
loras = []
|
|
if lora_path_1:
|
|
loras.append({"path": lora_path_1, "scale": lora_scale_1})
|
|
if lora_path_2:
|
|
loras.append({"path": lora_path_2, "scale": lora_scale_2})
|
|
if lora_path_3:
|
|
loras.append({"path": lora_path_3, "scale": lora_scale_3})
|
|
if lora_path_4:
|
|
loras.append({"path": lora_path_4, "scale": lora_scale_4})
|
|
if loras:
|
|
arguments["loras"] = loras
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(
|
|
"fal-ai/qwen-image-edit-plus-lora", arguments
|
|
)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error(model_name, e)
|
|
|
|
|
|
# https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=fal-ai/bytedance/seededit/v3/edit-image
|
|
class SeedEditV3:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image": ("IMAGE", {"tooltip": "Input image to edit or transform"}),
|
|
},
|
|
"optional": {
|
|
"guidance_scale": (
|
|
"FLOAT",
|
|
{"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.1, "tooltip": "CFG scale: how closely the output follows the prompt (higher = stricter)"},
|
|
),
|
|
"seed": ("INT", {"default": -1, "min": -1, "max": 2**32 - 1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image,
|
|
guidance_scale=0.5,
|
|
seed=-1,
|
|
):
|
|
model_name = "SeedEdit 3.0"
|
|
image_url = ImageUtils.upload_image(image)
|
|
|
|
endpoint = "fal-ai/bytedance/seededit/v3/edit-image"
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_url": image_url,
|
|
"guidance_scale": guidance_scale,
|
|
}
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(endpoint, arguments)
|
|
return ResultProcessor.process_single_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error(model_name, e)
|
|
|
|
|
|
# https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=fal-ai/bytedance/seedream/v4/edit
|
|
class SeedreamV4Edit:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_1": ("IMAGE", {"tooltip": "Input reference image 1"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "square_hd", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": ("INT", {"default": 3840, "min": 1024, "max": 4096, "tooltip": "Output width in pixels (used when image_size is 'custom')"}),
|
|
"height": ("INT", {"default": 2160, "min": 1024, "max": 4096, "tooltip": "Output height in pixels (used when image_size is 'custom')"}),
|
|
},
|
|
"optional": {
|
|
"image_2": ("IMAGE", {"tooltip": "Input reference image 2"}),
|
|
"image_3": ("IMAGE", {"tooltip": "Input reference image 3"}),
|
|
"image_4": ("IMAGE", {"tooltip": "Input reference image 4"}),
|
|
"image_5": ("IMAGE", {"tooltip": "Input reference image 5"}),
|
|
"image_6": ("IMAGE", {"tooltip": "Input reference image 6"}),
|
|
"image_7": ("IMAGE", {"tooltip": "Input reference image 7"}),
|
|
"image_8": ("IMAGE", {"tooltip": "Input reference image 8"}),
|
|
"image_9": ("IMAGE", {"tooltip": "Input reference image 9"}),
|
|
"image_10": ("IMAGE", {"tooltip": "Input reference image 10"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 6, "tooltip": "Number of images to generate per run"}),
|
|
"max_images": ("INT", {"default": 1, "min": 1, "max": 6, "tooltip": "Maximum images returned per generation; total inputs + outputs must not exceed 15"}),
|
|
"enable_safety_checker": ("BOOLEAN", {"default": True, "tooltip": "Run the safety checker on generated images"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
"seed": ("INT", {"default": -1, "min": -1, "max": 2**32 - 1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_1,
|
|
image_size,
|
|
width,
|
|
height,
|
|
num_images,
|
|
max_images,
|
|
image_2=None,
|
|
image_3=None,
|
|
image_4=None,
|
|
image_5=None,
|
|
image_6=None,
|
|
image_7=None,
|
|
image_8=None,
|
|
image_9=None,
|
|
image_10=None,
|
|
enable_safety_checker=True,
|
|
seed=-1,
|
|
sync_mode=False,
|
|
):
|
|
model_name = "Seedream 4.0 Edit"
|
|
|
|
# Upload all provided images (upload raises on failure)
|
|
image_urls = [
|
|
ImageUtils.upload_image(img)
|
|
for img in [
|
|
image_1,
|
|
image_2,
|
|
image_3,
|
|
image_4,
|
|
image_5,
|
|
image_6,
|
|
image_7,
|
|
image_8,
|
|
image_9,
|
|
image_10,
|
|
]
|
|
if img is not None
|
|
]
|
|
|
|
# the total number of images (image inputs + image outputs) must not exceed 15
|
|
max_total_images = 15
|
|
potential_total = len(image_urls) + (num_images * max_images)
|
|
if potential_total > max_total_images:
|
|
return ApiHandler.handle_image_generation_error(
|
|
model_name,
|
|
f"Total images (inputs + outputs) must be <= {max_total_images}. "
|
|
f"inputs={len(image_urls)}, num_images={num_images}, max_images={max_images}",
|
|
)
|
|
|
|
endpoint = "fal-ai/bytedance/seedream/v4/edit"
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_urls": image_urls,
|
|
"num_images": num_images,
|
|
"max_images": max_images,
|
|
"enable_safety_checker": enable_safety_checker,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(endpoint, arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error(model_name, e)
|
|
|
|
|
|
|
|
class NanoBananaTextToImage:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
},
|
|
"optional": {
|
|
"aspect_ratio": (
|
|
["21:9", "1:1", "4:3", "3:2", "2:3", "5:4", "4:5", "3:4", "16:9", "9:16"],
|
|
{"default": "1:1", "tooltip": "Aspect ratio of the generated image"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"output_format": (["jpeg", "png"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
aspect_ratio="1:1",
|
|
num_images=1,
|
|
output_format="png",
|
|
sync_mode=False,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"aspect_ratio": aspect_ratio,
|
|
"num_images": num_images,
|
|
"output_format": output_format,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/nano-banana", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Nano Banana Text-to-Image", e)
|
|
|
|
|
|
class NanoBananaEdit:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_1": ("IMAGE", {"tooltip": "Input reference image 1"}),
|
|
},
|
|
"optional": {
|
|
"image_2": ("IMAGE", {"tooltip": "Input reference image 2"}),
|
|
"image_3": ("IMAGE", {"tooltip": "Input reference image 3"}),
|
|
"image_4": ("IMAGE", {"tooltip": "Input reference image 4"}),
|
|
"images": ("IMAGE", {"default": None, "multiple": True, "tooltip": "Batched input images; every image in the batch is uploaded as a reference"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"output_format": (["jpeg", "png"], {"default": "jpeg", "tooltip": "File format of the generated image"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_1,
|
|
image_2=None,
|
|
image_3=None,
|
|
image_4=None,
|
|
images=None,
|
|
num_images=1,
|
|
output_format="jpeg",
|
|
):
|
|
# Upload all provided images
|
|
singleImages = ImageUtils.prepare_images([image_1, image_2, image_3, image_4])
|
|
batchImages = ImageUtils.prepare_images(images)
|
|
image_urls = singleImages + batchImages
|
|
|
|
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_urls": image_urls,
|
|
"num_images": num_images,
|
|
"output_format": output_format,
|
|
}
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/nano-banana/edit", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Nano Banana Edit", e)
|
|
|
|
|
|
class NanoBananaPro:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
},
|
|
"optional": {
|
|
"images": ("IMAGE", {"tooltip": "Batched input images; every image in the batch is uploaded as a reference"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"aspect_ratio": (
|
|
["auto", "21:9", "16:9", "3:2", "4:3", "5:4", "1:1", "4:5", "3:4", "2:3", "9:16"],
|
|
{"default": "1:1", "tooltip": "Aspect ratio of the generated image"},
|
|
),
|
|
"output_format": (["jpeg", "png", "webp"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
"resolution": (["1K", "2K", "4K"], {"default": "1K", "tooltip": "Output resolution tier of the generated image"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
images=None,
|
|
num_images=1,
|
|
aspect_ratio="1:1",
|
|
output_format="png",
|
|
resolution="1K",
|
|
sync_mode=False,
|
|
):
|
|
# Prepare image URLs from optional input, limit to 14 images max
|
|
if images is not None and hasattr(images, 'shape') and len(images.shape) == 4 and images.shape[0] > 14:
|
|
# If batch has more than 14 images, take only first 14
|
|
logger.warning(
|
|
"Nano Banana Pro: more than 14 input images provided; using the first 14"
|
|
)
|
|
images = images[:14]
|
|
image_urls = ImageUtils.prepare_images(images)
|
|
|
|
# Build base arguments
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_images": num_images,
|
|
"aspect_ratio": aspect_ratio,
|
|
"output_format": output_format,
|
|
"resolution": resolution,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
# Conditional endpoint routing based on whether ANY images provided
|
|
if len(image_urls) > 0:
|
|
# Use edit endpoint with image_urls array
|
|
endpoint = "fal-ai/nano-banana-pro/edit"
|
|
arguments["image_urls"] = image_urls
|
|
else:
|
|
# Use text-to-image endpoint (no image_urls parameter)
|
|
endpoint = "fal-ai/nano-banana-pro"
|
|
# Remove "auto" from aspect_ratio for text-to-image endpoint
|
|
if aspect_ratio == "auto":
|
|
arguments["aspect_ratio"] = "1:1"
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(endpoint, arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Nano Banana Pro", e)
|
|
|
|
|
|
class NanoBanana2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
},
|
|
"optional": {
|
|
"images": ("IMAGE", {"tooltip": "Batched input images; every image in the batch is uploaded as a reference"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"aspect_ratio": (
|
|
["auto", "21:9", "16:9", "3:2", "4:3", "5:4", "1:1", "4:5", "3:4", "2:3", "9:16"],
|
|
{"default": "1:1", "tooltip": "Aspect ratio of the generated image"},
|
|
),
|
|
"output_format": (["jpeg", "png", "webp"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
"resolution": (["0.5K", "1K", "2K", "4K"], {"default": "1K", "tooltip": "Output resolution tier of the generated image"}),
|
|
"seed": ("INT", {"default": -1, "min": -1, "max": 2147483647, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
"enable_web_search": ("BOOLEAN", {"default": False, "tooltip": "Allow the model to use web search for factual or current context"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
images=None,
|
|
num_images=1,
|
|
aspect_ratio="1:1",
|
|
output_format="png",
|
|
resolution="1K",
|
|
seed=-1,
|
|
enable_web_search=False,
|
|
sync_mode=False,
|
|
):
|
|
# Prepare image URLs from optional input, limit to 14 images max
|
|
if images is not None and hasattr(images, 'shape') and len(images.shape) == 4 and images.shape[0] > 14:
|
|
logger.warning(
|
|
"Nano Banana 2: more than 14 input images provided; using the first 14"
|
|
)
|
|
images = images[:14]
|
|
image_urls = ImageUtils.prepare_images(images)
|
|
|
|
# Build base arguments
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"num_images": num_images,
|
|
"aspect_ratio": aspect_ratio,
|
|
"output_format": output_format,
|
|
"resolution": resolution,
|
|
"enable_web_search": enable_web_search,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
# Add seed if specified
|
|
if seed != -1:
|
|
arguments["seed"] = seed
|
|
|
|
# Conditional endpoint routing based on whether images provided
|
|
if len(image_urls) > 0:
|
|
endpoint = "fal-ai/nano-banana-2/edit"
|
|
arguments["image_urls"] = image_urls
|
|
else:
|
|
endpoint = "fal-ai/nano-banana-2"
|
|
# Remove "auto" from aspect_ratio for text-to-image endpoint
|
|
if aspect_ratio == "auto":
|
|
arguments["aspect_ratio"] = "1:1"
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result(endpoint, arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Nano Banana 2", e)
|
|
|
|
|
|
class ReveTextToImage:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
},
|
|
"optional": {
|
|
"aspect_ratio": (
|
|
["16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16"],
|
|
{"default": "3:2", "tooltip": "Aspect ratio of the generated image"},
|
|
),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"output_format": (["jpeg", "png"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
aspect_ratio="1:1",
|
|
num_images=1,
|
|
output_format="png",
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"aspect_ratio": aspect_ratio,
|
|
"num_images": num_images,
|
|
"output_format": output_format,
|
|
}
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/reve/text-to-image", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Reve Text-to-Image", e)
|
|
|
|
|
|
class Dreamina31TextToImage:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "square_hd", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
},
|
|
"optional": {
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
"output_format": (["jpeg", "png"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
"seed": ("INT", {"default": -1, "min": -1, "max": 2**32 - 1, "tooltip": "Random seed for reproducible results; leave at the default for a random seed"}),
|
|
"enhance_prompt": ("BOOLEAN", {"default": False, "tooltip": "Let the model automatically enhance and expand the prompt"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size,
|
|
num_images=1,
|
|
sync_mode=False,
|
|
output_format="png",
|
|
seed=-1,
|
|
enhance_prompt=False,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_size": image_size,
|
|
"num_images": num_images,
|
|
"sync_mode": sync_mode,
|
|
"output_format": output_format,
|
|
"enhance_prompt": enhance_prompt,
|
|
}
|
|
|
|
if seed > 0:
|
|
arguments["seed"] = seed
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/bytedance/dreamina/v3.1/text-to-image", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("Dreamina v3.1 Text-to-Image", e)
|
|
|
|
|
|
class GPTImage15Edit:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"images": ("IMAGE", {"tooltip": "Batched input images; every image in the batch is uploaded as a reference"}),
|
|
},
|
|
"optional": {
|
|
"mask_image": ("IMAGE", {"tooltip": "Inpainting mask: white areas are regenerated, black areas are preserved"}),
|
|
"image_size": (["auto", "1024x1024", "1536x1024", "1024x1536"], {"default": "auto", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"}),
|
|
"background": (["auto", "transparent", "opaque"], {"default": "auto", "tooltip": "Background transparency of the generated image (transparent requires png/webp)"}),
|
|
"quality": (["low", "medium", "high"], {"default": "high", "tooltip": "Rendering quality; higher quality costs more and takes longer"}),
|
|
"input_fidelity": (["low", "high"], {"default": "high", "tooltip": "How closely the output preserves the input image(s)"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"output_format": (["jpeg", "png", "webp"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "edit_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def edit_image(
|
|
self,
|
|
prompt,
|
|
images,
|
|
mask_image=None,
|
|
image_size="auto",
|
|
background="auto",
|
|
quality="high",
|
|
input_fidelity="high",
|
|
num_images=1,
|
|
output_format="png",
|
|
sync_mode=False,
|
|
):
|
|
model_name = "GPT-Image 1.5"
|
|
|
|
# Prepare image URLs from input, limit to 16 images max
|
|
if images is not None and hasattr(images, 'shape') and len(images.shape) == 4 and images.shape[0] > 16:
|
|
# If batch has more than 16 images, take only first 16
|
|
logger.warning(
|
|
f"{model_name}: more than 16 input images provided; using the first 16"
|
|
)
|
|
images = images[:16]
|
|
image_urls = ImageUtils.prepare_images(images)
|
|
|
|
if len(image_urls) == 0:
|
|
return ApiHandler.handle_image_generation_error(
|
|
model_name, "No valid images provided."
|
|
)
|
|
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_urls": image_urls,
|
|
"image_size": image_size,
|
|
"background": background,
|
|
"quality": quality,
|
|
"input_fidelity": input_fidelity,
|
|
"num_images": num_images,
|
|
"output_format": output_format,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
# Add optional mask image
|
|
if mask_image is not None:
|
|
arguments["mask_image_url"] = ImageUtils.upload_image(mask_image)
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/gpt-image-1.5/edit", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error(model_name, e)
|
|
|
|
|
|
class GPTImage15:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
},
|
|
"optional": {
|
|
"image_size": (["1024x1024", "1536x1024", "1024x1536"], {"default": "1024x1024", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"}),
|
|
"background": (["auto", "transparent", "opaque"], {"default": "auto", "tooltip": "Background transparency of the generated image (transparent requires png/webp)"}),
|
|
"quality": (["low", "medium", "high"], {"default": "high", "tooltip": "Rendering quality; higher quality costs more and takes longer"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"output_format": (["jpeg", "png", "webp"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size="1024x1024",
|
|
background="auto",
|
|
quality="high",
|
|
num_images=1,
|
|
output_format="png",
|
|
sync_mode=False,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_size": image_size,
|
|
"background": background,
|
|
"quality": quality,
|
|
"num_images": num_images,
|
|
"output_format": output_format,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/gpt-image-1.5", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("GPT-Image 1.5", e)
|
|
|
|
|
|
class GPTImage2Edit:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
"image_1": ("IMAGE", {"tooltip": "Input reference image 1"}),
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "custom", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": ("INT", {"default": 3840, "min": 768, "max": 4096, "tooltip": "Output width in pixels (used when image_size is 'custom')"}),
|
|
"height": ("INT", {"default": 2160, "min": 768, "max": 4096, "tooltip": "Output height in pixels (used when image_size is 'custom')"}),
|
|
},
|
|
"optional": {
|
|
"image_2": ("IMAGE", {"tooltip": "Input reference image 2"}),
|
|
"image_3": ("IMAGE", {"tooltip": "Input reference image 3"}),
|
|
"image_4": ("IMAGE", {"tooltip": "Input reference image 4"}),
|
|
"image_5": ("IMAGE", {"tooltip": "Input reference image 5"}),
|
|
"image_6": ("IMAGE", {"tooltip": "Input reference image 6"}),
|
|
"image_7": ("IMAGE", {"tooltip": "Input reference image 7"}),
|
|
"image_8": ("IMAGE", {"tooltip": "Input reference image 8"}),
|
|
"image_9": ("IMAGE", {"tooltip": "Input reference image 9"}),
|
|
"image_10": ("IMAGE", {"tooltip": "Input reference image 10"}),
|
|
"image_11": ("IMAGE", {"tooltip": "Input reference image 11"}),
|
|
"image_12": ("IMAGE", {"tooltip": "Input reference image 12"}),
|
|
"image_13": ("IMAGE", {"tooltip": "Input reference image 13"}),
|
|
"image_14": ("IMAGE", {"tooltip": "Input reference image 14"}),
|
|
"image_15": ("IMAGE", {"tooltip": "Input reference image 15"}),
|
|
"image_16": ("IMAGE", {"tooltip": "Input reference image 16"}),
|
|
"mask_image": ("IMAGE", {"tooltip": "Inpainting mask: white areas are regenerated, black areas are preserved"}),
|
|
"background": (["auto", "transparent", "opaque"], {"default": "opaque", "tooltip": "Background transparency of the generated image (transparent requires png/webp)"}),
|
|
"quality": (["low", "medium", "high"], {"default": "high", "tooltip": "Rendering quality; higher quality costs more and takes longer"}),
|
|
"input_fidelity": (["low", "high"], {"default": "high", "tooltip": "How closely the output preserves the input image(s)"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"output_format": (["jpeg", "png", "webp"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "edit_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def edit_image(
|
|
self,
|
|
prompt,
|
|
image_1,
|
|
image_size,
|
|
width,
|
|
height,
|
|
image_2=None,
|
|
image_3=None,
|
|
image_4=None,
|
|
image_5=None,
|
|
image_6=None,
|
|
image_7=None,
|
|
image_8=None,
|
|
image_9=None,
|
|
image_10=None,
|
|
image_11=None,
|
|
image_12=None,
|
|
image_13=None,
|
|
image_14=None,
|
|
image_15=None,
|
|
image_16=None,
|
|
mask_image=None,
|
|
background="opaque",
|
|
quality="high",
|
|
input_fidelity="high",
|
|
num_images=1,
|
|
output_format="png",
|
|
sync_mode=False,
|
|
):
|
|
model_name = "GPT-Image 2 Edit"
|
|
|
|
# Upload all provided images (upload raises on failure)
|
|
image_urls = [
|
|
ImageUtils.upload_image(img)
|
|
for img in [
|
|
image_1,
|
|
image_2,
|
|
image_3,
|
|
image_4,
|
|
image_5,
|
|
image_6,
|
|
image_7,
|
|
image_8,
|
|
image_9,
|
|
image_10,
|
|
image_11,
|
|
image_12,
|
|
image_13,
|
|
image_14,
|
|
image_15,
|
|
image_16,
|
|
]
|
|
if img is not None
|
|
]
|
|
|
|
if len(image_urls) == 0:
|
|
return ApiHandler.handle_image_generation_error(
|
|
model_name, "No valid images provided."
|
|
)
|
|
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"image_urls": image_urls,
|
|
"background": background,
|
|
"quality": quality,
|
|
"input_fidelity": input_fidelity,
|
|
"num_images": num_images,
|
|
"output_format": output_format,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
|
|
if mask_image is not None:
|
|
arguments["mask_image_url"] = ImageUtils.upload_image(mask_image)
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/gpt-image-2/edit", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error(model_name, e)
|
|
|
|
|
|
class GPTImage2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True, "tooltip": "Text prompt describing the image to generate or the edit to apply"}),
|
|
},
|
|
"optional": {
|
|
"image_size": (
|
|
[
|
|
"square_hd",
|
|
"square",
|
|
"portrait_4_3",
|
|
"portrait_16_9",
|
|
"landscape_4_3",
|
|
"landscape_16_9",
|
|
"custom",
|
|
],
|
|
{"default": "custom", "tooltip": "Preset output size/aspect ratio; select 'custom' to use width and height"},
|
|
),
|
|
"width": ("INT", {"default": 3840, "min": 768, "max": 4096, "tooltip": "Output width in pixels (used when image_size is 'custom')"}),
|
|
"height": ("INT", {"default": 2160, "min": 768, "max": 4096, "tooltip": "Output height in pixels (used when image_size is 'custom')"}),
|
|
"background": (["auto", "transparent", "opaque"], {"default": "opaque", "tooltip": "Background transparency of the generated image (transparent requires png/webp)"}),
|
|
"quality": (["low", "medium", "high"], {"default": "high", "tooltip": "Rendering quality; higher quality costs more and takes longer"}),
|
|
"num_images": ("INT", {"default": 1, "min": 1, "max": 4, "tooltip": "Number of images to generate per run"}),
|
|
"output_format": (["jpeg", "png", "webp"], {"default": "png", "tooltip": "File format of the generated image"}),
|
|
"sync_mode": ("BOOLEAN", {"default": False, "tooltip": "Wait for generation and return the image data directly instead of a hosted URL"}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "generate_image"
|
|
CATEGORY = "FAL/Image"
|
|
|
|
def generate_image(
|
|
self,
|
|
prompt,
|
|
image_size="custom",
|
|
width=3840,
|
|
height=2160,
|
|
background="auto",
|
|
quality="high",
|
|
num_images=1,
|
|
output_format="png",
|
|
sync_mode=False,
|
|
):
|
|
arguments = {
|
|
"prompt": prompt,
|
|
"background": background,
|
|
"quality": quality,
|
|
"num_images": num_images,
|
|
"output_format": output_format,
|
|
"sync_mode": sync_mode,
|
|
}
|
|
|
|
if image_size == "custom":
|
|
arguments["image_size"] = {"width": width, "height": height}
|
|
else:
|
|
arguments["image_size"] = image_size
|
|
|
|
try:
|
|
result = ApiHandler.submit_and_get_result("fal-ai/gpt-image-2", arguments)
|
|
return ResultProcessor.process_image_result(result)
|
|
except Exception as e:
|
|
return ApiHandler.handle_image_generation_error("GPT-Image 2", e)
|
|
|
|
|
|
# Node class mappings
|
|
NODE_CLASS_MAPPINGS = {
|
|
"Ideogramv3_fal": Ideogramv3,
|
|
"Hidreamfull_fal": HidreamFull,
|
|
"FluxPro_fal": FluxPro,
|
|
"FluxDev_fal": FluxDev,
|
|
"FluxSchnell_fal": FluxSchnell,
|
|
"FluxPro11_fal": FluxPro11,
|
|
"FluxPro1Fill_fal": FluxPro1Fill,
|
|
"FluxUltra_fal": FluxUltra,
|
|
"FluxGeneral_fal": FluxGeneral,
|
|
"FluxLora_fal": FluxLora,
|
|
"Recraft_fal": Recraft,
|
|
"Sana_fal": Sana,
|
|
"FluxProKontext_fal": FluxProKontext,
|
|
"FluxProKontextMulti_fal": FluxProKontextMulti,
|
|
"FluxProKontextTextToImage_fal": FluxProKontextTextToImage,
|
|
"Imagen4Preview_fal": Imagen4PreviewNode,
|
|
"QwenImageEdit_fal": QwenImageEdit,
|
|
"QwenImageEditPlusLoRA_fal": QwenImageEditPlusLoRA,
|
|
"SeedEditV3_fal": SeedEditV3,
|
|
"SeedreamV4Edit_fal": SeedreamV4Edit,
|
|
"NanoBananaTextToImage_fal": NanoBananaTextToImage,
|
|
"NanoBananaEdit_fal": NanoBananaEdit,
|
|
"NanoBananaPro_fal": NanoBananaPro,
|
|
"NanoBanana2_fal": NanoBanana2,
|
|
"ReveTextToImage_fal": ReveTextToImage,
|
|
"Dreamina31TextToImage_fal": Dreamina31TextToImage,
|
|
"GPTImage15Edit_fal": GPTImage15Edit,
|
|
"GPTImage15_fal": GPTImage15,
|
|
"GPTImage2Edit_fal": GPTImage2Edit,
|
|
"GPTImage2_fal": GPTImage2,
|
|
}
|
|
|
|
|
|
# Display name mappings
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"Ideogramv3_fal": "Ideogramv3 (fal)",
|
|
"Hidreamfull_fal": "HidreamFull (fal)",
|
|
"FluxPro_fal": "Flux Pro (fal)",
|
|
"FluxDev_fal": "Flux Dev (fal)",
|
|
"FluxSchnell_fal": "Flux Schnell (fal)",
|
|
"FluxPro11_fal": "Flux Pro 1.1 (fal)",
|
|
"FluxPro1Fill_fal": "Flux Pro 1 Fill (fal)",
|
|
"FluxUltra_fal": "Flux Ultra (fal)",
|
|
"FluxGeneral_fal": "Flux General (fal)",
|
|
"FluxLora_fal": "Flux LoRA (fal)",
|
|
"Recraft_fal": "Recraft V3 (fal)",
|
|
"Sana_fal": "Sana (fal)",
|
|
"FluxProKontext_fal": "Flux Pro Kontext (fal)",
|
|
"FluxProKontextMulti_fal": "Flux Pro Kontext Multi (fal)",
|
|
"FluxProKontextTextToImage_fal": "Flux Pro Kontext Text-to-Image (fal)",
|
|
"Imagen4Preview_fal": "Imagen4 Preview (fal)",
|
|
"QwenImageEdit_fal": "Qwen Image Edit (fal)",
|
|
"QwenImageEditPlusLoRA_fal": "Qwen Image Edit Plus LoRA (fal)",
|
|
"SeedEditV3_fal": "SeedEdit 3.0 (fal)",
|
|
"SeedreamV4Edit_fal": "Seedream 4.0 Edit (fal)",
|
|
"NanoBananaTextToImage_fal": "Nano Banana Text-to-Image (fal)",
|
|
"NanoBananaEdit_fal": "Nano Banana Edit (fal)",
|
|
"NanoBananaPro_fal": "Nano Banana Pro (fal)",
|
|
"NanoBanana2_fal": "Nano Banana 2 (fal)",
|
|
"ReveTextToImage_fal": "Reve Text-to-Image (fal)",
|
|
"Dreamina31TextToImage_fal": "Dreamina v3.1 Text-to-Image (fal)",
|
|
"GPTImage15Edit_fal": "GPT-Image 1.5 Edit (fal)",
|
|
"GPTImage15_fal": "GPT-Image 1.5 (fal)",
|
|
"GPTImage2Edit_fal": "GPT-Image 2 Edit (fal)",
|
|
"GPTImage2_fal": "GPT-Image 2.0 (fal)",
|
|
}
|