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 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:
- 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_streamas the Custom Stream URL. In the stream configuration step, turn off Reduce connections to camera for this stream: it already is a go2rtc restream. - 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. - Click Save.
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. 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.
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. 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_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. 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.
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.