Skip to content
Fregata Docsv0.18.x (Beta)0.18

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:

go2rtc:
streams:
my_stream:
- "exec:ffmpeg <input flags> -i <source> <filters/encode> -rtsp_transport tcp -f rtsp {{output}}"
cameras:
my_camera:
ffmpeg:
inputs:
# Point Frigate at the restream, not the raw camera
- path: rtsp://127.0.0.1:8554/my_stream
roles: [detect, record]

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:

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:boxcolor=black@0.5 -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:

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:

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.