mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-07 14:05:28 +03:00
use ReplayState enum
This commit is contained in:
parent
b6fd86a066
commit
8dc68a8abd
@ -5,6 +5,7 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import threading
|
import threading
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from ruamel.yaml import YAML
|
from ruamel.yaml import YAML
|
||||||
|
|
||||||
@ -28,21 +29,55 @@ from frigate.util.config import find_config_file
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ReplayState(str, Enum):
|
||||||
|
"""State of the debug replay session lifecycle.
|
||||||
|
|
||||||
|
idle: no session
|
||||||
|
preparing_clip: ffmpeg concat is running, no replay camera yet
|
||||||
|
starting_camera: clip ready, publishing camera config update
|
||||||
|
active: replay camera is published; first frame may not have arrived yet
|
||||||
|
error: startup failed; error_message is set
|
||||||
|
"""
|
||||||
|
|
||||||
|
idle = "idle"
|
||||||
|
preparing_clip = "preparing_clip"
|
||||||
|
starting_camera = "starting_camera"
|
||||||
|
active = "active"
|
||||||
|
error = "error"
|
||||||
|
|
||||||
|
|
||||||
class DebugReplayManager:
|
class DebugReplayManager:
|
||||||
"""Manages a single debug replay session."""
|
"""Manages a single debug replay session."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._lock = threading.Lock()
|
self._lock = threading.Lock()
|
||||||
|
self._state: ReplayState = ReplayState.idle
|
||||||
|
self.error_message: str | None = None
|
||||||
self.replay_camera_name: str | None = None
|
self.replay_camera_name: str | None = None
|
||||||
self.source_camera: str | None = None
|
self.source_camera: str | None = None
|
||||||
self.clip_path: str | None = None
|
self.clip_path: str | None = None
|
||||||
self.start_ts: float | None = None
|
self.start_ts: float | None = None
|
||||||
self.end_ts: float | None = None
|
self.end_ts: float | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> ReplayState:
|
||||||
|
return self._state
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active(self) -> bool:
|
def active(self) -> bool:
|
||||||
"""Whether a replay session is currently active."""
|
"""Whether a replay session is in progress (preparing, starting, or active)."""
|
||||||
return self.replay_camera_name is not None
|
return self._state in (
|
||||||
|
ReplayState.preparing_clip,
|
||||||
|
ReplayState.starting_camera,
|
||||||
|
ReplayState.active,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _set_state(
|
||||||
|
self, state: ReplayState, error_message: str | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Internal state transition helper. Always pair `error` with an error_message."""
|
||||||
|
self._state = state
|
||||||
|
self.error_message = error_message if state == ReplayState.error else None
|
||||||
|
|
||||||
def start(
|
def start(
|
||||||
self,
|
self,
|
||||||
@ -208,6 +243,7 @@ class DebugReplayManager:
|
|||||||
self.clip_path = clip_path
|
self.clip_path = clip_path
|
||||||
self.start_ts = start_ts
|
self.start_ts = start_ts
|
||||||
self.end_ts = end_ts
|
self.end_ts = end_ts
|
||||||
|
self._set_state(ReplayState.active)
|
||||||
|
|
||||||
logger.info("Debug replay started: %s -> %s", source_camera, replay_name)
|
logger.info("Debug replay started: %s -> %s", source_camera, replay_name)
|
||||||
return replay_name
|
return replay_name
|
||||||
@ -258,6 +294,7 @@ class DebugReplayManager:
|
|||||||
self.clip_path = None
|
self.clip_path = None
|
||||||
self.start_ts = None
|
self.start_ts = None
|
||||||
self.end_ts = None
|
self.end_ts = None
|
||||||
|
self._set_state(ReplayState.idle)
|
||||||
|
|
||||||
logger.info("Debug replay stopped and cleaned up: %s", replay_name)
|
logger.info("Debug replay stopped and cleaned up: %s", replay_name)
|
||||||
|
|
||||||
|
|||||||
36
frigate/test/test_debug_replay.py
Normal file
36
frigate/test/test_debug_replay.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
"""Tests for DebugReplayManager state machine and async startup."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from frigate.debug_replay import DebugReplayManager, ReplayState
|
||||||
|
|
||||||
|
|
||||||
|
class TestDebugReplayManagerState(unittest.TestCase):
|
||||||
|
def test_initial_state_is_idle(self):
|
||||||
|
manager = DebugReplayManager()
|
||||||
|
|
||||||
|
self.assertEqual(manager.state, ReplayState.idle)
|
||||||
|
self.assertIsNone(manager.error_message)
|
||||||
|
self.assertFalse(manager.active)
|
||||||
|
|
||||||
|
def test_active_property_true_for_preparing_starting_and_active_states(self):
|
||||||
|
manager = DebugReplayManager()
|
||||||
|
|
||||||
|
manager._set_state(ReplayState.preparing_clip)
|
||||||
|
self.assertTrue(manager.active)
|
||||||
|
|
||||||
|
manager._set_state(ReplayState.starting_camera)
|
||||||
|
self.assertTrue(manager.active)
|
||||||
|
|
||||||
|
manager._set_state(ReplayState.active)
|
||||||
|
self.assertTrue(manager.active)
|
||||||
|
|
||||||
|
def test_active_property_false_for_idle_and_error_states(self):
|
||||||
|
manager = DebugReplayManager()
|
||||||
|
|
||||||
|
manager._set_state(ReplayState.idle)
|
||||||
|
self.assertFalse(manager.active)
|
||||||
|
|
||||||
|
manager._set_state(ReplayState.error, error_message="boom")
|
||||||
|
self.assertFalse(manager.active)
|
||||||
|
self.assertEqual(manager.error_message, "boom")
|
||||||
Loading…
Reference in New Issue
Block a user