feat: 新增启动欢迎通知、流式预览节点及多项功能更新

- 新增启动弹窗通知(绿色主题,支持关闭)
- 新增 StreamPreview 流式文本预览节点
- 新增 fileUpload、updateNotifier 前端 JS 模块
- 重构多个 client,统一错误处理
- 删除废弃节点 batch_nano_banana_v2、quan_neng_sheng_tu 等
- 将 .config 纳入版本控制(已清空密钥)
This commit is contained in:
Jony
2026-04-13 00:49:51 +08:00
parent bbc5f4a2c4
commit 92bcf65d14
28 changed files with 1381 additions and 3518 deletions
+46 -63
View File
@@ -1,9 +1,10 @@
"""
通用视频预览节点
ComfyUI 自定义节点,接收视频文件路径并在前端展示预览
视频预览节点
接收 VIDEO 类型,在前端内嵌播放器预览
"""
import os
import io
try:
import folder_paths
@@ -11,8 +12,6 @@ try:
except ImportError:
FOLDER_PATHS_AVAILABLE = False
SUPPORTED_VIDEO_EXTENSIONS = {".mp4", ".webm", ".mov", ".avi", ".mkv", ".flv", ".wmv", ".3gp"}
def _get_output_dir() -> str:
if FOLDER_PATHS_AVAILABLE:
@@ -22,86 +21,70 @@ def _get_output_dir() -> str:
class VideoPreview:
"""
通用视频预览节点
功能:
- 接收视频文件路径(STRING)
- 在 ComfyUI 前端节点上内嵌 <video> 播放器进行预览
- 支持 mp4, webm, mov, avi, mkv 等主流格式
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"预览视频": ("STRING", {"forceInput": True}),
"视频": ("VIDEO",),
},
}
RETURN_TYPES = ()
OUTPUT_NODE = True
FUNCTION = "preview"
CATEGORY = "video"
CATEGORY = "comfyui_o1key/Utils"
DESCRIPTION = (
"通用视频预览节点。\n"
"接收视频文件路径,在节点上显示视频播放器。\n"
"支持 mp4, webm, mov, avi, mkv 等主流视频格式。"
)
def preview(self, **kwargs) -> dict:
video_path = kwargs.get("预览视频", "")
if not video_path or not video_path.strip():
raise ValueError("视频路径为空")
video_path = video_path.strip()
if not os.path.isfile(video_path):
raise ValueError(f"视频文件不存在: {video_path}")
ext = os.path.splitext(video_path)[1].lower()
if ext not in SUPPORTED_VIDEO_EXTENSIONS:
raise ValueError(
f"不支持的视频格式 '{ext}'"
f"支持: {', '.join(sorted(SUPPORTED_VIDEO_EXTENSIONS))}"
)
output_dir = _get_output_dir()
abs_video = os.path.abspath(video_path)
abs_output = os.path.abspath(output_dir)
if abs_video.startswith(abs_output):
rel_path = os.path.relpath(abs_video, abs_output)
subfolder = os.path.dirname(rel_path).replace("\\", "/")
filename = os.path.basename(rel_path)
file_type = "output"
else:
filename = os.path.basename(abs_video)
subfolder = ""
file_type = "output"
# 如果文件不在 output 目录下,复制一份到 output/video/
target_dir = os.path.join(output_dir, "video")
os.makedirs(target_dir, exist_ok=True)
target_path = os.path.join(target_dir, filename)
if not os.path.exists(target_path) or abs_video != os.path.abspath(target_path):
import shutil
shutil.copy2(abs_video, target_path)
def preview(self, 视频) -> dict:
# 用官方接口取文件路径
source = 视频.get_stream_source()
if isinstance(source, io.BytesIO):
# BytesIO 情况:写到 output/video/ 临时文件
output_dir = os.path.join(_get_output_dir(), "video")
os.makedirs(output_dir, exist_ok=True)
filename = "preview_tmp.mp4"
tmp_path = os.path.join(output_dir, filename)
source.seek(0)
with open(tmp_path, "wb") as f:
f.write(source.read())
subfolder = "video"
else:
video_path = source
output_dir = _get_output_dir()
abs_video = os.path.abspath(video_path)
abs_output = os.path.abspath(output_dir)
file_size = os.path.getsize(abs_video)
size_mb = file_size / (1024 * 1024)
print(f"视频预览: {filename} ({size_mb:.1f}MB)")
if abs_video.startswith(abs_output):
rel_path = os.path.relpath(abs_video, abs_output)
subfolder = os.path.dirname(rel_path).replace("\\", "/")
filename = os.path.basename(rel_path)
else:
# 文件在 output 目录外,复制一份
target_dir = os.path.join(output_dir, "video")
os.makedirs(target_dir, exist_ok=True)
filename = os.path.basename(abs_video)
target_path = os.path.join(target_dir, filename)
if not os.path.exists(target_path):
import shutil
shutil.copy2(abs_video, target_path)
subfolder = "video"
return {
"ui": {
"videos": [{
"filename": filename,
"subfolder": subfolder,
"type": file_type,
"type": "output",
}],
}
}
NODE_CLASS_MAPPINGS = {
"VideoPreview": VideoPreview,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"VideoPreview": "预览视频",
}