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

> Install PyTrickle, write a minimal FrameProcessor, and run it against a local test stream using http-trickle.

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>By the end of this tutorial you will have a PyTrickle service running locally, processing video frames from a test stream, and returning a modified output stream you can verify with FFplay.</Tip>
</CenteredContainer>

***

**Prerequisites:**

* Python 3.8+ with pip
* PyTorch installed (CPU build works for this tutorial)
* FFmpeg on PATH
* Go installed (for the http-trickle test tool)

<CustomDivider />

<StyledSteps iconColor="#2d9a67" titleColor="var(--accent)">
  <StyledStep title="Install PyTrickle" icon="download">
    Clone the repository and install in editable mode:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    git clone https://github.com/livepeer/pytrickle.git
    cd pytrickle
    pip install -r requirements.txt
    pip install -e .
    ```

    Verify installation:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    python -c "from pytrickle import FrameProcessor, StreamServer; print('OK')"
    ```
  </StyledStep>

  <StyledStep title="Install http-trickle" icon="tool">
    The `http-trickle` binary provides a local trickle server for testing without a Livepeer Orchestrator:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    git clone https://github.com/livepeer/http-trickle.git ~/repos/http-trickle
    cd ~/repos/http-trickle
    make build
    ```

    After building, `http-trickle` is available at `~/repos/http-trickle/http-trickle`.
  </StyledStep>

  <StyledStep title="Write a FrameProcessor" icon="code">
    Create `my_processor.py` with a processor that applies a green tint to each frame:

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

    class GreenTintProcessor(FrameProcessor):

        async def initialize(self):
            self.intensity = 0.3
            print("Processor ready")

        async def process_video_async(self, frame: VideoFrame) -> Optional[VideoFrame]:
            tensor = frame.tensor.clone()
            # Boost the green channel
            tensor[:, :, 1] = (tensor[:, :, 1] + self.intensity).clamp(0, 1)
            return frame.replace_tensor(tensor)

        async def process_audio_async(self, frame: AudioFrame) -> Optional[List[AudioFrame]]:
            return [frame]

        def update_params(self, params: dict):
            if "intensity" in params:
                self.intensity = float(params["intensity"])
                print(f"Updated intensity to {self.intensity}")

    async def main():
        processor = GreenTintProcessor()
        await processor.start()
        app = StreamServer(
            frame_processor=processor,
            port=8000,
            capability_name="green-tint",
        )
        print("PyTrickle service running on port 8000")
        await app.run_forever()

    asyncio.run(main())
    ```
  </StyledStep>

  <StyledStep title="Start the service" icon="play">
    Run your processor:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    python my_processor.py
    ```

    Expected output:

    ```
    Processor ready
    PyTrickle service running on port 8000
    ```
  </StyledStep>

  <StyledStep title="Start a test stream" icon="video">
    In a second terminal, start the http-trickle relay and send a test stream to it using FFmpeg. Use a test video or a webcam input:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    # Terminal 2: start http-trickle relay on port 3389
    ~/repos/http-trickle/http-trickle --addr 127.0.0.1:3389

    # Terminal 3: push a test stream to the relay
    ffmpeg -re -f lavfi -i testsrc=size=512x512:rate=30 \
      -f lavfi -i sine=frequency=440 \
      -vcodec libx264 -acodec aac \
      -f trickle http://127.0.0.1:3389/input
    ```
  </StyledStep>

  <StyledStep title="Start a processing session" icon="bolt">
    Send an HTTP request to the PyTrickle service to begin processing:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    curl -X POST http://localhost:8000/api/stream/start \
      -H "Content-Type: application/json" \
      -d '{
        "subscribe_url": "http://127.0.0.1:3389/input",
        "publish_url": "http://127.0.0.1:3389/output",
        "gateway_request_id": "test-1",
        "params": {
          "width": 512,
          "height": 512,
          "max_framerate": 30
        }
      }'
    ```

    The service subscribes to `/input`, processes each frame through `GreenTintProcessor`, and publishes transformed frames to `/output`.
  </StyledStep>

  <StyledStep title="Verify the output" icon="check">
    In a fourth terminal, read the processed output stream with FFplay:

    ```bash theme={"theme":{"light":"github-light","dark":"dark-plus"}}
    cd ~/repos/http-trickle && \
    go run cmd/read2pipe/*.go --url http://127.0.0.1:3389/ --stream output | ffplay -
    ```

    You should see the test video with a visible green tint applied to each frame. The PyTrickle service logs processed frame counts to stdout.
  </StyledStep>
</StyledSteps>

<CustomDivider />

Replace the green tint logic in `process_video_async` with your own processing: load a PyTorch model in `initialize`, run inference in `process_video_async`, and return the output tensor. See <LinkArrow href="/v2/developers/build/ai-and-agents/realtime-ai/pytrickle/frame-processor" label="Frame Processor Reference" newline={false} /> for the full API.

To connect this service to the Livepeer Network, register it as a BYOC container following the same pattern as <LinkArrow href="/v2/developers/build/ai-and-agents/realtime-ai/comfystream/comfystream-as-byoc" label="ComfyStream as BYOC" newline={false} />.

<CustomDivider />

## Related Pages

You have a working PyTrickle service processing live frames. The [FrameProcessor reference](/v2/developers/build/ai-and-agents/realtime-ai/pytrickle/frame-processor) covers the full API for VideoFrame, AudioFrame, and session management.

<CardGroup cols={2}>
  <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="PyTrickle Overview" icon="grid" href="/v2/developers/build/ai-and-agents/realtime-ai/pytrickle/overview" arrow horizontal>
    Architecture, capabilities table, and prerequisites.
  </Card>

  <Card title="ComfyStream as BYOC" icon="box" href="/v2/developers/build/ai-and-agents/realtime-ai/comfystream/comfystream-as-byoc" arrow horizontal>
    How to register a real-time processing service with 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 the full request flow.
  </Card>
</CardGroup>
