新增圖片拖曳與剪貼簿上傳功能,優化檔案處理邏輯
This commit is contained in:
+38
-9
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user