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

View File

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