forked from Github/frigate
Ability to manually create events through the API (#3184)
* Move to events package * Improve handling of external events * Handle external events in the event queue * Pass in event processor * Check event json * Fix json parsing and change defaults * Fix snapshot saving * Hide % score when not available * Correct docs and add json example * Save event png db * Adjust image * Formatting * Add catch for failure ending event * Add init to modules * Fix naming * Formatting * Fix http creation * fix test * Change to PUT and include response in docs * Add ability to set bounding box locations in snapshot * Support multiple box annotations * Cleanup docs example response Co-authored-by: Blake Blackshear <blake@frigate.video> * Cleanup docs wording Co-authored-by: Blake Blackshear <blake@frigate.video> * Store full frame for thumbnail * Formatting * Set thumbnail height to 175 * Formatting --------- Co-authored-by: Blake Blackshear <blake@frigate.video>
This commit is contained in:
132
frigate/events/external.py
Normal file
132
frigate/events/external.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""Handle external events created by the user."""
|
||||
|
||||
import base64
|
||||
import cv2
|
||||
import datetime
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from multiprocessing.queues import Queue
|
||||
|
||||
from frigate.config import CameraConfig, FrigateConfig
|
||||
from frigate.const import CLIPS_DIR
|
||||
from frigate.events.maintainer import EventTypeEnum
|
||||
from frigate.util import draw_box_with_label
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExternalEventProcessor:
|
||||
def __init__(self, config: FrigateConfig, queue: Queue) -> None:
|
||||
self.config = config
|
||||
self.queue = queue
|
||||
self.default_thumbnail = None
|
||||
|
||||
def create_manual_event(
|
||||
self,
|
||||
camera: str,
|
||||
label: str,
|
||||
sub_label: Optional[str],
|
||||
duration: Optional[int],
|
||||
include_recording: bool,
|
||||
draw: dict[str, any],
|
||||
snapshot_frame: any,
|
||||
) -> str:
|
||||
now = datetime.datetime.now().timestamp()
|
||||
camera_config = self.config.cameras.get(camera)
|
||||
|
||||
# create event id and start frame time
|
||||
rand_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
|
||||
event_id = f"{now}-{rand_id}"
|
||||
|
||||
thumbnail = self._write_images(
|
||||
camera_config, label, event_id, draw, snapshot_frame
|
||||
)
|
||||
|
||||
self.queue.put(
|
||||
(
|
||||
EventTypeEnum.api,
|
||||
"new",
|
||||
camera_config,
|
||||
{
|
||||
"id": event_id,
|
||||
"label": label,
|
||||
"sub_label": sub_label,
|
||||
"camera": camera,
|
||||
"start_time": now,
|
||||
"end_time": now + duration if duration is not None else None,
|
||||
"thumbnail": thumbnail,
|
||||
"has_clip": camera_config.record.enabled and include_recording,
|
||||
"has_snapshot": True,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
return event_id
|
||||
|
||||
def finish_manual_event(self, event_id: str) -> None:
|
||||
"""Finish external event with indeterminate duration."""
|
||||
now = datetime.datetime.now().timestamp()
|
||||
self.queue.put(
|
||||
(EventTypeEnum.api, "end", None, {"id": event_id, "end_time": now})
|
||||
)
|
||||
|
||||
def _write_images(
|
||||
self,
|
||||
camera_config: CameraConfig,
|
||||
label: str,
|
||||
event_id: str,
|
||||
draw: dict[str, any],
|
||||
img_frame: any,
|
||||
) -> str:
|
||||
# write clean snapshot if enabled
|
||||
if camera_config.snapshots.clean_copy:
|
||||
ret, png = cv2.imencode(".png", img_frame)
|
||||
|
||||
if ret:
|
||||
with open(
|
||||
os.path.join(
|
||||
CLIPS_DIR,
|
||||
f"{camera_config.name}-{event_id}-clean.png",
|
||||
),
|
||||
"wb",
|
||||
) as p:
|
||||
p.write(png.tobytes())
|
||||
|
||||
# write jpg snapshot with optional annotations
|
||||
if draw.get("boxes") and isinstance(draw.get("boxes"), list):
|
||||
for box in draw.get("boxes"):
|
||||
x = box["box"][0] * camera_config.detect.width
|
||||
y = box["box"][1] * camera_config.detect.height
|
||||
width = box["box"][2] * camera_config.detect.width
|
||||
height = box["box"][3] * camera_config.detect.height
|
||||
|
||||
draw_box_with_label(
|
||||
img_frame,
|
||||
x,
|
||||
y,
|
||||
x + width,
|
||||
y + height,
|
||||
label,
|
||||
f"{box.get('score', '-')}% {int(width * height)}",
|
||||
thickness=2,
|
||||
color=box.get("color", (255, 0, 0)),
|
||||
)
|
||||
|
||||
ret, jpg = cv2.imencode(".jpg", img_frame)
|
||||
with open(
|
||||
os.path.join(CLIPS_DIR, f"{camera_config.name}-{event_id}.jpg"),
|
||||
"wb",
|
||||
) as j:
|
||||
j.write(jpg.tobytes())
|
||||
|
||||
# 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)
|
||||
ret, jpg = cv2.imencode(".jpg", thumb)
|
||||
return base64.b64encode(jpg.tobytes()).decode("utf-8")
|
||||
Reference in New Issue
Block a user