17 Commits
Author SHA1 Message Date
leolee 587ff0c450 Merge pull request #1 from ComfyNodePRs/update-publish-yaml
Update Github Action for Publishing to Comfy Registry
2025-05-24 13:47:39 +08:00
snomiao e81110a4ef chore(publish): update workflow for node publishing with permissions and version check 2025-04-13 10:10:36 +00:00
leoleexh 326b794386 更新 README 文档,添加了参考链接以适配新版本的 Topaz,并考虑未来功能扩展的建议。 2025-04-13 17:41:28 +08:00
leoleexh 60ff93abc7 更新readme 2025-04-13 17:39:14 +08:00
leoleexh 0bd347345f 更新readme 2025-04-13 17:35:54 +08:00
leoleexh db82d0eb5b 更新项目配置,删除不再使用的图片文件,修改项目名称和描述,调整版本号,更新仓库链接以反映新的项目维护者。 2025-04-13 17:23:59 +08:00
leoleexh db4372aa15 准发布版本 2025-04-13 16:47:10 +08:00
leoleexh 57234149a8 简化ok 2025-04-13 16:40:05 +08:00
leoleexh b14e20640d 完成脸部修复参数的实现,优化了相关功能并修复了已知问题,提升了用户体验。 2025-04-13 16:03:57 +08:00
leoleexh 89608c4f21 暂时-未完成 2025-04-12 22:34:26 +08:00
leoleexh 7160052c3a 进行中 2025-04-12 22:28:05 +08:00
leoleexh 395ee5feb3 更新 README 文档以提供更清晰的使用说明,添加 Topaz Photo AI 的设置步骤和注意事项;在 topaz.py 中添加新的图像处理设置类,包括去噪、文本恢复、超聚焦和裁剪填充功能,增强了参数的可配置性和用户体验。 2025-04-12 21:40:49 +08:00
leoleexh 8497eb03e3 增加修复脸部参数-未完成 2025-04-12 20:43:48 +08:00
leoleexh 98f07a1492 简化参数 2025-04-12 19:44:19 +08:00
leoleexh 295b145e01 准备加入脸部修复 2025-04-12 18:33:42 +08:00
leoleexh 0402e53bee 更新readme 2025-04-12 18:03:32 +08:00
leoleexh af25aeaad3 开放了出scale之外的参数。 2025-04-12 17:56:42 +08:00
14 changed files with 2037 additions and 332 deletions
+5 -1
View File
@@ -8,15 +8,19 @@ on:
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'leoleelxh' }}
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
uses: Comfy-Org/publish-node-action@v1
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+11 -1
View File
@@ -1 +1,11 @@
__pycache__
__pycache__
pyproject.toml
node.zip
node_modules
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env.development
.env.test
+26
View File
@@ -1,5 +1,31 @@
# Changelog
## [1.0.0] - 2025-04-19
### Added
- 全新的智能图像格式处理系统,支持多种 PyTorch 张量格式
- 新增 `find_output_file` 辅助函数,能智能查找 Topaz 处理后的输出文件
- 新增 `output_prefix` 可选参数,允许用户自定义输出文件名前缀
- 更全面的日志系统,提供详细的处理信息和错误诊断
### Fixed
- 修复了处理 PyTorch 张量时的格式兼容问题,现可正确处理 [B, H, W, C]、[C, H, W] 等多种格式
- 修复了输出文件检测逻辑,能够找到 Topaz 使用不同命名规则的输出文件
- 修复了临时文件清理机制中的异常处理问题
- 改进了图像加载过程,添加了 EXIF 方向处理和图像模式转换
### Changed
- 重构了 `process_topaz_image` 函数,添加了多重检测方法和重试机制
- 改进了 `save_images` 函数,现在能处理更多图像格式和异常情况
- 优化了输出图像的后处理逻辑,与 ComfyUI 更好地兼容
- 更新了错误处理机制,确保即使在处理失败时也能返回原始图像
### Improved
- 大幅提高了与 Topaz Photo AI 的通信可靠性
- 优化了图像处理流程,提供更详细的调试信息
- 增强了错误恢复能力,添加了自动重试机制
- 更清晰的日志输出,方便用户排查问题
## [Unreleased] - 2025-04-12
### Fixed
+248 -1
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

@@ -0,0 +1,60 @@
To use the Topaz Photo AI command line interface (CLI), follow the below instructions for your operating system.
Windows:
1. Open Command Prompt or Terminal
2. Type in:
cd "C:\\Program Files\\Topaz Labs LLC\\Topaz Photo AI"
3. Type in:
.\\tpai.exe --help
4. Type in:
.\\tpai.exe "folder/or/file/path/here"
![](https://cdn.sanity.io/images/r2plryeu/production/45fd256fb694c2ff306b5a16b987852a828d10cd-1787x919.png?q=90&fit=max&auto=format)
* * *
Processing Controls
-------------------
The CLI will use your Autopilot settings to process images. Open Topaz Photo AI and go to the Preferences > Autopilot menu.
Instructions on using the Preferences > Autopilot menu are [here](https://docs.topazlabs.com/photo-ai/enhancements/autopilot-and-configuration).
### Command Options
\--output, -o: Output folder to save images to. If it doesn't exist the program will attempt to create it.
\--overwrite: Allow overwriting of files. THIS IS DESTRUCTIVE.
\--recursive, -r: If given a folder path, it will recurse into subdirectories instead of just grabbing top level files.
Note: If output folder is specified, the input folder's structure will be recreated within the output as necessary.
### File Format Options:
\--format, -f: Set the output format. Accepts jpg, jpeg, png, tif, tiff, dng, or preserve. Default: preserve
Note: Preserve will attempt to preserve the exact input extension, but RAW files will still be converted to DNG.Format Specific Options:
\--quality, -q: JPEG quality for output. Must be between 0 and 100. Default: 95
\--compression, -c: PNG compression amount. Must be between 0 and 10. Default: 2
\--bit-depth, -d: TIFF bit depth. Must be either 8 or 16. Default: 16
\--tiff-compression: -tc: TIFF compression format. Must be "none", "lzw", or "zip".
Note: lzw is not allowed on 16-bit output and will be converted to zip.
### Debug Options:
\--showSettings: Shows the Autopilot settings for images before they are processed
\--skipProcessing: Skips processing the image (e.g., if you just want to know the settings)
\--verbose, -v: Print more log entries to console.
Return values:
0 - Success
1 - Partial Success (e.g., some files failed)
\-1 (255) - No valid files passed.
\-2 (254) - Invalid log token. Open the app normally to login.
\-3 (253) - An invalid argument was found.
+642
View File
@@ -0,0 +1,642 @@
直接回答
以下是关于通过命令行接口(CLI)调用 Topaz Photo AI(tpai.exe)的官方指定参数和开发文档的简要说明,以及如何在 ComfyUI 中进行集成的指导。由于 CLI 功能的复杂性和官方文档的局限性,我会尽量提供清晰的建议,但请注意,某些功能可能需要额外的配置或未来更新。
* 官方参数和文档:Topaz Photo AI 的 CLI 使用 Autopilot 设置(需在 GUI 中配置),支持基本的输出选项如格式、质量等,但目前无法直接通过 CLI 传递特定增强设置(如锐化、降噪)。
* ComfyUI 集成:需要创建自定义节点,通过 Python 调用 tpai.exe,处理图像并返回结果,但需先在 GUI 中设置 Autopilot。
官方 CLI 参数
Topaz Photo AI 的 CLI 位于 C:\\Program Files\\Topaz Labs LLC\\Topaz Photo AI\\tpai.exe,基本用法为:
* 运行命令:.\\tpai.exe "path/to/image/or/folder"。
* 关键选项包括:
* \--output, -o <folder>:指定输出文件夹。
* \--format, -f <format>:设置输出格式(如 jpg、png)。
* \--quality, -q <0-100>:设置 JPEG 质量,默认 95。
详细参数见下表:
|
选项
|
描述
|
值/备注
|
| --- | --- | --- |
|
\--output, -o <folder>
|
指定输出文件夹
|
如果文件夹不存在,将自动创建
|
|
\--overwrite
|
允许覆盖现有文件,注意此操作具有破坏性
|
\-
|
|
\--recursive, -r
|
如果提供文件夹路径,递归处理子目录中的所有图像
|
\-
|
|
\--format, -f <format>
|
设置输出格式
|
jpg
,
jpeg
,
png
,
tif
,
tiff
,
dng
,
preserve
(默认:
preserve
)
|
|
\--quality, -q <0-100>
|
设置 JPEG 质量
|
默认:95
|
|
\--compression, -c <0-10>
|
设置 PNG 压缩级别
|
默认:2
|
|
\`--bit-depth, -d <8
|
16>\`
|
设置 TIFF 位深度
|
|
\`--tiff-compression, -tc <none
|
lzw
|
zip>\`
|
* 注意:增强设置(如锐化、降噪)依赖于 GUI 中的 Autopilot 配置,无法通过 CLI 直接控制。
在 ComfyUI 中的集成
ComfyUI 是一个基于节点的 Stable Diffusion 接口,您需要创建自定义节点来调用 tpai.exe:
* 步骤:
1. 在 ComfyUI/custom\_nodes 下创建新目录(如 TopazPhotoAI)。
2. 编写 Python 脚本,接受图像输入,保存为临时文件,调用 CLI 处理后加载结果。
3. 示例代码见下文,需确保 Autopilot 设置已配置。
* 限制:由于 CLI 依赖 GUI 设置,灵活性有限,建议关注 Topaz Labs 的未来更新。
* * *
详细报告
以下是关于 Topaz Photo AI CLI 调用和 ComfyUI 集成的详细分析,基于官方文档和相关资源,旨在为开发提供全面指导。当前时间为 2025 年 4 月 13 日星期日 15:53 HKT,所有信息均基于此时间点可用的资料。
背景与概述
Topaz Photo AI 是一款由 Topaz Labs 开发的照片增强软件,适用于锐化、降噪和上采样等任务。它提供了一个命令行接口(CLI),通过 tpai.exe 允许用户在脚本或自动化流程中调用功能。官方文档位于 [Topaz Photo AI CLI 官方文档](https://docs.topazlabs.com/photo-ai/command-line-interface),提供了 CLI 的基本用法和参数。
用户希望了解 CLI 的指定参数和开发文档,并特别关注如何在 ComfyUI 中调用。ComfyUI 是一个基于节点的 Stable Diffusion 接口,广泛用于 AI 图像生成,允许通过自定义节点扩展功能。
官方 CLI 文档分析
根据官方文档,tpai.exe 位于 C:\\Program Files\\Topaz Labs LLC\\Topaz Photo AI,基本用法如下:
* 打开命令提示符或终端,导航至安装目录:
```bash
cd "C:\Program Files\Topaz Labs LLC\Topaz Photo AI"
```
* 查看帮助信息:
* 处理图像或文件夹:
```bash
.\tpai.exe "path/to/image/or/folder"
```
处理机制
CLI 的图像处理依赖于 Autopilot 设置,这些设置需在 Topaz Photo AI 的 GUI 中配置,具体路径为 Preferences > Autopilot。相关配置指南见 [Autopilot 配置](https://docs.topazlabs.com/photo-ai/enhancements/autopilot-and-configuration)。这意味着,CLI 无法直接通过命令传递特定的增强参数(如锐化强度、降噪级别),而是使用 GUI 中预设的设置。
可用 CLI 选项
以下是官方文档中列出的 CLI 选项,整理为表格形式:
|
选项
|
描述
|
值/备注
|
| --- | --- | --- |
|
\--output, -o <folder>
|
指定输出文件夹
|
如果文件夹不存在,将自动创建
|
|
\--overwrite
|
允许覆盖现有文件,注意此操作具有破坏性
|
\-
|
|
\--recursive, -r
|
如果提供文件夹路径,递归处理子目录中的所有图像
|
\-
|
|
\--format, -f <format>
|
设置输出格式
|
jpg
,
jpeg
,
png
,
tif
,
tiff
,
dng
,
preserve
(默认:
preserve
)
注意:RAW 文件即使选择
preserve
也会转换为 DNG
|
|
\--quality, -q <0-100>
|
设置 JPEG 质量
|
默认:95
|
|
\--compression, -c <0-10>
|
设置 PNG 压缩级别
|
默认:2
|
|
\`--bit-depth, -d <8
|
16>\`
|
设置 TIFF 位深度
|
|
\`--tiff-compression, -tc <none
|
lzw
|
zip>\`
|
调试选项
* \--showSettings:显示处理前的 Autopilot 设置。
* \--skipProcessing:跳过实际处理,仅用于检查设置。
* \--verbose, -v:增加控制台输出详细程度。
返回值
CLI 的返回代码如下:
* 0:成功
* 1:部分成功(部分文件处理失败)
* \-1 (255):未传递有效文件
* \-2 (254):无效日志令牌(可能需要通过 GUI 登录)
* \-3 (253):无效参数
局限性
从官方文档来看,CLI 当前不支持直接传递特定增强设置(如上采样比例、锐化强度),这可能限制其在自动化场景中的灵活性。社区论坛(如 [Topaz Community](https://community.topazlabs.com/t/cli-improvement-allow-input-arguments-for-enhancement-adjustments/43464))中,用户已提出相关功能请求,但截至 2025 年 4 月 13 日,官方文档未反映此类更新。
ComfyUI 集成指南
ComfyUI 是一个基于节点的 Stable Diffusion 接口,允许用户通过自定义节点扩展功能。集成 Topaz Photo AI CLI 需要创建一个自定义节点,通过 Python 调用 tpai.exe,处理图像并返回结果。
创建自定义节点
1. 目录结构:
* 在 ComfyUI/custom\_nodes 下创建新目录,例如 TopazPhotoAI。
* 在该目录下创建 Python 文件,例如 topaz\_photo\_ai.py。
2. 节点实现:
* 节点需继承 ComfyUI 的节点基类,定义输入输出和处理逻辑。
* 示例代码如下,示意如何调用 CLI:
```python
import os
import subprocess
from comfy.sd import SDNode
class TopazPhotoAINode(SDNode):
def __init__(self):
super().__init__()
self.input_image = None
self.output_folder = None
self.format = "preserve"
self.quality = 95
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"output_folder": ("STRING", {"default": "output"}),
"format": (["jpg", "jpeg", "png", "tif", "tiff", "dng", "preserve"],),
"quality": ("INT", {"default": 95, "min": 0, "max": 100}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("processed_image",)
def process(self, image, output_folder, format, quality):
# 保存输入图像到临时文件
temp_input_path = os.path.join(output_folder, "input.jpg")
image.save(temp_input_path)
# 构造 CLI 命令
tpai_path = r"C:\Program Files\Topaz Labs LLC\Topaz Photo AI\tpai.exe"
cmd = [
tpai_path,
temp_input_path,
"--output", output_folder,
"--format", format,
"--quality", str(quality),
]
# 执行 CLI 命令
subprocess.run(cmd, check=True)
# 加载处理后的图像
processed_image_path = os.path.join(output_folder, "output.jpg") # 根据实际输出调整
processed_image = Image.open(processed_image_path)
return (processed_image,)
NODE_CLASS_MAPPINGS = {
"TopazPhotoAI": TopazPhotoAINode,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"TopazPhotoAI": "Topaz Photo AI",
}
```
* 说明:
* 节点接受图像、输出文件夹、格式和质量作为输入。
* 保存输入图像到临时文件,调用 tpai.exe,加载处理后的图像并返回。
* 需确保输出文件夹存在或动态创建。
3. 安装与使用:
* 将 topaz\_photo\_ai.py 放入 custom\_nodes/TopazPhotoAI 目录。
* 重启 ComfyUI,节点“Topaz Photo AI”将出现在节点列表中。
* 连接图像输入,配置输出文件夹、格式和质量,运行工作流处理图像。
集成限制
由于 CLI 依赖 Autopilot 设置,用户需在 Topaz Photo AI GUI 中预先配置这些设置,这可能限制灵活性。例如,无法通过节点动态调整锐化或降噪强度。社区讨论(如 [Topaz Community](https://community.topazlabs.com/t/ptai-cli-photo-ai-cli-allow-arguments-to-influence-settings/43464))中,用户已表达希望 CLI 支持更多参数的愿望,但截至目前,官方文档未支持。
额外考虑
在开发过程中,注意以下几点:
* 文件路径管理:确保处理临时文件和输出文件时,路径正确且无权限问题。
* 性能优化:批量处理可能需要递归选项(\--recursive, -r),但需测试性能。
* 未来更新:如果 Topaz Labs 未来更新 CLI,支持更多参数(如增强设置),可相应调整节点逻辑。
相关资源与社区反馈
通过搜索发现,社区中存在一些 Python 脚本示例(如 [Topaz Photo AI CLI 示例](https://gist.github.com/mq1n/c46c0ae4de2fb72897ba13fa22826ab0)),但需注意,这些脚本可能涉及 Gigapixel AI 而非 Photo AI,需验证适用性。此外,Topaz Community 论坛(如 [CLI 改进建议](https://community.topazlabs.com/t/cli-improvement-allow-input-arguments-for-enhancement-adjustments/43464))提供了用户需求反馈,可作为未来发展的参考。
结论
Topaz Photo AI 的 CLI 提供基本的图像处理功能,但增强设置依赖 GUI 配置,限制了自动化灵活性。在 ComfyUI 中集成需创建自定义节点,通过 Python 调用 CLI 处理图像,但需预先配置 Autopilot 设置。建议关注官方更新,获取更多 CLI 参数支持。
* * *
关键引用
+70
View File
@@ -0,0 +1,70 @@
import os
import sys
import folder_paths
import comfy.model_management as model_management
from .topaz import (
init_topaz,
test_and_clean_topaz,
ComfyTopazPhoto,
NODE_CLASS_MAPPINGS as TOPAZ_NODE_CLASS_MAPPINGS,
NODE_DISPLAY_NAME_MAPPINGS as TOPAZ_NODE_DISPLAY_NAME_MAPPINGS
)
# 添加测试和清理节点
class ComfyTopazPhotoTestAndClean:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"tpai_exe": ("STRING", {"default": "C:\\Program Files\\Topaz Labs LLC\\Topaz Photo AI\\tpai.exe"}),
"clean_cache": (["True", "False"], {"default": "False"}),
"verbose": (["True", "False"], {"default": "True"}),
},
}
RETURN_TYPES = ("STRING", "STRING", "INT", "FLOAT", "FLOAT",)
RETURN_NAMES = ("status", "message", "cleaned_files", "cache_before_MB", "cache_after_MB",)
FUNCTION = "test_and_clean"
CATEGORY = "ComfyTopazPhoto"
def test_and_clean(self, tpai_exe, clean_cache, verbose):
# 将字符串转换为布尔值
clean_cache = (clean_cache == "True")
verbose = (verbose == "True")
# 验证 tpai_exe 路径是否存在
if not os.path.exists(tpai_exe):
return ("ERROR", f"Topaz Photo AI 可执行文件未找到: {tpai_exe}", 0, 0.0, 0.0)
# 调用测试和清理函数
results = test_and_clean_topaz(tpai_exe, clean_cache, verbose)
# 构建状态和消息
status = "SUCCESS" if results["success"] else "ERROR"
message = ""
if results["success"]:
message = "Topaz Photo AI 测试成功!"
if clean_cache:
message += f" 已清理 {results['cleaned_files']} 个缓存文件。"
else:
message = f"测试失败: {results['error_message']}"
# 计算缓存大小(MB)
cache_before_MB = results["cache_size_before"] / 1024 / 1024
cache_after_MB = results["cache_size_after"] / 1024 / 1024
return (status, message, results["cleaned_files"], cache_before_MB, cache_after_MB)
# 合并节点映射
NODE_CLASS_MAPPINGS = {
**TOPAZ_NODE_CLASS_MAPPINGS,
"ComfyTopazPhotoTestAndClean": ComfyTopazPhotoTestAndClean,
}
# 合并显示名称映射
NODE_DISPLAY_NAME_MAPPINGS = {
**TOPAZ_NODE_DISPLAY_NAME_MAPPINGS,
"ComfyTopazPhotoTestAndClean": "Test & Clean Topaz",
}
+8 -8
View File
@@ -1,14 +1,14 @@
[project]
name = "comfy-topaz"
description = "Comfy-Topaz is a custom node for ComfyUI, which integrates with Topaz Photo AI to enhance (upscale, sharpen, denoise, etc.) images, allowing this traditionally asynchronous step to become a part of ComfyUI workflows.\nNOTE: Requires licensed installation of Topaz Photo AI"
version = "1.0.1"
license = { file = "LICENSE" }
name = "Comfy-Topaz-Photo"
description = "A ComfyUI node for integrating Topaz Photo AI's powerful image enhancement capabilities"
version = "1.0.0"
license = {file = "LICENSE"}
[project.urls]
Repository = "https://github.com/choey/Comfy-Topaz"
Repository = "https://github.com/leoleelxh/Comfy-Topaz-Photo"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = "choey"
DisplayName = "Comfy-Topaz"
Icon = ""
PublisherId = "leoleexh"
DisplayName = "Comfy-Topaz-Photo"
Icon = "https://raw.githubusercontent.com/leoleelxh/Comfy-Topaz-Photo/main/sample.jpg"
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 818 KiB

+732 -316
View File
File diff suppressed because it is too large Load Diff
+230
View File
@@ -0,0 +1,230 @@
import os
import numpy as np
import torch
import json
import subprocess
import tempfile
import time
from PIL import Image
import folder_paths # Ensure this import is correct and folder_paths is accessible
import shutil # Added for fallback copy
# Simplified Upscale Settings Node
class ComfyTopazPhotoUpscaleSettings:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"enabled": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ("TOPAZ_UPSCALESETTINGS",)
FUNCTION = "get_settings"
CATEGORY = "ComfyTopazPhoto"
def get_settings(self, enabled):
# Returns only enable status and module name
settings = {
"enabled": enabled,
"module": "enhance", # Use Topaz Enhance module
}
return (settings,)
# Simplified Sharpen Settings Node
class ComfyTopazPhotoSharpenSettings:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"enabled": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("TOPAZ_SHARPENSETTINGS",)
FUNCTION = "get_settings"
CATEGORY = "ComfyTopazPhoto"
def get_settings(self, enabled):
# Returns only enable status and module name
settings = {
"enabled": enabled,
"module": "sharpen",
}
return (settings,)
# Simplified Face Recovery Settings Node
class ComfyTopazPhotoFaceRecoverySettings:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"enabled": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("TOPAZ_FACERECOVERYSETTINGS",)
FUNCTION = "get_settings"
CATEGORY = "ComfyTopazPhoto"
def get_settings(self, enabled):
# Returns only enable status and guessed module name
settings = {
"enabled": enabled,
"module": "faceRecover", # Module name remains a guess
}
return (settings,)
# Main Node
class ComfyTopazPhoto:
def __init__(self):
# Ensure temporary directory exists
self.output_dir = folder_paths.get_temp_directory()
if not os.path.exists(self.output_dir):
os.makedirs(self.output_dir)
self.type = "temp"
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE",),
"tpai_exe": ("STRING", {"default": ""}),
"compression": ("INT", {"default": 2, "min": 0, "max": 10}),
},
"optional": {
"upscale": ("TOPAZ_UPSCALESETTINGS",),
"sharpen": ("TOPAZ_SHARPENSETTINGS",),
"face_recovery": ("TOPAZ_FACERECOVERYSETTINGS",),
}
}
RETURN_TYPES = ("IMAGE", "STRING", "STRING")
FUNCTION = "process"
CATEGORY = "ComfyTopazPhoto"
def process(self, images, tpai_exe, compression, upscale=None, sharpen=None, face_recovery=None):
if not tpai_exe or not os.path.exists(tpai_exe):
raise ValueError("[ComfyTopazPhoto] Error: tpai.exe path is not valid or not provided.")
batch_results = []
autopilot_settings_str = "N/A"
final_settings_json = "{}" # Default empty JSON
for i, image in enumerate(images):
input_path = None # Initialize paths
output_path = None
process = None # Initialize process variable
filters = {} # Reset filters for each image
try:
# Convert tensor to PIL
img_np = image.cpu().numpy()
img_pil = Image.fromarray((img_np * 255).astype(np.uint8))
# Create temp input file using tempfile for unique names
with tempfile.NamedTemporaryFile(dir=self.output_dir, suffix=".png", delete=False) as temp_input_file:
input_path = temp_input_file.name
# Ensure the file handle is closed before saving, or save directly
img_pil.save(input_path, pnginfo=None, compress_level=6)
# Create temp output file path
temp_output_file = tempfile.NamedTemporaryFile(dir=self.output_dir, suffix=".png", delete=False)
output_path = temp_output_file.name
temp_output_file.close() # Close handle immediately
# Build filters JSON (Simplified logic)
if upscale and upscale.get("enabled", False):
filters[upscale.get("module", "enhance")] = {} # Use Autopilot settings
if sharpen and sharpen.get("enabled", False):
filters[sharpen.get("module", "sharpen")] = {} # Use Autopilot settings
if face_recovery and face_recovery.get("enabled", False):
filters[face_recovery.get("module", "faceRecover")] = {} # Use Autopilot settings
# --- Processing Logic ---
settings_json_for_run = "{}"
if not filters:
# No filters enabled, copy original image to output path
print(f"[ComfyTopazPhoto] Warning: No Topaz filters enabled for image {i+1}. Returning original image.")
try:
if os.path.exists(output_path): os.remove(output_path)
# Use copy instead of link for reliability
shutil.copy2(input_path, output_path)
except Exception as e:
print(f"[ComfyTopazPhoto] Error copying original image: {e}")
raise RuntimeError(f"Failed to prepare original image for output: {e}")
else:
# Filters enabled, run tpai.exe
settings_json_for_run = json.dumps({"filters": filters})
if i == 0: final_settings_json = settings_json_for_run # Store JSON for the first processed image
# Build command, ensure paths are quoted
command_parts = [
f'"{tpai_exe}"', # Assume tpai_exe might need quotes
f'"{input_path}"',
'--output', f'"{output_path}"',
'--compression', str(compression),
'--override',
'--settings', settings_json_for_run # Pass JSON string directly
]
command_str = " ".join(command_parts)
print(f"[ComfyTopazPhoto] Executing: {command_str}")
startupinfo = None
if os.name == 'nt':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
process = subprocess.Popen(command_str, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo, text=True, encoding='utf-8', errors='replace')
stdout, stderr = process.communicate()
return_code = process.returncode
print(f"[ComfyTopazPhoto] stdout:\n{stdout}")
if stderr: print(f"[ComfyTopazPhoto] stderr:\n{stderr}")
print(f"[ComfyTopazPhoto] Return code: {return_code}")
# Extract Autopilot settings from stdout
for line in stdout.splitlines():
if line.startswith('Autopilot settings: '):
autopilot_settings_str = line.split('Autopilot settings: ', 1)[1]
break
# Check for errors
if return_code != 0 and return_code != 1: # 0=Success, 1=Partial success
error_message = f"tpai.exe failed with return code {return_code}. "
error_codes = {255: "No valid files passed.", 254: "Invalid log token. Login via GUI.", 253: "Invalid argument."}
error_message += error_codes.get(return_code, "Check console/logs.")
raise RuntimeError(f"[ComfyTopazPhoto] Error: {error_message}")
if not os.path.exists(output_path) or os.path.getsize(output_path) == 0:
raise RuntimeError(f"[ComfyTopazPhoto] Error: Output file missing or empty: {output_path}")
# --- Load Output Image ---
img_out_pil = Image.open(output_path).convert("RGB")
img_out_np = np.array(img_out_pil).astype(np.float32) / 255.0
img_out_tensor = torch.from_numpy(img_out_np).unsqueeze(0)
batch_results.append(img_out_tensor)
except Exception as e:
print(f"[ComfyTopazPhoto] Error processing image {i+1} ({input_path if input_path else 'N/A'}): {e}")
# Stop the batch on first error
raise RuntimeError(f"Error processing image {i+1}: {e}") from e
finally:
# --- Cleanup --- Ensures temp files are removed ---
if process and process.poll() is None:
try: process.kill()
except Exception: pass
if input_path and os.path.exists(input_path):
try: os.remove(input_path)
except OSError as e: print(f"Error removing temp input file {input_path}: {e}")
if output_path and os.path.exists(output_path):
try: os.remove(output_path)
except OSError as e: print(f"Error removing temp output file {output_path}: {e}")
# --- Final Check and Return ---
if not batch_results:
# This case should ideally be caught earlier if copy/processing fails
raise RuntimeError("[ComfyTopazPhoto] Error: No images were successfully processed or prepared.")
output_images = torch.cat(batch_results, dim=0)
return (output_images, final_settings_json, autopilot_settings_str)
+5 -5
View File
@@ -14,7 +14,7 @@ const ext = {
});
},
async beforeRegisterNodeDef(nodeType, nodeData, _app) {
if (nodeData.name === 'TopazPhotoAI') {
if (nodeData.name === 'ComfyTopazPhoto') {
const ensureTpai = async (node) => {
const tpaiWidget = node.widgets.find(w => w.name === "tpai_exe");
if (tpaiWidget && tpaiWidget.value === "") {
@@ -23,15 +23,15 @@ const ext = {
}
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = onConfigure ? onConfigure.apply(this, arguments) : undefined;
nodeType.prototype.onConfigure = function(...args) {
const r = onConfigure ? onConfigure.apply(this, args) : undefined;
ensureTpai(this);
return r;
};
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
nodeType.prototype.onNodeCreated = function(...args) {
const r = onNodeCreated ? onNodeCreated.apply(this, args) : undefined;
ensureTpai(this);
return r;
};