- 新增 nodes/K_video.py,节点显示名称「K26 图生视频」
- 支持起始帧(必填)、尾帧(可选)图片输入
- 参数:提示词、模式(pro)、时长(5/10s)、生成音频(关闭/打开)
- 模型名按规则动态拼接:kling-v2-6-{mode}-{dur}s-{voice}
- 尾帧有输入时通过 metadata.image_tail 传参
- 走三段式流程:提交 → 轮询 → 下载,带进度条
- 注册至 NODE_CLASS_MAPPINGS / NODE_DISPLAY_NAME_MAPPINGS
129 lines
4.7 KiB
Python
129 lines
4.7 KiB
Python
"""
|
||
Comfyui_o1key - ComfyUI 自定义节点集合
|
||
通过 api.o1key.com 调用 AI 模型进行图像生成和文本生成
|
||
|
||
项目结构:
|
||
├── nodes/ # 节点实现
|
||
├── utils/ # 工具模块
|
||
├── clients/ # API 客户端
|
||
└── __init__.py # 节点注册入口
|
||
"""
|
||
|
||
# 检查更新(仅在启动时检查一次)
|
||
try:
|
||
from .utils.update_checker import check_for_updates, notify_new_version
|
||
|
||
if check_for_updates():
|
||
notify_new_version()
|
||
except Exception:
|
||
# 静默失败,不影响插件加载
|
||
pass
|
||
|
||
import ssl
|
||
|
||
from .nodes import NanoBananaPro, BatchNanoBananaPro, GoogleGemini, LoadFile, ImageStitchPro, SaveCleanImage, BatchCleanMetadata, VideoPreview, GoogleVeo, FluxImageEdit, UniversalLLMChat, KlingVideo, KlingFirstLastFrame, KlingMotionControlTest, AspectRatioPreset, MultiResPreview, BatchImagesO1key, Seedance, SeedanceMultiModal, StreamPreview, DoubaoImage, O1keyGPTImage, KVideo
|
||
|
||
# 报错弹框友好文案(不修改原节点代码,仅在外层统一处理)
|
||
_MSG_TIMEOUT = "API 请求超时,请稍后重试或检查网络。"
|
||
_MSG_SSL_NETWORK = (
|
||
"本地网络不太稳定!解决方案如下:\n"
|
||
"1. 重启程序再试试看 (优先)\n"
|
||
"2. 调整一下网络环境,如wifi或宽带等\n"
|
||
"3. 切换VPN节点,或更换代理模式\n"
|
||
"4. 关掉杀毒软件或防火墙\n"
|
||
"5. 关掉浏览器VPN插件,避免冲突"
|
||
)
|
||
|
||
def _wrap_generate_for_error_display(cls, attr="generate"):
|
||
original = getattr(cls, attr, None)
|
||
if original is None:
|
||
return
|
||
def wrapped(self, *args, **kwargs):
|
||
try:
|
||
return original(self, *args, **kwargs)
|
||
except TimeoutError as e:
|
||
msg = (str(e) or "").strip()
|
||
if not msg:
|
||
msg = _MSG_TIMEOUT
|
||
raise TimeoutError(msg) from None
|
||
except (ssl.SSLError, OSError) as e:
|
||
err_str = str(e)
|
||
if "DECRYPTION_FAILED_OR_BAD_RECORD_MAC" in err_str or "decryption failed or bad record mac" in err_str.lower():
|
||
raise RuntimeError(_MSG_SSL_NETWORK) from None
|
||
raise
|
||
setattr(cls, attr, wrapped)
|
||
|
||
_wrap_generate_for_error_display(NanoBananaPro)
|
||
_wrap_generate_for_error_display(BatchNanoBananaPro)
|
||
|
||
# ComfyUI 节点注册
|
||
NODE_CLASS_MAPPINGS = {
|
||
"NanoBananaPro": NanoBananaPro,
|
||
"BatchNanoBananaPro": BatchNanoBananaPro,
|
||
"GoogleGemini": GoogleGemini,
|
||
"LoadFile": LoadFile,
|
||
"ImageStitchPro": ImageStitchPro,
|
||
"SaveCleanImage": SaveCleanImage,
|
||
"BatchCleanMetadata": BatchCleanMetadata,
|
||
"VideoPreview": VideoPreview,
|
||
"GoogleVeo": GoogleVeo,
|
||
"FluxImageEdit": FluxImageEdit,
|
||
"UniversalLLMChat": UniversalLLMChat,
|
||
"KlingVideo": KlingVideo,
|
||
"KlingFirstLastFrame": KlingFirstLastFrame,
|
||
"KlingMotionControlTest": KlingMotionControlTest,
|
||
"AspectRatioPreset": AspectRatioPreset,
|
||
"MultiResPreview": MultiResPreview,
|
||
"BatchImagesO1key": BatchImagesO1key,
|
||
"Seedance": Seedance,
|
||
"SeedanceMultiModal": SeedanceMultiModal,
|
||
"StreamPreview": StreamPreview,
|
||
"DoubaoImage": DoubaoImage,
|
||
"O1keyGPTImage": O1keyGPTImage,
|
||
"KVideo": KVideo,
|
||
}
|
||
|
||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||
"NanoBananaPro": "Nano Banana",
|
||
"BatchNanoBananaPro": "批量 Nano Banana",
|
||
"GoogleGemini": "Google Gemini",
|
||
"LoadFile": "加载文件",
|
||
"ImageStitchPro": "图像拼接 Pro",
|
||
"SaveCleanImage": "保存图像(防AI识别)",
|
||
"BatchCleanMetadata": "批量任务(防AI识别)",
|
||
"VideoPreview": "预览视频",
|
||
"GoogleVeo": "Google Veo - ab",
|
||
"FluxImageEdit": "Flux2 图像编辑",
|
||
"UniversalLLMChat": "全能LLM对话助手",
|
||
"KlingVideo": "文/图生视频 自研模型",
|
||
"KlingFirstLastFrame": "首尾帧生视频 自研模型",
|
||
"KlingMotionControlTest": "动作控制 自研模型",
|
||
"AspectRatioPreset": "图片宽高比预设",
|
||
"MultiResPreview": "预览图像(v2)",
|
||
"BatchImagesO1key": "加载图像(批量)",
|
||
"Seedance": "Seedance 视频生成",
|
||
"SeedanceMultiModal": "Seedance 多模态参考生视频",
|
||
"StreamPreview": "流式文本预览",
|
||
"DoubaoImage": "豆包生图",
|
||
"O1keyGPTImage": "o1key GPT Image",
|
||
"KVideo": "K26 图生视频",
|
||
}
|
||
|
||
WEB_DIRECTORY = "./web"
|
||
|
||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||
|
||
# 注册 /o1key/input_dir 接口,供前端文件上传按钮获取 input 目录绝对路径
|
||
try:
|
||
from aiohttp import web
|
||
from server import PromptServer
|
||
import folder_paths
|
||
|
||
@PromptServer.instance.routes.get("/o1key/input_dir")
|
||
async def get_input_dir(request):
|
||
import os
|
||
path = os.path.abspath(folder_paths.get_input_directory())
|
||
return web.json_response({"path": path})
|
||
except Exception:
|
||
pass
|