Files

776 lines
30 KiB
Python

import os
import requests
from .utils import get_fal_key
from PIL import Image
import io
import base64
import torch
import numpy as np
from io import BytesIO
import tempfile
# Import DEBUG flag from nodes
try:
from .nodes import DEBUG
except ImportError:
DEBUG = True
# ComfyUI tensor to PIL conversion
def tensor2pil(img_tensor):
"""Convert ComfyUI tensor (B, H, W, C) to PIL image."""
# Handle list of tensors
if isinstance(img_tensor, list):
img_tensor = img_tensor[0]
if not torch.is_tensor(img_tensor):
raise TypeError(f"Input is not a torch tensor: {type(img_tensor)}")
# Clone and detach to avoid modifying original
img = img_tensor.detach().cpu().clamp(0, 1)
# Debug info
if DEBUG:
print(f"[tensor2pil] Input shape: {img.shape}, dtype: {img.dtype}")
# Handle different tensor formats
shape = img.shape
# Remove batch dimension if present
if len(shape) == 4:
# Assuming (B, H, W, C) format
if shape[0] == 1:
img = img[0] # Remove batch dimension
else:
# Take first image from batch
img = img[0]
if DEBUG:
print(f"[tensor2pil] Taking first image from batch of {shape[0]}")
# Now we should have (H, W, C)
if len(img.shape) == 3:
# Check if it's (C, H, W) format instead
if img.shape[0] in [1, 3, 4] and img.shape[0] < img.shape[1] and img.shape[0] < img.shape[2]:
# Likely (C, H, W) format, transpose to (H, W, C)
img = img.permute(1, 2, 0)
# Handle different channel counts
if img.shape[2] == 1: # Grayscale
img = img.repeat(1, 1, 3)
elif img.shape[2] == 4: # RGBA
img = img[:, :, :3] # Drop alpha
elif len(img.shape) == 2:
# Grayscale image, add channel dimension
img = img.unsqueeze(-1).repeat(1, 1, 3)
# Convert to numpy and then PIL
img_np = (img * 255).byte().numpy()
pil_img = Image.fromarray(img_np)
if DEBUG:
print(f"[tensor2pil] Output PIL size: {pil_img.size}, mode: {pil_img.mode}")
return pil_img
# TODO: import fal_client if available in the ComfyUI environment
try:
import fal_client
from fal_client import submit, InProgress
# Set up fal_client with API key
from .utils import get_fal_key
try:
fal_key = get_fal_key()
os.environ["FAL_KEY"] = fal_key
if DEBUG:
print(f"[fal_client] FAL_KEY set successfully")
except RuntimeError as e:
# Re-raise with the detailed error message
print(str(e))
fal_client = None
raise e
except Exception as e:
print(f"Warning: Could not set FAL_KEY: {e}")
fal_client = None
except ImportError as e:
fal_client = None
print(f"Warning: fal_client not installed or import error: {e}")
print("Please install fal-client with: pip install fal-client")
except RuntimeError:
# Key error already printed above
fal_client = None
def call_kontext_api(prompt, image, aspect_ratio, num_images, seed, guidance_scale, output_format, raw, image_prompt_strength, num_inference_steps, safety_tolerance):
"""
Call the Fal Kontext API and return output images and info string.
"""
if fal_client is None:
# Try to import and get key again to show the proper error message
try:
from .utils import get_fal_key
get_fal_key()
except RuntimeError as e:
# This will show the detailed key error message
raise e
except:
pass
# If we're here, it's a different issue
raise ImportError("fal_client is not installed. Please install fal-client in your environment with: pip install fal-client")
# Convert tensor to PIL image if needed
pil_image = tensor2pil(image) if not isinstance(image, Image.Image) else image
# Upload image to Fal storage instead of converting to base64
try:
image_url = upload_image_to_fal(pil_image)
if DEBUG:
print(f"[call_kontext_api] Uploaded image to: {image_url}")
except Exception as e:
error_msg = f"Failed to upload image: {str(e)}"
print(f"[call_kontext_api] ERROR: {error_msg}")
raise RuntimeError(error_msg)
if DEBUG:
print(f"[call_kontext_api] Input PIL image size: {pil_image.size}, mode: {pil_image.mode}")
print(f"[call_kontext_api] Uploaded image URL: {image_url}")
payload = {
"prompt": prompt,
"image_url": image_url,
"safety_tolerance": str(safety_tolerance),
"seed": int(seed),
"guidance_scale": float(guidance_scale),
"num_images": int(num_images),
"output_format": output_format,
"raw": bool(raw),
"image_prompt_strength": float(image_prompt_strength),
"num_inference_steps": int(num_inference_steps),
"sync_mode": True # Get images directly without CDN
}
if aspect_ratio and aspect_ratio != "Match input image":
payload["aspect_ratio"] = aspect_ratio
if DEBUG:
debug_payload = payload.copy()
debug_payload["image_url"] = image_url # Show actual URL for debugging
print(f"[call_kontext_api] Payload: {debug_payload}")
logs = []
def on_queue_update(update):
if hasattr(update, 'logs'):
for log in update.logs:
log_msg = log.get("message", str(log)) if isinstance(log, dict) else str(log)
logs.append(log_msg)
if DEBUG:
print(f"[call_kontext_api] Queue log: {log_msg}")
try:
if DEBUG:
print(f"[call_kontext_api] Calling Fal API...")
# Debug: Check what's available in fal_client
print(f"[call_kontext_api] fal_client type: {type(fal_client)}")
print(f"[call_kontext_api] fal_client attributes: {dir(fal_client)}")
# Try different methods based on what's available
if hasattr(fal_client, 'subscribe'):
result = fal_client.subscribe(
"fal-ai/flux-pro/kontext",
arguments=payload,
with_logs=True,
on_queue_update=on_queue_update,
)
elif hasattr(fal_client, 'run'):
# Try the run method as an alternative
result = fal_client.run(
"fal-ai/flux-pro/kontext",
arguments=payload,
)
elif hasattr(fal_client, 'submit'):
# Try submit method - this might return a different structure
handle = fal_client.submit(
"fal-ai/flux-pro/kontext",
arguments=payload,
)
# Wait for completion
result = handle.get()
else:
# Try as a callable module
result = fal_client(
"fal-ai/flux-pro/kontext",
**payload
)
if DEBUG:
print(f"[call_kontext_api] API call completed")
print(f"[call_kontext_api] Result type: {type(result)}")
if hasattr(result, '__dict__'):
print(f"[call_kontext_api] Result attributes: {list(result.__dict__.keys())}")
except Exception as e:
error_msg = f"Fal API call failed: {str(e)}"
print(f"[call_kontext_api] ERROR: {error_msg}")
import traceback
if DEBUG:
print(f"[call_kontext_api] Full traceback:\n{traceback.format_exc()}")
return [], error_msg
# Parse result
output_images = []
info_lines = []
passed_nsfw_filtering = True # Default to True (not blocked)
# Extract request ID
request_id = getattr(result, 'request_id', None)
if not request_id and hasattr(result, '__getitem__'):
try:
request_id = result.get('request_id')
except:
pass
if request_id:
info_lines.append(f"Request ID: {request_id}")
# Extract seed
result_seed = None
if hasattr(result, 'seed'):
result_seed = result.seed
elif hasattr(result, '__getitem__'):
try:
result_seed = result.get('seed')
except:
pass
elif hasattr(result, 'data') and isinstance(result.data, dict):
result_seed = result.data.get('seed')
if result_seed is not None:
info_lines.append(f"Seed: {result_seed}")
if DEBUG:
print(f"[call_kontext_api] Parsing result...")
print(f"[call_kontext_api] Result content: {str(result)[:500]}...") # First 500 chars
# Try to extract images from various possible response formats
images_data = None
# Method 1: Direct attribute access
if hasattr(result, 'images'):
images_data = result.images
if DEBUG:
print(f"[call_kontext_api] Found images via result.images")
# Method 2: Dictionary-style access
elif hasattr(result, '__getitem__'):
try:
images_data = result.get('images') or result.get('output')
if DEBUG:
print(f"[call_kontext_api] Found images via dict access")
except:
pass
# Method 3: Data attribute
elif hasattr(result, 'data'):
if isinstance(result.data, dict):
images_data = result.data.get('images') or result.data.get('output')
if DEBUG:
print(f"[call_kontext_api] Found images via result.data")
else:
images_data = result.data
if DEBUG:
print(f"[call_kontext_api] Using result.data directly")
# Method 4: Direct dict
elif isinstance(result, dict):
images_data = result.get('images') or result.get('output')
if DEBUG:
print(f"[call_kontext_api] Result is dict, found images")
# Extract NSFW status
has_nsfw_concepts = None
if hasattr(result, 'has_nsfw_concepts'):
has_nsfw_concepts = result.has_nsfw_concepts
elif hasattr(result, '__getitem__'):
try:
has_nsfw_concepts = result.get('has_nsfw_concepts')
except:
pass
elif hasattr(result, 'data') and isinstance(result.data, dict):
has_nsfw_concepts = result.data.get('has_nsfw_concepts')
if has_nsfw_concepts is not None:
if isinstance(has_nsfw_concepts, list) and len(has_nsfw_concepts) > 0:
# has_nsfw_concepts[0] is True when NSFW detected, so we invert it
passed_nsfw_filtering = not bool(has_nsfw_concepts[0])
else:
# Invert the boolean
passed_nsfw_filtering = not bool(has_nsfw_concepts)
if passed_nsfw_filtering:
info_lines.append("✓ Content passed safety check")
else:
info_lines.append("⚠️ NSFW content detected and blocked")
if images_data is None:
error_msg = f"Could not find images in result. Result type: {type(result)}"
print(f"[call_kontext_api] ERROR: {error_msg}")
if DEBUG and hasattr(result, '__dict__'):
print(f"[call_kontext_api] Result dict: {result.__dict__}")
info_lines.append(f"Error: {error_msg}")
images_data = []
elif DEBUG:
print(f"[call_kontext_api] Found {len(images_data) if hasattr(images_data, '__len__') else 'unknown number of'} images")
# Process images
for i, img_data in enumerate(images_data):
try:
if DEBUG:
print(f"[call_kontext_api] Processing image {i+1}/{len(images_data)}")
img_url = None # Track the URL for info output
if isinstance(img_data, dict):
# Handle dict with url field
img_url = img_data.get("url") or img_data.get("image_url")
if img_url:
img_data = img_url
if isinstance(img_data, str):
if img_data.startswith("data:image"):
# base64 data URI
if DEBUG:
print(f"[call_kontext_api] Image {i+1} is base64 data URI")
header, base64_data = img_data.split(",", 1)
img_bytes = io.BytesIO(base64.b64decode(base64_data))
pil_img = Image.open(img_bytes)
output_images.append(pil_img)
info_lines.append(f"Image {i+1}: Base64 encoded ({pil_img.size[0]}x{pil_img.size[1]})")
elif img_data.startswith("http"):
# URL, try to fetch
img_url = img_data
if DEBUG:
print(f"[call_kontext_api] Image {i+1} is URL: {img_data}")
resp = requests.get(img_data)
resp.raise_for_status()
pil_img = Image.open(io.BytesIO(resp.content))
output_images.append(pil_img)
info_lines.append(f"Image {i+1}: {img_url}")
else:
error_msg = f"Unknown image format for image {i+1}"
print(f"[call_kontext_api] ERROR: {error_msg}")
info_lines.append(f"Error: {error_msg}")
elif isinstance(img_data, Image.Image):
# Already a PIL Image
if DEBUG:
print(f"[call_kontext_api] Image {i+1} is already PIL Image")
output_images.append(img_data)
info_lines.append(f"Image {i+1}: PIL Image ({img_data.size[0]}x{img_data.size[1]})")
except Exception as e:
error_msg = f"Failed to process image {i+1}: {str(e)}"
print(f"[call_kontext_api] ERROR: {error_msg}")
info_lines.append(f"Error: {error_msg}")
if DEBUG:
print(f"[call_kontext_api] Successfully processed {len(output_images)} images")
print(f"[call_kontext_api] Passed NSFW filtering: {passed_nsfw_filtering}")
if not output_images:
# Fallback to input image
error_msg = "No output images from API, returning input image"
print(f"[call_kontext_api] WARNING: {error_msg}")
info_lines.append(f"Warning: {error_msg}")
output_images = [tensor2pil(image) if not isinstance(image, Image.Image) else image]
# Add logs to info
if logs:
info_lines.append("API Logs:\n" + "\n".join(logs))
# Check for errors in result
if hasattr(result, 'error') and result.error:
error_msg = f"API Error: {result.error}"
print(f"[call_kontext_api] ERROR: {error_msg}")
info_lines.append(f"Error: {error_msg}")
info = "\n".join(info_lines)
return output_images, info, passed_nsfw_filtering
def resize_to_max_pixels(img: Image.Image, max_pixels: int = 1250000) -> Image.Image:
"""Resize image to fit under max_pixels while maintaining aspect ratio."""
w, h = img.size
if w * h <= max_pixels:
return img
aspect = w / h
new_h = int((max_pixels / aspect) ** 0.5)
new_w = int(new_h * aspect)
if DEBUG:
print(f"[resize_to_max_pixels] Resizing image from {w}x{h} to {new_w}x{new_h} to fit under {max_pixels} pixels")
return img.resize((new_w, new_h), Image.LANCZOS)
def upload_image_to_fal(image):
"""Upload an image to Fal's storage and return the URL."""
if fal_client is None:
raise ImportError("fal_client is not installed. Please install fal-client in your environment with: pip install fal-client")
# Convert tensor to PIL image if needed
pil_image = tensor2pil(image) if not isinstance(image, Image.Image) else image
# Resize image if needed
pil_image = resize_to_max_pixels(pil_image)
# Save to temporary file
temp_file = None
try:
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
pil_image.save(tmp, format='PNG')
temp_file = tmp.name
if DEBUG:
print(f"[upload_image_to_fal] Saved image to temp file: {temp_file}")
# Upload to Fal storage
url = fal_client.upload_file(temp_file)
if DEBUG:
print(f"[upload_image_to_fal] Uploaded image to Fal storage: {url}")
return url
except Exception as e:
error_msg = f"Failed to upload image to Fal storage: {str(e)}"
print(f"[upload_image_to_fal] ERROR: {error_msg}")
raise RuntimeError(error_msg)
finally:
# Clean up temp file
if temp_file and os.path.exists(temp_file):
try:
os.remove(temp_file)
if DEBUG:
print(f"[upload_image_to_fal] Cleaned up temp file: {temp_file}")
except Exception as e:
if DEBUG:
print(f"[upload_image_to_fal] Warning: Failed to clean up temp file {temp_file}: {e}")
def decode_base64_image(data_uri):
"""Decode a base64 data URI to a PIL image."""
header, encoded = data_uri.split(',', 1)
img_bytes = base64.b64decode(encoded)
return Image.open(BytesIO(img_bytes))
def call_kontext_max_api(prompt, images, aspect_ratio, seed, guidance_scale, output_format, raw, image_prompt_strength, num_inference_steps, safety_tolerance):
"""
Call the Fal Kontext Max API with multiple images and return output images, info string, and NSFW status.
"""
if fal_client is None:
# Try to import and get key again to show the proper error message
try:
from .utils import get_fal_key
get_fal_key()
except RuntimeError as e:
# This will show the detailed key error message
raise e
except:
pass
# If we're here, it's a different issue
raise ImportError("fal_client is not installed. Please install fal-client in your environment with: pip install fal-client")
# Convert tensors to PIL images if needed and resize them
pil_images = []
for img in images:
pil_img = tensor2pil(img) if not isinstance(img, Image.Image) else img
pil_img = resize_to_max_pixels(pil_img) # Resize if needed
pil_images.append(pil_img)
# Upload images to Fal storage instead of converting to base64
image_urls = []
for i, pil_img in enumerate(pil_images):
try:
url = upload_image_to_fal(pil_img)
image_urls.append(url)
if DEBUG:
print(f"[call_kontext_max_api] Uploaded image {i+1} to: {url}")
except Exception as e:
error_msg = f"Failed to upload image {i+1}: {str(e)}"
print(f"[call_kontext_max_api] ERROR: {error_msg}")
raise RuntimeError(error_msg)
if DEBUG:
print(f"[call_kontext_max_api] Input PIL images sizes: {[img.size for img in pil_images]}")
print(f"[call_kontext_max_api] Uploaded image URLs: {image_urls}")
payload = {
"prompt": prompt,
"image_urls": image_urls,
"safety_tolerance": str(safety_tolerance),
"seed": int(seed),
"guidance_scale": float(guidance_scale),
"num_images": 1, # Always generate one image
"output_format": output_format,
"raw": bool(raw),
"image_prompt_strength": float(image_prompt_strength),
"num_inference_steps": int(num_inference_steps),
"sync_mode": True # Get images directly without CDN
}
if aspect_ratio and aspect_ratio != "Match input image":
payload["aspect_ratio"] = aspect_ratio
if DEBUG:
debug_payload = payload.copy()
debug_payload["image_urls"] = image_urls # Show actual URLs for debugging
print(f"[call_kontext_max_api] Payload: {debug_payload}")
logs = []
def on_queue_update(update):
if hasattr(update, 'logs'):
for log in update.logs:
log_msg = log.get("message", str(log)) if isinstance(log, dict) else str(log)
logs.append(log_msg)
if DEBUG:
print(f"[call_kontext_max_api] Queue log: {log_msg}")
try:
if DEBUG:
print(f"[call_kontext_max_api] Calling Fal API...")
# Try different methods based on what's available
if hasattr(fal_client, 'subscribe'):
result = fal_client.subscribe(
"fal-ai/flux-pro/kontext/max/multi",
arguments=payload,
with_logs=True,
on_queue_update=on_queue_update,
)
elif hasattr(fal_client, 'run'):
result = fal_client.run(
"fal-ai/flux-pro/kontext/max/multi",
arguments=payload,
)
elif hasattr(fal_client, 'submit'):
handle = fal_client.submit(
"fal-ai/flux-pro/kontext/max/multi",
arguments=payload,
)
result = handle.get()
else:
result = fal_client(
"fal-ai/flux-pro/kontext/max/multi",
**payload
)
if DEBUG:
print(f"[call_kontext_max_api] API call completed")
except Exception as e:
error_msg = f"Fal API call failed: {str(e)}"
print(f"[call_kontext_max_api] ERROR: {error_msg}")
import traceback
if DEBUG:
print(f"[call_kontext_max_api] Full traceback:\n{traceback.format_exc()}")
return [], error_msg, False
# Parse result
output_images = []
info_lines = []
passed_nsfw_filtering = True # Default to True (not blocked)
# Extract request ID
request_id = getattr(result, 'request_id', None)
if not request_id and hasattr(result, '__getitem__'):
try:
request_id = result.get('request_id')
except:
pass
if request_id:
info_lines.append(f"Request ID: {request_id}")
# Extract seed
result_seed = None
if hasattr(result, 'seed'):
result_seed = result.seed
elif hasattr(result, '__getitem__'):
try:
result_seed = result.get('seed')
except:
pass
elif hasattr(result, 'data') and isinstance(result.data, dict):
result_seed = result.data.get('seed')
if result_seed is not None:
info_lines.append(f"Seed: {result_seed}")
# Extract NSFW status
has_nsfw_concepts = None
if hasattr(result, 'has_nsfw_concepts'):
has_nsfw_concepts = result.has_nsfw_concepts
elif hasattr(result, '__getitem__'):
try:
has_nsfw_concepts = result.get('has_nsfw_concepts')
except:
pass
elif hasattr(result, 'data') and isinstance(result.data, dict):
has_nsfw_concepts = result.data.get('has_nsfw_concepts')
# Check NSFW status - if any image is flagged, consider it blocked
if has_nsfw_concepts is not None:
if isinstance(has_nsfw_concepts, list):
passed_nsfw_filtering = not any(has_nsfw_concepts)
else:
passed_nsfw_filtering = not bool(has_nsfw_concepts)
# Add NSFW status to info
nsfw_status = "✓ Passed" if passed_nsfw_filtering else "⚠️ Blocked"
info_lines.append(f"Safety Check: {nsfw_status}")
if has_nsfw_concepts is not None and isinstance(has_nsfw_concepts, list):
for i, nsfw in enumerate(has_nsfw_concepts):
status = "Blocked" if nsfw else "Passed"
info_lines.append(f"Image {i+1}: {status}")
# Try to extract images from various possible response formats
images_data = None
# Method 1: Direct attribute access
if hasattr(result, 'images'):
images_data = result.images
# Method 2: Dictionary-style access
elif hasattr(result, '__getitem__'):
try:
images_data = result.get('images') or result.get('output')
except:
pass
# Method 3: Data attribute
elif hasattr(result, 'data'):
if isinstance(result.data, dict):
images_data = result.data.get('images') or result.data.get('output')
if images_data:
# Process each image
for img_data in images_data:
try:
# Handle both direct URLs/URIs and dictionary format
if isinstance(img_data, dict):
# Extract URL from dictionary format
img_url = img_data.get('url', '')
if not img_url:
continue
img_data = img_url
if img_data.startswith("data:"):
# Handle base64 data URI
img = decode_base64_image(img_data)
elif img_data.startswith("http"):
# Handle regular URL
response = requests.get(img_data)
response.raise_for_status()
img = Image.open(BytesIO(response.content))
else:
if DEBUG:
print(f"[call_kontext_max_api] Skipping unknown image format: {img_data[:100]}...")
continue
output_images.append(img)
except Exception as e:
if DEBUG:
print(f"[call_kontext_max_api] Error processing image: {e}")
continue
# Add any logs to info
if logs:
info_lines.append("\nLogs:")
info_lines.extend(logs)
# Join info lines
info = "\n".join(info_lines)
return output_images, info, passed_nsfw_filtering
class FalKontextMaxMultiImageNode:
"""Node for Fal Kontext Max API with multiple image inputs."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"multiline": True}),
"image1": ("IMAGE",),
"aspect_ratio": (["Match input image", "1:1", "4:3", "3:4", "16:9", "9:16", "21:9", "9:21"],),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"guidance_scale": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 20.0, "step": 0.1}),
"output_format": (["jpeg", "png"],),
"disable_prompt_enhancement": ("BOOLEAN", {"default": False}),
"image_prompt_strength": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1.0, "step": 0.01}),
"num_inference_steps": ("INT", {"default": 30, "min": 1, "max": 100}),
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "6"}),
},
"optional": {
"image2": ("IMAGE",),
"image3": ("IMAGE",),
"image4": ("IMAGE",),
}
}
RETURN_TYPES = ("IMAGE", "STRING", "BOOLEAN")
RETURN_NAMES = ("images", "info", "passed_nsfw_filtering")
FUNCTION = "process"
CATEGORY = "Fal/Kontext"
def process(self, prompt, image1, aspect_ratio, seed, guidance_scale, output_format,
disable_prompt_enhancement, image_prompt_strength, num_inference_steps, safety_tolerance,
image2=None, image3=None, image4=None):
# Collect all non-None images
images = [img for img in [image1, image2, image3, image4] if img is not None]
# Call API with all images
output_images, info, passed_nsfw_filtering = call_kontext_max_api(
prompt=prompt,
images=images,
aspect_ratio=aspect_ratio,
seed=seed,
guidance_scale=guidance_scale,
output_format=output_format,
raw=disable_prompt_enhancement,
image_prompt_strength=image_prompt_strength,
num_inference_steps=num_inference_steps,
safety_tolerance=safety_tolerance
)
# Check if we got any images back
if not output_images:
error_msg = "No images were generated. Check the info output for details."
if DEBUG:
print(f"[FalKontextMaxMultiImageNode] {error_msg}")
# Return a black image tensor of the same size as input for error cases
if len(images) > 0:
sample_img = tensor2pil(images[0])
width, height = sample_img.size
black_tensor = torch.zeros((1, height, width, 3), dtype=torch.float32)
return (black_tensor, f"{error_msg}\n\n{info}", False)
else:
black_tensor = torch.zeros((1, 512, 512, 3), dtype=torch.float32)
return (black_tensor, f"{error_msg}\n\n{info}", False)
# Convert PIL images to tensors
tensors = []
for img in output_images:
try:
# Convert PIL to numpy array
img_np = np.array(img)
# Normalize to 0-1 range
img_np = img_np.astype(np.float32) / 255.0
# Convert to tensor and add batch dimension
img_tensor = torch.from_numpy(img_np).unsqueeze(0)
tensors.append(img_tensor)
except Exception as e:
if DEBUG:
print(f"[FalKontextMaxMultiImageNode] Error converting image to tensor: {e}")
continue
# Check if we have any valid tensors
if not tensors:
error_msg = "Failed to convert generated images to tensors. Check the info output for details."
if DEBUG:
print(f"[FalKontextMaxMultiImageNode] {error_msg}")
if len(images) > 0:
sample_img = tensor2pil(images[0])
width, height = sample_img.size
black_tensor = torch.zeros((1, height, width, 3), dtype=torch.float32)
return (black_tensor, f"{error_msg}\n\n{info}", False)
else:
black_tensor = torch.zeros((1, 512, 512, 3), dtype=torch.float32)
return (black_tensor, f"{error_msg}\n\n{info}", False)
# Since we're always generating one image, just return the first tensor
return (tensors[0], info, passed_nsfw_filtering)