diff --git a/image_info_node.py b/image_info_node.py index da88b0a..4d742c1 100644 --- a/image_info_node.py +++ b/image_info_node.py @@ -3,6 +3,7 @@ import json import torch import numpy as np from PIL import Image, ImageOps +import folder_paths # 新增:用於獲取 ComfyUI 的標準路徑 class ImageWorkflowExtractor: @classmethod @@ -16,7 +17,6 @@ class ImageWorkflowExtractor: }, } - # 修改輸出類型:將原本的 raw_info (STRING) 改為 image (IMAGE) RETURN_TYPES = ("JSON", "STRING", "STRING", "IMAGE") RETURN_NAMES = ("workflow_json", "clean_text", "selected_path", "image") FUNCTION = "extract_info" @@ -24,29 +24,56 @@ class ImageWorkflowExtractor: def extract_info(self, image_or_dir, search_by, search_query, seed): target_path = image_or_dir.strip() + + # ========================================== + # 0. 智慧路徑解析 (支援絕對路徑與上傳路徑) + # ========================================== + # 邏輯:先檢查是否為電腦上的絕對路徑 -> 再檢查 ComfyUI input 資料夾 -> 最後檢查 output 資料夾 + + final_path = target_path + + if os.path.exists(target_path): + final_path = os.path.abspath(target_path) + else: + # 嘗試從 input 資料夾尋找 (這是 Drag & Drop 上傳的預設位置) + input_dir = folder_paths.get_input_directory() + input_path = os.path.join(input_dir, target_path) + + # 嘗試從 output 資料夾尋找 + output_dir = folder_paths.get_output_directory() + output_path = os.path.join(output_dir, target_path) + + if os.path.exists(input_path): + final_path = input_path + elif os.path.exists(output_path): + final_path = output_path + else: + # 如果都找不到,保持原樣,讓後面的邏輯去報錯 + final_path = os.path.abspath(target_path) + selected_file = "" # ========================================== - # 1. 檔案選取邏輯 + # 1. 檔案選取邏輯 (單檔或資料夾隨機) # ========================================== - if os.path.isfile(target_path): - selected_file = target_path - elif os.path.isdir(target_path): + if os.path.isfile(final_path): + selected_file = final_path + elif os.path.isdir(final_path): valid_extensions = ('.png', '.webp', '.jpg', '.jpeg') files = sorted([ - os.path.join(target_path, f) - for f in os.listdir(target_path) + os.path.join(final_path, f) + for f in os.listdir(final_path) if f.lower().endswith(valid_extensions) ]) if not files: # 若無檔案,回傳一個空的黑色張量避免系統崩潰 - return ({}, "No images found", target_path, torch.zeros((1, 64, 64, 3))) + return ({}, "No images found", final_path, torch.zeros((1, 64, 64, 3))) index = seed % len(files) selected_file = files[index] else: - return ({}, "Invalid path", target_path, torch.zeros((1, 64, 64, 3))) + return ({}, "Invalid path or file not found", final_path, torch.zeros((1, 64, 64, 3))) # ========================================== # 2. 資訊提取與圖像轉換輸出 @@ -63,9 +90,11 @@ class ImageWorkflowExtractor: for node in nodes: match = False if search_by == "ID": + # ID 轉字串比對 if str(node.get("id")) == search_query.strip(): match = True else: + # Type 轉字串比對 if node.get("type") == search_query.strip(): match = True diff --git a/web/image_drag_drop.js b/web/image_drag_drop.js new file mode 100644 index 0000000..d241286 --- /dev/null +++ b/web/image_drag_drop.js @@ -0,0 +1,105 @@ +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; + +app.registerExtension({ + name: "DynamicTags.ImageDrop", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "WorkflowMetadataReader") { + + // ============================================================================= + // Helper: 圖片上傳處理 + // ============================================================================= + async function uploadFile(file, node) { + try { + const body = new FormData(); + body.append("image", file); + body.append("overwrite", "true"); + + const resp = await api.fetchApi("/upload/image", { + method: "POST", + body, + }); + + if (resp.status === 200) { + const data = await resp.json(); + const filename = data.name; + + const pathWidget = node.widgets.find(w => w.name === "image_or_dir"); + if (pathWidget) { + pathWidget.value = filename; + node.setDirtyCanvas(true, true); + } + } else { + alert(`Upload failed: ${resp.statusText}`); + } + } catch (error) { + console.error("Upload error:", error); + } + } + + // ============================================================================= + // Logic 1: 拖曳 (Drag & Drop) 事件攔截 + // ============================================================================= + + const onDragOverOriginal = nodeType.prototype.onDragOver; + nodeType.prototype.onDragOver = function(e) { + if (onDragOverOriginal) onDragOverOriginal.apply(this, arguments); + return true; + }; + + const onDragDropOriginal = nodeType.prototype.onDragDrop; + nodeType.prototype.onDragDrop = function(e) { + if (onDragDropOriginal) onDragDropOriginal.apply(this, arguments); + + if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) { + const file = e.dataTransfer.files[0]; + + if (file.type.startsWith("image/")) { + e.preventDefault(); + e.stopPropagation(); + + uploadFile(file, this); + return true; + } + } + return false; + }; + + // ============================================================================= + // Logic 2: 剪貼簿 (Paste) 事件攔截 + // ============================================================================= + + const onSelectedOriginal = nodeType.prototype.onSelected; + nodeType.prototype.onSelected = function() { + if (onSelectedOriginal) onSelectedOriginal.apply(this, arguments); + + this._pasteHandler = (e) => { + if (!app.canvas.selected_nodes || !app.canvas.selected_nodes[this.id]) return; + + if (e.clipboardData && e.clipboardData.files && e.clipboardData.files.length > 0) { + const file = e.clipboardData.files[0]; + if (file.type.startsWith("image/")) { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + + uploadFile(file, this); + } + } + }; + + window.addEventListener("paste", this._pasteHandler, true); + }; + + const onDeselectedOriginal = nodeType.prototype.onDeselected; + nodeType.prototype.onDeselected = function() { + if (onDeselectedOriginal) onDeselectedOriginal.apply(this, arguments); + + if (this._pasteHandler) { + window.removeEventListener("paste", this._pasteHandler, true); + this._pasteHandler = null; + } + }; + } + } +}); \ No newline at end of file