diff --git a/.gitignore b/.gitignore index e69de29..840668c 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,24 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python + +# Virtual environments +venv/ +env/ +ENV/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Project specific +*.log diff --git a/README.md b/README.md index a6daf6a..5396cd9 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ A ComfyUI custom node that provides flexible image loading with support for both - **Subfolder Support**: Option to include subfolders in the search - **Resolution Output**: Automatically outputs image width and height - **Reproducible Random**: Seed control for consistent random selection +- **🆕 Image Preview**: Real-time preview of loaded images in the UI +- **🆕 File Browser**: Convenient "Browse..." button for file/folder selection ## Installation @@ -20,6 +22,16 @@ A ComfyUI custom node that provides flexible image loading with support for both 2. Restart ComfyUI +3. The node will appear in the "image" category as "Random Image Picker" + +## Usage Tips + +- **Using the Browse Button**: Click "Browse..." to open your system's file/folder selector + - Note: Due to browser security limitations, you may need to manually enter the full path +- **Default Path**: The input field defaults to your ComfyUI input directory +- **Path Format**: Use forward slashes `/` or double backslashes `\\` on Windows +- **Preview**: The loaded image automatically appears in the node's preview area + ## Usage ### Node Inputs @@ -27,10 +39,13 @@ A ComfyUI custom node that provides flexible image loading with support for both - **path** (STRING): - In Single mode: Full path to an image file - In Folder mode: Full path to a folder containing images + - 🆕 **Browse Button**: Click "Browse..." to select files/folders using your file explorer + - Default: ComfyUI input directory - **folder_mode** (BOOLEAN): - `OFF` (Single): Load the specified image file - `ON` (Folder): Randomly select an image from the folder + - Note: The browse dialog changes based on this setting - **include_subfolders** (BOOLEAN): - `OFF`: Only scan the specified folder @@ -42,7 +57,7 @@ A ComfyUI custom node that provides flexible image loading with support for both ### Node Outputs -- **IMAGE**: The loaded image in ComfyUI tensor format +- **IMAGE**: The loaded image in ComfyUI tensor format (with real-time preview) - **width** (INT): Image width in pixels - **height** (INT): Image height in pixels @@ -89,6 +104,8 @@ Randomly selects one image from `D:/images/` and all its subfolders - Output tensor format: `[batch, height, width, channels]` - Pixel values normalized to 0.0-1.0 range - Uses PyTorch tensors for ComfyUI compatibility +- Real-time preview in ComfyUI UI (OUTPUT_NODE enabled) +- Custom JavaScript widget for enhanced file selection ## License diff --git a/random_image_picker.py b/random_image_picker.py index accd7cd..a78d5cb 100644 --- a/random_image_picker.py +++ b/random_image_picker.py @@ -20,12 +20,34 @@ class RandomImagePicker: @classmethod def INPUT_TYPES(cls): + # Get default input directory from ComfyUI + try: + default_path = folder_paths.get_input_directory() + except: + default_path = "" + return { "required": { - "path": ("STRING", {"default": "", "multiline": False}), - "folder_mode": ("BOOLEAN", {"default": False, "label_on": "Folder", "label_off": "Single"}), - "include_subfolders": ("BOOLEAN", {"default": False, "label_on": "ON", "label_off": "OFF"}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "path": ("STRING", { + "default": default_path, + "multiline": False, + "placeholder": "Path to image file or folder" + }), + "folder_mode": ("BOOLEAN", { + "default": False, + "label_on": "Folder", + "label_off": "Single" + }), + "include_subfolders": ("BOOLEAN", { + "default": False, + "label_on": "ON", + "label_off": "OFF" + }), + "seed": ("INT", { + "default": 0, + "min": 0, + "max": 0xffffffffffffffff + }), }, } @@ -33,6 +55,7 @@ class RandomImagePicker: RETURN_NAMES = ("IMAGE", "width", "height") FUNCTION = "load_image" CATEGORY = "image" + OUTPUT_NODE = True @staticmethod def get_image_files(directory: str, include_subfolders: bool = False) -> List[str]: diff --git a/web/random_image_picker.js b/web/random_image_picker.js new file mode 100644 index 0000000..b7dddd2 --- /dev/null +++ b/web/random_image_picker.js @@ -0,0 +1,71 @@ +import { app } from "../../scripts/app.js"; + +app.registerExtension({ + name: "Comfy.RandomImagePicker", + + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "RandomImagePicker") { + const onNodeCreated = nodeType.prototype.onNodeCreated; + + nodeType.prototype.onNodeCreated = function () { + const result = onNodeCreated?.apply(this, arguments); + + // Add file browser button + const pathWidget = this.widgets.find(w => w.name === "path"); + if (pathWidget) { + // Add button to open file dialog + const button = this.addWidget("button", "Browse...", null, () => { + // Create file input element + const input = document.createElement("input"); + + // Get folder_mode widget value + const folderModeWidget = this.widgets.find(w => w.name === "folder_mode"); + const isFolderMode = folderModeWidget?.value || false; + + if (isFolderMode) { + // Folder selection + input.type = "file"; + input.webkitdirectory = true; + input.directory = true; + } else { + // File selection + input.type = "file"; + input.accept = "image/png, image/jpeg, image/webp, image/bmp, image/gif"; + } + + input.onchange = (e) => { + if (e.target.files && e.target.files[0]) { + if (isFolderMode) { + // Get folder path from first file + const firstFile = e.target.files[0]; + const fullPath = firstFile.webkitRelativePath || firstFile.name; + const folderPath = fullPath.split('/')[0]; + pathWidget.value = folderPath; + } else { + // Get file path + const file = e.target.files[0]; + // Note: For security reasons, browsers don't expose full file paths + // We can only get the file name, not the full path + // Users will need to manually enter the full path or use relative paths + pathWidget.value = file.name; + + // Show info message + console.log("Note: Due to browser security, full path is not available. Please enter the complete path manually."); + } + } + }; + + input.click(); + }); + + // Add helper text + this.addWidget("text", "helper", "Tip: Enter full path or use ComfyUI input folder", () => {}, { + serialize: false + }); + } + + return result; + }; + } + } +});