Major redesign: Direct file reading for single mode

Breaking changes:
- Single mode: Now uses file picker with direct browser-based image reading
- Folder mode: Uses manual path input only
- Renamed 'image' parameter to 'folder_path' for clarity
- Added 'image_data' optional parameter for base64 image data

Implementation:
- JavaScript: FileReader API to read image as base64
- Python: Decode base64 and convert to tensor (no file system access needed)
- Better UX: No confusing error messages about missing paths
- Clear separation: File picker for single, text input for folder

Benefits:
- Single mode works without file system paths
- No browser security issues
- Cleaner user experience
- Explicit mode separation
This commit is contained in:
solidlime
2026-01-07 21:21:19 +09:00
parent f453a4fe47
commit bd275c4941
2 changed files with 80 additions and 41 deletions
+53 -22
View File
@@ -28,17 +28,17 @@ class RandomImagePicker:
return {
"required": {
"image": ("STRING", {
"folder_path": ("STRING", {
"default": default_path,
"multiline": False,
"dynamicPrompts": False,
"tooltip": "Enter full path to image file (Single mode) or folder (Folder mode). Use 📁 button to help select."
"tooltip": "Folder path for random image selection (Folder mode only)"
}),
"folder_mode": ("BOOLEAN", {
"default": False,
"label_on": "Folder",
"label_off": "Single",
"tooltip": "Single: Load specific file | Folder: Random from folder"
"tooltip": "Single: Use file picker | Folder: Random from folder path"
}),
"include_subfolders": ("BOOLEAN", {
"default": False,
@@ -53,6 +53,13 @@ class RandomImagePicker:
"tooltip": "Random seed for reproducible selection in Folder mode"
}),
},
"optional": {
"image_data": ("STRING", {
"default": "",
"multiline": True,
"forceInput": True,
}),
}
}
RETURN_TYPES = ("IMAGE", "INT", "INT")
@@ -119,32 +126,27 @@ class RandomImagePicker:
return img_tensor, width, height
def load_image(self, image: str, folder_mode: bool, include_subfolders: bool, seed: int):
def load_image(self, folder_path: str, folder_mode: bool, include_subfolders: bool, seed: int, image_data: str = ""):
"""
Load image based on mode settings.
Args:
image: File path (single mode) or file path to extract folder from (folder mode)
folder_path: Folder path for random selection (folder mode)
folder_mode: True for folder mode, False for single file mode
include_subfolders: Whether to scan subfolders
seed: Random seed for folder mode
image_data: Base64 encoded image data from file picker (single mode)
Returns:
Tuple of (image_tensor, width, height)
"""
if not image or not os.path.exists(image):
raise ValueError(f"Path does not exist: {image}")
if folder_mode:
# Folder mode: extract folder from file path and pick random image
if os.path.isfile(image):
# Extract folder path from file path
folder_path = os.path.dirname(image)
elif os.path.isdir(image):
# Direct folder path
folder_path = image
else:
raise ValueError(f"Invalid path: {image}")
# Folder mode: pick random image from folder
if not folder_path or not os.path.exists(folder_path):
raise ValueError(f"Folder path does not exist: {folder_path}")
if not os.path.isdir(folder_path):
raise ValueError(f"Folder mode requires a directory path: {folder_path}")
image_files = self.get_image_files(folder_path, include_subfolders)
@@ -159,12 +161,41 @@ class RandomImagePicker:
print(f"[Random Image Picker] Selected: {selected_image}")
return self.load_image_file(selected_image)
else:
# Single file mode
if not os.path.isfile(image):
raise ValueError(f"Single file mode enabled but path is not a file: {image}")
# Single file mode: use image_data from file picker
if not image_data:
raise ValueError("Please select an image file using the 📁 Choose File button")
print(f"[Random Image Picker] Loading: {image}")
return self.load_image_file(image)
# Parse base64 image data
import base64
import io
try:
# Remove data URL prefix if present
if ',' in image_data:
image_data = image_data.split(',', 1)[1]
# Decode base64
img_bytes = base64.b64decode(image_data)
img = Image.open(io.BytesIO(img_bytes))
# Convert to RGB if needed
if img.mode != 'RGB':
img = img.convert('RGB')
# Get dimensions
width, height = img.size
# Convert to numpy array
img_array = np.array(img).astype(np.float32) / 255.0
# Convert to torch tensor and add batch dimension
img_tensor = torch.from_numpy(img_array)[None,]
print(f"[Random Image Picker] Loaded from file picker: {width}x{height}")
return (img_tensor, width, height)
except Exception as e:
raise ValueError(f"Failed to load image from file picker: {str(e)}")
# Node registration
+27 -19
View File
@@ -11,8 +11,16 @@ app.registerExtension({
const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
const node = this;
// Add browse button
// Add file picker button
node.addWidget("button", "📁 Choose File", null, () => {
const folderModeWidget = node.widgets.find(w => w.name === "folder_mode");
const isFolderMode = folderModeWidget?.value || false;
if (isFolderMode) {
alert("⚠️ Folder Mode is enabled.\n\nPlease switch to Single mode to use the file picker,\nor enter the folder path in the 'folder_path' field above.");
return;
}
const input = document.createElement("input");
input.type = "file";
input.accept = "image/png,image/jpeg,image/jpg,image/webp,image/bmp,image/gif";
@@ -20,27 +28,27 @@ app.registerExtension({
input.onchange = (e) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
const imageWidget = node.widgets.find(w => w.name === "image");
if (imageWidget) {
// Show selected file name
const fileName = file.name;
console.log("Selected file:", fileName);
// Read file as base64
const reader = new FileReader();
reader.onload = (event) => {
const base64Data = event.target.result;
// Show dialog with instructions
const currentPath = imageWidget.value || "";
const dialogText = `File selected: ${fileName}\n\n` +
`⚠️ IMPORTANT:\n` +
`Due to browser security, the full path cannot be automatically detected.\n\n` +
`Please copy and paste the FULL PATH to this file in the 'image' field above.\n\n` +
`Example:\n` +
`D:\\Images\\${fileName}\n` +
`or\n` +
`C:\\Users\\YourName\\Pictures\\${fileName}\n\n` +
`Current default: ${currentPath || '(not set)'}`;
// Find or create the image_data widget
let imageDataWidget = node.widgets.find(w => w.name === "image_data");
if (imageDataWidget) {
imageDataWidget.value = base64Data;
}
alert(dialogText);
}
console.log(`[Random Image Picker] File loaded: ${file.name} (${(base64Data.length / 1024).toFixed(2)} KB)`);
alert(`✅ Image loaded successfully!\n\nFile: ${file.name}\nSize: ${(file.size / 1024).toFixed(2)} KB\n\nYou can now run the workflow.`);
};
reader.onerror = () => {
alert("❌ Failed to read the image file.\n\nPlease try again.");
};
reader.readAsDataURL(file);
}
};