diff --git a/README.ZH_CN.md b/README.ZH_CN.md index 72426d7..14ab421 100644 --- a/README.ZH_CN.md +++ b/README.ZH_CN.md @@ -508,6 +508,12 @@ git clone https://github.com/1hew/ComfyUI-1hewNodes | Save Video | 保存 VIDEO 对象并返回路径,沿用容器扩展名并生成 Alpha 预览 | +### 🎛️ 条件编码节点 +| 节点名称 | 功能描述 | +|---------|----------| +| Text Encode QwenImageEdit Keep Size | Qwen 图文编辑条件编码,支持多图视觉编码、尺寸保持策略与参考潜空间 | + + ### 🔊 音频节点 | 节点名称 | 功能描述 | |---------|----------| diff --git a/README.md b/README.md index 087dde2..202d24c 100644 --- a/README.md +++ b/README.md @@ -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 | |-----------|-------------| diff --git a/imgs/Stargazers.png b/imgs/Stargazers.png index f72338e..5bdfc59 100644 --- a/imgs/Stargazers.png +++ b/imgs/Stargazers.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ddeb1c5d300da986a4581874687164488759fb75ecf28e166ebf992c89f29329 -size 102142 +oid sha256:288350ffd657a77ea24f8c8e241184da8208fbbcadbc64f01b63b5443d96982e +size 99239 diff --git a/nodes/io/load_image.py b/nodes/io/load_image.py index f41dbdf..405818c 100644 --- a/nodes/io/load_image.py +++ b/nodes/io/load_image.py @@ -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() diff --git a/nodes/io/load_video.py b/nodes/io/load_video.py index 3a057b4..52bc23e 100644 --- a/nodes/io/load_video.py +++ b/nodes/io/load_video.py @@ -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" ) diff --git a/nodes/io/load_video_to_image.py b/nodes/io/load_video_to_image.py index a731a63..1ede0eb 100644 --- a/nodes/io/load_video_to_image.py +++ b/nodes/io/load_video_to_image.py @@ -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: diff --git a/pyproject.toml b/pyproject.toml index c596bb4..2ee7f65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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] diff --git a/web/js/load_image.js b/web/js/load_image.js index 9f4bc79..3ab9af7 100644 --- a/web/js/load_image.js +++ b/web/js/load_image.js @@ -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) { diff --git a/web/js/load_video.js b/web/js/load_video.js index 27f5509..daeaa70 100644 --- a/web/js/load_video.js +++ b/web/js/load_video.js @@ -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(); }