mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-13 02:02:48 +03:00
CI / AMD64 Build (push) Waiting to run
CI / AMD64 Smoke Test (push) Blocked by required conditions
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
go2rtc's create_config.py imported frigate.config.env, which ran the frigate.config package init and loaded every detector plugin. Since the onnxruntime 1.30 bump, importing onnxruntime takes about 5s on some hosts, so go2rtc started after Frigate's cameras and every ffmpeg process failed to connect to the restream until it came up. The env helpers now live in frigate.util.env, so go2rtc's config script no longer imports onnxruntime.
207 lines
7.9 KiB
Python
207 lines
7.9 KiB
Python
"""Creates a go2rtc config file."""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from typing import Any
|
|
|
|
from ruamel.yaml import YAML
|
|
|
|
sys.path.insert(0, "/opt/frigate")
|
|
from frigate.const import (
|
|
BIRDSEYE_PIPE,
|
|
LIBAVFORMAT_VERSION_MAJOR,
|
|
)
|
|
from frigate.ffmpeg_presets import parse_preset_hardware_acceleration_encode
|
|
from frigate.util.config import find_config_file, resolve_ffmpeg_path
|
|
from frigate.util.env import apply_config_env_vars, substitute_frigate_vars
|
|
from frigate.util.live_streams import raw_transcode_streams
|
|
from frigate.util.services import (
|
|
is_go2rtc_arbitrary_exec_allowed,
|
|
is_restricted_go2rtc_source,
|
|
)
|
|
|
|
sys.path.remove("/opt/frigate")
|
|
|
|
yaml = YAML()
|
|
|
|
config_file = find_config_file()
|
|
|
|
try:
|
|
with open(config_file) as f:
|
|
raw_config = f.read()
|
|
|
|
if config_file.endswith((".yaml", ".yml")):
|
|
config: dict[str, Any] = yaml.load(raw_config)
|
|
elif config_file.endswith(".json"):
|
|
config: dict[str, Any] = json.loads(raw_config)
|
|
except FileNotFoundError:
|
|
config: dict[str, Any] = {}
|
|
|
|
# No validator runs here, so install environment_vars ourselves. FRIGATE_
|
|
# names only: anything else lands in os.environ, where the exec gate reads
|
|
# GO2RTC_ALLOW_ARBITRARY_EXEC.
|
|
config_env_vars = config.get("environment_vars")
|
|
apply_config_env_vars(
|
|
{
|
|
key: value
|
|
for key, value in config_env_vars.items()
|
|
if str(key).startswith("FRIGATE_")
|
|
}
|
|
if isinstance(config_env_vars, dict)
|
|
else {}
|
|
)
|
|
|
|
go2rtc_config: dict[str, Any] = config.get("go2rtc", {})
|
|
|
|
# Need to enable CORS for go2rtc so the frigate integration / card work automatically
|
|
if go2rtc_config.get("api") is None:
|
|
go2rtc_config["api"] = {"origin": "*"}
|
|
elif go2rtc_config["api"].get("origin") is None:
|
|
go2rtc_config["api"]["origin"] = "*"
|
|
|
|
# Need to set default location for HA config
|
|
if go2rtc_config.get("hass") is None:
|
|
go2rtc_config["hass"] = {"config": "/homeassistant"}
|
|
|
|
# we want to ensure that logs are easy to read
|
|
if go2rtc_config.get("log") is None:
|
|
go2rtc_config["log"] = {"format": "text"}
|
|
elif go2rtc_config["log"].get("format") is None:
|
|
go2rtc_config["log"]["format"] = "text"
|
|
|
|
# ensure there is a default webrtc config
|
|
if go2rtc_config.get("webrtc") is None:
|
|
go2rtc_config["webrtc"] = {}
|
|
|
|
if go2rtc_config["webrtc"].get("candidates") is None:
|
|
default_candidates = []
|
|
# use internal candidate if it was discovered when running through the add-on
|
|
internal_candidate = os.environ.get("FRIGATE_GO2RTC_WEBRTC_CANDIDATE_INTERNAL")
|
|
if internal_candidate is not None:
|
|
default_candidates.append(internal_candidate)
|
|
# should set default stun server so webrtc can work
|
|
default_candidates.append("stun:8555")
|
|
|
|
go2rtc_config["webrtc"]["candidates"] = default_candidates
|
|
|
|
if go2rtc_config.get("rtsp", {}).get("username") is not None:
|
|
go2rtc_config["rtsp"]["username"] = substitute_frigate_vars(
|
|
go2rtc_config["rtsp"]["username"]
|
|
)
|
|
|
|
if go2rtc_config.get("rtsp", {}).get("password") is not None:
|
|
go2rtc_config["rtsp"]["password"] = substitute_frigate_vars(
|
|
go2rtc_config["rtsp"]["password"]
|
|
)
|
|
|
|
# ensure ffmpeg path is set correctly
|
|
path = config.get("ffmpeg", {}).get("path", "default")
|
|
ffmpeg_path = resolve_ffmpeg_path(path, "ffmpeg")
|
|
|
|
if go2rtc_config.get("ffmpeg") is None:
|
|
go2rtc_config["ffmpeg"] = {"bin": ffmpeg_path}
|
|
elif go2rtc_config["ffmpeg"].get("bin") is None:
|
|
go2rtc_config["ffmpeg"]["bin"] = ffmpeg_path
|
|
|
|
# need to replace ffmpeg command when using ffmpeg4
|
|
if LIBAVFORMAT_VERSION_MAJOR < 59:
|
|
rtsp_args = "-fflags nobuffer -flags low_delay -stimeout 10000000 -user_agent go2rtc/ffmpeg -rtsp_transport tcp -i {input}"
|
|
if go2rtc_config.get("ffmpeg") is None:
|
|
go2rtc_config["ffmpeg"] = {"rtsp": rtsp_args}
|
|
elif go2rtc_config["ffmpeg"].get("rtsp") is None:
|
|
go2rtc_config["ffmpeg"]["rtsp"] = rtsp_args
|
|
|
|
|
|
for name in list(go2rtc_config.get("streams", {})):
|
|
stream = go2rtc_config["streams"][name]
|
|
|
|
if isinstance(stream, str):
|
|
try:
|
|
formatted_stream = substitute_frigate_vars(stream)
|
|
if is_restricted_go2rtc_source(formatted_stream):
|
|
print(
|
|
f"[ERROR] Stream '{name}' uses a restricted source (echo/expr/exec) which is disabled by default for security. "
|
|
f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources."
|
|
)
|
|
del go2rtc_config["streams"][name]
|
|
continue
|
|
go2rtc_config["streams"][name] = formatted_stream
|
|
except ValueError as e:
|
|
print(
|
|
"[ERROR] Invalid substitution found, see https://docs.frigate.video/configuration/restream#advanced-restream-configurations for more info."
|
|
)
|
|
sys.exit(e)
|
|
|
|
elif isinstance(stream, list):
|
|
filtered_streams = []
|
|
for i, stream_item in enumerate(stream):
|
|
try:
|
|
formatted_stream = substitute_frigate_vars(stream_item)
|
|
if is_restricted_go2rtc_source(formatted_stream):
|
|
print(
|
|
f"[ERROR] Stream '{name}' item {i + 1} uses a restricted source (echo/expr/exec) which is disabled by default for security. "
|
|
f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources."
|
|
)
|
|
continue
|
|
|
|
filtered_streams.append(formatted_stream)
|
|
except ValueError as e:
|
|
print(
|
|
"[ERROR] Invalid substitution found, see https://docs.frigate.video/configuration/restream#advanced-restream-configurations for more info."
|
|
)
|
|
sys.exit(e)
|
|
|
|
if filtered_streams:
|
|
go2rtc_config["streams"][name] = filtered_streams
|
|
else:
|
|
print(
|
|
f"[ERROR] Stream '{name}' was removed because all sources were restricted (echo/expr/exec). "
|
|
f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources."
|
|
)
|
|
del go2rtc_config["streams"][name]
|
|
|
|
elif isinstance(stream, dict):
|
|
# The map form ({"url": ...}) lets go2rtc resolve the source
|
|
# recursively, so it is effectively a dynamic way to generate the URL
|
|
# for a stream. That can only be backed by an exec source, so it cannot
|
|
# be allowed unless arbitrary exec is explicitly enabled. When it is
|
|
# enabled, leave the map untouched for go2rtc to resolve.
|
|
if not is_go2rtc_arbitrary_exec_allowed():
|
|
print(
|
|
f"[ERROR] Stream '{name}' uses a dynamic source format which is disabled by default for security. "
|
|
f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources."
|
|
)
|
|
del go2rtc_config["streams"][name]
|
|
continue
|
|
|
|
# add transcoded live streams; a user stream with the same name wins here and
|
|
# fails Frigate's config validation
|
|
transcoded_streams = raw_transcode_streams(config)
|
|
|
|
if transcoded_streams:
|
|
if go2rtc_config.get("streams") is None:
|
|
go2rtc_config["streams"] = {}
|
|
|
|
for name, source in transcoded_streams.items():
|
|
go2rtc_config["streams"].setdefault(name, source)
|
|
|
|
# add birdseye restream stream if enabled
|
|
if config.get("birdseye", {}).get("restream", False):
|
|
birdseye: dict[str, Any] = config.get("birdseye")
|
|
|
|
input = f"-f rawvideo -pix_fmt yuv420p -video_size {birdseye.get('width', 1280)}x{birdseye.get('height', 720)} -r 10 -i {BIRDSEYE_PIPE}"
|
|
ffmpeg_cmd = f"exec:{parse_preset_hardware_acceleration_encode(ffmpeg_path, config.get('ffmpeg', {}).get('hwaccel_args', ''), input, '-rtsp_transport tcp -f rtsp {output}')}"
|
|
|
|
if go2rtc_config.get("streams"):
|
|
go2rtc_config["streams"]["birdseye"] = ffmpeg_cmd
|
|
else:
|
|
go2rtc_config["streams"] = {"birdseye": ffmpeg_cmd}
|
|
|
|
# Write go2rtc_config to /dev/shm/go2rtc.yaml
|
|
with open("/dev/shm/go2rtc.yaml", "w") as f:
|
|
yaml.dump(go2rtc_config, f)
|
|
|
|
# config contains camera credentials; do not leave it world-readable
|
|
os.chmod("/dev/shm/go2rtc.yaml", 0o640)
|