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:
Nicolas Mowen
2023-05-19 04:16:11 -06:00
committed by GitHub
parent 6d0c2ec5c8
commit e357715a8c
12 changed files with 466 additions and 180 deletions

View File

@@ -1,8 +1,8 @@
import base64
from datetime import datetime, timedelta, timezone
import copy
import glob
import logging
import glob
import json
import os
import subprocess as sp
@@ -34,6 +34,7 @@ from playhouse.shortcuts import model_to_dict
from frigate.config import FrigateConfig
from frigate.const import CLIPS_DIR, MAX_SEGMENT_DURATION, RECORD_DIR
from frigate.models import Event, Recordings, Timeline
from frigate.events.external import ExternalEventProcessor
from frigate.object_processing import TrackedObject
from frigate.plus import PlusApi
from frigate.ptz import OnvifController
@@ -60,6 +61,7 @@ def create_app(
detected_frames_processor,
storage_maintainer: StorageMaintainer,
onvif: OnvifController,
external_processor: ExternalEventProcessor,
plus_api: PlusApi,
):
app = Flask(__name__)
@@ -79,6 +81,7 @@ def create_app(
app.detected_frames_processor = detected_frames_processor
app.storage_maintainer = storage_maintainer
app.onvif = onvif
app.external_processor = external_processor
app.plus_api = plus_api
app.camera_error_image = None
app.hwaccel_errors = []
@@ -195,7 +198,7 @@ def send_to_plus(id):
return make_response(jsonify({"success": False, "message": message}), 404)
# events from before the conversion to relative dimensions cant include annotations
if any(d > 1 for d in event.data["box"]):
if any(d > 1 for d in event.box):
include_annotation = None
if event.end_time is None:
@@ -251,8 +254,8 @@ def send_to_plus(id):
event.save()
if not include_annotation is None:
region = event.data["region"]
box = event.data["box"]
region = event.region
box = event.box
try:
current_app.plus_api.add_annotation(
@@ -293,7 +296,7 @@ def false_positive(id):
return make_response(jsonify({"success": False, "message": message}), 404)
# events from before the conversion to relative dimensions cant include annotations
if any(d > 1 for d in event.data["box"]):
if any(d > 1 for d in event.box):
message = f"Events prior to 0.13 cannot be submitted as false positives"
logger.error(message)
return make_response(jsonify({"success": False, "message": message}), 400)
@@ -310,15 +313,11 @@ def false_positive(id):
# need to refetch the event now that it has a plus_id
event = Event.get(Event.id == id)
region = event.data["region"]
box = event.data["box"]
region = event.region
box = event.box
# provide top score if score is unavailable
score = (
(event.data["top_score"] if event.data["top_score"] else event.top_score)
if event.data["score"] is None
else event.data["score"]
)
score = event.top_score if event.score is None else event.score
try:
current_app.plus_api.add_false_positive(
@@ -759,7 +758,6 @@ def events():
Event.top_score,
Event.false_positive,
Event.box,
Event.data,
]
if camera != "all":
@@ -848,6 +846,58 @@ def events():
return jsonify([model_to_dict(e, exclude=excluded_fields) for e in events])
@bp.route("/events/<camera_name>/<label>/create", methods=["POST"])
def create_event(camera_name, label):
if not camera_name or not current_app.frigate_config.cameras.get(camera_name):
return jsonify(
{"success": False, "message": f"{camera_name} is not a valid camera."}, 404
)
if not label:
return jsonify({"success": False, "message": f"{label} must be set."}, 404)
json: dict[str, any] = request.get_json(silent=True) or {}
try:
frame = current_app.detected_frames_processor.get_current_frame(camera_name)
event_id = current_app.external_processor.create_manual_event(
camera_name,
label,
json.get("sub_label", None),
json.get("duration", 30),
json.get("include_recording", True),
json.get("draw", {}),
frame,
)
except Exception as e:
logger.error(f"The error is {e}")
return jsonify(
{"success": False, "message": f"An unknown error occurred: {e}"}, 404
)
return jsonify(
{
"success": True,
"message": "Successfully created event.",
"event_id": event_id,
},
200,
)
@bp.route("/events/<event_id>/end", methods=["PUT"])
def end_event(event_id):
try:
current_app.external_processor.finish_manual_event(event_id)
except:
return jsonify(
{"success": False, "message": f"{event_id} must be set and valid."}, 404
)
return jsonify({"success": True, "message": f"Event successfully ended."}, 200)
@bp.route("/config")
def config():
config = current_app.frigate_config.dict()
@@ -866,11 +916,6 @@ def config():
config["plus"] = {"enabled": current_app.plus_api.is_active()}
for detector, detector_config in config["detectors"].items():
detector_config["model"][
"labelmap"
] = current_app.frigate_config.model.merged_labelmap
return jsonify(config)