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.
Before you start
Section titled “Before you start”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:
- Set
GO2RTC_ALLOW_ARBITRARY_EXEC=truein Settings → Environment Variables (menu-bar tray). - 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.
Timestamp and text overlays (drawtext)
Section titled “Timestamp and text overlays (drawtext)”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:[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.
GPU-accelerated rotation (VideoToolbox)
Section titled “GPU-accelerated rotation (VideoToolbox)”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_vldkeeps the decoded frames on the GPU sotranspose_vtand the encoder operate on them without a round-trip to system memory.transpose_vt=dir=clockrotates 90° clockwise. Usedir=cclockfor counter-clockwise; chain two for 180° (transpose_vt=dir=clock,transpose_vt=dir=clock).-b:v 4000kis 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.
Splitting a dual-fisheye camera
Section titled “Splitting a dual-fisheye camera”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.
Verifying and debugging
Section titled “Verifying and debugging”- 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-escapedffmpegcommand directly in a terminal against your camera to confirm the filter chain and encoder work, then add thestr.formatbrace-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.