#!/usr/bin/env python3 """将 ComfyUI workflow.json (UI格式) 转换为 workflow_api.json (API格式) 用法: python convert_workflow.py refcontrol_pose.json python convert_workflow.py refcontrol_pose.json -o output_api.json """ import argparse import json from pathlib import Path def convert(workflow: dict) -> dict: """ workflow.json → workflow_api.json UI格式: nodes[] 每个节点有 id/type/inputs[]/outputs[]/widgets_values[]/links[] API格式: {node_id: {class_type, inputs: {param: value_or_link}}} 连接关系: links[] = [link_id, src_node, src_slot, dst_node, dst_slot] widgets_values 按顺序对应节点没有连线的输入参数 """ nodes = workflow.get("nodes", []) links = workflow.get("links", []) # 建立 link_id → [src_node_id, src_slot] 的映射 link_map = {} for link in links: link_id, src_node, src_slot, dst_node, dst_slot = link[:5] link_map[link_id] = [str(src_node), src_slot] api = {} for node in nodes: node_id = str(node["id"]) class_type = node.get("type", "") # 跳过纯 UI 节点(Note、MarkdownNote 等无实际计算的节点) if class_type in ("Note", "MarkdownNote", "PrimitiveNode"): continue # 跳过 mode=4(disabled)的节点 if node.get("mode") == 4: continue inputs_def = node.get("inputs", []) # 有连线的输入槽 widgets_values = node.get("widgets_values", []) # 无连线的参数值 inputs = {} # 先处理有连线的输入槽 linked_slots = set() for inp in inputs_def: link_id = inp.get("link") if link_id is not None and link_id in link_map: inputs[inp["name"]] = link_map[link_id] linked_slots.add(inp["name"]) # 再处理 widgets_values(按顺序填入没有连线的参数) # 需要知道节点有哪些 widget 参数,通过排除已连线的输入来推断 # widgets 对应的参数名无法从 workflow.json 直接获取,只能按顺序赋值 # 用 widget_idx_0, widget_idx_1 ... 作为 key,实际使用时按需重命名 for i, val in enumerate(widgets_values): key = f"widget_{i}" inputs[key] = val api[node_id] = { "class_type": class_type, "inputs": inputs, } return api def main(): parser = argparse.ArgumentParser(description="Convert workflow.json to workflow_api.json") parser.add_argument("input", help="输入 workflow.json 路径") parser.add_argument("-o", "--output", help="输出路径,默认在原文件名加 _api 后缀") args = parser.parse_args() input_path = Path(args.input) output_path = Path(args.output) if args.output else input_path.with_stem(input_path.stem + "_api") with open(input_path, "r", encoding="utf-8") as f: workflow = json.load(f) # 支持两种格式:直接是 nodes[] 的对象,或者包含 nodes[] 的对象 if "nodes" not in workflow: print("ERROR: 不是有效的 workflow.json,缺少 nodes 字段") return api = convert(workflow) with open(output_path, "w", encoding="utf-8") as f: json.dump(api, f, indent=2, ensure_ascii=False) print(f"转换完成: {output_path}") print(f"节点数: {len(api)}") for node_id, node in api.items(): print(f" [{node_id}] {node['class_type']}") if __name__ == "__main__": main()