mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-03 02:21:13 +03:00
Some checks are pending
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
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
* Handle back seeking going to previous clip * scope /recordings/unavailable query to the caller's allowed cameras * listen for config updates in activity manager * don't set search after awaited request Intentionally do NOT setSearch() to mark the open event submitted. This runs after the awaited request, by which point the user may have closed the dialog; re-setting the parent's selected event would resurrect it and the force-open effect would reopen it (see #23599). The local "submitted" state covers the open card, and mutate() updates the events cache so the grid and any future open reflect the result. * fix ruff #23201 removed pathlib import but for some reason it's just now causing ruff to fail --------- Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
188 lines
7.4 KiB
Python
188 lines
7.4 KiB
Python
"""Creates a go2rtc config file."""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from ruamel.yaml import YAML
|
|
|
|
sys.path.insert(0, "/opt/frigate")
|
|
from frigate.config.env import substitute_frigate_vars
|
|
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.services import (
|
|
is_go2rtc_arbitrary_exec_allowed,
|
|
is_restricted_go2rtc_source,
|
|
)
|
|
|
|
sys.path.remove("/opt/frigate")
|
|
|
|
yaml = YAML()
|
|
|
|
FRIGATE_ENV_VARS = {k: v for k, v in os.environ.items() if k.startswith("FRIGATE_")}
|
|
# read docker secret files as env vars too
|
|
if os.path.isdir("/run/secrets"):
|
|
for secret_file in os.listdir("/run/secrets"):
|
|
if secret_file.startswith("FRIGATE_"):
|
|
FRIGATE_ENV_VARS[secret_file] = (
|
|
Path(os.path.join("/run/secrets", secret_file)).read_text().strip()
|
|
)
|
|
|
|
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] = {}
|
|
|
|
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 = stream.format(**FRIGATE_ENV_VARS)
|
|
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 KeyError 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 = stream_item.format(**FRIGATE_ENV_VARS)
|
|
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 KeyError 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 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)
|