工作中有需求需要把一些功能整合到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"
}
    Logo

    火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

    更多推荐