{ lib, pkgs, config, ... }: let cfg = config.mj.services.frigate; proberScript = pkgs.writeShellApplication { name = "go2rtc-prober"; runtimeInputs = with pkgs; [ systemd ffmpeg ]; text = '' set -x while true; do FAILED=0 for input in vno4-dome-{panorama,ptz}-{orig,high,low}; do timeout 30s \ ffprobe -hide_banner "rtsp://localhost:8554/''${input}" || FAILED=1 done [[ "$FAILED" == 1 ]] && systemctl restart --no-block go2rtc.service sleep 5m done ''; }; timelapseScript = pkgs.writeShellApplication { name = "timelapse-r11"; runtimeInputs = with pkgs; [ ffmpeg ]; text = '' set -x NOW=$(date +%F_%T) DATE=''${NOW%_*} TIME=''${NOW#*_} mkdir -p /var/lib/timelapse-r11/"''${DATE}" EXITCODE=0 ffmpeg -hide_banner -y \ -rtsp_transport tcp \ -i "rtsp://frigate:''${FRIGATE_RTSP_PASSWORD}@192.168.188.10/cam/realmonitor?channel=2&subtype=0" \ -vframes 1 \ /var/lib/timelapse-r11/"''${DATE}"/"ptz-''${TIME}.jpg" || EXITCODE=$? ffmpeg -hide_banner -y \ -rtsp_transport tcp \ -i "rtsp://frigate:''${FRIGATE_RTSP_PASSWORD}@192.168.188.10/cam/realmonitor?channel=1&subtype=0" \ -vframes 1 \ /var/lib/timelapse-r11/"''${DATE}"/"panorama-''${TIME}.jpg" || EXITCODE=$? exit "$EXITCODE" ''; }; in { options.mj.services.frigate = with lib.types; { enable = lib.mkEnableOption "enable frigate"; secretsEnv = lib.mkOption { type = path; }; }; config = lib.mkIf cfg.enable { mj.base.unitstatus.units = [ "timelapse-r11" "go2rtc" "frigate" ]; systemd.timers.timelapse-r11 = { timerConfig.OnCalendar = "*-*-* 7..19:00/5 Europe/Vilnius"; wantedBy = [ "timers.target" ]; }; systemd.services = { go2rtc-prober = { wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = lib.getExe proberScript; RestartSec = 300; Restart = "always"; }; }; timelapse-r11 = { preStart = "ln -sf $CREDENTIALS_DIRECTORY/secrets.env /run/timelapse-r11/secrets.env"; serviceConfig = { ExecStart = lib.getExe timelapseScript; EnvironmentFile = [ "-/run/timelapse-r11/secrets.env" ]; LoadCredential = [ "secrets.env:${cfg.secretsEnv}" ]; RuntimeDirectory = "timelapse-r11"; StateDirectory = "timelapse-r11"; DynamicUser = true; }; }; go2rtc = { preStart = "ln -sf $CREDENTIALS_DIRECTORY/secrets.env /run/go2rtc/secrets.env"; serviceConfig = { EnvironmentFile = [ "-/run/go2rtc/secrets.env" ]; RuntimeDirectory = "go2rtc"; LoadCredential = [ "secrets.env:${cfg.secretsEnv}" ]; }; }; }; services.go2rtc = { enable = true; settings = { # https://github.com/AlexxIT/go2rtc/issues/831 #log = { # format = "text"; # level = "trace"; #}; streams = { "vno4-dome-panorama-orig" = [ "ffmpeg:rtsp://frigate:\${FRIGATE_RTSP_PASSWORD}@192.168.188.10/cam/realmonitor?channel=1&subtype=2" ]; "vno4-dome-panorama-high" = [ "ffmpeg:rtsp://localhost:8554/vno4-dome-panorama-orig#hardware=vaapi#video=h264" ]; "vno4-dome-panorama-low" = [ "ffmpeg:rtsp://localhost:8554/vno4-dome-panorama-orig#hardware=vaapi#video=h264#width=1280" ]; "vno4-dome-ptz-orig" = [ "ffmpeg:rtsp://frigate:\${FRIGATE_RTSP_PASSWORD}@192.168.188.10/cam/realmonitor?channel=2&subtype=0#video=copy#audio=copy" ]; "vno4-dome-ptz-high" = [ "ffmpeg:rtsp://localhost:8554/vno4-dome-ptz-orig#hardware=vaapi#video=h264#audio=copy" ]; "vno4-dome-ptz-low" = [ "ffmpeg:rtsp://localhost:8554/vno4-dome-ptz-orig#hardware=vaapi#video=h264#width=1280#audio=copy" ]; }; }; }; services.frigate = { enable = true; hostname = "r1.jakstys.lt"; settings = { ffmpeg.hwaccel_args = "preset-vaapi"; mqtt = { enabled = true; host = "::"; }; detect = { enabled = true; fps = 5; }; detectors = { coral = { type = "edgetpu"; device = "usb"; enabled = true; }; }; record = { enabled = true; retain = { days = 7; mode = "all"; }; }; cameras = { vno4-dome-panorama = let masks = [ "0.308,0.235,0.305,1,0,1,0,0,0.587,0.005" "0.766,1,0.867,0,1,0,1,1" ]; in { enabled = true; motion.mask = masks; objects.mask = masks; ffmpeg = { output_args = { record = "preset-record-generic"; }; inputs = [ { path = "rtsp://localhost:8554/vno4-dome-panorama-high"; roles = [ "record" ]; } { path = "rtsp://localhost:8554/vno4-dome-panorama-low"; roles = [ "detect" ]; } ]; }; }; vno4-dome-ptz = let masks = [ "0,0.115,1,0.115,1,0,0,0" ]; in { enabled = true; motion.mask = masks; objects.mask = masks; ffmpeg = { output_args = { record = "preset-record-generic-audio-copy"; }; inputs = [ { path = "rtsp://localhost:8554/vno4-dome-ptz-high"; roles = [ "record" "audio" ]; } { path = "rtsp://localhost:8554/vno4-dome-ptz-low"; roles = [ "detect" ]; } ]; }; }; }; }; }; }; }