Merge remote-tracking branch 'origin/release-0.11.0' into gstreamer

This commit is contained in:
YS 2022-03-23 13:30:01 +03:00
commit 5f580b64bd
7 changed files with 145 additions and 34 deletions

View File

@ -186,13 +186,24 @@ Sets retain to true for the event id.
Sets retain to false for the event id (event may be deleted quickly after removing). Sets retain to false for the event id (event may be deleted quickly after removing).
### `POST /api/events/<id>/sub_label`
Set a sub label for an event. For example to update `person` -> `person's name` if they were recognized with facial recognition.
Sub labels must be 20 characters or shorter.
```json
{
"subLabel": "some_string"
}
```
### `GET /api/events/<id>/thumbnail.jpg` ### `GET /api/events/<id>/thumbnail.jpg`
Returns a thumbnail for the event id optimized for notifications. Works while the event is in progress and after completion. Passing `?format=android` will convert the thumbnail to 2:1 aspect ratio. Returns a thumbnail for the event id optimized for notifications. Works while the event is in progress and after completion. Passing `?format=android` will convert the thumbnail to 2:1 aspect ratio.
### `GET /api/<camera_name>/<label>/thumbnail.jpg` ### `GET /api/<camera_name>/<label>/thumbnail.jpg`
Returns the thumbnail from the latest event for the given camera and label combo. Using `any` as the label will return the latest thumbnail regardless of type. Returns the thumbnail from the latest event for the given camera and label combo. Using `any` as the label will return the latest thumbnail regardless of type.
### `GET /api/events/<id>/clip.mp4` ### `GET /api/events/<id>/clip.mp4`

View File

@ -14,14 +14,22 @@ from frigate.models import Event
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def should_insert_db(prev_event, current_event):
"""If current event has new clip or snapshot."""
return (
(not prev_event["has_clip"] and not prev_event["has_snapshot"])
and (current_event["has_clip"] or current_event["has_snapshot"])
)
def should_update_db(prev_event, current_event): def should_update_db(prev_event, current_event):
"""If current_event has updated fields and (clip or snapshot)."""
return ( return (
prev_event["top_score"] != current_event["top_score"] (current_event["has_clip"] or current_event["has_snapshot"])
or prev_event["entered_zones"] != current_event["entered_zones"] and (prev_event["top_score"] != current_event["top_score"]
or prev_event["thumbnail"] != current_event["thumbnail"] or prev_event["entered_zones"] != current_event["entered_zones"]
or prev_event["has_clip"] != current_event["has_clip"] or prev_event["thumbnail"] != current_event["thumbnail"]
or prev_event["has_snapshot"] != current_event["has_snapshot"] or prev_event["has_clip"] != current_event["has_clip"]
or prev_event["has_snapshot"] != current_event["has_snapshot"])
) )
@ -58,33 +66,52 @@ class EventProcessor(threading.Thread):
if event_type == "start": if event_type == "start":
self.events_in_process[event_data["id"]] = event_data self.events_in_process[event_data["id"]] = event_data
elif event_type == "update" and should_insert_db(
self.events_in_process[event_data["id"]], event_data
):
self.events_in_process[event_data["id"]] = event_data
# TODO: this will generate a lot of db activity possibly
Event.insert(
id=event_data["id"],
label=event_data["label"],
camera=camera,
start_time=event_data["start_time"] - event_config.pre_capture,
end_time=None,
top_score=event_data["top_score"],
false_positive=event_data["false_positive"],
zones=list(event_data["entered_zones"]),
thumbnail=event_data["thumbnail"],
region=event_data["region"],
box=event_data["box"],
area=event_data["area"],
has_clip=event_data["has_clip"],
has_snapshot=event_data["has_snapshot"],
).execute()
elif event_type == "update" and should_update_db( elif event_type == "update" and should_update_db(
self.events_in_process[event_data["id"]], event_data self.events_in_process[event_data["id"]], event_data
): ):
self.events_in_process[event_data["id"]] = event_data self.events_in_process[event_data["id"]] = event_data
# TODO: this will generate a lot of db activity possibly # TODO: this will generate a lot of db activity possibly
if event_data["has_clip"] or event_data["has_snapshot"]: Event.update(
Event.replace( label=event_data["label"],
id=event_data["id"], camera=camera,
label=event_data["label"], start_time=event_data["start_time"] - event_config.pre_capture,
camera=camera, end_time=None,
start_time=event_data["start_time"] - event_config.pre_capture, top_score=event_data["top_score"],
end_time=None, false_positive=event_data["false_positive"],
top_score=event_data["top_score"], zones=list(event_data["entered_zones"]),
false_positive=event_data["false_positive"], thumbnail=event_data["thumbnail"],
zones=list(event_data["entered_zones"]), region=event_data["region"],
thumbnail=event_data["thumbnail"], box=event_data["box"],
region=event_data["region"], area=event_data["area"],
box=event_data["box"], has_clip=event_data["has_clip"],
area=event_data["area"], has_snapshot=event_data["has_snapshot"],
has_clip=event_data["has_clip"], ).where(Event.id == event_data["id"]).execute()
has_snapshot=event_data["has_snapshot"],
).execute()
elif event_type == "end": elif event_type == "end":
if event_data["has_clip"] or event_data["has_snapshot"]: if event_data["has_clip"] or event_data["has_snapshot"]:
Event.replace( Event.update(
id=event_data["id"],
label=event_data["label"], label=event_data["label"],
camera=camera, camera=camera,
start_time=event_data["start_time"] - event_config.pre_capture, start_time=event_data["start_time"] - event_config.pre_capture,
@ -98,7 +125,7 @@ class EventProcessor(threading.Thread):
area=event_data["area"], area=event_data["area"],
has_clip=event_data["has_clip"], has_clip=event_data["has_clip"],
has_snapshot=event_data["has_snapshot"], has_snapshot=event_data["has_snapshot"],
).execute() ).where(Event.id == event_data["id"]).execute()
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)) self.event_processed_queue.put((event_data["id"], camera))

View File

@ -125,14 +125,14 @@ def set_retain(id):
event = Event.get(Event.id == id) event = Event.get(Event.id == id)
except DoesNotExist: except DoesNotExist:
return make_response( return make_response(
jsonify({"success": False, "message": "Event" + id + " not found"}), 404 jsonify({"success": False, "message": "Event " + id + " not found"}), 404
) )
event.retain_indefinitely = True event.retain_indefinitely = True
event.save() event.save()
return make_response( return make_response(
jsonify({"success": True, "message": "Event" + id + " retained"}), 200 jsonify({"success": True, "message": "Event " + id + " retained"}), 200
) )
@ -142,16 +142,42 @@ def delete_retain(id):
event = Event.get(Event.id == id) event = Event.get(Event.id == id)
except DoesNotExist: except DoesNotExist:
return make_response( return make_response(
jsonify({"success": False, "message": "Event" + id + " not found"}), 404 jsonify({"success": False, "message": "Event " + id + " not found"}), 404
) )
event.retain_indefinitely = False event.retain_indefinitely = False
event.save() event.save()
return make_response( return make_response(
jsonify({"success": True, "message": "Event" + id + " un-retained"}), 200 jsonify({"success": True, "message": "Event " + id + " un-retained"}), 200
) )
@bp.route("/events/<id>/sub_label", methods=("POST",))
def set_sub_label(id):
try:
event = Event.get(Event.id == id)
except DoesNotExist:
return make_response(
jsonify({"success": False, "message": "Event " + id + " not found"}), 404
)
if request.json:
new_sub_label = request.json.get("subLabel")
else:
new_sub_label = None
if new_sub_label and len(new_sub_label) > 20:
return make_response(
jsonify({"success": False, "message": new_sub_label + " exceeds the 20 character limit for sub_label"}), 400
)
event.sub_label = new_sub_label
event.save()
return make_response(
jsonify({"success": True, "message": "Event " + id + " sub label set to " + new_sub_label}), 200
)
@bp.route("/events/<id>", methods=("DELETE",)) @bp.route("/events/<id>", methods=("DELETE",))
def delete_event(id): def delete_event(id):
@ -159,7 +185,7 @@ def delete_event(id):
event = Event.get(Event.id == id) event = Event.get(Event.id == id)
except DoesNotExist: except DoesNotExist:
return make_response( return make_response(
jsonify({"success": False, "message": "Event" + id + " not found"}), 404 jsonify({"success": False, "message": "Event " + id + " not found"}), 404
) )
media_name = f"{event.camera}-{event.id}" media_name = f"{event.camera}-{event.id}"
@ -174,7 +200,7 @@ def delete_event(id):
event.delete_instance() event.delete_instance()
return make_response( return make_response(
jsonify({"success": True, "message": "Event" + id + " deleted"}), 200 jsonify({"success": True, "message": "Event " + id + " deleted"}), 200
) )

View File

@ -6,6 +6,7 @@ from playhouse.sqlite_ext import *
class Event(Model): class Event(Model):
id = CharField(null=False, primary_key=True, max_length=30) id = CharField(null=False, primary_key=True, max_length=30)
label = CharField(index=True, max_length=20) label = CharField(index=True, max_length=20)
sub_label = CharField(max_length=20, null=True)
camera = CharField(index=True, max_length=20) camera = CharField(index=True, max_length=20)
start_time = DateTimeField() start_time = DateTimeField()
end_time = DateTimeField() end_time = DateTimeField()

View File

@ -0,0 +1,46 @@
"""Peewee migrations -- 008_add_sub_label.py.
Some examples (model - class or model name)::
> Model = migrator.orm['model_name'] # Return model in current state by name
> migrator.sql(sql) # Run custom SQL
> migrator.python(func, *args, **kwargs) # Run python code
> migrator.create_model(Model) # Create a model (could be used as decorator)
> migrator.remove_model(model, cascade=True) # Remove a model
> migrator.add_fields(model, **fields) # Add fields to a model
> migrator.change_fields(model, **fields) # Change fields
> migrator.remove_fields(model, *field_names, cascade=True)
> migrator.rename_field(model, old_field_name, new_field_name)
> migrator.rename_table(model, new_table_name)
> migrator.add_index(model, *col_names, unique=False)
> migrator.drop_index(model, *col_names)
> migrator.add_not_null(model, *field_names)
> migrator.drop_not_null(model, *field_names)
> migrator.add_default(model, field_name, default)
"""
import datetime as dt
import peewee as pw
from playhouse.sqlite_ext import *
from decimal import ROUND_HALF_EVEN
from frigate.models import Event
try:
import playhouse.postgres_ext as pw_pext
except ImportError:
pass
SQL = pw.SQL
def migrate(migrator, database, fake=False, **kwargs):
migrator.add_fields(
Event,
sub_label=pw.CharField(max_length=20, null=True),
)
def rollback(migrator, database, fake=False, **kwargs):
migrator.remove_fields(Event, ["sub_label"])

View File

@ -102,7 +102,7 @@ export function EventCard({ camera, event, delay }) {
const end = fromUnixTime(event.end_time); const end = fromUnixTime(event.end_time);
const hours = differenceInHours(end, start); const hours = differenceInHours(end, start);
const minutes = differenceInMinutes(end, start) - hours * 60; const minutes = differenceInMinutes(end, start) - hours * 60;
const seconds = differenceInSeconds(end, start) - hours * 60 - minutes * 60; const seconds = differenceInSeconds(end, start) - hours * 60 * 60 - minutes * 60;
duration = ''; duration = '';
if (hours) duration += `${hours}h `; if (hours) duration += `${hours}h `;
if (minutes) duration += `${minutes}m `; if (minutes) duration += `${minutes}m `;

View File

@ -315,7 +315,7 @@ export default function Events({ path, ...props }) {
<div className="m-2 flex grow"> <div className="m-2 flex grow">
<div className="flex flex-col grow"> <div className="flex flex-col grow">
<div className="capitalize text-lg font-bold"> <div className="capitalize text-lg font-bold">
{event.label} ({(event.top_score * 100).toFixed(0)}%) {event.sub_label ? `${event.label}: ${event.sub_label}` : event.label} ({(event.top_score * 100).toFixed(0)}%)
</div> </div>
<div className="text-sm"> <div className="text-sm">
{new Date(event.start_time * 1000).toLocaleDateString()}{' '} {new Date(event.start_time * 1000).toLocaleDateString()}{' '}