> ## 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.

# PyTrickle

> PyTrickle is the Python SDK for building real-time video processing services over the trickle protocol, using the FrameProcessor abstraction.

export const TableCell = ({children, align = "left", header = false, style = {}, className = "", ...rest}) => {
  const Component = header ? "th" : "td";
  return <Component className={className} style={{
    padding: "0.75rem 1rem",
    textAlign: align,
    border: header ? "none" : "1px solid var(--lp-color-border-default)",
    ...style
  }} {...rest}>
      {children}
    </Component>;
};

export const TableRow = ({children, header = false, hover = false, style = {}, className = "", ...rest}) => {
  const rowId = `table-row-${Math.random().toString(36).substr(2, 9)}`;
  return <>
      {hover && <style>{`
          #${rowId}:hover {
            background-color: var(--lp-color-bg-card);
          }
        `}</style>}
      <tr id={rowId} className={className} style={{
    ...header && ({
      backgroundColor: "var(--lp-color-accent-strong)",
      color: "var(--lp-color-on-accent)",
      fontWeight: "bold"
    }),
    ...style
  }} {...rest}>
        {children}
      </tr>
    </>;
};

export const StyledTable = ({children, variant = "default", style = {}, className = "", ...rest}) => {
  const wrapperVariants = {
    default: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "var(--lp-color-bg-card)",
      overflow: "hidden"
    },
    bordered: {
      border: "2px solid var(--lp-color-accent)",
      backgroundColor: "var(--lp-color-bg-page)",
      overflow: "hidden"
    },
    minimal: {
      border: "none",
      backgroundColor: "transparent",
      overflow: "visible"
    }
  };
  return <div data-docs-styled-table-shell className={className} style={{
    width: "100%",
    padding: 0,
    margin: 0,
    ...wrapperVariants[variant],
    ...style
  }} {...rest}>
      <table data-docs-styled-table style={{
    width: "100%",
    borderCollapse: "collapse",
    borderSpacing: 0,
    margin: 0,
    backgroundColor: "transparent"
  }}>
        {children}
      </table>
    </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>PyTrickle handles stream ingestion, frame decoding, and video encoding. You implement one method: process\_video\_async. The SDK delivers frames as PyTorch tensors and encodes your output back into the stream.</Tip>
</CenteredContainer>

***

PyTrickle (`livepeer/pytrickle`) is a Python package for building custom real-time video processing services over the trickle protocol. It sits one level below ComfyStream: where ComfyStream runs ComfyUI workflows, PyTrickle provides the `FrameProcessor` abstraction for arbitrary Python processing logic backed by asyncio.

The package handles the trickle protocol connection, video decoding via FFmpeg, frame delivery as PyTorch tensors, and video encoding of processed output. A PyTrickle service exposes a `StreamServer` HTTP API that Livepeer Orchestrators connect to when routing `live-video-to-video` jobs.

<CustomDivider />

## FrameProcessor

`FrameProcessor` is the base class for all PyTrickle processing services. Subclass it and implement `process_video_async` to define how each frame is transformed.

```python theme={"theme":{"light":"github-light","dark":"dark-plus"}}
from pytrickle import FrameProcessor, StreamServer
from pytrickle.frames import VideoFrame, AudioFrame
from typing import Optional, List

class MyProcessor(FrameProcessor):

    async def initialize(self):
        # Load AI models or initialise any resources here.
        # Called once before the first frame arrives.
        pass

    async def process_video_async(self, frame: VideoFrame) -> Optional[VideoFrame]:
        # frame.tensor is a PyTorch tensor: [H, W, C], float32, 0-1 range
        tensor = frame.tensor.clone()
        # Apply your model, filter, or transform here.
        return frame.replace_tensor(tensor)

    async def process_audio_async(self, frame: AudioFrame) -> Optional[List[AudioFrame]]:
        # Return the frame unchanged or process it.
        return [frame]

    def update_params(self, params: dict):
        # Called when the orchestrator sends a parameter update.
        # Update instance variables to change behaviour at runtime.
        pass
```

`VideoFrame.tensor` is a CPU PyTorch tensor in HWC format with float32 values in \[0, 1]. `frame.replace_tensor(new_tensor)` creates a new `VideoFrame` with the updated pixel data while preserving frame metadata.

## StreamServer

`StreamServer` wraps a `FrameProcessor` and exposes the HTTP endpoint that Orchestrators connect to:

```python theme={"theme":{"light":"github-light","dark":"dark-plus"}}
async def main():
    processor = MyProcessor()
    await processor.start()  # calls initialize()

    app = StreamServer(
        frame_processor=processor,
        port=8000,
        capability_name="my-processor",
    )
    await app.run_forever()

import asyncio
asyncio.run(main())
```

The `StreamServer` accepts trickle stream connections and runs the processor loop. The HTTP API includes endpoints for starting and stopping streams, querying status, and receiving parameter updates.

<CustomDivider />

## SDK Responsibilities

<StyledTable variant="bordered">
  <thead>
    <TableRow header>
      <TableCell header>Concern</TableCell>
      <TableCell header>PyTrickle responsibility</TableCell>
      <TableCell header>Developer responsibility</TableCell>
    </TableRow>
  </thead>

  <tbody>
    <TableRow>
      <TableCell>Trickle protocol connection</TableCell>
      <TableCell>Full: subscribe and publish segments</TableCell>
      <TableCell>None</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Video decoding</TableCell>
      <TableCell>Full: FFmpeg-backed decoder, tensor conversion</TableCell>
      <TableCell>None</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Frame delivery</TableCell>
      <TableCell>Full: async queue, backpressure, reconnect</TableCell>
      <TableCell>None</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Frame processing</TableCell>
      <TableCell>Calls `process_video_async` per frame</TableCell>
      <TableCell>Implement processing logic</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Audio handling</TableCell>
      <TableCell>Detects mono/stereo/multi-channel, auto-converts</TableCell>
      <TableCell>Implement `process_audio_async` or pass through</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Video encoding</TableCell>
      <TableCell>Full: encodes processed tensor back to stream</TableCell>
      <TableCell>None</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Parameter updates</TableCell>
      <TableCell>Receives and calls `update_params`</TableCell>
      <TableCell>Implement `update_params`</TableCell>
    </TableRow>

    <TableRow>
      <TableCell>Reconnection</TableCell>
      <TableCell>Automatic on trickle subscriber disconnect</TableCell>
      <TableCell>None</TableCell>
    </TableRow>
  </tbody>
</StyledTable>

<CustomDivider />

## Prerequisites

* Python 3.8+
* PyTorch (install separately; version must match your CUDA version)
* FFmpeg available on PATH
* `http-trickle` binary for local testing: `git clone https://github.com/livepeer/http-trickle.git && cd http-trickle && make build`

<CustomDivider />

## Related Pages

Use the [PyTrickle quickstart](/v2/developers/build/ai-and-agents/realtime-ai/pytrickle/pytrickle-quickstart) for a working service in under 20 minutes.

<CardGroup cols={2}>
  <Card title="PyTrickle Quickstart" icon="bolt" href="/v2/developers/build/ai-and-agents/realtime-ai/pytrickle/pytrickle-quickstart" arrow horizontal>
    Install PyTrickle, write a minimal FrameProcessor, and run it against a test stream.
  </Card>

  <Card title="Frame Processor Reference" icon="code" href="/v2/developers/build/ai-and-agents/realtime-ai/pytrickle/frame-processor" arrow horizontal>
    Full FrameProcessor API: VideoFrame, AudioFrame, update\_params, and StreamServer options.
  </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 PyTrickle services fit into the Livepeer pipeline.
  </Card>

  <Card title="ComfyStream Workflow Authoring" icon="workflow" href="/v2/developers/build/ai-and-agents/realtime-ai/comfystream/workflow-authoring" arrow horizontal>
    ComfyUI-based alternative for real-time AI pipelines without custom Python code.
  </Card>
</CardGroup>
