2023-04-23 17:45:19 +02:00
|
|
|
"""Record events for object, audio, etc. detections."""
|
|
|
|
|
|
|
|
import logging
|
|
|
|
import queue
|
2023-05-29 12:31:17 +02:00
|
|
|
import threading
|
2023-07-16 14:42:56 +02:00
|
|
|
from multiprocessing import Queue
|
2023-05-29 12:31:17 +02:00
|
|
|
from multiprocessing.synchronize import Event as MpEvent
|
2023-04-23 17:45:19 +02:00
|
|
|
|
|
|
|
from frigate.config import FrigateConfig
|
2023-12-05 14:04:22 +01:00
|
|
|
from frigate.const import ALL_ATTRIBUTE_LABELS
|
2023-05-19 12:16:11 +02:00
|
|
|
from frigate.events.maintainer import EventTypeEnum
|
2023-04-23 17:45:19 +02:00
|
|
|
from frigate.models import Timeline
|
2023-07-06 16:28:50 +02:00
|
|
|
from frigate.util.builtin import to_relative_box
|
2023-04-24 14:24:28 +02:00
|
|
|
|
2023-04-23 17:45:19 +02:00
|
|
|
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:
|
|
|
|
threading.Thread.__init__(self)
|
|
|
|
self.name = "timeline_processor"
|
|
|
|
self.config = config
|
|
|
|
self.queue = queue
|
|
|
|
self.stop_event = stop_event
|
2023-12-13 03:48:52 +01:00
|
|
|
self.pre_event_cache: dict[str, list[dict[str, any]]] = {}
|
2023-04-23 17:45:19 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2023-04-30 19:07:14 +02:00
|
|
|
if input_type == EventTypeEnum.tracked_object:
|
2023-04-23 17:45:19 +02:00
|
|
|
self.handle_object_detection(
|
|
|
|
camera, event_type, prev_event_data, event_data
|
|
|
|
)
|
2023-12-21 13:52:54 +01:00
|
|
|
elif input_type == EventTypeEnum.api:
|
|
|
|
self.handle_api_entry(camera, event_type, event_data)
|
2023-04-23 17:45:19 +02:00
|
|
|
|
2023-12-13 03:48:52 +01:00
|
|
|
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()
|
|
|
|
|
2023-04-23 17:45:19 +02:00
|
|
|
def handle_object_detection(
|
|
|
|
self,
|
|
|
|
camera: str,
|
|
|
|
event_type: str,
|
|
|
|
prev_event_data: dict[any, any],
|
|
|
|
event_data: dict[any, any],
|
2023-12-13 03:48:52 +01:00
|
|
|
) -> bool:
|
2023-04-23 17:45:19 +02:00
|
|
|
"""Handle object detection."""
|
2023-12-13 03:48:52 +01:00
|
|
|
save = False
|
2023-04-23 17:45:19 +02:00
|
|
|
camera_config = self.config.cameras[camera]
|
|
|
|
|
2023-04-24 14:24:28 +02:00
|
|
|
timeline_entry = {
|
|
|
|
Timeline.timestamp: event_data["frame_time"],
|
|
|
|
Timeline.camera: camera,
|
|
|
|
Timeline.source: "tracked_object",
|
|
|
|
Timeline.source_id: event_data["id"],
|
|
|
|
Timeline.data: {
|
|
|
|
"box": to_relative_box(
|
|
|
|
camera_config.detect.width,
|
|
|
|
camera_config.detect.height,
|
|
|
|
event_data["box"],
|
|
|
|
),
|
|
|
|
"label": event_data["label"],
|
2023-12-13 03:48:52 +01:00
|
|
|
"sub_label": event_data.get("sub_label"),
|
2023-04-24 14:24:28 +02:00
|
|
|
"region": to_relative_box(
|
|
|
|
camera_config.detect.width,
|
|
|
|
camera_config.detect.height,
|
|
|
|
event_data["region"],
|
|
|
|
),
|
2023-10-07 16:17:18 +02:00
|
|
|
"attribute": "",
|
2023-04-24 14:24:28 +02:00
|
|
|
},
|
|
|
|
}
|
2023-04-23 17:45:19 +02:00
|
|
|
if event_type == "start":
|
2023-04-24 14:24:28 +02:00
|
|
|
timeline_entry[Timeline.class_type] = "visible"
|
2023-12-13 03:48:52 +01:00
|
|
|
save = True
|
2023-08-01 04:43:48 +02:00
|
|
|
elif event_type == "update":
|
|
|
|
if (
|
2023-12-13 03:48:52 +01:00
|
|
|
len(prev_event_data["current_zones"]) < len(event_data["current_zones"])
|
2023-10-25 01:24:59 +02:00
|
|
|
and not event_data["stationary"]
|
2023-08-01 04:43:48 +02:00
|
|
|
):
|
|
|
|
timeline_entry[Timeline.class_type] = "entered_zone"
|
|
|
|
timeline_entry[Timeline.data]["zones"] = event_data["current_zones"]
|
2023-12-13 03:48:52 +01:00
|
|
|
save = True
|
2023-08-01 04:43:48 +02:00
|
|
|
elif prev_event_data["stationary"] != event_data["stationary"]:
|
|
|
|
timeline_entry[Timeline.class_type] = (
|
|
|
|
"stationary" if event_data["stationary"] else "active"
|
|
|
|
)
|
2023-12-13 03:48:52 +01:00
|
|
|
save = True
|
2023-10-07 16:17:18 +02:00
|
|
|
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]
|
2023-12-13 03:48:52 +01:00
|
|
|
save = True
|
2023-12-05 14:04:22 +01:00
|
|
|
elif not prev_event_data.get("sub_label") and event_data.get("sub_label"):
|
|
|
|
sub_label = event_data["sub_label"][0]
|
|
|
|
|
|
|
|
if sub_label not in ALL_ATTRIBUTE_LABELS:
|
|
|
|
timeline_entry[Timeline.class_type] = "sub_label"
|
|
|
|
timeline_entry[Timeline.data]["sub_label"] = sub_label
|
2023-12-13 03:48:52 +01:00
|
|
|
save = True
|
2023-04-23 17:45:19 +02:00
|
|
|
elif event_type == "end":
|
2023-12-13 03:48:52 +01:00
|
|
|
timeline_entry[Timeline.class_type] = "gone"
|
|
|
|
save = True
|
|
|
|
|
|
|
|
if save:
|
|
|
|
self.insert_or_save(timeline_entry, prev_event_data, event_data)
|
2023-12-21 13:52:54 +01:00
|
|
|
|
|
|
|
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
|