mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-19 05:02:49 +03:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf418dd6b6 | ||
|
|
aa42b17911 |
@@ -10,13 +10,15 @@ 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.const import BIRDSEYE_PIPE
|
||||
from frigate.ffmpeg_presets import parse_preset_hardware_acceleration_encode
|
||||
from frigate.util.config import find_config_file, resolve_ffmpeg_path
|
||||
from frigate.util.config import (
|
||||
find_config_file,
|
||||
is_valid_ffmpeg_path,
|
||||
resolve_ffmpeg_path,
|
||||
)
|
||||
from frigate.util.services import (
|
||||
apply_go2rtc_ffmpeg_config,
|
||||
is_go2rtc_arbitrary_exec_allowed,
|
||||
is_restricted_go2rtc_source,
|
||||
)
|
||||
@@ -92,20 +94,17 @@ if go2rtc_config.get("rtsp", {}).get("password") is not None:
|
||||
|
||||
# ensure ffmpeg path is set correctly
|
||||
path = config.get("ffmpeg", {}).get("path", "default")
|
||||
if not is_valid_ffmpeg_path(path):
|
||||
print(
|
||||
"[ERROR] ffmpeg path contains spaces, quotes or shell characters, using the default ffmpeg instead."
|
||||
)
|
||||
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 key in apply_go2rtc_ffmpeg_config(go2rtc_config, ffmpeg_path):
|
||||
print(
|
||||
f"[ERROR] go2rtc option '{key}' was ignored because it is disabled by default for security. "
|
||||
f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec."
|
||||
)
|
||||
|
||||
|
||||
for name in list(go2rtc_config.get("streams", {})):
|
||||
|
||||
@@ -5,6 +5,8 @@ location /auth {
|
||||
## Essential Proxy Configuration
|
||||
internal;
|
||||
proxy_pass $upstream_auth;
|
||||
# $go2rtc_stream_proxy is only set by the go2rtc stream locations
|
||||
uninitialized_variable_warn off;
|
||||
|
||||
## Headers
|
||||
|
||||
|
||||
+18
-14
@@ -74,6 +74,7 @@ from frigate.util.config import (
|
||||
from frigate.util.schema import get_config_schema
|
||||
from frigate.util.services import (
|
||||
Go2rtcStreamIssue,
|
||||
find_restricted_go2rtc_exec_changes,
|
||||
find_restricted_go2rtc_stream_changes,
|
||||
get_nvidia_driver_info,
|
||||
process_logs,
|
||||
@@ -524,26 +525,26 @@ def config_raw():
|
||||
)
|
||||
|
||||
|
||||
def _go2rtc_streams(raw_config: str) -> Any:
|
||||
"""Load go2rtc.streams with the same loader Frigate and go2rtc startup use.
|
||||
def _go2rtc_config(raw_config: str) -> dict[str, Any]:
|
||||
"""Load the go2rtc section with the same loader Frigate and go2rtc startup use.
|
||||
|
||||
Raises ruamel.yaml.YAMLError when the config cannot be parsed.
|
||||
"""
|
||||
loaded = ruamel.yaml.YAML().load(raw_config)
|
||||
go2rtc = loaded.get("go2rtc") if isinstance(loaded, dict) else None
|
||||
return go2rtc.get("streams") if isinstance(go2rtc, dict) else None
|
||||
return go2rtc if isinstance(go2rtc, dict) else {}
|
||||
|
||||
|
||||
def _restricted_go2rtc_streams_response(
|
||||
def _restricted_go2rtc_response(
|
||||
old_raw_config: str, new_raw_config: str
|
||||
) -> JSONResponse | None:
|
||||
"""Reject config changes that add restricted go2rtc stream sources.
|
||||
"""Reject config changes that add restricted go2rtc sources or options.
|
||||
|
||||
The same sources are rejected by PUT /go2rtc/streams, and go2rtc would
|
||||
otherwise load them from the saved config on the next restart.
|
||||
"""
|
||||
try:
|
||||
new_streams = _go2rtc_streams(new_raw_config)
|
||||
new_go2rtc = _go2rtc_config(new_raw_config)
|
||||
except ruamel.yaml.YAMLError:
|
||||
logger.warning("Rejected config change that could not be parsed")
|
||||
return JSONResponse(
|
||||
@@ -552,16 +553,21 @@ def _restricted_go2rtc_streams_response(
|
||||
)
|
||||
|
||||
try:
|
||||
old_streams = _go2rtc_streams(old_raw_config)
|
||||
old_go2rtc = _go2rtc_config(old_raw_config)
|
||||
except ruamel.yaml.YAMLError:
|
||||
# with no readable saved config, every source is treated as new
|
||||
old_streams = None
|
||||
old_go2rtc = {}
|
||||
|
||||
issues = find_restricted_go2rtc_stream_changes(old_streams, new_streams)
|
||||
if not issues:
|
||||
issues = find_restricted_go2rtc_stream_changes(
|
||||
old_go2rtc.get("streams"), new_go2rtc.get("streams")
|
||||
)
|
||||
exec_keys = find_restricted_go2rtc_exec_changes(old_go2rtc, new_go2rtc)
|
||||
if not issues and not exec_keys:
|
||||
return None
|
||||
|
||||
messages = []
|
||||
if exec_keys:
|
||||
messages.append(f"Restricted go2rtc option: {', '.join(exec_keys)}")
|
||||
for issue, label in (
|
||||
(Go2rtcStreamIssue.restricted, "Restricted go2rtc stream source in"),
|
||||
(Go2rtcStreamIssue.unknown_variable, "Unknown variable in go2rtc stream"),
|
||||
@@ -665,9 +671,7 @@ def config_save(save_option: str, body: Any = Body(media_type="text/plain")):
|
||||
except OSError:
|
||||
old_raw_config = ""
|
||||
|
||||
restricted_response = _restricted_go2rtc_streams_response(
|
||||
old_raw_config, new_config
|
||||
)
|
||||
restricted_response = _restricted_go2rtc_response(old_raw_config, new_config)
|
||||
if restricted_response is not None:
|
||||
return restricted_response
|
||||
|
||||
@@ -983,7 +987,7 @@ def config_set(request: Request, body: AppConfigSetBody):
|
||||
),
|
||||
status_code=400,
|
||||
)
|
||||
restricted_response = _restricted_go2rtc_streams_response(
|
||||
restricted_response = _restricted_go2rtc_response(
|
||||
old_raw_config, new_raw_config
|
||||
)
|
||||
if restricted_response is not None:
|
||||
|
||||
@@ -2,7 +2,7 @@ from enum import Enum
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from frigate.util.config import resolve_ffmpeg_path
|
||||
from frigate.util.config import is_valid_ffmpeg_path, resolve_ffmpeg_path
|
||||
|
||||
from ..base import FrigateBaseModel
|
||||
from ..env import EnvString
|
||||
@@ -87,6 +87,16 @@ class FfmpegConfig(FrigateBaseModel):
|
||||
description="Default GPU index used for hardware acceleration if available.",
|
||||
)
|
||||
|
||||
@field_validator("path")
|
||||
@classmethod
|
||||
def validate_path(cls, v: str) -> str:
|
||||
if not is_valid_ffmpeg_path(v):
|
||||
raise ValueError(
|
||||
"FFmpeg path must be a version alias or an absolute path without "
|
||||
"spaces, quotes or shell characters"
|
||||
)
|
||||
return v
|
||||
|
||||
@property
|
||||
def ffmpeg_path(self) -> str:
|
||||
return resolve_ffmpeg_path(self.path, "ffmpeg")
|
||||
|
||||
@@ -85,6 +85,23 @@ class TestConfigGo2rtcStreamRestrictions(BaseTestHttp):
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
|
||||
def _set_go2rtc(self, go2rtc: dict):
|
||||
with AuthTestClient(self.app) as client:
|
||||
return client.put(
|
||||
"/config/set",
|
||||
json={"config_data": {"go2rtc": go2rtc}, "requires_restart": 1},
|
||||
)
|
||||
|
||||
def _save_go2rtc(self, go2rtc: dict):
|
||||
config = copy.deepcopy(_CONFIG)
|
||||
config["go2rtc"].update(go2rtc)
|
||||
with AuthTestClient(self.app) as client:
|
||||
return client.post(
|
||||
"/config/save?save_option=saveonly",
|
||||
content=_dump(config),
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
|
||||
# --- config/set ---
|
||||
|
||||
def test_set_rejects_injected_ffmpeg_source(self):
|
||||
@@ -215,3 +232,84 @@ class TestConfigGo2rtcStreamRestrictions(BaseTestHttp):
|
||||
resp = self._set_streams({"new_stream": [src]})
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert src in self._read_config()
|
||||
|
||||
# --- go2rtc ffmpeg and ngrok options ---
|
||||
|
||||
def test_save_rejects_ffmpeg_bin_override(self):
|
||||
before = self._read_config()
|
||||
resp = self._save_go2rtc({"ffmpeg": {"bin": "/bin/sh", "global": "-c 'id' x"}})
|
||||
assert resp.status_code == 400
|
||||
assert resp.json()["message"] == (
|
||||
"Restricted go2rtc option: ffmpeg.bin, ffmpeg.global"
|
||||
)
|
||||
assert self._read_config() == before
|
||||
|
||||
def test_set_rejects_ffmpeg_bin_override(self):
|
||||
before = self._read_config()
|
||||
resp = self._set_go2rtc({"ffmpeg": {"bin": "/bin/sh"}})
|
||||
assert resp.status_code == 400
|
||||
assert self._read_config() == before
|
||||
|
||||
def test_save_rejects_ffmpeg_template_override(self):
|
||||
before = self._read_config()
|
||||
resp = self._save_go2rtc({"ffmpeg": {"rtsp": "-i {input} -y /config/x"}})
|
||||
assert resp.status_code == 400
|
||||
assert "ffmpeg.rtsp" in resp.json()["message"]
|
||||
assert self._read_config() == before
|
||||
|
||||
def test_set_rejects_custom_ffmpeg_template(self):
|
||||
# a stream can reference any template key with #video=<name>
|
||||
before = self._read_config()
|
||||
resp = self._set_go2rtc({"ffmpeg": {"evil": "-y /config/x"}})
|
||||
assert resp.status_code == 400
|
||||
assert self._read_config() == before
|
||||
|
||||
def test_save_rejects_ngrok(self):
|
||||
before = self._read_config()
|
||||
resp = self._save_go2rtc({"ngrok": {"command": "/bin/sh -c id"}})
|
||||
assert resp.status_code == 400
|
||||
assert resp.json()["message"] == "Restricted go2rtc option: ngrok.command"
|
||||
assert self._read_config() == before
|
||||
|
||||
def test_set_rejects_ngrok(self):
|
||||
before = self._read_config()
|
||||
resp = self._set_go2rtc({"ngrok": {"command": "/bin/sh -c id"}})
|
||||
assert resp.status_code == 400
|
||||
assert self._read_config() == before
|
||||
|
||||
def test_save_keeps_existing_ffmpeg_template_on_disk(self):
|
||||
go2rtc = {"ffmpeg": {"global": "-vaapi_device /dev/dri/renderD128"}}
|
||||
config = copy.deepcopy(_CONFIG)
|
||||
config["go2rtc"].update(go2rtc)
|
||||
with open(self.config_path, "w") as f:
|
||||
f.write(_dump(config))
|
||||
|
||||
resp = self._save_go2rtc(go2rtc)
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
resp = self._save_go2rtc({"ffmpeg": {"global": "-hide_banner"}})
|
||||
assert resp.status_code == 400
|
||||
|
||||
def test_exec_options_allowed_when_override_set(self):
|
||||
with patch.dict(os.environ, {"GO2RTC_ALLOW_ARBITRARY_EXEC": "true"}):
|
||||
resp = self._save_go2rtc(
|
||||
{
|
||||
"ffmpeg": {"bin": "/usr/bin/ffmpeg", "rtsp": "-i {input}"},
|
||||
"ngrok": {"command": "ngrok tcp 8555"},
|
||||
}
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert "ngrok tcp 8555" in self._read_config()
|
||||
|
||||
def test_save_rejects_unsafe_ffmpeg_path(self):
|
||||
before = self._read_config()
|
||||
config = copy.deepcopy(_CONFIG)
|
||||
config["ffmpeg"] = {"path": "/bin/sh -c id;#"}
|
||||
with AuthTestClient(self.app) as client:
|
||||
resp = client.post(
|
||||
"/config/save?save_option=saveonly",
|
||||
content=_dump(config),
|
||||
headers={"Content-Type": "text/plain"},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert self._read_config() == before
|
||||
|
||||
@@ -11,6 +11,7 @@ from frigate.config import BirdseyeModeEnum, FrigateConfig
|
||||
from frigate.const import MODEL_CACHE_DIR
|
||||
from frigate.detectors import DetectorTypeEnum
|
||||
from frigate.util.builtin import deep_merge
|
||||
from frigate.util.config import resolve_ffmpeg_path
|
||||
|
||||
|
||||
class TestConfig(unittest.TestCase):
|
||||
@@ -1765,5 +1766,54 @@ class TestAttributeFilterDefaults(unittest.TestCase):
|
||||
self.assertEqual(face_filter.min_score, 0.3)
|
||||
|
||||
|
||||
class TestFfmpegPath(unittest.TestCase):
|
||||
# ffmpeg commands are split on spaces, so this would run /bin/sh
|
||||
UNSAFE_PATH = "/bin/sh -c id;#"
|
||||
|
||||
def setUp(self):
|
||||
self.minimal = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
"inputs": [
|
||||
{"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]}
|
||||
]
|
||||
},
|
||||
"detect": {"height": 1080, "width": 1920, "fps": 5},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
def test_custom_path_allowed(self):
|
||||
config = deep_merge(
|
||||
{"ffmpeg": {"path": "/usr/lib/jellyfin-ffmpeg"}}, self.minimal
|
||||
)
|
||||
frigate_config = FrigateConfig(**config)
|
||||
self.assertEqual(
|
||||
frigate_config.cameras["back"].ffmpeg.ffmpeg_path,
|
||||
"/usr/lib/jellyfin-ffmpeg/bin/ffmpeg",
|
||||
)
|
||||
|
||||
def test_unsafe_global_path_rejected(self):
|
||||
for path in (self.UNSAFE_PATH, "/opt/my ffmpeg", "/opt/'x'", "/opt/x\n"):
|
||||
config = deep_merge({"ffmpeg": {"path": path}}, self.minimal)
|
||||
with self.assertRaises(ValidationError, msg=path):
|
||||
FrigateConfig(**config)
|
||||
|
||||
def test_unsafe_camera_path_rejected(self):
|
||||
config = deep_merge(
|
||||
{"cameras": {"back": {"ffmpeg": {"path": self.UNSAFE_PATH}}}},
|
||||
self.minimal,
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
FrigateConfig(**config)
|
||||
|
||||
def test_resolve_falls_back_for_unsafe_path(self):
|
||||
self.assertEqual(
|
||||
resolve_ffmpeg_path(self.UNSAFE_PATH), resolve_ffmpeg_path("default")
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Tests for the go2rtc ffmpeg and ngrok options applied at startup."""
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from frigate.util.services import apply_go2rtc_ffmpeg_config
|
||||
|
||||
FFMPEG_PATH = "/usr/lib/ffmpeg/7.0/bin/ffmpeg"
|
||||
|
||||
|
||||
class TestApplyGo2rtcFfmpegConfig(unittest.TestCase):
|
||||
def setUp(self):
|
||||
env_patch = patch.dict(os.environ)
|
||||
env_patch.start()
|
||||
self.addCleanup(env_patch.stop)
|
||||
os.environ.pop("GO2RTC_ALLOW_ARBITRARY_EXEC", None)
|
||||
|
||||
def test_sets_bin_when_missing(self):
|
||||
config = {}
|
||||
assert apply_go2rtc_ffmpeg_config(config, FFMPEG_PATH) == []
|
||||
assert config["ffmpeg"] == {"bin": FFMPEG_PATH}
|
||||
|
||||
def test_forces_bin_override(self):
|
||||
config = {"ffmpeg": {"bin": "/bin/sh", "global": "-c 'id' x"}}
|
||||
assert apply_go2rtc_ffmpeg_config(config, FFMPEG_PATH) == ["ffmpeg.bin"]
|
||||
assert config["ffmpeg"]["bin"] == FFMPEG_PATH
|
||||
|
||||
def test_keeps_saved_templates(self):
|
||||
config = {"ffmpeg": {"global": "-vaapi_device /dev/dri/renderD128"}}
|
||||
assert apply_go2rtc_ffmpeg_config(config, FFMPEG_PATH) == []
|
||||
assert config["ffmpeg"]["global"] == "-vaapi_device /dev/dri/renderD128"
|
||||
|
||||
def test_drops_ngrok(self):
|
||||
config = {"ngrok": {"command": "/bin/sh -c id"}}
|
||||
assert apply_go2rtc_ffmpeg_config(config, FFMPEG_PATH) == ["ngrok"]
|
||||
assert "ngrok" not in config
|
||||
|
||||
def test_allows_bin_and_ngrok_when_override_set(self):
|
||||
config = {
|
||||
"ffmpeg": {"bin": "/usr/bin/ffmpeg"},
|
||||
"ngrok": {"command": "ngrok tcp 8555"},
|
||||
}
|
||||
with patch.dict(os.environ, {"GO2RTC_ALLOW_ARBITRARY_EXEC": "true"}):
|
||||
assert apply_go2rtc_ffmpeg_config(config, FFMPEG_PATH) == []
|
||||
assert config["ffmpeg"]["bin"] == "/usr/bin/ffmpeg"
|
||||
assert config["ngrok"] == {"command": "ngrok tcp 8555"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+16
-3
@@ -3,6 +3,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
from typing import Any
|
||||
|
||||
@@ -23,6 +24,15 @@ logger = logging.getLogger(__name__)
|
||||
CURRENT_CONFIG_VERSION = "0.18-0"
|
||||
DEFAULT_CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yml")
|
||||
|
||||
# ffmpeg commands are built as strings and split on whitespace, so a custom path
|
||||
# must stay a single plain token
|
||||
_CUSTOM_FFMPEG_PATH = re.compile(r"/[\w.+@/-]+")
|
||||
|
||||
|
||||
def is_valid_ffmpeg_path(path: str) -> bool:
|
||||
"""Check that a custom ffmpeg path has no whitespace, quotes or shell syntax."""
|
||||
return not path.startswith("/") or _CUSTOM_FFMPEG_PATH.fullmatch(path) is not None
|
||||
|
||||
|
||||
def resolve_ffmpeg_path(path: str, binary: str = "ffmpeg") -> str:
|
||||
"""Resolve an ffmpeg version alias or custom path to a binary path.
|
||||
@@ -30,10 +40,13 @@ def resolve_ffmpeg_path(path: str, binary: str = "ffmpeg") -> str:
|
||||
A bare version alias that is no longer bundled (for example one that was
|
||||
dropped when the default version changed) falls back to the default
|
||||
bundled version so existing configs keep working across an upgrade or a
|
||||
revert. Custom install paths (anything absolute) are used as-is.
|
||||
revert. Custom install paths (anything absolute) are used as-is, unless
|
||||
they fail `is_valid_ffmpeg_path`, which also falls back to the default.
|
||||
"""
|
||||
if path == "default" or (
|
||||
not path.startswith("/") and path not in INCLUDED_FFMPEG_VERSIONS
|
||||
if (
|
||||
path == "default"
|
||||
or not is_valid_ffmpeg_path(path)
|
||||
or (not path.startswith("/") and path not in INCLUDED_FFMPEG_VERSIONS)
|
||||
):
|
||||
version = DEFAULT_FFMPEG_VERSION
|
||||
elif path in INCLUDED_FFMPEG_VERSIONS:
|
||||
|
||||
@@ -1103,6 +1103,74 @@ def find_restricted_go2rtc_stream_changes(
|
||||
return issues
|
||||
|
||||
|
||||
# go2rtc runs `ffmpeg.bin` and `ngrok.command`, and every other `ffmpeg` key is a
|
||||
# template of ffmpeg arguments that `#video=<name>` and friends can reference
|
||||
_GO2RTC_EXEC_SECTIONS = ("ffmpeg", "ngrok")
|
||||
|
||||
|
||||
def find_restricted_go2rtc_exec_changes(
|
||||
old_go2rtc: dict[str, Any] | None, new_go2rtc: dict[str, Any] | None
|
||||
) -> list[str]:
|
||||
"""Return the go2rtc ffmpeg and ngrok keys added or changed through the API.
|
||||
|
||||
These are only accepted with arbitrary exec enabled. Keys already in the
|
||||
saved config are left alone, the same as stream sources.
|
||||
"""
|
||||
if is_go2rtc_arbitrary_exec_allowed():
|
||||
return []
|
||||
|
||||
old_go2rtc = old_go2rtc if isinstance(old_go2rtc, dict) else {}
|
||||
new_go2rtc = new_go2rtc if isinstance(new_go2rtc, dict) else {}
|
||||
changed: list[str] = []
|
||||
|
||||
for section in _GO2RTC_EXEC_SECTIONS:
|
||||
old = old_go2rtc.get(section)
|
||||
new = new_go2rtc.get(section)
|
||||
|
||||
if not isinstance(new, dict):
|
||||
if new is not None and new != old:
|
||||
changed.append(section)
|
||||
continue
|
||||
|
||||
old = old if isinstance(old, dict) else {}
|
||||
changed.extend(
|
||||
f"{section}.{key}" for key, value in new.items() if old.get(key) != value
|
||||
)
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def apply_go2rtc_ffmpeg_config(
|
||||
go2rtc_config: dict[str, Any], ffmpeg_path: str
|
||||
) -> list[str]:
|
||||
"""Set go2rtc's ffmpeg binary for startup.
|
||||
|
||||
Unless arbitrary exec is enabled, `ffmpeg.bin` is forced to Frigate's ffmpeg
|
||||
and `ngrok` is removed. Templates in the saved config are kept.
|
||||
|
||||
Returns:
|
||||
The keys that were overridden or removed
|
||||
"""
|
||||
allowed = is_go2rtc_arbitrary_exec_allowed()
|
||||
restricted: list[str] = []
|
||||
|
||||
ffmpeg = go2rtc_config.get("ffmpeg")
|
||||
if not isinstance(ffmpeg, dict):
|
||||
ffmpeg = go2rtc_config["ffmpeg"] = {}
|
||||
|
||||
if ffmpeg.get("bin") is None:
|
||||
ffmpeg["bin"] = ffmpeg_path
|
||||
elif ffmpeg["bin"] != ffmpeg_path and not allowed:
|
||||
restricted.append("ffmpeg.bin")
|
||||
ffmpeg["bin"] = ffmpeg_path
|
||||
|
||||
if "ngrok" in go2rtc_config and not allowed:
|
||||
restricted.append("ngrok")
|
||||
del go2rtc_config["ngrok"]
|
||||
|
||||
return restricted
|
||||
|
||||
|
||||
def ffprobe_stream(ffmpeg, path: str, detailed: bool = False) -> sp.CompletedProcess:
|
||||
"""Run ffprobe on stream."""
|
||||
clean_path = escape_special_characters(path)
|
||||
|
||||
Reference in New Issue
Block a user