> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflow Authoring

> How to build ComfyUI workflows for real-time video processing with ComfyStream and connect them to a live video source.

export const StyledStep = ({title, icon, titleSize = 'h3', iconColor = null, titleColor = null, children, className = '', style = {}, ...rest}) => {
  const styledTitle = titleColor ? <span style={{
    color: titleColor
  }}>{title}</span> : title;
  return <Step title={styledTitle} icon={icon} iconColor={iconColor || undefined} titleSize={titleSize} className={className} style={style} {...rest}>
      {children}
    </Step>;
};

export const StyledSteps = ({children, iconColor, titleColor, lineColor, iconSize = '24px', className = '', style = {}, ...rest}) => {
  const resolvedIconColor = iconColor || 'var(--accent-dark, #18794E)';
  const resolvedTitleColor = titleColor || 'var(--lp-color-accent)';
  const resolvedLineColor = lineColor || 'var(--lp-color-accent)';
  return <div className={['docs-styled-steps', className].filter(Boolean).join(' ')} style={style} {...rest}>
      <style>{`
        .docs-styled-steps .steps > div > div.absolute > div {
          background-color: ${resolvedIconColor};
        }
        .docs-styled-steps .steps > div > div.w-full > p {
          color: ${resolvedTitleColor};
        }
        .docs-styled-steps .steps > div > div.absolute.w-px {
          background-color: ${resolvedLineColor};
        }
        .docs-styled-steps .steps > div:last-child > div.absolute.w-px::after {
          content: '';
          position: absolute;
          bottom: 0;
          left: 50%;
          transform: translateX(-50%);
          width: 6px;
          height: 6px;
          background-color: ${resolvedLineColor};
          transform: translateX(-50%) rotate(45deg);
        }
      `}</style>
      <div>
        <Steps>{children}</Steps>
      </div>
    </div>;
};

export const CenteredContainer = ({children, maxWidth = "800px", padding = "0", preset = "default", width = "", minWidth = "", marginRight = "", marginBottom = "", textAlign = "", style = {}, className = "", ...rest}) => {
  const presets = {
    default: {},
    fitContent: {
      width: "fit-content",
      minWidth: "fit-content"
    },
    readable70: {
      width: "70%",
      minWidth: "fit-content"
    },
    readable80: {
      width: "80%",
      minWidth: "fit-content"
    },
    readable90: {
      width: "90%"
    },
    wide900: {
      maxWidth: "900px"
    }
  };
  const presetStyle = presets[preset] || presets.default;
  return <div className={className} style={{
    maxWidth: presetStyle.maxWidth || maxWidth,
    margin: "0 auto",
    padding: padding,
    ...presetStyle.width ? {
      width: presetStyle.width
    } : {},
    ...presetStyle.minWidth ? {
      minWidth: presetStyle.minWidth
    } : {},
    ...width ? {
      width
    } : {},
    ...minWidth ? {
      minWidth
    } : {},
    ...marginRight ? {
      marginRight
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...textAlign ? {
      textAlign
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

<CenteredContainer preset="readable90">
  <Tip>ComfyStream runs any ComfyUI img2img workflow on a live video stream. Build the workflow in ComfyUI desktop, export it as API JSON, and point ComfyStream at that file.</Tip>
</CenteredContainer>

***

ComfyStream applies ComfyUI workflows to incoming video frames in real time. The workflow runs as-is: any node that works in ComfyUI batch mode works in ComfyStream, provided it accepts a latent or image input representing the current frame and returns a transformed output.

This guide covers building a workflow suitable for streaming, installing ComfyStream, and connecting it to a live video source.

<CustomDivider />

## Prerequisites

* ComfyUI installed locally or in a Docker environment
* An NVIDIA GPU (RTX 3090 or later recommended for StreamDiffusion workflows; lighter models work on lower VRAM)
* Python 3.10+ with PyTorch installed
* FFmpeg available on PATH

<CustomDivider />

## Installation

<StyledSteps iconColor="#2d9a67" titleColor="var(--accent)">
  <StyledStep title="Install ComfyStream" icon="download">
    Install ComfyStream as a Python package into your ComfyUI environment:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    pip install git+https://github.com/livepeer/comfystream.git
    ```

    Alternatively, deploy via Docker using the `livepeer/comfystream` image, or use the `livepeer-comfystream` RunPod template for cloud deployment.
  </StyledStep>

  <StyledStep title="Copy auxiliary custom nodes" icon="puzzle">
    If running ComfyStream as a custom node inside an existing ComfyUI installation, copy the auxiliary nodes into your `custom_nodes` folder:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    cp -r comfystream/custom_nodes/* /path/to/ComfyUI/custom_nodes/
    ```

    Existing custom nodes in your workspace are reused automatically when processing the video stream.
  </StyledStep>

  <StyledStep title="Download models" icon="cloud-download">
    Use the provided download script to fetch commonly used models:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    bash scripts/download_models.sh
    ```

    For StreamDiffusion workflows, the script downloads the LCM-LoRA weights and a base checkpoint. Refer to `scripts/README.md` for the full model list and per-workflow requirements.
  </StyledStep>
</StyledSteps>

<CustomDivider />

## Workflow Structure

A streaming workflow differs from a standard batch workflow in one respect: it must accept the current video frame as input and return the processed frame as output on each forward pass.

In ComfyUI, build a workflow that includes:

* A **Load Image** node (or equivalent) as the entry point for the incoming frame
* Your processing chain (samplers, ControlNet, LoRA, etc.)
* A **Save Image** or **Preview Image** node as the terminal output node

When ComfyStream runs this workflow, it intercepts the Load Image node to inject each decoded frame and captures the output before it writes to disk. The rest of the workflow is unchanged.

Export the workflow in API format from ComfyUI: **Workflow > Export (API Format)**. This produces a JSON file that ComfyStream loads at startup.

<CustomDivider />

## Running ComfyStream

Start the ComfyStream server pointing at your ComfyUI workspace and the workflow JSON:

```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
python server/app.py \
  --workspace /path/to/ComfyUI \
  --workflow /path/to/your_workflow.json \
  --port 8188
```

The server exposes a WebRTC signalling endpoint and a browser UI. Open the UI in a browser, select your webcam or paste an RTMP stream URL, and click **Connect**. The server negotiates a WebRTC connection, begins decoding incoming frames, runs each frame through the workflow, and streams the output back to the browser.

For remote server deployments, allow inbound and outbound UDP traffic on ports 1024-65535 for WebRTC ICE negotiation, or use `--media-ports` to restrict to a specific range:

```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
python server/app.py --workspace /path/to/ComfyUI --media-ports 10000,10001,10002
```

<CustomDivider />

## Gateway Integration

To route a ComfyStream instance through the Livepeer Network, the Orchestrator connects to the ComfyStream service over the trickle protocol. The Orchestrator discovers the service address and forwards jobs from Gateway clients to it.

See <LinkArrow href="/v2/developers/build/ai-and-agents/realtime-ai/comfystream/comfystream-as-byoc" label="ComfyStream as BYOC" newline={false} /> for how to register a ComfyStream instance as a BYOC container with a Livepeer Orchestrator so it appears as a routable `live-video-to-video` capability on the network.

<CustomDivider />

## Related Pages

The workflow is ready for live use. Register it as a BYOC container to make it available on the network via [ComfyStream as BYOC](/v2/developers/build/ai-and-agents/realtime-ai/ComfyStream/ComfyStream-as-byoc).

<CardGroup cols={2}>
  <Card title="ComfyStream as BYOC" icon="box" href="/v2/developers/build/ai-and-agents/realtime-ai/comfystream/comfystream-as-byoc" arrow horizontal>
    Registering a ComfyStream instance as a BYOC capability on a Livepeer Orchestrator.
  </Card>

  <Card title="Real-Time AI Overview" icon="video" href="/v2/developers/build/ai-and-agents/realtime-ai/overview" arrow horizontal>
    Cascade architecture and how trickle protocol connects the pipeline components.
  </Card>

  <Card title="PyTrickle Overview" icon="code" href="/v2/developers/build/ai-and-agents/realtime-ai/pytrickle/overview" arrow horizontal>
    Python SDK for custom real-time processing if you need logic beyond ComfyUI workflows.
  </Card>

  <Card title="BYOC Overview" icon="box" href="/v2/developers/build/compute/byoc/overview" arrow horizontal>
    Container interface and registration steps for all BYOC pipeline types.
  </Card>
</CardGroup>
