mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	* Fix the `Any` typing hint treewide There has been confusion between the Any type[1] and the any function[2] in typing hints. [1] https://docs.python.org/3/library/typing.html#typing.Any [2] https://docs.python.org/3/library/functions.html#any * Fix typing for various frame_shape members Frame shapes are most likely defined by height and width, so a single int cannot express that. * Wrap gpu stats functions in Optional[] These can return `None`, so they need to be `Type | None`, which is what `Optional` expresses very nicely. * Fix return type in get_latest_segment_datetime Returns a datetime object, not an integer. * Make the return type of FrameManager.write optional This is necessary since the SharedMemoryFrameManager.write function can return None. * Fix total_seconds() return type in get_tz_modifiers The function returns a float, not an int. https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds * Account for floating point results in to_relative_box Because the function uses division the return types may either be int or float. * Resolve ruff deprecation warning The config has been split into formatter and linter, and the global options are deprecated.
		
			
				
	
	
		
			189 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Record events for object, audio, etc. detections."""
 | |
| 
 | |
| import logging
 | |
| import queue
 | |
| import threading
 | |
| from multiprocessing import Queue
 | |
| from multiprocessing.synchronize import Event as MpEvent
 | |
| from typing import Any
 | |
| 
 | |
| from frigate.config import FrigateConfig
 | |
| from frigate.events.maintainer import EventStateEnum, EventTypeEnum
 | |
| from frigate.models import Timeline
 | |
| from frigate.util.builtin import to_relative_box
 | |
| 
 | |
| logger = logging.getLogger(__name__)
 | |
| 
 | |
| 
 | |
| class TimelineProcessor(threading.Thread):
 | |
|     """Handle timeline queue and update DB."""
 | |
| 
 | |
|     def __init__(
 | |
|         self,
 | |
|         config: FrigateConfig,
 | |
|         queue: Queue,
 | |
|         stop_event: MpEvent,
 | |
|     ) -> None:
 | |
|         super().__init__(name="timeline_processor")
 | |
|         self.config = config
 | |
|         self.queue = queue
 | |
|         self.stop_event = stop_event
 | |
|         self.pre_event_cache: dict[str, list[dict[str, Any]]] = {}
 | |
| 
 | |
|     def run(self) -> None:
 | |
|         while not self.stop_event.is_set():
 | |
|             try:
 | |
|                 (
 | |
|                     camera,
 | |
|                     input_type,
 | |
|                     event_type,
 | |
|                     prev_event_data,
 | |
|                     event_data,
 | |
|                 ) = self.queue.get(timeout=1)
 | |
|             except queue.Empty:
 | |
|                 continue
 | |
| 
 | |
|             if input_type == EventTypeEnum.tracked_object:
 | |
|                 # None prev_event_data is only allowed for the start of an event
 | |
|                 if event_type != EventStateEnum.start and prev_event_data is None:
 | |
|                     continue
 | |
| 
 | |
|                 self.handle_object_detection(
 | |
|                     camera, event_type, prev_event_data, event_data
 | |
|                 )
 | |
|             elif input_type == EventTypeEnum.api:
 | |
|                 self.handle_api_entry(camera, event_type, event_data)
 | |
| 
 | |
|     def insert_or_save(
 | |
|         self,
 | |
|         entry: dict[str, Any],
 | |
|         prev_event_data: dict[Any, Any],
 | |
|         event_data: dict[Any, Any],
 | |
|     ) -> None:
 | |
|         """Insert into db or cache."""
 | |
|         id = entry[Timeline.source_id]
 | |
|         if not event_data["has_clip"] and not event_data["has_snapshot"]:
 | |
|             # the related event has not been saved yet, should be added to cache
 | |
|             if id in self.pre_event_cache.keys():
 | |
|                 self.pre_event_cache[id].append(entry)
 | |
|             else:
 | |
|                 self.pre_event_cache[id] = [entry]
 | |
|         else:
 | |
|             # the event is saved, insert to db and insert cached into db
 | |
|             if id in self.pre_event_cache.keys():
 | |
|                 for e in self.pre_event_cache[id]:
 | |
|                     Timeline.insert(e).execute()
 | |
| 
 | |
|                 self.pre_event_cache.pop(id)
 | |
| 
 | |
|             Timeline.insert(entry).execute()
 | |
| 
 | |
|     def handle_object_detection(
 | |
|         self,
 | |
|         camera: str,
 | |
|         event_type: str,
 | |
|         prev_event_data: dict[Any, Any],
 | |
|         event_data: dict[Any, Any],
 | |
|     ) -> bool:
 | |
|         """Handle object detection."""
 | |
|         save = False
 | |
|         camera_config = self.config.cameras[camera]
 | |
|         event_id = event_data["id"]
 | |
| 
 | |
|         timeline_entry = {
 | |
|             Timeline.timestamp: event_data["frame_time"],
 | |
|             Timeline.camera: camera,
 | |
|             Timeline.source: "tracked_object",
 | |
|             Timeline.source_id: event_id,
 | |
|             Timeline.data: {
 | |
|                 "box": to_relative_box(
 | |
|                     camera_config.detect.width,
 | |
|                     camera_config.detect.height,
 | |
|                     event_data["box"],
 | |
|                 ),
 | |
|                 "label": event_data["label"],
 | |
|                 "sub_label": event_data.get("sub_label"),
 | |
|                 "region": to_relative_box(
 | |
|                     camera_config.detect.width,
 | |
|                     camera_config.detect.height,
 | |
|                     event_data["region"],
 | |
|                 ),
 | |
|                 "attribute": "",
 | |
|             },
 | |
|         }
 | |
| 
 | |
|         # update sub labels for existing entries that haven't been added yet
 | |
|         if (
 | |
|             prev_event_data != None
 | |
|             and prev_event_data["sub_label"] != event_data["sub_label"]
 | |
|             and event_id in self.pre_event_cache.keys()
 | |
|         ):
 | |
|             for e in self.pre_event_cache[event_id]:
 | |
|                 e[Timeline.data]["sub_label"] = event_data["sub_label"]
 | |
| 
 | |
|         if event_type == EventStateEnum.start:
 | |
|             timeline_entry[Timeline.class_type] = "visible"
 | |
|             save = True
 | |
|         elif event_type == EventStateEnum.update:
 | |
|             if (
 | |
|                 len(prev_event_data["current_zones"]) < len(event_data["current_zones"])
 | |
|                 and not event_data["stationary"]
 | |
|             ):
 | |
|                 timeline_entry[Timeline.class_type] = "entered_zone"
 | |
|                 timeline_entry[Timeline.data]["zones"] = event_data["current_zones"]
 | |
|                 save = True
 | |
|             elif prev_event_data["stationary"] != event_data["stationary"]:
 | |
|                 timeline_entry[Timeline.class_type] = (
 | |
|                     "stationary" if event_data["stationary"] else "active"
 | |
|                 )
 | |
|                 save = True
 | |
|             elif prev_event_data["attributes"] == {} and event_data["attributes"] != {}:
 | |
|                 timeline_entry[Timeline.class_type] = "attribute"
 | |
|                 timeline_entry[Timeline.data]["attribute"] = list(
 | |
|                     event_data["attributes"].keys()
 | |
|                 )[0]
 | |
|                 save = True
 | |
|         elif event_type == EventStateEnum.end:
 | |
|             timeline_entry[Timeline.class_type] = "gone"
 | |
|             save = True
 | |
| 
 | |
|         if save:
 | |
|             self.insert_or_save(timeline_entry, prev_event_data, event_data)
 | |
| 
 | |
|     def handle_api_entry(
 | |
|         self,
 | |
|         camera: str,
 | |
|         event_type: str,
 | |
|         event_data: dict[Any, Any],
 | |
|     ) -> bool:
 | |
|         if event_type != "new":
 | |
|             return False
 | |
| 
 | |
|         if event_data.get("type", "api") == "audio":
 | |
|             timeline_entry = {
 | |
|                 Timeline.class_type: "heard",
 | |
|                 Timeline.timestamp: event_data["start_time"],
 | |
|                 Timeline.camera: camera,
 | |
|                 Timeline.source: "audio",
 | |
|                 Timeline.source_id: event_data["id"],
 | |
|                 Timeline.data: {
 | |
|                     "label": event_data["label"],
 | |
|                     "sub_label": event_data.get("sub_label"),
 | |
|                 },
 | |
|             }
 | |
|         else:
 | |
|             timeline_entry = {
 | |
|                 Timeline.class_type: "external",
 | |
|                 Timeline.timestamp: event_data["start_time"],
 | |
|                 Timeline.camera: camera,
 | |
|                 Timeline.source: "api",
 | |
|                 Timeline.source_id: event_data["id"],
 | |
|                 Timeline.data: {
 | |
|                     "label": event_data["label"],
 | |
|                     "sub_label": event_data.get("sub_label"),
 | |
|                 },
 | |
|             }
 | |
| 
 | |
|         Timeline.insert(timeline_entry).execute()
 | |
|         return True
 |