remove clip_ready event type

this doesnt really mean anything more than "end" anymore. new has_clip property added
This commit is contained in:
Blake Blackshear 2021-09-15 07:16:52 -05:00
parent 86a5b46c68
commit 1b2134c49e
3 changed files with 96 additions and 95 deletions

View File

@ -36,7 +36,7 @@ Message published for each changed event. The first message is published when th
```json ```json
{ {
"type": "update", // new, update, end or clip_ready "type": "update", // new, update, end
"before": { "before": {
"id": "1607123955.475377-mxklsc", "id": "1607123955.475377-mxklsc",
"camera": "front_door", "camera": "front_door",
@ -53,7 +53,9 @@ Message published for each changed event. The first message is published when th
"region": [264, 450, 667, 853], "region": [264, 450, 667, 853],
"current_zones": ["driveway"], "current_zones": ["driveway"],
"entered_zones": ["yard", "driveway"], "entered_zones": ["yard", "driveway"],
"thumbnail": null "thumbnail": null,
"has_snapshot": false,
"has_clip": false
}, },
"after": { "after": {
"id": "1607123955.475377-mxklsc", "id": "1607123955.475377-mxklsc",
@ -71,7 +73,9 @@ Message published for each changed event. The first message is published when th
"region": [218, 440, 693, 915], "region": [218, 440, 693, 915],
"current_zones": ["yard", "driveway"], "current_zones": ["yard", "driveway"],
"entered_zones": ["yard", "driveway"], "entered_zones": ["yard", "driveway"],
"thumbnail": null "thumbnail": null,
"has_snapshot": false,
"has_clip": false
} }
} }
``` ```

View File

@ -29,38 +29,6 @@ class EventProcessor(threading.Thread):
self.events_in_process = {} self.events_in_process = {}
self.stop_event = stop_event self.stop_event = stop_event
def should_create_clip(self, camera, event_data):
if event_data["false_positive"]:
return False
record_config: RecordConfig = self.config.cameras[camera].record
# Recording is disabled
if not record_config.enabled:
return False
# If there are required zones and there is no overlap
required_zones = record_config.events.required_zones
if len(required_zones) > 0 and not set(event_data["entered_zones"]) & set(
required_zones
):
logger.debug(
f"Not creating clip for {event_data['id']} because it did not enter required zones"
)
return False
# If the required objects are not present
if (
record_config.events.objects is not None
and event_data["label"] not in record_config.events.objects
):
logger.debug(
f"Not creating clip for {event_data['id']} because it did not contain required objects"
)
return False
return True
def run(self): def run(self):
while not self.stop_event.is_set(): while not self.stop_event.is_set():
try: try:
@ -74,11 +42,9 @@ class EventProcessor(threading.Thread):
self.events_in_process[event_data["id"]] = event_data self.events_in_process[event_data["id"]] = event_data
if event_type == "end": if event_type == "end":
has_clip = self.should_create_clip(camera, event_data)
event_config: EventsConfig = self.config.cameras[camera].record.events event_config: EventsConfig = self.config.cameras[camera].record.events
if has_clip or event_data["has_snapshot"]: if event_data["has_clip"] or event_data["has_snapshot"]:
Event.create( Event.create(
id=event_data["id"], id=event_data["id"],
label=event_data["label"], label=event_data["label"],
@ -89,12 +55,12 @@ class EventProcessor(threading.Thread):
false_positive=event_data["false_positive"], false_positive=event_data["false_positive"],
zones=list(event_data["entered_zones"]), zones=list(event_data["entered_zones"]),
thumbnail=event_data["thumbnail"], thumbnail=event_data["thumbnail"],
has_clip=has_clip, has_clip=event_data["has_clip"],
has_snapshot=event_data["has_snapshot"], has_snapshot=event_data["has_snapshot"],
) )
del self.events_in_process[event_data["id"]] del self.events_in_process[event_data["id"]]
self.event_processed_queue.put((event_data["id"], camera, has_clip)) self.event_processed_queue.put((event_data["id"], camera))
logger.info(f"Exiting event processor...") logger.info(f"Exiting event processor...")

View File

@ -16,7 +16,7 @@ from typing import Callable, Dict
import cv2 import cv2
import numpy as np import numpy as np
from frigate.config import CameraConfig, FrigateConfig from frigate.config import CameraConfig, SnapshotsConfig, RecordConfig, FrigateConfig
from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR
from frigate.edgetpu import load_labels from frigate.edgetpu import load_labels
from frigate.util import ( from frigate.util import (
@ -73,6 +73,8 @@ class TrackedObject:
self.current_zones = [] self.current_zones = []
self.entered_zones = set() self.entered_zones = set()
self.false_positive = True self.false_positive = True
self.has_clip = False
self.has_snapshot = False
self.top_score = self.computed_score = 0.0 self.top_score = self.computed_score = 0.0
self.thumbnail_data = None self.thumbnail_data = None
self.last_updated = 0 self.last_updated = 0
@ -176,6 +178,8 @@ class TrackedObject:
"region": self.obj_data["region"], "region": self.obj_data["region"],
"current_zones": self.current_zones.copy(), "current_zones": self.current_zones.copy(),
"entered_zones": list(self.entered_zones).copy(), "entered_zones": list(self.entered_zones).copy(),
"has_clip": self.has_clip,
"has_snapshot": self.has_snapshot,
} }
if include_thumbnail: if include_thumbnail:
@ -611,9 +615,46 @@ class TrackedObjectProcessor(threading.Thread):
obj.previous = after obj.previous = after
def end(camera, obj: TrackedObject, current_frame_time): def end(camera, obj: TrackedObject, current_frame_time):
snapshot_config = self.config.cameras[camera].snapshots # populate has_snapshot
event_data = obj.to_dict(include_thumbnail=True) obj.has_snapshot = self.should_save_snapshot(camera, obj)
event_data["has_snapshot"] = False obj.has_clip = self.should_retain_recording(camera, obj)
# write the snapshot to disk
if obj.has_snapshot:
snapshot_config: SnapshotsConfig = self.config.cameras[camera].snapshots
jpg_bytes = obj.get_jpg_bytes(
timestamp=snapshot_config.timestamp,
bounding_box=snapshot_config.bounding_box,
crop=snapshot_config.crop,
height=snapshot_config.height,
quality=snapshot_config.quality,
)
if jpg_bytes is None:
logger.warning(f"Unable to save snapshot for {obj.obj_data['id']}.")
else:
with open(
os.path.join(CLIPS_DIR, f"{camera}-{obj.obj_data['id']}.jpg"),
"wb",
) as j:
j.write(jpg_bytes)
# write clean snapshot if enabled
if snapshot_config.clean_copy:
png_bytes = obj.get_clean_png()
if png_bytes is None:
logger.warning(
f"Unable to save clean snapshot for {obj.obj_data['id']}."
)
else:
with open(
os.path.join(
CLIPS_DIR,
f"{camera}-{obj.obj_data['id']}-clean.png",
),
"wb",
) as p:
p.write(png_bytes)
if not obj.false_positive: if not obj.false_positive:
message = { message = {
"before": obj.previous, "before": obj.previous,
@ -623,46 +664,8 @@ class TrackedObjectProcessor(threading.Thread):
self.client.publish( self.client.publish(
f"{self.topic_prefix}/events", json.dumps(message), retain=False f"{self.topic_prefix}/events", json.dumps(message), retain=False
) )
# write snapshot to disk if enabled
if snapshot_config.enabled and self.should_save_snapshot(camera, obj):
jpg_bytes = obj.get_jpg_bytes(
timestamp=snapshot_config.timestamp,
bounding_box=snapshot_config.bounding_box,
crop=snapshot_config.crop,
height=snapshot_config.height,
quality=snapshot_config.quality,
)
if jpg_bytes is None:
logger.warning(
f"Unable to save snapshot for {obj.obj_data['id']}."
)
else:
with open(
os.path.join(
CLIPS_DIR, f"{camera}-{obj.obj_data['id']}.jpg"
),
"wb",
) as j:
j.write(jpg_bytes)
event_data["has_snapshot"] = True
# write clean snapshot if enabled self.event_queue.put(("end", camera, obj.to_dict(include_thumbnail=True)))
if snapshot_config.clean_copy:
png_bytes = obj.get_clean_png()
if png_bytes is None:
logger.warning(
f"Unable to save clean snapshot for {obj.obj_data['id']}."
)
else:
with open(
os.path.join(
CLIPS_DIR,
f"{camera}-{obj.obj_data['id']}-clean.png",
),
"wb",
) as p:
p.write(png_bytes)
self.event_queue.put(("end", camera, event_data))
def snapshot(camera, obj: TrackedObject, current_frame_time): def snapshot(camera, obj: TrackedObject, current_frame_time):
mqtt_config = self.config.cameras[camera].mqtt mqtt_config = self.config.cameras[camera].mqtt
@ -711,8 +714,16 @@ class TrackedObjectProcessor(threading.Thread):
self.zone_data = defaultdict(lambda: defaultdict(dict)) self.zone_data = defaultdict(lambda: defaultdict(dict))
def should_save_snapshot(self, camera, obj: TrackedObject): def should_save_snapshot(self, camera, obj: TrackedObject):
if obj.false_positive:
return False
snapshot_config: SnapshotsConfig = self.config.cameras[camera].snapshots
if not snapshot_config.enabled:
return False
# if there are required zones and there is no overlap # if there are required zones and there is no overlap
required_zones = self.config.cameras[camera].snapshots.required_zones required_zones = snapshot_config.required_zones
if len(required_zones) > 0 and not obj.entered_zones & set(required_zones): if len(required_zones) > 0 and not obj.entered_zones & set(required_zones):
logger.debug( logger.debug(
f"Not creating snapshot for {obj.obj_data['id']} because it did not enter required zones" f"Not creating snapshot for {obj.obj_data['id']} because it did not enter required zones"
@ -721,6 +732,36 @@ class TrackedObjectProcessor(threading.Thread):
return True return True
def should_retain_recording(self, camera, obj: TrackedObject):
if obj.false_positive:
return False
record_config: RecordConfig = self.config.cameras[camera].record
# Recording is disabled
if not record_config.enabled:
return False
# If there are required zones and there is no overlap
required_zones = record_config.events.required_zones
if len(required_zones) > 0 and not set(obj.entered_zones) & set(required_zones):
logger.debug(
f"Not creating clip for {obj.obj_data['id']} because it did not enter required zones"
)
return False
# If the required objects are not present
if (
record_config.events.objects is not None
and obj.obj_data["label"] not in record_config.events.objects
):
logger.debug(
f"Not creating clip for {obj.obj_data['id']} because it did not contain required objects"
)
return False
return True
def should_mqtt_snapshot(self, camera, obj: TrackedObject): def should_mqtt_snapshot(self, camera, obj: TrackedObject):
# if there are required zones and there is no overlap # if there are required zones and there is no overlap
required_zones = self.config.cameras[camera].mqtt.required_zones required_zones = self.config.cameras[camera].mqtt.required_zones
@ -815,17 +856,7 @@ class TrackedObjectProcessor(threading.Thread):
# cleanup event finished queue # cleanup event finished queue
while not self.event_processed_queue.empty(): while not self.event_processed_queue.empty():
event_id, camera, clip_created = self.event_processed_queue.get() event_id, camera = self.event_processed_queue.get()
if clip_created:
obj = self.camera_states[camera].tracked_objects[event_id]
message = {
"before": obj.previous,
"after": obj.to_dict(),
"type": "clip_ready",
}
self.client.publish(
f"{self.topic_prefix}/events", json.dumps(message), retain=False
)
self.camera_states[camera].finished(event_id) self.camera_states[camera].finished(event_id)
logger.info(f"Exiting object processor...") logger.info(f"Exiting object processor...")