From 229d134cb44db01285da2dd913f515abc45d418a Mon Sep 17 00:00:00 2001 From: Nick Mowen Date: Thu, 20 Apr 2023 15:37:36 -0600 Subject: [PATCH] Handle timeline queue from tracked object data --- frigate/app.py | 8 ++++ frigate/models.py | 2 +- frigate/timeline.py | 97 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 frigate/timeline.py diff --git a/frigate/app.py b/frigate/app.py index d6a9ce61f..5859aa553 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -30,6 +30,7 @@ from frigate.plus import PlusApi from frigate.record import RecordingCleanup, RecordingMaintainer from frigate.stats import StatsEmitter, stats_init from frigate.storage import StorageMaintainer +from frigate.timeline import TimelineProcessor from frigate.version import VERSION from frigate.video import capture_camera, track_camera from frigate.watchdog import FrigateWatchdog @@ -135,6 +136,9 @@ class FrigateApp: # Queue for recordings info self.recordings_info_queue: Queue = mp.Queue() + # Queue for timeline events + self.timeline_queue: Queue = mp.Queue() + def init_database(self) -> None: # Migrate DB location old_db_path = os.path.join(CLIPS_DIR, "frigate.db") @@ -314,6 +318,10 @@ class FrigateApp: self.storage_maintainer = StorageMaintainer(self.config, self.stop_event) self.storage_maintainer.start() + def start_timeline_processor(self) -> None: + self.timeline_processor = TimelineProcessor(self.timeline_queue, self.stop_event) + self.timeline_processor.start() + def start_stats_emitter(self) -> None: self.stats_emitter = StatsEmitter( self.config, diff --git a/frigate/models.py b/frigate/models.py index 7634e71a4..6cb96a5ba 100644 --- a/frigate/models.py +++ b/frigate/models.py @@ -36,7 +36,7 @@ class Timeline(Model): # type: ignore[misc] timestamp = DateTimeField() camera = CharField(index=True, max_length=20) source = CharField(index=True, max_length=20) # ex: tracked object, audio, external - class_type = CharField(max_length=50) # ex: entered_front_yard, heard_dog_barking + class_type = CharField(max_length=50) # ex: entered_zone, audio_heard data = JSONField() # ex: tracked object id, region, box, etc. diff --git a/frigate/timeline.py b/frigate/timeline.py new file mode 100644 index 000000000..6dfae68e5 --- /dev/null +++ b/frigate/timeline.py @@ -0,0 +1,97 @@ +"""Record events for object, audio, etc. detections.""" + +import logging +import threading +import queue + +from enum import Enum + +from frigate.models import Timeline + +from multiprocessing.queues import Queue +from multiprocessing.synchronize import Event as MpEvent + +logger = logging.getLogger(__name__) + + +class InputTypeEnum(str, Enum): + # audio = "audio" + # external = "external" + tracked_object = "tracked_object" + + +class TimelineProcessor(threading.Thread): + """Handle timeline queue and update DB.""" + + def __init__(self, queue: Queue, stop_event: MpEvent) -> None: + threading.Thread.__init__(self) + self.name = "timeline_processor" + self.queue = queue + self.stop_event = stop_event + + 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 == InputTypeEnum.object: + self.handle_object_detection( + camera, event_type, prev_event_data, event_data + ) + + def handle_object_detection( + self, + camera: str, + event_type: str, + prev_event_data: dict[any, any], + event_data: dict[any, any], + ) -> None: + """Handle object detection.""" + if event_type == "start": + Timeline.insert( + timestamp=event_data["frame_time"], + camera=camera, + source="tracked_object", + class_type="visible", + data={ + "event_id": event_data["id"], + "region": event_data["region"], + "box": event_data["box"], + }, + ) + elif ( + event_type == "update" + and prev_event_data["current_zones"] != event_type["current_zones"] + ): + Timeline.insert( + timestamp=event_data["frame_time"], + camera=camera, + source="tracked_object", + class_type="entered_zone", + data={ + "event_id": event_data["id"], + "region": event_data["region"], + "box": event_data["box"], + "zones": event_data["current_zones"], + }, + ) + elif event_type == "end": + Timeline.insert( + timestamp=event_data["frame_time"], + camera=camera, + source="tracked_object", + class_type="gone", + data={ + "event_id": event_data["id"], + "region": event_data["region"], + "box": event_data["box"], + }, + )