Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: Ability to set sub labels for specific events #2949

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0b49071
Add sub label to model and set / delete funs
NickM-27 Mar 11, 2022
3996b17
Add migrations for sub label
NickM-27 Mar 11, 2022
b7b62a3
Tweaks to API and model
NickM-27 Mar 11, 2022
a47eee4
Show sublabel if available
NickM-27 Mar 11, 2022
b55b1cb
Cleanups
NickM-27 Mar 11, 2022
4ce4450
Update docs
NickM-27 Mar 11, 2022
ecee7b4
Show person in UI title
NickM-27 Mar 11, 2022
3e903a5
Fix typo and don't fail on no json
NickM-27 Mar 11, 2022
cc0d3bf
Transfer sub labels for in progress events
NickM-27 Mar 11, 2022
73cb586
Remove sublabel reset
NickM-27 Mar 11, 2022
8a42db2
Remove person only check
NickM-27 Mar 12, 2022
11d7b31
Make default null
NickM-27 Mar 12, 2022
5d56836
Update docs and formatting
NickM-27 Mar 12, 2022
7dc75d2
Make default null
NickM-27 Mar 12, 2022
7977e78
Make nullable in migration
NickM-27 Mar 12, 2022
47e09a9
Undo null
NickM-27 Mar 12, 2022
9a5d3d1
Update model to accept null
NickM-27 Mar 13, 2022
f7d7b2f
Update migration to accept null
NickM-27 Mar 13, 2022
f4b2fba
Don't set to default values
NickM-27 Mar 13, 2022
6771a82
Remove redundant defaults and update http logic
NickM-27 Mar 13, 2022
52c9e69
Only need a single route
NickM-27 Mar 13, 2022
f4f5fac
Enforce 20 character limit in http
NickM-27 Mar 13, 2022
19caba5
Update docs to mention 20 character limit
NickM-27 Mar 13, 2022
6658b2c
Cleanup
NickM-27 Mar 13, 2022
701da57
Separate insert and update to make sure updated values are retained w…
NickM-27 Mar 13, 2022
985c57a
Use insert instead of replace
NickM-27 Mar 13, 2022
745e7b2
Remove redundant if and have should_update_db include clip or snapsho…
NickM-27 Mar 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion docs/docs/integrations/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

### `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`

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`

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`

Expand Down
77 changes: 52 additions & 25 deletions frigate/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@

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):
"""If current_event has updated fields and (clip or snapshot)."""
return (
prev_event["top_score"] != current_event["top_score"]
or prev_event["entered_zones"] != current_event["entered_zones"]
or prev_event["thumbnail"] != current_event["thumbnail"]
or prev_event["has_clip"] != current_event["has_clip"]
or prev_event["has_snapshot"] != current_event["has_snapshot"]
(current_event["has_clip"] or current_event["has_snapshot"])
and (prev_event["top_score"] != current_event["top_score"]
or prev_event["entered_zones"] != current_event["entered_zones"]
or prev_event["thumbnail"] != current_event["thumbnail"]
or prev_event["has_clip"] != current_event["has_clip"]
or prev_event["has_snapshot"] != current_event["has_snapshot"])
)


Expand Down Expand Up @@ -58,33 +66,52 @@ def run(self):
if event_type == "start":
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(
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
if event_data["has_clip"] or event_data["has_snapshot"]:
Event.replace(
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()
Event.update(
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"],
).where(Event.id == event_data["id"]).execute()

elif event_type == "end":
if event_data["has_clip"] or event_data["has_snapshot"]:
Event.replace(
id=event_data["id"],
Event.update(
label=event_data["label"],
camera=camera,
start_time=event_data["start_time"] - event_config.pre_capture,
Expand All @@ -98,7 +125,7 @@ def run(self):
area=event_data["area"],
has_clip=event_data["has_clip"],
has_snapshot=event_data["has_snapshot"],
).execute()
).where(Event.id == event_data["id"]).execute()

del self.events_in_process[event_data["id"]]
self.event_processed_queue.put((event_data["id"], camera))
Expand Down
38 changes: 32 additions & 6 deletions frigate/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,14 @@ def set_retain(id):
event = Event.get(Event.id == id)
except DoesNotExist:
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.save()

return make_response(
jsonify({"success": True, "message": "Event" + id + " retained"}), 200
jsonify({"success": True, "message": "Event " + id + " retained"}), 200
)


Expand All @@ -143,24 +143,50 @@ def delete_retain(id):
event = Event.get(Event.id == id)
except DoesNotExist:
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.save()

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",))
def delete_event(id):
try:
event = Event.get(Event.id == id)
except DoesNotExist:
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}"
Expand All @@ -175,7 +201,7 @@ def delete_event(id):

event.delete_instance()
return make_response(
jsonify({"success": True, "message": "Event" + id + " deleted"}), 200
jsonify({"success": True, "message": "Event " + id + " deleted"}), 200
)


Expand Down
1 change: 1 addition & 0 deletions frigate/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
class Event(Model):
id = CharField(null=False, primary_key=True, max_length=30)
label = CharField(index=True, max_length=20)
sub_label = CharField(max_length=20, null=True)
camera = CharField(index=True, max_length=20)
start_time = DateTimeField()
end_time = DateTimeField()
Expand Down
46 changes: 46 additions & 0 deletions migrations/008_add_sub_label.py
Original file line number Diff line number Diff line change
@@ -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"])
2 changes: 1 addition & 1 deletion web/src/routes/Events.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export default function Events({ path, ...props }) {
<div className="m-2 flex grow">
<div className="flex flex-col grow">
<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 className="text-sm">
{new Date(event.start_time * 1000).toLocaleDateString()}{' '}
Expand Down