Make camera recordings mover asynchronous

This commit is contained in:
Nick Mowen 2023-06-14 16:21:17 -06:00
parent ff90db30e6
commit c217c34e3d

View File

@ -1,5 +1,6 @@
"""Maintain recording segments in cache.""" """Maintain recording segments in cache."""
import asyncio
import datetime import datetime
import logging import logging
import multiprocessing as mp import multiprocessing as mp
@ -13,6 +14,7 @@ from collections import defaultdict
from multiprocessing.synchronize import Event as MpEvent from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path from pathlib import Path
from typing import Any, Tuple from typing import Any, Tuple
import traceback
import psutil import psutil
@ -42,7 +44,7 @@ class RecordingMaintainer(threading.Thread):
self.recordings_info: dict[str, Any] = defaultdict(list) self.recordings_info: dict[str, Any] = defaultdict(list)
self.end_time_cache: dict[str, Tuple[datetime.datetime, float]] = {} self.end_time_cache: dict[str, Tuple[datetime.datetime, float]] = {}
def move_files(self) -> None: async def move_files(self) -> None:
cache_files = sorted( cache_files = sorted(
[ [
d d
@ -121,9 +123,16 @@ class RecordingMaintainer(threading.Thread):
) )
.order_by(Event.start_time) .order_by(Event.start_time)
) )
for r in recordings:
cache_path = r["cache_path"] await asyncio.gather(
start_time = r["start_time"] *(self.validate_segment(camera, events, r) for r in recordings)
)
async def validate_segment(
self, camera: str, events: Event, recording: dict[str, any]
) -> None:
cache_path = recording["cache_path"]
start_time = recording["start_time"]
# Just delete files if recordings are turned off # Just delete files if recordings are turned off
if ( if (
@ -132,7 +141,7 @@ class RecordingMaintainer(threading.Thread):
): ):
Path(cache_path).unlink(missing_ok=True) Path(cache_path).unlink(missing_ok=True)
self.end_time_cache.pop(cache_path, None) self.end_time_cache.pop(cache_path, None)
continue return
if cache_path in self.end_time_cache: if cache_path in self.end_time_cache:
end_time, duration = self.end_time_cache[cache_path] end_time, duration = self.end_time_cache[cache_path]
@ -163,11 +172,9 @@ class RecordingMaintainer(threading.Thread):
f"Failed to probe corrupt segment {cache_path} : {p.returncode} - {str(p.stderr)}" f"Failed to probe corrupt segment {cache_path} : {p.returncode} - {str(p.stderr)}"
) )
logger.warning( logger.warning(f"Discarding a corrupt recording segment: {cache_path}")
f"Discarding a corrupt recording segment: {cache_path}"
)
Path(cache_path).unlink(missing_ok=True) Path(cache_path).unlink(missing_ok=True)
continue return
# if cached file's start_time is earlier than the retain days for the camera # if cached file's start_time is earlier than the retain days for the camera
if start_time <= ( if start_time <= (
@ -191,17 +198,12 @@ class RecordingMaintainer(threading.Thread):
# if the event is in progress or ends after the recording starts, keep it # if the event is in progress or ends after the recording starts, keep it
# and stop looking at events # and stop looking at events
if ( if event.end_time is None or event.end_time >= start_time.timestamp():
event.end_time is None
or event.end_time >= start_time.timestamp()
):
overlaps = True overlaps = True
break break
if overlaps: if overlaps:
record_mode = self.config.cameras[ record_mode = self.config.cameras[camera].record.events.retain.mode
camera
].record.events.retain.mode
# move from cache to recordings immediately # move from cache to recordings immediately
self.store_segment( self.store_segment(
camera, camera,
@ -214,12 +216,8 @@ class RecordingMaintainer(threading.Thread):
# if it doesn't overlap with an event, go ahead and drop the segment # if it doesn't overlap with an event, go ahead and drop the segment
# if it ends more than the configured pre_capture for the camera # if it ends more than the configured pre_capture for the camera
else: else:
pre_capture = self.config.cameras[ pre_capture = self.config.cameras[camera].record.events.pre_capture
camera most_recently_processed_frame_time = self.recordings_info[camera][-1][0]
].record.events.pre_capture
most_recently_processed_frame_time = self.recordings_info[
camera
][-1][0]
retain_cutoff = most_recently_processed_frame_time - pre_capture retain_cutoff = most_recently_processed_frame_time - pre_capture
if end_time.timestamp() < retain_cutoff: if end_time.timestamp() < retain_cutoff:
Path(cache_path).unlink(missing_ok=True) Path(cache_path).unlink(missing_ok=True)
@ -386,12 +384,13 @@ class RecordingMaintainer(threading.Thread):
break break
try: try:
self.move_files() asyncio.run(self.move_files())
except Exception as e: except Exception as e:
logger.error( logger.error(
"Error occurred when attempting to maintain recording cache" "Error occurred when attempting to maintain recording cache"
) )
logger.error(e) logger.error(e)
logger.error(str(traceback.print_exc()))
duration = datetime.datetime.now().timestamp() - run_start duration = datetime.datetime.now().timestamp() - run_start
wait_time = max(0, 5 - duration) wait_time = max(0, 5 - duration)