Skip to content

Custom ffmpeg pipelines

Some things Frigate and go2rtc can’t do natively — burning a timestamp into the picture, rotating a ceiling-mounted camera on the GPU, splitting a dual-fisheye sensor into two views — you can do by defining a go2rtc exec: stream: a custom ffmpeg command that produces an RTSP feed Frigate then consumes like any other camera. Fregata ships a full-featured ffmpeg with drawtext, the VideoToolbox filters (transpose_vt, scale_vt), and the VideoToolbox encoders, so these pipelines run hardware-accelerated on Apple Silicon.

exec: sources run arbitrary local commands, so Fregata disables them by default. You have to opt in — see Custom go2rtc stream sources for the full security rationale. In short:

  1. Set GO2RTC_ALLOW_ARBITRARY_EXEC=true in Settings → Environment Variables (menu-bar tray).
  2. Restart Fregata — environment-variable changes take effect only when Frigate starts.

Without this, the streams below are dropped from the generated go2rtc config and a line naming them is written to the Frigate log.

One rule governs every recipe here: stream values are expanded with Python’s str.format() before go2rtc sees them (so you can drop in {FRIGATE_*} variables), which means every literal { or } must be doubled. That is why the RTSP output placeholder is written {{output}} (it collapses to the {output} token go2rtc fills in) and why an ffmpeg expansion such as drawtext’s %{localtime} is written %{{localtime}}. See The {{output}} token and brace-doubling.

The general shape of every recipe has two parts. First, define the exec: stream:

config.yml only. The go2rtc streams page in Settings refuses exec:, echo:, and expr: sources, so add them in config.yml (in any text editor, or the web UI's Configuration Editor).

go2rtc:
streams:
my_stream:
- "exec:ffmpeg <input flags> -i <source> <filters/encode> -rtsp_transport tcp -f rtsp {{output}}"

go2rtc only reads go2rtc.streams when it starts, so restart Fregata (Restart Frigate in the tray) before going on. Then point the Frigate camera at the restream instead of the raw camera:

  1. To add a new camera, go to Settings → Global configuration → Camera management and click Add New Camera. Choose Manual selection and the brand Other, and enter rtsp://127.0.0.1:8554/my_stream as the Custom Stream URL. In the stream configuration step, turn off Reduce connections to camera for this stream: it already is a go2rtc restream.
  2. For a camera that already exists, go to Settings → Camera configuration → Streams (FFmpeg), pick the camera, and set the path of its entry under Camera inputs to rtsp://127.0.0.1:8554/my_stream, with the Detect and Record roles switched on.
  3. Click Save.

Store camera passwords as FRIGATE_* environment variables (e.g. {FRIGATE_CAM_PW} below) rather than in plaintext — see Environment variables.

Burn a live clock into the top-left corner. %{localtime} must be written %{{localtime}} so it survives the str.format pass and reaches ffmpeg intact. Add the stream under go2rtc.streams in config.yml:

go2rtc:
streams:
front_osd:
- "exec:ffmpeg -rtsp_transport tcp -i rtsp://user:{FRIGATE_CAM_PW}@10.0.0.5/main -vf drawtext=fontfile=/System/Library/Fonts/Supplemental/Arial.ttf:text='%{{localtime}}':x=12:y=12:fontsize=36:fontcolor=white:box=1:[email protected] -c:v h264_videotoolbox -b:v 4000k -rtsp_transport tcp -f rtsp {{output}}"

Adjust the fontfile= path to any .ttf on your Mac. Anything after drawtext= is a normal ffmpeg filter — see the ffmpeg drawtext docs for %{{localtime}} format strings, positioning, and multi-line text.

go2rtc’s built-in #rotate is CPU-only, so a rotated stream burns a core per camera. VideoToolbox’s transpose_vt filter does the rotation on the GPU with a zero-copy path — decode, rotate, and encode all stay on the hardware. Add the stream under go2rtc.streams in config.yml:

go2rtc:
streams:
garage_rotated:
- "exec:ffmpeg -hwaccel videotoolbox -hwaccel_output_format videotoolbox_vld -rtsp_transport tcp -i rtsp://user:{FRIGATE_CAM_PW}@10.0.0.6/main -vf transpose_vt=dir=clock -c:v h264_videotoolbox -b:v 4000k -rtsp_transport tcp -f rtsp {{output}}"
  • -hwaccel videotoolbox -hwaccel_output_format videotoolbox_vld keeps the decoded frames on the GPU so transpose_vt and the encoder operate on them without a round-trip to system memory.
  • transpose_vt=dir=clock rotates 90° clockwise. Use dir=cclock for counter-clockwise; chain two for 180° (transpose_vt=dir=clock,transpose_vt=dir=clock).
  • -b:v 4000k is not optional — the VideoToolbox encoders need an explicit bitrate. Size it to the camera’s resolution.
  • Don’t add low-latency encoder flags here; they aren’t needed for a restream and can hurt compatibility.

Then set the Frigate camera’s input to rtsp://127.0.0.1:8554/garage_rotated, as in the shape above.

A 180°/360° camera that publishes two fisheye circles in one frame can be split into two independent Frigate cameras by cropping each half in its own exec: stream. Add both under go2rtc.streams in config.yml:

go2rtc:
streams:
porch_left:
- "exec:ffmpeg -hwaccel videotoolbox -rtsp_transport tcp -i rtsp://user:{FRIGATE_CAM_PW}@10.0.0.7/main -vf crop=iw/2:ih:0:0 -c:v h264_videotoolbox -b:v 3000k -rtsp_transport tcp -f rtsp {{output}}"
porch_right:
- "exec:ffmpeg -hwaccel videotoolbox -rtsp_transport tcp -i rtsp://user:{FRIGATE_CAM_PW}@10.0.0.7/main -vf crop=iw/2:ih:iw/2:0 -c:v h264_videotoolbox -b:v 3000k -rtsp_transport tcp -f rtsp {{output}}"

crop=iw/2:ih:0:0 takes the left half; crop=iw/2:ih:iw/2:0 takes the right. Each restream becomes its own camera in config.yml.

  • The stream never appears. Check the Frigate log (tray → Settings → Open Frigate Logs). A restricted-source or brace-doubling problem is logged with the stream name and the reason — a dropped stream is never silent. The most common cause is an un-doubled brace ({output} instead of {{output}}, or %{localtime} instead of %{{localtime}}).
  • Confirm the raw pipeline first. Before wiring it into config.yml, run the un-escaped ffmpeg command directly in a terminal against your camera to confirm the filter chain and encoder work, then add the str.format brace-doubling.
  • Re-encoding has a cost. Every recipe here decodes and re-encodes, which uses a VideoToolbox session and some GPU time per camera. That’s fine for a handful of streams; if you’re rotating or overlaying many cameras, watch the numbers on the Performance page.

For the underlying restream configuration keys (names, sub-streams, #backchannel), Fregata follows upstream Frigate — see the Frigate restream reference.