270 lines
8.7 KiB
Python
270 lines
8.7 KiB
Python
import os
|
|
import glob
|
|
|
|
|
|
class FilePathsForLoop:
|
|
"""
|
|
Iterates through files in a folder one at a time.
|
|
Returns each file path individually for processing in a loop.
|
|
Connect to any node that processes files, then to FilePathsLoopClose.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"folder_path": ("STRING", {"default": ""}),
|
|
"extension": ("STRING", {"default": "*.png"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "INT", "INT")
|
|
RETURN_NAMES = ("current_filepath", "current_filename", "current_index", "total_file_count")
|
|
FUNCTION = "iterate_files"
|
|
CATEGORY = "FilePathsFromFolder/Loop"
|
|
OUTPUT_NODE = False
|
|
OUTPUT_IS_LIST = (True, True, True, True)
|
|
|
|
def iterate_files(self, folder_path, extension):
|
|
# Build search pattern
|
|
search_pattern = os.path.join(folder_path, extension)
|
|
|
|
# Find all matching files
|
|
matched_files = glob.glob(search_pattern)
|
|
matched_files.sort()
|
|
|
|
total_count = len(matched_files)
|
|
|
|
# If no files, return empty lists
|
|
if not matched_files:
|
|
return ([], [], [], [])
|
|
|
|
# Build output lists
|
|
filepaths = []
|
|
filenames = []
|
|
indices = []
|
|
counts = []
|
|
|
|
for idx, filepath in enumerate(matched_files):
|
|
basename = os.path.basename(filepath)
|
|
filename_without_ext = os.path.splitext(basename)[0]
|
|
|
|
filepaths.append(filepath)
|
|
filenames.append(filename_without_ext)
|
|
indices.append(idx)
|
|
counts.append(total_count)
|
|
|
|
# Return as lists - OUTPUT_IS_LIST tells ComfyUI this is a batch
|
|
return (filepaths, filenames, indices, counts)
|
|
|
|
|
|
class FilePathsLoopClose:
|
|
"""
|
|
Closes the file paths loop.
|
|
Connect FilePathsEnsureExecution output here to ensure all nodes in the loop execute.
|
|
This ensures all iterations complete before proceeding.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"current_index": ("INT", {"default": 0, "forceInput": True}),
|
|
},
|
|
"optional": {
|
|
"passthrough": ("*",),
|
|
}
|
|
}
|
|
|
|
INPUT_IS_LIST = True
|
|
RETURN_TYPES = ("INT",)
|
|
RETURN_NAMES = ("processed_count",)
|
|
FUNCTION = "close_loop"
|
|
CATEGORY = "FilePathsFromFolder/Loop"
|
|
OUTPUT_NODE = True
|
|
|
|
def close_loop(self, current_index, passthrough=None):
|
|
# Count total iterations
|
|
total = len(current_index) if isinstance(current_index, list) else 1
|
|
return (total,)
|
|
|
|
|
|
class FilePathsEnsureExecution:
|
|
"""
|
|
Ensures connected nodes execute by passing through their outputs.
|
|
Use this to chain multiple nodes that need to execute during each loop iteration.
|
|
If you need more than 5 inputs, you can chain multiple of these nodes together.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"optional": {
|
|
"passthrough_1": ("*",),
|
|
"passthrough_2": ("*",),
|
|
"passthrough_3": ("*",),
|
|
"passthrough_4": ("*",),
|
|
"passthrough_5": ("*",),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("*",)
|
|
RETURN_NAMES = ("passthrough",)
|
|
FUNCTION = "passthrough"
|
|
CATEGORY = "FilePathsFromFolder/Loop"
|
|
OUTPUT_NODE = False
|
|
|
|
def passthrough(self, **kwargs):
|
|
# Return the first non-None input as passthrough
|
|
for key in sorted(kwargs.keys()):
|
|
if kwargs[key] is not None:
|
|
return (kwargs[key],)
|
|
return (None,)
|
|
|
|
|
|
class LoadFilePathsFromFolder:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"folder_path": ("STRING", {"default": ""}),
|
|
"extension": ("STRING", {"default": "*.png"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "INT")
|
|
RETURN_NAMES = ("filepaths", "filenames", "count")
|
|
FUNCTION = "load_files"
|
|
CATEGORY = "FilePathsFromFolder"
|
|
|
|
def load_files(self, folder_path, extension):
|
|
# Build search pattern
|
|
search_pattern = os.path.join(folder_path, extension)
|
|
|
|
# Find all matching files
|
|
matched_files = glob.glob(search_pattern)
|
|
matched_files.sort()
|
|
|
|
# Extract file paths and names
|
|
filepaths = []
|
|
filenames = []
|
|
|
|
for filepath in matched_files:
|
|
filepaths.append(filepath)
|
|
# Get filename without extension
|
|
basename = os.path.basename(filepath)
|
|
filename_without_ext = os.path.splitext(basename)[0]
|
|
filenames.append(filename_without_ext)
|
|
|
|
# Join with line breaks
|
|
filepaths_str = "\n".join(filepaths)
|
|
filenames_str = "\n".join(filenames)
|
|
count = len(matched_files)
|
|
|
|
return (filepaths_str, filenames_str, count)
|
|
|
|
|
|
class GetPathByIndex:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"filepaths": ("STRING", {"default": "", "multiline": True}),
|
|
"filenames": ("STRING", {"default": "", "multiline": True}),
|
|
"index": ("INT", {"default": 0, "min": 0}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING")
|
|
RETURN_NAMES = ("filepath", "filename")
|
|
FUNCTION = "get_by_index"
|
|
CATEGORY = "FilePathsFromFolder"
|
|
|
|
def get_by_index(self, filepaths, filenames, index):
|
|
# Split by line breaks
|
|
filepath_list = filepaths.split("\n") if filepaths else []
|
|
filename_list = filenames.split("\n") if filenames else []
|
|
|
|
# Get item at index (default to empty if out of range)
|
|
filepath = filepath_list[index] if index < len(filepath_list) else ""
|
|
filename = filename_list[index] if index < len(filename_list) else ""
|
|
|
|
return (filepath, filename)
|
|
|
|
|
|
class FilePathsLoopSyncTrigger:
|
|
"""
|
|
Batch synchronization trigger: splits batch into trigger + batch passthrough.
|
|
- 'trigger' output: single value, connects to nodes that run ONCE (cleanup/checkpoint/etc.)
|
|
- 'anything' output: original batch (N items), connects to FilePathsLoopSyncResume
|
|
Use with FilePathsLoopSyncResume to ensure one-time operation completes before batch continues.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"anything": ("*", {"forceInput": True}),
|
|
}
|
|
}
|
|
|
|
INPUT_IS_LIST = True
|
|
RETURN_TYPES = ("*", "*")
|
|
RETURN_NAMES = ("anything", "trigger")
|
|
OUTPUT_IS_LIST = (True, False)
|
|
FUNCTION = "trigger_once"
|
|
CATEGORY = "FilePathsFromFolder/Loop"
|
|
OUTPUT_NODE = False
|
|
|
|
def trigger_once(self, anything):
|
|
# Split: single trigger for one-time ops + original batch to continue
|
|
trigger_value = anything[0] if isinstance(anything, list) and len(anything) > 0 else anything
|
|
return (anything, trigger_value)
|
|
|
|
|
|
class FilePathsLoopSyncResume:
|
|
"""
|
|
Batch synchronization resume: waits for single operation, then continues batch.
|
|
Just a wait point - doesn't modify data.
|
|
Connect batch from Trigger's 'anything' output here.
|
|
Connect completion signal from your one-time operation to 'wait_for'.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"anything": ("*", {"forceInput": True}),
|
|
"wait_for": ("*",),
|
|
}
|
|
}
|
|
|
|
INPUT_IS_LIST = True
|
|
RETURN_TYPES = ("*",)
|
|
RETURN_NAMES = ("anything",)
|
|
OUTPUT_IS_LIST = (True,)
|
|
FUNCTION = "resume_batch"
|
|
CATEGORY = "FilePathsFromFolder/Loop"
|
|
OUTPUT_NODE = False
|
|
|
|
def resume_batch(self, anything, wait_for=None):
|
|
# Simple passthrough after waiting for completion
|
|
return (anything,)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"LoadFilePathsFromFolder": LoadFilePathsFromFolder,
|
|
"GetPathByIndex": GetPathByIndex,
|
|
"FilePathsForLoop": FilePathsForLoop,
|
|
"FilePathsLoopClose": FilePathsLoopClose,
|
|
"FilePathsEnsureExecution": FilePathsEnsureExecution,
|
|
"FilePathsLoopSyncTrigger": FilePathsLoopSyncTrigger,
|
|
"FilePathsLoopSyncResume": FilePathsLoopSyncResume,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"LoadFilePathsFromFolder": "Load File Paths From Folder",
|
|
"GetPathByIndex": "Get File Path By Index",
|
|
"FilePathsForLoop": "File Paths For Loop Start",
|
|
"FilePathsLoopClose": "File Paths For Loop End",
|
|
"FilePathsEnsureExecution": "File Paths For Loop Ensure Execution",
|
|
"FilePathsLoopSyncTrigger": "File Paths For Loop Sync Trigger",
|
|
"FilePathsLoopSyncResume": "File Paths For Loop Sync Resume",
|
|
}
|