diff --git a/frigate/camera/__init__.py b/frigate/camera/__init__.py index 0461c98cb..85831653e 100644 --- a/frigate/camera/__init__.py +++ b/frigate/camera/__init__.py @@ -1,26 +1,27 @@ import multiprocessing as mp -from multiprocessing.managers import SyncManager +import queue +from multiprocessing.managers import SyncManager, ValueProxy from multiprocessing.sharedctypes import Synchronized from multiprocessing.synchronize import Event class CameraMetrics: - camera_fps: Synchronized - detection_fps: Synchronized - detection_frame: Synchronized - process_fps: Synchronized - skipped_fps: Synchronized - read_start: Synchronized - audio_rms: Synchronized - audio_dBFS: Synchronized + camera_fps: ValueProxy[float] + detection_fps: ValueProxy[float] + detection_frame: ValueProxy[float] + process_fps: ValueProxy[float] + skipped_fps: ValueProxy[float] + read_start: ValueProxy[float] + audio_rms: ValueProxy[float] + audio_dBFS: ValueProxy[float] - frame_queue: mp.Queue + frame_queue: queue.Queue - process_pid: Synchronized - capture_process_pid: Synchronized - ffmpeg_pid: Synchronized - reconnects_last_hour: Synchronized - stalls_last_hour: Synchronized + process_pid: ValueProxy[int] + capture_process_pid: ValueProxy[int] + ffmpeg_pid: ValueProxy[int] + reconnects_last_hour: ValueProxy[int] + stalls_last_hour: ValueProxy[int] def __init__(self, manager: SyncManager): self.camera_fps = manager.Value("d", 0) @@ -56,14 +57,14 @@ class PTZMetrics: reset: Event def __init__(self, *, autotracker_enabled: bool): - self.autotracker_enabled = mp.Value("i", autotracker_enabled) + self.autotracker_enabled = mp.Value("i", autotracker_enabled) # type: ignore[assignment] - self.start_time = mp.Value("d", 0) - self.stop_time = mp.Value("d", 0) - self.frame_time = mp.Value("d", 0) - self.zoom_level = mp.Value("d", 0) - self.max_zoom = mp.Value("d", 0) - self.min_zoom = mp.Value("d", 0) + self.start_time = mp.Value("d", 0) # type: ignore[assignment] + self.stop_time = mp.Value("d", 0) # type: ignore[assignment] + self.frame_time = mp.Value("d", 0) # type: ignore[assignment] + self.zoom_level = mp.Value("d", 0) # type: ignore[assignment] + self.max_zoom = mp.Value("d", 0) # type: ignore[assignment] + self.min_zoom = mp.Value("d", 0) # type: ignore[assignment] self.tracking_active = mp.Event() self.motor_stopped = mp.Event() diff --git a/frigate/camera/activity_manager.py b/frigate/camera/activity_manager.py index 3f229e490..38425add9 100644 --- a/frigate/camera/activity_manager.py +++ b/frigate/camera/activity_manager.py @@ -37,6 +37,9 @@ class CameraActivityManager: self.__init_camera(camera_config) def __init_camera(self, camera_config: CameraConfig) -> None: + if camera_config.name is None: + return + self.last_camera_activity[camera_config.name] = {} self.camera_all_object_counts[camera_config.name] = Counter() self.camera_active_object_counts[camera_config.name] = Counter() @@ -114,7 +117,7 @@ class CameraActivityManager: self.last_camera_activity = new_activity def compare_camera_activity( - self, camera: str, new_activity: dict[str, Any] + self, camera: str, new_activity: list[dict[str, Any]] ) -> None: all_objects = Counter( obj["label"].replace("-verified", "") for obj in new_activity @@ -175,6 +178,9 @@ class AudioActivityManager: self.__init_camera(camera_config) def __init_camera(self, camera_config: CameraConfig) -> None: + if camera_config.name is None: + return + self.current_audio_detections[camera_config.name] = {} def update_activity(self, new_activity: dict[str, dict[str, Any]]) -> None: @@ -202,7 +208,7 @@ class AudioActivityManager: def compare_audio_activity( self, camera: str, new_detections: list[tuple[str, float]], now: float - ) -> None: + ) -> bool: camera_config = self.config.cameras.get(camera) if camera_config is None: return False diff --git a/frigate/camera/maintainer.py b/frigate/camera/maintainer.py index 9cfdcc7f3..c4ddc51e8 100644 --- a/frigate/camera/maintainer.py +++ b/frigate/camera/maintainer.py @@ -102,7 +102,7 @@ class CameraMaintainer(threading.Thread): f"recommend increasing it to at least {shm_stats['min_shm']}MB." ) - return shm_stats["shm_frame_count"] + return int(shm_stats["shm_frame_count"]) def __start_camera_processor( self, name: str, config: CameraConfig, runtime: bool = False @@ -152,10 +152,10 @@ class CameraMaintainer(threading.Thread): camera_stop_event, self.config.logger, ) - self.camera_processes[config.name] = camera_process + self.camera_processes[name] = camera_process camera_process.start() - self.camera_metrics[config.name].process_pid.value = camera_process.pid - logger.info(f"Camera processor started for {config.name}: {camera_process.pid}") + self.camera_metrics[name].process_pid.value = camera_process.pid + logger.info(f"Camera processor started for {name}: {camera_process.pid}") def __start_camera_capture( self, name: str, config: CameraConfig, runtime: bool = False @@ -219,7 +219,7 @@ class CameraMaintainer(threading.Thread): logger.info(f"Closing frame queue for {camera}") empty_and_close_queue(self.camera_metrics[camera].frame_queue) - def run(self): + def run(self) -> None: self.__init_historical_regions() # start camera processes diff --git a/frigate/camera/state.py b/frigate/camera/state.py index f609a05f9..7355afe6b 100644 --- a/frigate/camera/state.py +++ b/frigate/camera/state.py @@ -31,26 +31,26 @@ logger = logging.getLogger(__name__) class CameraState: def __init__( self, - name, + name: str, config: FrigateConfig, frame_manager: SharedMemoryFrameManager, ptz_autotracker_thread: PtzAutoTrackerThread, - ): + ) -> None: self.name = name self.config = config self.camera_config = config.cameras[name] self.frame_manager = frame_manager self.best_objects: dict[str, TrackedObject] = {} self.tracked_objects: dict[str, TrackedObject] = {} - self.frame_cache = {} - self.zone_objects = defaultdict(list) + self.frame_cache: dict[float, dict[str, Any]] = {} + self.zone_objects: defaultdict[str, list[Any]] = defaultdict(list) self._current_frame = np.zeros(self.camera_config.frame_shape_yuv, np.uint8) self.current_frame_lock = threading.Lock() self.current_frame_time = 0.0 - self.motion_boxes = [] - self.regions = [] - self.previous_frame_id = None - self.callbacks = defaultdict(list) + self.motion_boxes: list[tuple[int, int, int, int]] = [] + self.regions: list[tuple[int, int, int, int]] = [] + self.previous_frame_id: str | None = None + self.callbacks: defaultdict[str, list[Callable]] = defaultdict(list) self.ptz_autotracker_thread = ptz_autotracker_thread self.prev_enabled = self.camera_config.enabled @@ -62,10 +62,10 @@ class CameraState: motion_boxes = self.motion_boxes.copy() regions = self.regions.copy() - frame_copy = cv2.cvtColor(frame_copy, cv2.COLOR_YUV2BGR_I420) + frame_copy = cv2.cvtColor(frame_copy, cv2.COLOR_YUV2BGR_I420) # type: ignore[assignment] # draw on the frame if draw_options.get("mask"): - mask_overlay = np.where(self.camera_config.motion.rasterized_mask == [0]) + mask_overlay = np.where(self.camera_config.motion.rasterized_mask == [0]) # type: ignore[attr-defined] frame_copy[mask_overlay] = [0, 0, 0] if draw_options.get("bounding_boxes"): @@ -97,7 +97,7 @@ class CameraState: and obj["id"] == self.ptz_autotracker_thread.ptz_autotracker.tracked_object[ self.name - ].obj_data["id"] + ].obj_data["id"] # type: ignore[attr-defined] and obj["frame_time"] == frame_time ): thickness = 5 @@ -109,10 +109,12 @@ class CameraState: if ( self.camera_config.onvif.autotracking.zooming != ZoomingModeEnum.disabled + and self.camera_config.detect.width is not None + and self.camera_config.detect.height is not None ): max_target_box = self.ptz_autotracker_thread.ptz_autotracker.tracked_object_metrics[ self.name - ]["max_target_box"] + ]["max_target_box"] # type: ignore[index] side_length = max_target_box * ( max( self.camera_config.detect.width, @@ -221,14 +223,14 @@ class CameraState: ) if draw_options.get("timestamp"): - color = self.camera_config.timestamp_style.color + ts_color = self.camera_config.timestamp_style.color draw_timestamp( frame_copy, frame_time, self.camera_config.timestamp_style.format, font_effect=self.camera_config.timestamp_style.effect, font_thickness=self.camera_config.timestamp_style.thickness, - font_color=(color.blue, color.green, color.red), + font_color=(ts_color.blue, ts_color.green, ts_color.red), position=self.camera_config.timestamp_style.position, ) @@ -273,10 +275,10 @@ class CameraState: return frame_copy - def finished(self, obj_id): + def finished(self, obj_id: str) -> None: del self.tracked_objects[obj_id] - def on(self, event_type: str, callback: Callable): + def on(self, event_type: str, callback: Callable[..., Any]) -> None: self.callbacks[event_type].append(callback) def update( @@ -286,7 +288,7 @@ class CameraState: current_detections: dict[str, dict[str, Any]], motion_boxes: list[tuple[int, int, int, int]], regions: list[tuple[int, int, int, int]], - ): + ) -> None: current_frame = self.frame_manager.get( frame_name, self.camera_config.frame_shape_yuv ) @@ -313,7 +315,7 @@ class CameraState: f"{self.name}: New object, adding {frame_time} to frame cache for {id}" ) self.frame_cache[frame_time] = { - "frame": np.copy(current_frame), + "frame": np.copy(current_frame), # type: ignore[arg-type] "object_id": id, } @@ -356,7 +358,8 @@ class CameraState: if thumb_update and current_frame is not None: # ensure this frame is stored in the cache if ( - updated_obj.thumbnail_data["frame_time"] == frame_time + updated_obj.thumbnail_data is not None + and updated_obj.thumbnail_data["frame_time"] == frame_time and frame_time not in self.frame_cache ): logger.debug( @@ -397,7 +400,7 @@ class CameraState: # TODO: can i switch to looking this up and only changing when an event ends? # maintain best objects - camera_activity: dict[str, list[Any]] = { + camera_activity: dict[str, Any] = { "motion": len(motion_boxes) > 0, "objects": [], } @@ -411,10 +414,7 @@ class CameraState: sub_label = None if obj.obj_data.get("sub_label"): - if ( - obj.obj_data.get("sub_label")[0] - in self.config.model.all_attributes - ): + if obj.obj_data["sub_label"][0] in self.config.model.all_attributes: label = obj.obj_data["sub_label"][0] else: label = f"{object_type}-verified" @@ -449,14 +449,19 @@ class CameraState: # if the object is a higher score than the current best score # or the current object is older than desired, use the new object if ( - is_better_thumbnail( + current_best.thumbnail_data is not None + and obj.thumbnail_data is not None + and is_better_thumbnail( object_type, current_best.thumbnail_data, obj.thumbnail_data, self.camera_config.frame_shape, ) - or (now - current_best.thumbnail_data["frame_time"]) - > self.camera_config.best_image_timeout + or ( + current_best.thumbnail_data is not None + and (now - current_best.thumbnail_data["frame_time"]) + > self.camera_config.best_image_timeout + ) ): self.send_mqtt_snapshot(obj, object_type) else: @@ -472,7 +477,9 @@ class CameraState: if obj.thumbnail_data is not None } current_best_frames = { - obj.thumbnail_data["frame_time"] for obj in self.best_objects.values() + obj.thumbnail_data["frame_time"] + for obj in self.best_objects.values() + if obj.thumbnail_data is not None } thumb_frames_to_delete = [ t @@ -540,7 +547,7 @@ class CameraState: with open( os.path.join( CLIPS_DIR, - f"{self.camera_config.name}-{event_id}-clean.webp", + f"{self.name}-{event_id}-clean.webp", ), "wb", ) as p: @@ -549,7 +556,7 @@ class CameraState: # create thumbnail with max height of 175 and save width = int(175 * img_frame.shape[1] / img_frame.shape[0]) thumb = cv2.resize(img_frame, dsize=(width, 175), interpolation=cv2.INTER_AREA) - thumb_path = os.path.join(THUMB_DIR, self.camera_config.name) + thumb_path = os.path.join(THUMB_DIR, self.name) os.makedirs(thumb_path, exist_ok=True) cv2.imwrite(os.path.join(thumb_path, f"{event_id}.webp"), thumb) diff --git a/frigate/ffmpeg_presets.py b/frigate/ffmpeg_presets.py index 03cd3c414..4ebbd6c80 100644 --- a/frigate/ffmpeg_presets.py +++ b/frigate/ffmpeg_presets.py @@ -530,7 +530,9 @@ PRESETS_RECORD_OUTPUT = { } -def parse_preset_output_record(arg: Any, force_record_hvc1: bool) -> Optional[list[str]]: +def parse_preset_output_record( + arg: Any, force_record_hvc1: bool +) -> Optional[list[str]]: """Return the correct preset if in preset format otherwise return None.""" if not isinstance(arg, str): return None diff --git a/frigate/mypy.ini b/frigate/mypy.ini index 441b5080d..cde4a3fe6 100644 --- a/frigate/mypy.ini +++ b/frigate/mypy.ini @@ -24,21 +24,9 @@ no_implicit_reexport = true [mypy-frigate.*] ignore_errors = false -[mypy-frigate.__main__] -disallow_untyped_calls = false - -[mypy-frigate.app] -disallow_untyped_calls = false - -[mypy-frigate.watchdog] -disallow_untyped_calls = false - [mypy-frigate.api.*] ignore_errors = true -[mypy-frigate.camera.*] -ignore_errors = true - [mypy-frigate.config.*] ignore_errors = true @@ -66,13 +54,7 @@ ignore_errors = true [mypy-frigate.ptz.*] ignore_errors = true -[mypy-frigate.stats.emitter] -ignore_errors = true - -[mypy-frigate.stats.prometheus] -ignore_errors = true - -[mypy-frigate.stats.util] +[mypy-frigate.stats.*] ignore_errors = true [mypy-frigate.test.*] diff --git a/frigate/track/tracked_object.py b/frigate/track/tracked_object.py index 7d46b72fd..44d270875 100644 --- a/frigate/track/tracked_object.py +++ b/frigate/track/tracked_object.py @@ -67,8 +67,8 @@ class TrackedObject: self.has_snapshot = False self.top_score = self.computed_score = 0.0 self.thumbnail_data: dict[str, Any] | None = None - self.last_updated = 0 - self.last_published = 0 + self.last_updated: float = 0 + self.last_published: float = 0 self.frame = None self.active = True self.pending_loitering = False