114 lines
3.7 KiB
Python
114 lines
3.7 KiB
Python
# ======================================================================
|
|
# Node Pack: artyclaw-comfy
|
|
# Author: artyclaw
|
|
# Version: 1.0.0
|
|
# Description: A power-user toolkit for prompt logic, IO, and RNG.
|
|
# ======================================================================
|
|
|
|
# folder_file_selector.py
|
|
# ComfyUI Custom Node: Folder File Selector
|
|
# - Shows files from a folder as a dropdown (if available at import)
|
|
# - Input: folder_path (STRING)
|
|
# - Output: full file path (STRING)
|
|
#
|
|
# Note: the dropdown list is built at import time. Changing folder_path in the UI
|
|
# will NOT repopulate the dropdown (Comfy limitation). You can press "R" to reload
|
|
# custom nodes after changing the folder on disk.
|
|
|
|
import os
|
|
import time
|
|
|
|
# change this to whatever folder you want scanned when Comfy starts / reloads
|
|
DEFAULT_FOLDER = "doc/autoprompt"
|
|
|
|
def _scan_files(folder: str):
|
|
try:
|
|
if not os.path.isabs(folder):
|
|
folder = os.path.abspath(folder)
|
|
if not os.path.exists(folder):
|
|
return []
|
|
files = [
|
|
f for f in os.listdir(folder)
|
|
if os.path.isfile(os.path.join(folder, f))
|
|
]
|
|
files.sort(key=str.lower)
|
|
return files
|
|
except Exception:
|
|
return []
|
|
|
|
|
|
class FolderFileSelector:
|
|
# scan once at import
|
|
_INITIAL_FILES = _scan_files(DEFAULT_FOLDER)
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
inputs_required = {
|
|
"folder_path": ("STRING", {
|
|
"multiline": False,
|
|
"default": DEFAULT_FOLDER,
|
|
"tooltip": "Folder to list files from. Dropdown is built at node import."
|
|
}),
|
|
}
|
|
|
|
if cls._INITIAL_FILES:
|
|
inputs_required["file_select"] = (cls._INITIAL_FILES, {
|
|
"default": cls._INITIAL_FILES[0],
|
|
"tooltip": f"Files found in {DEFAULT_FOLDER} at load time."
|
|
})
|
|
else:
|
|
# fallback so old workflows don't error
|
|
inputs_required["file_select"] = ("STRING", {
|
|
"multiline": False,
|
|
"default": "",
|
|
"tooltip": "No files found at load time. Type a filename here."
|
|
})
|
|
|
|
return {
|
|
"required": inputs_required,
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("file_path",)
|
|
FUNCTION = "select_file"
|
|
CATEGORY = "IO/Filesystem"
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, folder_path, file_select):
|
|
"""
|
|
If the folder is edited on disk, you can press R to reload nodes and rebuild
|
|
the dropdown. Here we just make sure changing folder_path or selection
|
|
will re-run the node.
|
|
"""
|
|
try:
|
|
mtime = os.path.getmtime(os.path.abspath(folder_path))
|
|
except Exception:
|
|
mtime = time.time()
|
|
return (os.path.abspath(folder_path), file_select, mtime)
|
|
|
|
def select_file(self, folder_path: str, file_select):
|
|
# normalize
|
|
folder_abs = os.path.abspath(folder_path)
|
|
# file_select could be bool if old workflow
|
|
if isinstance(file_select, bool):
|
|
file_select = ""
|
|
|
|
# build full path
|
|
full_path = os.path.join(folder_abs, str(file_select)) if file_select else folder_abs
|
|
|
|
# if the chosen file doesn't exist, we still return the constructed path
|
|
# so you can see what it tried to do
|
|
if not os.path.exists(full_path):
|
|
print(f"[FolderFileSelector] Warning: file does not exist: {full_path}")
|
|
|
|
return (full_path,)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"FolderFileSelector": FolderFileSelector,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"FolderFileSelector": "Folder File Selector",
|
|
}
|