mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-01-23 04:28:32 +03:00
216 lines
7.9 KiB
Python
216 lines
7.9 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Creates a go2rtc config file with proper permissions."""
|
||
|
|
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import shutil
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
try:
|
||
|
|
from ruamel.yaml import YAML
|
||
|
|
except ImportError:
|
||
|
|
print("Installing required packages...")
|
||
|
|
import subprocess
|
||
|
|
subprocess.check_call([sys.executable, "-m", "pip", "install", "ruamel.yaml"])
|
||
|
|
from ruamel.yaml import YAML
|
||
|
|
|
||
|
|
# Add frigate to path
|
||
|
|
sys.path.insert(0, "/opt/frigate")
|
||
|
|
try:
|
||
|
|
from frigate.const import (
|
||
|
|
BIRDSEYE_PIPE,
|
||
|
|
DEFAULT_FFMPEG_VERSION,
|
||
|
|
INCLUDED_FFMPEG_VERSIONS,
|
||
|
|
LIBAVFORMAT_VERSION_MAJOR,
|
||
|
|
)
|
||
|
|
from frigate.ffmpeg_presets import parse_preset_hardware_acceleration_encode
|
||
|
|
from frigate.util.config import find_config_file
|
||
|
|
except ImportError:
|
||
|
|
# Fallback if imports fail
|
||
|
|
print("Warning: Could not import from frigate module. Using fallback values.")
|
||
|
|
BIRDSEYE_PIPE = "/tmp/birdseye"
|
||
|
|
DEFAULT_FFMPEG_VERSION = "v5.1.2"
|
||
|
|
INCLUDED_FFMPEG_VERSIONS = ["v5.1.2", "v4.4"]
|
||
|
|
LIBAVFORMAT_VERSION_MAJOR = 59
|
||
|
|
|
||
|
|
def parse_preset_hardware_acceleration_encode(ffmpeg_path, hwaccel_args, input_args, output_args):
|
||
|
|
return f"{ffmpeg_path} {hwaccel_args} {input_args} {output_args}"
|
||
|
|
|
||
|
|
def find_config_file():
|
||
|
|
config_file = os.environ.get("CONFIG_FILE", "/config/config.yml")
|
||
|
|
if os.path.exists(config_file):
|
||
|
|
return config_file
|
||
|
|
|
||
|
|
# Try common locations
|
||
|
|
for ext in [".yml", ".yaml", ".json"]:
|
||
|
|
for path in ["/config/config", "/etc/frigate/config"]:
|
||
|
|
if os.path.exists(f"{path}{ext}"):
|
||
|
|
return f"{path}{ext}"
|
||
|
|
return "/config/config.yml"
|
||
|
|
|
||
|
|
# Remove frigate from path
|
||
|
|
if "/opt/frigate" in sys.path:
|
||
|
|
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()
|
||
|
|
print(f"Using config file: {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:
|
||
|
|
print(f"Config file not found: {config_file}")
|
||
|
|
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"] = go2rtc_config["rtsp"]["username"].format(
|
||
|
|
**FRIGATE_ENV_VARS
|
||
|
|
)
|
||
|
|
|
||
|
|
if go2rtc_config.get("rtsp", {}).get("password") is not None:
|
||
|
|
go2rtc_config["rtsp"]["password"] = go2rtc_config["rtsp"]["password"].format(
|
||
|
|
**FRIGATE_ENV_VARS
|
||
|
|
)
|
||
|
|
|
||
|
|
# ensure ffmpeg path is set correctly
|
||
|
|
path = config.get("ffmpeg", {}).get("path", "default")
|
||
|
|
if path == "default":
|
||
|
|
ffmpeg_path = f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffmpeg"
|
||
|
|
elif path in INCLUDED_FFMPEG_VERSIONS:
|
||
|
|
ffmpeg_path = f"/usr/lib/ffmpeg/{path}/bin/ffmpeg"
|
||
|
|
else:
|
||
|
|
ffmpeg_path = f"{path}/bin/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 go2rtc_config.get("streams", {}):
|
||
|
|
stream = go2rtc_config["streams"][name]
|
||
|
|
|
||
|
|
if isinstance(stream, str):
|
||
|
|
try:
|
||
|
|
go2rtc_config["streams"][name] = go2rtc_config["streams"][name].format(
|
||
|
|
**FRIGATE_ENV_VARS
|
||
|
|
)
|
||
|
|
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):
|
||
|
|
for i, stream in enumerate(stream):
|
||
|
|
try:
|
||
|
|
go2rtc_config["streams"][name][i] = stream.format(**FRIGATE_ENV_VARS)
|
||
|
|
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)
|
||
|
|
|
||
|
|
# 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 multiple possible locations with proper permissions
|
||
|
|
config_paths = [
|
||
|
|
"/dev/shm/go2rtc.yaml", # Primary location
|
||
|
|
"/tmp/go2rtc.yaml", # Fallback location 1
|
||
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "go2rtc.yaml") # Fallback location 2
|
||
|
|
]
|
||
|
|
|
||
|
|
success = False
|
||
|
|
for config_path in config_paths:
|
||
|
|
try:
|
||
|
|
# Try to create directory if it doesn't exist
|
||
|
|
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||
|
|
|
||
|
|
# Write to a temporary file first
|
||
|
|
temp_path = f"{config_path}.tmp"
|
||
|
|
with open(temp_path, "w") as f:
|
||
|
|
yaml.dump(go2rtc_config, f)
|
||
|
|
|
||
|
|
# Make the file world-readable and writable
|
||
|
|
os.chmod(temp_path, 0o666)
|
||
|
|
|
||
|
|
# Move the temporary file to the final location
|
||
|
|
shutil.move(temp_path, config_path)
|
||
|
|
|
||
|
|
print(f"Successfully wrote go2rtc config to {config_path}")
|
||
|
|
success = True
|
||
|
|
break
|
||
|
|
except (PermissionError, OSError) as e:
|
||
|
|
print(f"Failed to write to {config_path}: {e}")
|
||
|
|
|
||
|
|
if not success:
|
||
|
|
print("ERROR: Could not write go2rtc config to any location")
|
||
|
|
sys.exit(1)
|