从零搭建ComfyUI自定义节点
工作中有需求需要把一些功能整合到comfyUI工作流中,例如(infiniteTalk数字人工作流中加入语音合成工具),下面也将以这个任务为例,记录一些如何从0搭建自己的custom node。
工作中有需求需要把一些功能整合到comfyUI工作流中,例如(infiniteTalk数字人工作流中加入语音合成工具),下面也将以这个任务为例,记录一些如何从0搭建自己的custom node。
1. 节点位置
所有的custom node都在/ComfyUI/custom_nodes目录下,comfyui在执行main.py的时候会扫描目录下所有的.py文件,对每个找到的 Python 模块(.py 文件),ComfyUI 会尝试将其作为 Python 模块导入(使用 importlib 动态导入)。所以简单的节点只需要在/custom_nodes目录下新建一个python文件即可。
2. 文件格式要求
.py文件中只要包含特定的字典结构就会被ComfyUI读取,如下图所示
NODE_CLASS_MAPPINGS = {
"VoiceCloningAPI": VoiceCloningAPINode
}
NODE_DISPLAY_NAME_MAPPINGS = {
"VoiceCloningAPI": "语音克隆API调用"
}
NODE_CLASS_MAPPINGS:键为节点类的内部名称,值为节点类(如{"MyNode": MyNodeClass})。NODE_DISPLAY_NAME_MAPPINGS:键为节点类的内部名称,值为节点在 UI 中显示的友称(如{"MyNode": "我的自定义节点"})。
下面我们需要定义这个class,模板如下
class VoiceCloningAPINode:
"""访问语音克隆API并返回音频文件名的节点"""
@classmethod
##定义输入
def INPUT_TYPES(cls):
return {
"required": {}
}
##定义返回
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("音频文件名",)
FUNCTION = "generate_voice"
CATEGORY = "音频处理/语音克隆"
##定义函数
def generate_voice()
3.节点输入格式
需要返回一个包含所需输入类型的字典,
required指节点必须的输入,optional指可选择的。
下面列举几个常见的输入类型。
String,INT,FLOAT,BOOLEAN注意都需要大写。下拉框的写法是["1","2","3",]注意保证括号里是一个列表。还有一些具体的属性可以参考官网(数据类型 - ComfyUI)
return {
"required": {
"text": ("STRING", {"default": "我爱毛主席我爱天安门"}),
"text_language": ([
"zh",
"en",
"ja",
], {"default": "zh", "tooltip": "The tts language"}),
"speed": ("FLOAT", {"default": 1.0, "min": 0.5, "max": 2.0, "step": 0.1}),
"base64": ("BOOLEAN", {"default": False}),
"filename": ("STRING", {"default": "", "tooltip": "留空则自动生成唯一文件名"}),
}
"optional":{
"model": ("MODEL",),
"vae": ("VAE",),
"clip": ("CLIP",),
}
}
4.节点返回设置
如果节点有返回,需要定义三个,设置output_node=TRUE,定义返回类型和返回的名称。
OUTPUT_NODE = True
# 输出的数据类型,需要大写
RETURN_TYPES = ("IMAGE","CONDITIONING","CONDITIONING","INT", "FLOAT", "STRING",)
RETURN_NAMES = ()
5. function功能
节点的核心,这个节点能干什么,在这里定义和实现。首先需要设置FUNCTION="下面要定义的函数名",我写的这个节点的功能是根据输入的文本和声纹设置,访问api接口,保留返回的二进制文件。
FUNCTION = "generate_voice"
CATEGORY = "音频处理/语音克隆"
def generate_voice(self, text, text_language, voice_source, speed, base64, filename):
try:
# 生成随机文件名(如果用户未指定)
if not filename.strip():
random_suffix = uuid.uuid4().hex[:6]
filename = f"gewuTTS_{random_suffix}.mp3"
# 使用ComfyUI标准输入目录构建路径
file_path = os.path.join(input_dir, filename)
payload = {
"text": text,
"text_language": text_language,
"voice_source": voice_source,
"speed": speed,
"base64": "True" if base64 else "False"
}
response = requests.post(
"",
json=payload,
stream=True
)
if response.status_code == 200:
with open(file_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=1024):
if chunk:
f.write(chunk)
print(f"音频已保存到: {file_path}")
# 只返回文件名(不包含路径)
return (file_path,)
else:
raise Exception(f"API请求失败,状态码: {response.status_code}, 响应内容: {response.text}")
except Exception as e:
print(f"生成语音时出错: {str(e)}")
raise
6. 最简单的模板
class example:
def __init__(self):
pass
CATEGORY = gategory/example
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
},
}
OUTPUT_NODE = True
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("整数",)
FUNCTION = "test"
def test(self,):
pass
NODE_CLASS_MAPPINGS = {
"test": test
}
NODE_DISPLAY_NAME_MAPPINGS = {
"test": "test"
}
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)