This commit is contained in:
yige1127
2026-01-09 19:53:32 +08:00
parent 1584e77834
commit 2a4e63eb1f
9 changed files with 125 additions and 70 deletions
+6
View File
@@ -508,6 +508,12 @@ git clone https://github.com/1hew/ComfyUI-1hewNodes
| Save Video | 保存 VIDEO 对象并返回路径,沿用容器扩展名并生成 Alpha 预览 |
### 🎛️ 条件编码节点
| 节点名称 | 功能描述 |
|---------|----------|
| Text Encode QwenImageEdit Keep Size | Qwen 图文编辑条件编码,支持多图视觉编码、尺寸保持策略与参考潜空间 |
### 🔊 音频节点
| 节点名称 | 功能描述 |
|---------|----------|
+6
View File
@@ -508,6 +508,12 @@ git clone https://github.com/1hew/ComfyUI-1hewNodes
| Save Video | Save a VIDEO object to disk with container extension preservation and alpha preview |
### 🎛️ Conditioning Nodes
| Node Name | Description |
|-----------|-------------|
| Text Encode QwenImageEdit Keep Size | Qwen image-edit conditioning encoder combining vision inputs and text, supports size preservation modes and reference latents |
### 🔊 Audio Nodes
| Node Name | Description |
|-----------|-------------|
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ddeb1c5d300da986a4581874687164488759fb75ecf28e166ebf992c89f29329
size 102142
oid sha256:288350ffd657a77ea24f8c8e241184da8208fbbcadbc64f01b63b5443d96982e
size 99239
+19 -11
View File
@@ -37,7 +37,7 @@ class LoadImage(io.ComfyNode):
category="1hewNodes/io",
inputs=[
io.Image.Input("get_image_size", optional=True),
io.String.Input("path", default=""),
io.String.Input("file", default=""),
io.Int.Input("index", default=0, min=-8192, max=8192, step=1),
io.Boolean.Input("include_subdir", default=True),
io.Boolean.Input("all", default=False),
@@ -227,11 +227,11 @@ class LoadImage(io.ComfyNode):
return image_paths
@classmethod
def IS_CHANGED(cls, path, include_subdir, **kwargs):
if not path:
def IS_CHANGED(cls, file, include_subdir, **kwargs):
if not file:
return float("nan")
image_paths = cls.get_image_paths(path, include_subdir)
image_paths = cls.get_image_paths(file, include_subdir)
if not image_paths:
return float("nan")
m = hashlib.sha256()
@@ -281,13 +281,13 @@ class LoadImage(io.ComfyNode):
@classmethod
async def execute(
cls,
path: str,
file: str,
index: int,
all: bool,
include_subdir: bool,
get_image_size: torch.Tensor | None = None,
) -> io.NodeOutput:
image_paths = cls.get_image_paths(path, include_subdir)
image_paths = cls.get_image_paths(file, include_subdir)
count = len(image_paths)
if count == 0:
@@ -345,9 +345,9 @@ class LoadImage(io.ComfyNode):
else:
idx = index % count
path = image_paths[idx]
selected_path = image_paths[idx]
try:
img, mask = cls.load_image(path)
img, mask = cls.load_image(selected_path)
tensor = cls.pil2tensor(img)
mask_tensor = mask.unsqueeze(0).unsqueeze(-1)
@@ -359,7 +359,7 @@ class LoadImage(io.ComfyNode):
output_image = tensor
output_mask = mask_tensor.squeeze(-1)
except Exception as e:
print(f"Error loading image {path}: {e}")
print(f"Error loading image {selected_path}: {e}")
return io.NodeOutput(None, None)
return io.NodeOutput(
@@ -479,7 +479,11 @@ async def upload_images(request):
@PromptServer.instance.routes.get("/1hew/view_image_from_folder")
async def view_image_from_folder(request):
folder = request.query.get("path") or request.query.get("folder")
folder = (
request.query.get("file")
or request.query.get("path")
or request.query.get("folder")
)
index_str = request.query.get("index", "0")
include_subdir = (
(request.query.get("include_subdir") or "true").lower()
@@ -603,7 +607,11 @@ async def view_image_from_folder(request):
@PromptServer.instance.routes.get("/1hew/resolve_image_from_folder")
async def resolve_image_from_folder(request):
folder = request.query.get("path") or request.query.get("folder")
folder = (
request.query.get("file")
or request.query.get("path")
or request.query.get("folder")
)
index_str = request.query.get("index", "0")
include_subdir = (
(request.query.get("include_subdir") or "true").lower()
+17 -13
View File
@@ -931,7 +931,7 @@ class LoadVideo(io.ComfyNode):
display_name="Load Video",
category="1hewNodes/io",
inputs=[
io.String.Input("path", default=""),
io.String.Input("file", default=""),
io.Int.Input("frame_limit", default=0, min=0, max=100000, step=1),
io.Float.Input("fps", default=0.0, min=0.0, max=120.0, step=1.0),
io.Int.Input("start_skip", default=0, min=0, max=100000, step=1),
@@ -946,7 +946,7 @@ class LoadVideo(io.ComfyNode):
@classmethod
async def execute(
cls,
path: str,
file: str,
start_skip: int,
end_skip: int,
fps: float,
@@ -955,8 +955,8 @@ class LoadVideo(io.ComfyNode):
video_index: int,
include_subdir: bool,
) -> io.NodeOutput:
path = (path or "").strip().strip('"').strip("'")
video_paths = cls.get_video_paths(path, include_subdir)
file = (file or "").strip().strip('"').strip("'")
video_paths = cls.get_video_paths(file, include_subdir)
count = len(video_paths)
if count == 0:
@@ -1230,8 +1230,8 @@ class LoadVideo(io.ComfyNode):
return video_paths
@classmethod
def IS_CHANGED(cls, path, include_subdir, **kwargs):
path = (path or "").strip().strip('"').strip("'")
def IS_CHANGED(cls, file, include_subdir, **kwargs):
file = (file or "").strip().strip('"').strip("'")
start_skip = int(kwargs.get("start_skip") or 0)
end_skip = int(kwargs.get("end_skip") or 0)
fps = float(kwargs.get("fps") or 0.0)
@@ -1242,19 +1242,19 @@ class LoadVideo(io.ComfyNode):
f"video_index:{video_index}|start_skip:{start_skip}|end_skip:{end_skip}"
f"|fps:{fps}|frame_limit:{frame_limit}|format:{format}"
)
if os.path.isfile(path):
if os.path.isfile(file):
try:
mtime = os.path.getmtime(path)
mtime = os.path.getmtime(file)
return hashlib.sha256(
f"{path}:{mtime}:{settings_key}".encode()
f"{file}:{mtime}:{settings_key}".encode()
).hexdigest()
except OSError:
return float("nan")
if not os.path.isdir(path):
if not os.path.isdir(file):
return float("nan")
video_paths = cls.get_video_paths(path, include_subdir)
video_paths = cls.get_video_paths(file, include_subdir)
m = hashlib.sha256()
m.update(f"{settings_key}|include_subdir:{include_subdir}".encode())
for video_path in video_paths:
@@ -1469,7 +1469,9 @@ async def upload_videos(request):
@PromptServer.instance.routes.get("/1hew/video_info_from_folder")
async def video_info_from_folder(request):
path = request.query.get("path") or request.query.get("folder")
path = request.query.get("file") or request.query.get("path") or request.query.get(
"folder"
)
index_str = (
request.query.get("index") or request.query.get("video_index") or "0"
)
@@ -1552,7 +1554,9 @@ async def video_info_from_folder(request):
@PromptServer.instance.routes.get("/1hew/view_video_from_folder")
async def view_video_from_folder(request):
path = request.query.get("path") or request.query.get("folder")
path = request.query.get("file") or request.query.get("path") or request.query.get(
"folder"
)
index_str = (
request.query.get("index") or request.query.get("video_index") or "0"
)
+11 -11
View File
@@ -596,7 +596,7 @@ class LoadVideoToImage(io.ComfyNode):
display_name="Load Video to Image",
category="1hewNodes/io",
inputs=[
io.String.Input("path", default=""),
io.String.Input("file", default=""),
io.Int.Input("frame_limit", default=0, min=0, max=100000, step=1),
io.Float.Input("fps", default=0.0, min=0.0, max=120.0, step=1.0),
io.Int.Input("start_skip", default=0, min=0, max=100000, step=1),
@@ -616,7 +616,7 @@ class LoadVideoToImage(io.ComfyNode):
@classmethod
async def execute(
cls,
path: str,
file: str,
fps: float,
frame_limit: int,
start_skip: int,
@@ -625,8 +625,8 @@ class LoadVideoToImage(io.ComfyNode):
video_index: int,
include_subdir: bool,
) -> io.NodeOutput:
path = (path or "").strip().strip('"').strip("'")
video_paths = cls.get_video_paths(path, include_subdir)
file = (file or "").strip().strip('"').strip("'")
video_paths = cls.get_video_paths(file, include_subdir)
count = len(video_paths)
if count == 0:
@@ -892,19 +892,19 @@ class LoadVideoToImage(io.ComfyNode):
return video_paths
@classmethod
def IS_CHANGED(cls, path, include_subdir, **kwargs):
path = (path or "").strip().strip('"').strip("'")
if os.path.isfile(path):
def IS_CHANGED(cls, file, include_subdir, **kwargs):
file = (file or "").strip().strip('"').strip("'")
if os.path.isfile(file):
try:
mtime = os.path.getmtime(path)
return hashlib.sha256(f"{path}:{mtime}".encode()).hexdigest()
mtime = os.path.getmtime(file)
return hashlib.sha256(f"{file}:{mtime}".encode()).hexdigest()
except OSError:
return float("nan")
if not os.path.isdir(path):
if not os.path.isdir(file):
return float("nan")
video_paths = cls.get_video_paths(path, include_subdir)
video_paths = cls.get_video_paths(file, include_subdir)
m = hashlib.sha256()
for video_path in video_paths:
try:
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "ComfyUI-1hewNodes"
description = "This is a custom node collection for ComfyUI that provides some utility nodes."
version = "3.2.4"
version = "3.2.5"
license = {file = "LICENSE"}
[project.urls]
+45 -14
View File
@@ -72,8 +72,8 @@ app.registerExtension({
nodeType.prototype.onConfigure = function () {
const r = onConfigure ? onConfigure.apply(this, arguments) : undefined;
if (this.widgets) {
const pathWidget = this.widgets.find((w) => w.name === "path");
if (pathWidget && pathWidget.value) {
const fileWidget = this.widgets.find((w) => w.name === "file");
if (fileWidget && fileWidget.value) {
this._comfy1hewLoadImagePendingPreview = true;
setTimeout(() => {
const update = this.updatePreview;
@@ -103,7 +103,7 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
const pathWidget = this.widgets.find((w) => w.name === "path");
const fileWidget = this.widgets.find((w) => w.name === "file");
const indexWidget = this.widgets.find((w) => w.name === "index");
const includeSubdirWidget = this.widgets.find(
(w) => w.name === "include_subdir"
@@ -175,10 +175,10 @@ app.registerExtension({
}
}
if (pathWidget) {
pathWidget.value = finalPath;
if (typeof pathWidget.callback === "function") {
pathWidget.callback();
if (fileWidget) {
fileWidget.value = finalPath;
if (typeof fileWidget.callback === "function") {
fileWidget.callback();
}
}
@@ -248,6 +248,37 @@ app.registerExtension({
container.appendChild(imageEl);
container.appendChild(infoEl);
const fileInputEl = document.createElement("input");
fileInputEl.type = "file";
fileInputEl.accept = "image/*";
fileInputEl.style.display = "none";
container.appendChild(fileInputEl);
const uploadWidget = this.addWidget(
"button",
"choose file to upload",
"image",
() => {
app.canvas.node_widget = null;
try {
fileInputEl.value = "";
} catch {}
fileInputEl.click();
}
);
uploadWidget.serialize = false;
fileInputEl.addEventListener("change", async () => {
const file = fileInputEl.files && fileInputEl.files[0];
if (!file) return;
try {
await uploadFilesAsFolder(
[{ file: file, relativePath: file?.name }],
false
);
} catch {}
});
imageEl.addEventListener("contextmenu", (e) => {
e.preventDefault();
const node = this;
@@ -287,7 +318,7 @@ app.registerExtension({
this._comfy1hewLoadImageReqId = (this._comfy1hewLoadImageReqId || 0) + 1;
const reqId = this._comfy1hewLoadImageReqId;
const path = pathWidget.value;
const file = fileWidget.value;
const index = indexWidget.value;
const includeSubdir = includeSubdirWidget
? includeSubdirWidget.value
@@ -296,8 +327,8 @@ app.registerExtension({
if (this.imageWidget) {
this.imageWidget._comfy1hew_maxAspectRatio = all ? 1.25 : null;
}
const trimmedPath = String(path || "").trim();
if (trimmedPath === "") {
const trimmedFile = String(file || "").trim();
if (trimmedFile === "") {
this._comfy1hewLoadImageWasEmptyPath = true;
const lastImgSrc = this._comfy1hewLoadImageLastImgSrc;
@@ -377,7 +408,7 @@ app.registerExtension({
this._comfy1hewLoadImageWasEmptyPath = false;
const params = new URLSearchParams({
path: path,
file: file,
include_subdir: includeSubdir,
t: Date.now(),
});
@@ -476,13 +507,13 @@ app.registerExtension({
};
// 监听 widget 变化
if (pathWidget) pathWidget.callback = this.updatePreview;
if (fileWidget) fileWidget.callback = this.updatePreview;
if (indexWidget) indexWidget.callback = this.updatePreview;
if (includeSubdirWidget) includeSubdirWidget.callback = this.updatePreview;
if (allWidget) allWidget.callback = this.updatePreview;
const computeStateKey = () => {
const p = String(pathWidget ? pathWidget.value : "");
const p = String(fileWidget ? fileWidget.value : "");
const i = String(indexWidget ? indexWidget.value : 0);
const s = String(includeSubdirWidget ? includeSubdirWidget.value : true);
const a = String(allWidget ? allWidget.value : false);
@@ -564,7 +595,7 @@ app.registerExtension({
setTimeout(ensurePreviewLayout, 500);
// 初始加载
if (pathWidget && pathWidget.value) {
if (fileWidget && fileWidget.value) {
this.updatePreview();
}
if (this._comfy1hewLoadImagePendingPreview) {
+18 -18
View File
@@ -28,8 +28,8 @@ app.registerExtension({
? onConfigure.apply(this, arguments)
: undefined;
if (this.widgets) {
const pathWidget = this.widgets.find((w) => w.name === "path");
if (pathWidget && pathWidget.value) {
const fileWidget = this.widgets.find((w) => w.name === "file");
if (fileWidget && fileWidget.value) {
setTimeout(() => {
const update = this.updatePreview;
if (update) update();
@@ -52,7 +52,7 @@ app.registerExtension({
? onNodeCreated.apply(this, arguments)
: undefined;
const pathWidget = this.widgets.find((w) => w.name === "path");
const fileWidget = this.widgets.find((w) => w.name === "file");
const indexWidget = this.widgets.find((w) => w.name === "video_index");
const includeSubdirWidget = this.widgets.find(
(w) => w.name === "include_subdir"
@@ -479,7 +479,7 @@ app.registerExtension({
container.appendChild(fileInputEl);
// Add upload button widget
const uploadWidget = this.addWidget("button", "choose video to upload", "image", () => {
const uploadWidget = this.addWidget("button", "choose file to upload", "image", () => {
app.canvas.node_widget = null; // Clear active widget
fileInputEl.click();
});
@@ -508,10 +508,10 @@ app.registerExtension({
return;
}
if (pathWidget) {
pathWidget.value = newPath;
if (typeof pathWidget.callback === "function") {
pathWidget.callback();
if (fileWidget) {
fileWidget.value = newPath;
if (typeof fileWidget.callback === "function") {
fileWidget.callback();
}
}
if (this.updatePreview) {
@@ -549,10 +549,10 @@ app.registerExtension({
return;
}
if (pathWidget) {
pathWidget.value = folder;
if (typeof pathWidget.callback === "function") {
pathWidget.callback();
if (fileWidget) {
fileWidget.value = folder;
if (typeof fileWidget.callback === "function") {
fileWidget.callback();
}
}
@@ -690,11 +690,11 @@ app.registerExtension({
setTimeout(ensurePreviewLayout, 800);
this.updatePreview = async () => {
const path = pathWidget.value;
const file = fileWidget.value;
const index = indexWidget.value;
const includeSubdir = includeSubdirWidget.value;
if (!path) {
if (!file) {
this._comfy1hewVideoInfo = null;
this.videoWidget.aspectRatio = undefined;
infoEl.innerText = "";
@@ -706,7 +706,7 @@ app.registerExtension({
}
const params = new URLSearchParams({
path: path,
file: file,
index: index,
include_subdir: includeSubdir,
t: Date.now(),
@@ -725,7 +725,7 @@ app.registerExtension({
try {
const infoParams = new URLSearchParams({
path: path,
file: file,
index: index,
include_subdir: includeSubdir,
});
@@ -774,11 +774,11 @@ app.registerExtension({
return r2;
};
if (pathWidget) pathWidget.callback = this.updatePreview;
if (fileWidget) fileWidget.callback = this.updatePreview;
if (indexWidget) indexWidget.callback = this.updatePreview;
if (includeSubdirWidget) includeSubdirWidget.callback = this.updatePreview;
if (pathWidget && pathWidget.value) {
if (fileWidget && fileWidget.value) {
this.updatePreview();
}