no need to write debug replay camera to config

This commit is contained in:
Josh Hawkins 2026-02-28 16:30:42 -06:00
parent 31708b792b
commit 14143c4b3e
2 changed files with 36 additions and 75 deletions

View File

@ -46,7 +46,6 @@ from frigate.db.sqlitevecq import SqliteVecQueueDatabase
from frigate.debug_replay import (
DebugReplayManager,
cleanup_replay_cameras,
cleanup_replay_cameras_db,
)
from frigate.embeddings import EmbeddingProcess, EmbeddingsContext
from frigate.events.audio import AudioProcessor
@ -536,8 +535,6 @@ class FrigateApp:
set_file_limit()
# Start frigate services.
# Clean up any stale replay cameras before services iterate the cameras dict
stale_replay_cameras = cleanup_replay_cameras(self.config)
self.replay_manager = DebugReplayManager()
self.init_camera_metrics()
@ -551,8 +548,9 @@ class FrigateApp:
self.bind_database()
self.check_db_data_migrations()
# Deferred DB cleanup for replay cameras (database is now bound)
cleanup_replay_cameras_db(stale_replay_cameras)
# Clean up any stale replay camera artifacts (filesystem + DB)
cleanup_replay_cameras()
self.init_inter_process_communicator()
self.start_detectors()
self.init_dispatcher()

View File

@ -6,6 +6,8 @@ import shutil
import subprocess as sp
import threading
from ruamel.yaml import YAML
from frigate.config import FrigateConfig
from frigate.config.camera.updater import (
CameraConfigUpdateEnum,
@ -20,7 +22,6 @@ from frigate.const import (
THUMB_DIR,
)
from frigate.models import Event, Recordings, ReviewSegment, Timeline
from frigate.util.builtin import update_yaml_file_bulk
from frigate.util.config import find_config_file
logger = logging.getLogger(__name__)
@ -176,22 +177,19 @@ class DebugReplayManager:
source_config, replay_name, clip_path
)
# Write to YAML config
# Build an in-memory config with the replay camera added
config_file = find_config_file()
update_yaml_file_bulk(config_file, {f"cameras.{replay_name}": camera_dict})
# Re-parse the full config to get a fully initialized CameraConfig
yaml_parser = YAML()
with open(config_file, "r") as f:
new_raw_config = f.read()
config_data = yaml_parser.load(f)
if "cameras" not in config_data or config_data["cameras"] is None:
config_data["cameras"] = {}
config_data["cameras"][replay_name] = camera_dict
try:
new_config = FrigateConfig.parse(new_raw_config)
new_config = FrigateConfig.parse_object(config_data)
except Exception as e:
# Rollback YAML change
try:
update_yaml_file_bulk(config_file, {f"cameras.{replay_name}": ""})
except Exception:
logger.warning("Failed to rollback replay camera YAML entry")
raise RuntimeError(f"Failed to validate replay camera config: {e}")
# Update the running config
@ -253,13 +251,6 @@ class DebugReplayManager:
# Remove filesystem artifacts
self._cleanup_files(replay_name)
# Remove from YAML config
config_file = find_config_file()
try:
update_yaml_file_bulk(config_file, {f"cameras.{replay_name}": ""})
except Exception as e:
logger.error("Failed to remove replay camera from YAML: %s", e)
# Reset state
self.replay_camera_name = None
self.source_camera = None
@ -411,48 +402,38 @@ class DebugReplayManager:
logger.error("Failed to remove replay cache: %s", e)
def cleanup_replay_cameras(frigate_config: FrigateConfig) -> list[str]:
"""Remove any stale replay cameras from config and YAML on startup.
def cleanup_replay_cameras() -> None:
"""Remove any stale replay camera artifacts on startup.
This must be called BEFORE services start iterating the cameras dict.
DB cleanup is deferred to cleanup_replay_cameras_db() after the database
is bound.
Since replay cameras are memory-only and never written to YAML, they
won't appear in the config after a restart. This function cleans up
filesystem and database artifacts from any replay that was running when
the process stopped.
Args:
frigate_config: The current Frigate configuration
Returns:
List of removed replay camera names (for deferred DB cleanup)
Must be called AFTER the database is bound.
"""
replay_cameras = [
name
for name in list(frigate_config.cameras.keys())
if name.startswith(REPLAY_CAMERA_PREFIX)
]
stale_cameras: set[str] = set()
if not replay_cameras:
return []
# Scan filesystem for leftover replay artifacts to derive camera names
for dir_path in [RECORD_DIR, CLIPS_DIR, THUMB_DIR]:
if os.path.isdir(dir_path):
for entry in os.listdir(dir_path):
if entry.startswith(REPLAY_CAMERA_PREFIX):
stale_cameras.add(entry)
logger.info("Cleaning up stale replay cameras: %s", replay_cameras)
if os.path.isdir(REPLAY_DIR):
for entry in os.listdir(REPLAY_DIR):
if entry.startswith(REPLAY_CAMERA_PREFIX) and entry.endswith(".mp4"):
stale_cameras.add(entry.removesuffix(".mp4"))
config_file = find_config_file()
updates = {}
if not stale_cameras:
return
for camera_name in replay_cameras:
# Remove from running config
frigate_config.cameras.pop(camera_name, None)
# Mark for YAML removal
updates[f"cameras.{camera_name}"] = ""
logger.info("Cleaning up stale replay camera artifacts: %s", list(stale_cameras))
# Remove from YAML
try:
update_yaml_file_bulk(config_file, updates)
except Exception as e:
logger.error("Failed to clean up replay cameras from YAML: %s", e)
# Clean replay directory and files (no DB needed)
manager = DebugReplayManager()
for camera_name in replay_cameras:
for camera_name in stale_cameras:
manager._cleanup_db(camera_name)
manager._cleanup_files(camera_name)
if os.path.exists(REPLAY_DIR):
@ -460,21 +441,3 @@ def cleanup_replay_cameras(frigate_config: FrigateConfig) -> list[str]:
shutil.rmtree(REPLAY_DIR)
except Exception as e:
logger.error("Failed to remove replay cache directory: %s", e)
return replay_cameras
def cleanup_replay_cameras_db(replay_cameras: list[str]) -> None:
"""Clean up database rows for stale replay cameras.
Must be called AFTER the database is bound.
Args:
replay_cameras: List of replay camera names from cleanup_replay_cameras()
"""
if not replay_cameras:
return
manager = DebugReplayManager()
for camera_name in replay_cameras:
manager._cleanup_db(camera_name)