Simple File Path Loop
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# ComfyUI-FilePathsFromFolder
|
||||
|
||||
A simple ComfyUI custom node pack for loading and selecting files from folders.
|
||||
A simple ComfyUI custom node pack for loading and selecting files from folders, with for-loop support for batch processing.
|
||||
|
||||
## Nodes
|
||||
|
||||
@@ -30,6 +30,76 @@ Selects a specific file from the lists by index.
|
||||
- `filename`: Selected filename
|
||||
- `filepath`: Selected filepath
|
||||
|
||||
## Loop Nodes
|
||||
|
||||
### FilePathsForLoop
|
||||
Initiates iteration through files in a folder. Uses ComfyUI's list-based batching to process each file individually.
|
||||
|
||||
**Parameters:**
|
||||
- `folder_path`: Path to the folder to scan
|
||||
- `extension`: File extension pattern (e.g., "*.png", "*.jpg", "*.txt")
|
||||
|
||||
**Outputs:**
|
||||
- `filepath`: Full path of current file (list output - each item processed separately)
|
||||
- `filename`: Filename without extension (list output)
|
||||
- `current_index`: Current iteration index, 0-based (list output)
|
||||
- `total_count`: Total number of files (list output)
|
||||
|
||||
**Important:** This node outputs lists with `OUTPUT_IS_LIST = True`. ComfyUI will automatically run all connected nodes once for each item in the list. Each connected node receives a **single value** from the list per execution, not the entire list.
|
||||
|
||||
### FilePathsLoopClose
|
||||
Closes the file paths loop and collects iteration results.
|
||||
|
||||
**Inputs:**
|
||||
- `current_index`: Current index from FilePathsForLoop (required, force input)
|
||||
- `passthrough_1` through `passthrough_5`: Optional outputs from nodes in the loop
|
||||
|
||||
**Outputs:**
|
||||
- `total_iterations`: Total number of iterations completed
|
||||
|
||||
**Usage:** This is the termination point of your loop. Connect the `current_index` from FilePathsForLoop (or through other nodes) to ensure the loop completes. You can also connect outputs from processing nodes to the passthrough inputs to collect results.
|
||||
|
||||
### FilePathsEnsureExecution
|
||||
Ensures connected nodes execute during loop iteration by collecting their outputs.
|
||||
|
||||
**Inputs:**
|
||||
- `input_1` through `input_10`: Optional inputs (supports any type with wildcard `*`)
|
||||
|
||||
**Outputs:**
|
||||
- `passthrough`: The first non-None input value
|
||||
|
||||
**Usage:** Connect outputs from any nodes that need to execute within the loop to this node's inputs. Then connect this node's output downstream (e.g., to FilePathsLoopClose) to ensure all connected nodes complete execution.
|
||||
|
||||
## How ComfyUI List-Based Loops Work
|
||||
|
||||
When a node has `OUTPUT_IS_LIST = True`:
|
||||
1. The node returns lists: `["file1.png", "file2.png", "file3.png"]`
|
||||
2. ComfyUI automatically executes **all downstream nodes once per list item**
|
||||
3. Each execution receives **one item from the list**, not the whole list
|
||||
4. **Iteration 1**: downstream nodes receive `"file1.png"`
|
||||
5. **Iteration 2**: downstream nodes receive `"file2.png"`
|
||||
6. **Iteration 3**: downstream nodes receive `"file3.png"`
|
||||
|
||||
This is ComfyUI's native batching/looping mechanism.
|
||||
|
||||
## Example Workflows
|
||||
|
||||
### Simple Loop Processing
|
||||
1. **FilePathsForLoop** - Set folder path and extension (e.g., "C:/images", "*.png")
|
||||
2. Connect `filepath` to your image loader node (e.g., Load Image)
|
||||
3. Connect your processing nodes (e.g., filters, effects)
|
||||
4. Connect the final processing output to **FilePathsEnsureExecution** inputs
|
||||
5. Connect **FilePathsForLoop**'s `current_index` to **FilePathsLoopClose**'s `current_index`
|
||||
6. (Optional) Connect **FilePathsEnsureExecution**'s output to **FilePathsLoopClose**'s `passthrough_1`
|
||||
|
||||
This setup will automatically process each file in the folder sequentially.
|
||||
|
||||
### Why Three Nodes?
|
||||
|
||||
- **FilePathsForLoop**: Initiates the loop, provides file paths and metadata
|
||||
- **FilePathsEnsureExecution**: Optional helper to chain dependencies and ensure execution order
|
||||
- **FilePathsLoopClose**: Terminates the loop, provides completion signal, collects results
|
||||
|
||||
## Installation
|
||||
|
||||
Place this folder in your ComfyUI `custom_nodes` directory and restart ComfyUI.
|
||||
|
||||
Binary file not shown.
@@ -2,6 +2,133 @@ 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 = ("filepath", "filename", "current_index", "total_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 any outputs you want to collect from the loop iterations.
|
||||
This ensures all iterations complete before proceeding.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"current_index": ("INT", {"default": 0, "forceInput": True}),
|
||||
},
|
||||
"optional": {
|
||||
"passthrough_1": ("*",),
|
||||
"passthrough_2": ("*",),
|
||||
"passthrough_3": ("*",),
|
||||
"passthrough_4": ("*",),
|
||||
"passthrough_5": ("*",),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("total_iterations",)
|
||||
FUNCTION = "close_loop"
|
||||
CATEGORY = "FilePathsFromFolder/Loop"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def close_loop(self, current_index, **kwargs):
|
||||
# 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.
|
||||
Connect up to 10 node outputs, then connect this node's output to FilePathsLoopClose.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"input_1": ("*",),
|
||||
"input_2": ("*",),
|
||||
"input_3": ("*",),
|
||||
"input_4": ("*",),
|
||||
"input_5": ("*",),
|
||||
"input_6": ("*",),
|
||||
"input_7": ("*",),
|
||||
"input_8": ("*",),
|
||||
"input_9": ("*",),
|
||||
"input_10": ("*",),
|
||||
}
|
||||
}
|
||||
|
||||
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):
|
||||
@@ -75,10 +202,15 @@ class GetPathByIndex:
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LoadFilePathsFromFolder": LoadFilePathsFromFolder,
|
||||
"GetPathByIndex": GetPathByIndex,
|
||||
"FilePathsForLoop": FilePathsForLoop,
|
||||
"FilePathsLoopClose": FilePathsLoopClose,
|
||||
"FilePathsEnsureExecution": FilePathsEnsureExecution,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LoadFilePathsFromFolder": "Load File Paths From Folder",
|
||||
"GetPathByIndex": "Get Path By Index",
|
||||
"FilePathsForLoop": "File Paths For Loop",
|
||||
"FilePathsLoopClose": "File Paths Loop Close",
|
||||
"FilePathsEnsureExecution": "File Paths Ensure Execution",
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user