mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-06-21 03:41:55 +03:00
Debug replay fixes (#23270)
* ensure motion masks from source camera are copied to replay * stop polling debug_replay/status after live_ready * use vod for constructing replay clips
This commit is contained in:
parent
a576ad5218
commit
5ef8b9b924
@ -86,10 +86,15 @@ class DebugReplayStopResponse(BaseModel):
|
|||||||
async def start_debug_replay(request: Request, body: DebugReplayStartBody):
|
async def start_debug_replay(request: Request, body: DebugReplayStartBody):
|
||||||
"""Start a debug replay session asynchronously."""
|
"""Start a debug replay session asynchronously."""
|
||||||
replay_manager = request.app.replay_manager
|
replay_manager = request.app.replay_manager
|
||||||
|
internal_port = request.app.frigate_config.networking.listen.internal
|
||||||
|
if type(internal_port) is str:
|
||||||
|
internal_port = int(internal_port.split(":")[-1])
|
||||||
|
|
||||||
source = RecordingDebugReplaySource(
|
source = RecordingDebugReplaySource(
|
||||||
source_camera=body.camera,
|
source_camera=body.camera,
|
||||||
start_ts=body.start_time,
|
start_ts=body.start_time,
|
||||||
end_ts=body.end_time,
|
end_ts=body.end_time,
|
||||||
|
internal_port=internal_port,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -245,11 +245,23 @@ class DebugReplayManager:
|
|||||||
"frame_shape",
|
"frame_shape",
|
||||||
"raw_mask",
|
"raw_mask",
|
||||||
"mask",
|
"mask",
|
||||||
"improved_contrast_enabled",
|
"enabled_in_config",
|
||||||
"rasterized_mask",
|
"rasterized_mask",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if source_config.motion.mask:
|
||||||
|
motion_dict["mask"] = {
|
||||||
|
mask_id: (
|
||||||
|
mask_cfg.model_dump(
|
||||||
|
exclude={"raw_coordinates", "enabled_in_config"}
|
||||||
|
)
|
||||||
|
if mask_cfg is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
for mask_id, mask_cfg in source_config.motion.mask.items()
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
"""Debug replay startup job: ffmpeg concat + camera config publish.
|
"""Debug replay startup job: ffmpeg remux + camera config publish.
|
||||||
|
|
||||||
The runner orchestrates the async portion of starting a debug replay
|
The runner orchestrates the async portion of starting a debug replay
|
||||||
session. The DebugReplayManager (in frigate.debug_replay) owns session
|
session. The DebugReplayManager (in frigate.debug_replay) owns session
|
||||||
@ -153,15 +153,22 @@ class DebugReplaySource(ABC):
|
|||||||
class RecordingDebugReplaySource(DebugReplaySource):
|
class RecordingDebugReplaySource(DebugReplaySource):
|
||||||
"""Replay source backed by the Recordings table.
|
"""Replay source backed by the Recordings table.
|
||||||
|
|
||||||
Builds a concat playlist of recording files covering the time range
|
Feeds ffmpeg the internal VOD endpoint so segments with mismatched
|
||||||
and feeds it to ffmpeg's concat demuxer.
|
SPS/PPS (e.g. across day/night transitions) stitch cleanly via HLS
|
||||||
|
discontinuities.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, source_camera: str, start_ts: float, end_ts: float) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
source_camera: str,
|
||||||
|
start_ts: float,
|
||||||
|
end_ts: float,
|
||||||
|
internal_port: int,
|
||||||
|
) -> None:
|
||||||
self._camera = source_camera
|
self._camera = source_camera
|
||||||
self._start_ts = start_ts
|
self._start_ts = start_ts
|
||||||
self._end_ts = end_ts
|
self._end_ts = end_ts
|
||||||
self._concat_file: Optional[str] = None
|
self._internal_port = internal_port
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source_camera(self) -> str:
|
def source_camera(self) -> str:
|
||||||
@ -185,18 +192,16 @@ class RecordingDebugReplaySource(DebugReplaySource):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def ffmpeg_input_args(self, working_dir: str) -> list[str]:
|
def ffmpeg_input_args(self, working_dir: str) -> list[str]:
|
||||||
replay_name = f"{REPLAY_CAMERA_PREFIX}{self._camera}"
|
playlist_url = (
|
||||||
concat_file = os.path.join(working_dir, f"{replay_name}_concat.txt")
|
f"http://127.0.0.1:{self._internal_port}/vod/{self._camera}"
|
||||||
recordings = query_recordings(self._camera, self._start_ts, self._end_ts)
|
f"/start/{self._start_ts}/end/{self._end_ts}/index.m3u8"
|
||||||
with open(concat_file, "w") as f:
|
)
|
||||||
for recording in recordings:
|
return [
|
||||||
f.write(f"file '{recording.path}'\n")
|
"-protocol_whitelist",
|
||||||
self._concat_file = concat_file
|
"pipe,file,http,tcp",
|
||||||
return ["-f", "concat", "-safe", "0", "-i", concat_file]
|
"-i",
|
||||||
|
playlist_url,
|
||||||
def cleanup(self, working_dir: str) -> None:
|
]
|
||||||
if self._concat_file:
|
|
||||||
_remove_silent(self._concat_file)
|
|
||||||
|
|
||||||
|
|
||||||
class ExportDebugReplaySource(DebugReplaySource):
|
class ExportDebugReplaySource(DebugReplaySource):
|
||||||
|
|||||||
@ -101,7 +101,10 @@ class TestStartDebugReplayJob(unittest.TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
start_debug_replay_job(
|
start_debug_replay_job(
|
||||||
source=RecordingDebugReplaySource(
|
source=RecordingDebugReplaySource(
|
||||||
source_camera="missing", start_ts=100.0, end_ts=200.0
|
source_camera="missing",
|
||||||
|
start_ts=100.0,
|
||||||
|
end_ts=200.0,
|
||||||
|
internal_port=5000,
|
||||||
),
|
),
|
||||||
frigate_config=self.frigate_config,
|
frigate_config=self.frigate_config,
|
||||||
config_publisher=self.publisher,
|
config_publisher=self.publisher,
|
||||||
@ -112,7 +115,10 @@ class TestStartDebugReplayJob(unittest.TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
start_debug_replay_job(
|
start_debug_replay_job(
|
||||||
source=RecordingDebugReplaySource(
|
source=RecordingDebugReplaySource(
|
||||||
source_camera="front", start_ts=200.0, end_ts=100.0
|
source_camera="front",
|
||||||
|
start_ts=200.0,
|
||||||
|
end_ts=100.0,
|
||||||
|
internal_port=5000,
|
||||||
),
|
),
|
||||||
frigate_config=self.frigate_config,
|
frigate_config=self.frigate_config,
|
||||||
config_publisher=self.publisher,
|
config_publisher=self.publisher,
|
||||||
@ -126,7 +132,10 @@ class TestStartDebugReplayJob(unittest.TestCase):
|
|||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
start_debug_replay_job(
|
start_debug_replay_job(
|
||||||
source=RecordingDebugReplaySource(
|
source=RecordingDebugReplaySource(
|
||||||
source_camera="front", start_ts=100.0, end_ts=200.0
|
source_camera="front",
|
||||||
|
start_ts=100.0,
|
||||||
|
end_ts=200.0,
|
||||||
|
internal_port=5000,
|
||||||
),
|
),
|
||||||
frigate_config=self.frigate_config,
|
frigate_config=self.frigate_config,
|
||||||
config_publisher=self.publisher,
|
config_publisher=self.publisher,
|
||||||
@ -156,7 +165,10 @@ class TestStartDebugReplayJob(unittest.TestCase):
|
|||||||
):
|
):
|
||||||
job_id = start_debug_replay_job(
|
job_id = start_debug_replay_job(
|
||||||
source=RecordingDebugReplaySource(
|
source=RecordingDebugReplaySource(
|
||||||
source_camera="front", start_ts=100.0, end_ts=200.0
|
source_camera="front",
|
||||||
|
start_ts=100.0,
|
||||||
|
end_ts=200.0,
|
||||||
|
internal_port=5000,
|
||||||
),
|
),
|
||||||
frigate_config=self.frigate_config,
|
frigate_config=self.frigate_config,
|
||||||
config_publisher=self.publisher,
|
config_publisher=self.publisher,
|
||||||
@ -193,7 +205,10 @@ class TestStartDebugReplayJob(unittest.TestCase):
|
|||||||
):
|
):
|
||||||
start_debug_replay_job(
|
start_debug_replay_job(
|
||||||
source=RecordingDebugReplaySource(
|
source=RecordingDebugReplaySource(
|
||||||
source_camera="front", start_ts=100.0, end_ts=200.0
|
source_camera="front",
|
||||||
|
start_ts=100.0,
|
||||||
|
end_ts=200.0,
|
||||||
|
internal_port=5000,
|
||||||
),
|
),
|
||||||
frigate_config=self.frigate_config,
|
frigate_config=self.frigate_config,
|
||||||
config_publisher=self.publisher,
|
config_publisher=self.publisher,
|
||||||
@ -203,7 +218,10 @@ class TestStartDebugReplayJob(unittest.TestCase):
|
|||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
start_debug_replay_job(
|
start_debug_replay_job(
|
||||||
source=RecordingDebugReplaySource(
|
source=RecordingDebugReplaySource(
|
||||||
source_camera="front", start_ts=100.0, end_ts=200.0
|
source_camera="front",
|
||||||
|
start_ts=100.0,
|
||||||
|
end_ts=200.0,
|
||||||
|
internal_port=5000,
|
||||||
),
|
),
|
||||||
frigate_config=self.frigate_config,
|
frigate_config=self.frigate_config,
|
||||||
config_publisher=self.publisher,
|
config_publisher=self.publisher,
|
||||||
@ -271,7 +289,10 @@ class TestRunnerHappyPath(unittest.TestCase):
|
|||||||
):
|
):
|
||||||
start_debug_replay_job(
|
start_debug_replay_job(
|
||||||
source=RecordingDebugReplaySource(
|
source=RecordingDebugReplaySource(
|
||||||
source_camera="front", start_ts=100.0, end_ts=200.0
|
source_camera="front",
|
||||||
|
start_ts=100.0,
|
||||||
|
end_ts=200.0,
|
||||||
|
internal_port=5000,
|
||||||
),
|
),
|
||||||
frigate_config=self.frigate_config,
|
frigate_config=self.frigate_config,
|
||||||
config_publisher=self.publisher,
|
config_publisher=self.publisher,
|
||||||
@ -342,7 +363,10 @@ class TestRunnerFailurePath(unittest.TestCase):
|
|||||||
):
|
):
|
||||||
start_debug_replay_job(
|
start_debug_replay_job(
|
||||||
source=RecordingDebugReplaySource(
|
source=RecordingDebugReplaySource(
|
||||||
source_camera="front", start_ts=100.0, end_ts=200.0
|
source_camera="front",
|
||||||
|
start_ts=100.0,
|
||||||
|
end_ts=200.0,
|
||||||
|
internal_port=5000,
|
||||||
),
|
),
|
||||||
frigate_config=self.frigate_config,
|
frigate_config=self.frigate_config,
|
||||||
config_publisher=self.publisher,
|
config_publisher=self.publisher,
|
||||||
@ -420,7 +444,10 @@ class TestRunnerCancellation(unittest.TestCase):
|
|||||||
):
|
):
|
||||||
start_debug_replay_job(
|
start_debug_replay_job(
|
||||||
source=RecordingDebugReplaySource(
|
source=RecordingDebugReplaySource(
|
||||||
source_camera="front", start_ts=100.0, end_ts=200.0
|
source_camera="front",
|
||||||
|
start_ts=100.0,
|
||||||
|
end_ts=200.0,
|
||||||
|
internal_port=5000,
|
||||||
),
|
),
|
||||||
frigate_config=self.frigate_config,
|
frigate_config=self.frigate_config,
|
||||||
config_publisher=self.publisher,
|
config_publisher=self.publisher,
|
||||||
|
|||||||
@ -121,7 +121,7 @@ export default function Replay() {
|
|||||||
mutate: refreshStatus,
|
mutate: refreshStatus,
|
||||||
isLoading,
|
isLoading,
|
||||||
} = useSWR<DebugReplayStatus>("debug_replay/status", {
|
} = useSWR<DebugReplayStatus>("debug_replay/status", {
|
||||||
refreshInterval: 1000,
|
refreshInterval: (latestData) => (latestData?.live_ready ? 0 : 1000),
|
||||||
});
|
});
|
||||||
const { payload: replayJob } =
|
const { payload: replayJob } =
|
||||||
useJobStatus<DebugReplayJobResults>("debug_replay");
|
useJobStatus<DebugReplayJobResults>("debug_replay");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user