| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- #!/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()
|