Pipe Python RGB Frames into FFmpeg on macOS

This article explains how to efficiently pipe raw RGB video frames generated in a Python script directly into FFmpeg on macOS to create a video file. By using Python’s built-in subprocess module, you can stream raw byte data from your script straight to FFmpeg’s standard input, eliminating the need to save temporary image files to your disk.

Prerequisites

To get started on macOS, you must have FFmpeg installed. The easiest way to install it is via Homebrew. Open your terminal and run:

brew install ffmpeg

You will also need numpy to generate sample frame data. You can install it using pip:

pip install numpy

The Python Implementation

The following Python script generates a sequence of colored frames and pipes them directly into FFmpeg to output an MP4 video.

import subprocess
import numpy as np

# Define video properties
width = 1920
height = 1080
fps = 30
duration_seconds = 5
total_frames = fps * duration_seconds

# Configure the FFmpeg command
# -f rawvideo: Tells FFmpeg the input is raw video data
# -pix_fmt rgb24: Specifies that each pixel has 3 bytes (Red, Green, Blue)
# -s: Sets the frame resolution
# -r: Sets the input frame rate
# -i -: Tells FFmpeg to read the input from stdin (the pipe)
ffmpeg_cmd = [
    'ffmpeg',
    '-y',  # Overwrite output file if it exists
    '-f', 'rawvideo',
    '-pix_fmt', 'rgb24',
    '-s', f'{width}x{height}',
    '-r', str(fps),
    '-i', '-',  # Read from stdin
    '-c:v', 'libx264',  # Encode using H.264
    '-pix_fmt', 'yuv420p',  # Ensure compatibility with macOS QuickTime Player
    'output.mp4'
]

# Start the FFmpeg process with stdin mapped to a pipe
process = subprocess.Popen(ffmpeg_cmd, stdin=subprocess.PIPE)

print("Piping frames to FFmpeg...")

for frame_idx in range(total_frames):
    # Create a dummy RGB frame (moving color gradient)
    blue_val = int(255 * (frame_idx / total_frames))
    frame = np.zeros((height, width, 3), dtype=np.uint8)
    frame[:, :, 0] = 100  # Red channel
    frame[:, :, 1] = 150  # Green channel
    frame[:, :, 2] = blue_val  # Animating Blue channel

    # Write the raw bytes of the frame directly to FFmpeg's stdin
    process.stdin.write(frame.tobytes())

# Close the stdin pipe and wait for FFmpeg to finish encoding
process.stdin.close()
process.wait()

print("Video encoding complete: output.mp4")

How It Works

  1. FFmpeg Configuration: The -i - parameter directs FFmpeg to accept input from standard input (stdin). Specifying -f rawvideo and -pix_fmt rgb24 is crucial because raw byte streams do not contain header information, so FFmpeg must be explicitly told how to interpret the incoming bytes.
  2. Subprocess Pipe: subprocess.Popen launches FFmpeg as a background process and opens a write-only pipe (stdin=subprocess.PIPE) linked to it.
  3. Writing Bytes: The numpy array frame.tobytes() converts the 3D RGB array into a flat sequence of bytes. Since the format is rgb24, FFmpeg expects exactly width * height * 3 bytes per frame.
  4. Closing the Stream: Once all frames are written, process.stdin.close() signals to FFmpeg that the video stream has ended, allowing FFmpeg to finalize and write the container file (output.mp4).