Files
2025-12-25 21:13:44 +01:00

224 lines
7.0 KiB
Python

"""
ComfyUI Custom Node: Batch Image Loader
This node loads all images from a folder and passes them one by one through the workflow
"""
import os
import numpy as np
from PIL import Image
import torch
import folder_paths
class BatchImageLoaderNode:
"""
A node that loads all images from a folder and outputs them as a batch
Each image will be processed through the workflow automatically
"""
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
# Get the input directory from ComfyUI
input_dir = folder_paths.get_input_directory()
return {
"required": {
"folder_path": ("STRING", {
"multiline": False,
"default": input_dir
}),
"image_extensions": ("STRING", {
"multiline": False,
"default": "png,jpg,jpeg,webp,bmp,gif"
}),
"sort_by": (["name", "modified", "created"], {
"default": "name"
}),
},
"optional": {
"start_index": ("INT", {
"default": 0,
"min": 0,
"max": 10000
}),
"max_images": ("INT", {
"default": 0, # 0 means all images
"min": 0,
"max": 10000
}),
}
}
RETURN_TYPES = ("IMAGE", "STRING", "INT",)
RETURN_NAMES = ("images", "filenames", "count",)
OUTPUT_NODE = False
OUTPUT_IS_LIST = (True, True, False,)
FUNCTION = "load_images"
CATEGORY = "image"
def load_images(self, folder_path, image_extensions="png,jpg,jpeg,webp,bmp,gif",
sort_by="name", start_index=0, max_images=0):
"""
Load all images from a folder
Args:
folder_path: Path to the folder containing images
image_extensions: Comma-separated list of allowed extensions
sort_by: How to sort files (name, modified, created)
start_index: Skip first N images
max_images: Maximum number of images to load (0 = all)
Returns:
tuple: (list of image tensors, list of filenames, count)
"""
# Parse extensions
extensions = [ext.strip().lower() for ext in image_extensions.split(',')]
extensions = [f".{ext}" if not ext.startswith('.') else ext for ext in extensions]
# Check if folder exists
if not os.path.exists(folder_path):
print(f"[BatchImageLoader] Folder not found: {folder_path}")
return ([], [], 0)
if not os.path.isdir(folder_path):
print(f"[BatchImageLoader] Path is not a directory: {folder_path}")
return ([], [], 0)
# Get all image files
image_files = []
for filename in os.listdir(folder_path):
file_ext = os.path.splitext(filename)[1].lower()
if file_ext in extensions:
full_path = os.path.join(folder_path, filename)
if os.path.isfile(full_path):
image_files.append({
'path': full_path,
'name': filename
})
if not image_files:
print(f"[BatchImageLoader] No images found in: {folder_path}")
return ([], [], 0)
# Sort files
if sort_by == "name":
image_files.sort(key=lambda x: x['name'])
elif sort_by == "modified":
image_files.sort(key=lambda x: os.path.getmtime(x['path']))
elif sort_by == "created":
image_files.sort(key=lambda x: os.path.getctime(x['path']))
# Apply start_index and max_images
if start_index > 0:
image_files = image_files[start_index:]
if max_images > 0 and len(image_files) > max_images:
image_files = image_files[:max_images]
# Load images
images = []
filenames = []
for img_info in image_files:
try:
img = Image.open(img_info['path'])
# Convert to RGB if necessary
if img.mode == 'I':
img = img.point(lambda i: i * (1 / 255))
if img.mode != 'RGB':
img = img.convert('RGB')
# Convert to tensor (same format as ComfyUI's LoadImage node)
img_array = np.array(img).astype(np.float32) / 255.0
img_tensor = torch.from_numpy(img_array)[None,]
images.append(img_tensor)
filenames.append(img_info['name'])
except Exception as e:
print(f"[BatchImageLoader] Error loading {img_info['name']}: {str(e)}")
continue
count = len(images)
print(f"[BatchImageLoader] Loaded {count} images from {folder_path}")
return (images, filenames, count)
class ImageFromBatchNode:
"""
A node that extracts a single image from a batch by index
Useful for manual iteration or testing specific images
"""
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE", {"forceInput": True}),
"index": ("INT", {
"default": 0,
"min": 0,
"max": 10000
}),
}
}
RETURN_TYPES = ("IMAGE", "INT",)
RETURN_NAMES = ("image", "index",)
OUTPUT_NODE = False
INPUT_IS_LIST = True
FUNCTION = "get_image"
CATEGORY = "image"
def get_image(self, images, index):
"""
Get a specific image from the batch by index
Args:
images: List of image tensors
index: Index to retrieve (0-based)
Returns:
Single image tensor and its index
"""
# Handle the input format
if isinstance(index, list):
index = index[0]
if isinstance(images, list) and len(images) > 0:
# Make sure index is within bounds
actual_index = index % len(images)
return (images[actual_index], actual_index)
# Return empty tensor if no images
empty_tensor = torch.zeros((1, 64, 64, 3))
return (empty_tensor, 0)
# Node registration
NODE_CLASS_MAPPINGS = {
"BatchImageLoaderNode": BatchImageLoaderNode,
"ImageFromBatchNode": ImageFromBatchNode,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"BatchImageLoaderNode": "Batch Images (Folder Loader)",
"ImageFromBatchNode": "Get Image From Batch",
}
WEB_DIRECTORY = "./web"
# Debug output to confirm loading
print("\n" + "="*50)
print("Batch Image Loader Node - Loading...")
print(f"Registered nodes: {list(NODE_CLASS_MAPPINGS.keys())}")
print("="*50 + "\n")