import type { Node, Edge } from '@vue-flow/core' import type { StudioNodeData, GraphJson } from './types' import { NODE_TYPES } from './nodeTypes' const FPS_MAP: Record = { '23.98': [24000, 1001], '24': [24, 1], '25': [25, 1], '29.97': [30000, 1001], '30': [30, 1], '50': [50, 1], '59.94': [60000, 1001], '60': [60, 1], } function parseFps(fps: string): { fps_num: number; fps_den: number } { const [num, den] = FPS_MAP[fps] ?? [25, 1] return { fps_num: num, fps_den: den } } function parseRes(res: string): { width: number; height: number } { const [w, h] = (res || '1920x1080').split('x').map(Number) return { width: w || 1920, height: h || 1080 } } // Looks up the port kind from the node type definition function portKindFor(node: Node | undefined, handleId: string): string | undefined { if (!node) return undefined const def = NODE_TYPES[(node.data as StudioNodeData).nodeType] return def?.ports.find(p => p.id === handleId)?.kind } // Looks up the backend port key from the node type definition, falls back to handleToPort function portKeyFor(node: Node | undefined, handleId: string): string { if (node) { const def = NODE_TYPES[(node.data as StudioNodeData).nodeType] const portKey = def?.ports.find(p => p.id === handleId)?.portKey if (portKey) return portKey } return handleToPort(handleId) } // Converts a Vue Flow handle id to the NODE_CONFIG port key used in graph JSON function handleToPort(handle: string): string { return handle.replace(/-(?:in|out)(?:-\d+)?$/, '_flow_id') } export function buildGraph(vfNodes: Node[], vfEdges: Edge[]): GraphJson { const nodeMap = new Map(vfNodes.map(n => [n.id, n.data as StudioNodeData])) const nodes = vfNodes.map(n => { const d = n.data as StudioNodeData const typeDef = NODE_TYPES[d.nodeType] // Only include non-format params in the graph JSON node params const params: Record = {} if (typeDef) { for (const p of typeDef.params) { if (!p.isFormat && d.params[p.key] != null) { params[p.key] = d.params[p.key] } } } return { id: n.id, type: d.nodeType, params } }) const edges = vfEdges.map((e) => { const srcNode = vfNodes.find(n => n.id === e.source) const tgtNode = vfNodes.find(n => n.id === e.target) const srcData = nodeMap.get(e.source) const srcHandle = e.sourceHandle ?? 'video-out' const tgtHandle = e.targetHandle ?? 'video-in' const kind = portKindFor(srcNode, srcHandle) ?? 'video' let format: Record = { kind } if (kind === 'video') { const { width, height } = parseRes(String(srcData?.params.res ?? '1920x1080')) const { fps_num, fps_den } = parseFps(String(srcData?.params.fps ?? '25')) format = { kind, width, height, fps_num, fps_den } } else { format = { kind, sample_rate: Number(srcData?.params.sample_rate ?? 48000), channels: Number(srcData?.params.channels ?? 2), bit_depth: 32, } } return { from: e.source, from_port: portKeyFor(srcNode, srcHandle), to: e.target, to_port: portKeyFor(tgtNode, tgtHandle), format, } }) return { nodes, edges } }