mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-05 10:45:21 +03:00
Move to events package
This commit is contained in:
parent
6d0c2ec5c8
commit
27f01d1dca
@ -295,3 +295,12 @@ Get ffprobe output for camera feed paths.
|
|||||||
### `GET /api/<camera_name>/ptz/info`
|
### `GET /api/<camera_name>/ptz/info`
|
||||||
|
|
||||||
Get PTZ info for the camera.
|
Get PTZ info for the camera.
|
||||||
|
|
||||||
|
### `POST /api/events/manual/<camera_name>/<label>/create`
|
||||||
|
|
||||||
|
Create a manual API with a given `label` (ex: doorbell press) to capture a specific event besides an object being detected.
|
||||||
|
NOTE: This call will return the unique ID for the event which will be required to `end` the event.
|
||||||
|
|
||||||
|
### `POST /api/events/manual/<event_id>/end`
|
||||||
|
|
||||||
|
End a specific manual event.
|
||||||
|
|||||||
@ -28,7 +28,8 @@ from frigate.const import (
|
|||||||
RECORD_DIR,
|
RECORD_DIR,
|
||||||
)
|
)
|
||||||
from frigate.object_detection import ObjectDetectProcess
|
from frigate.object_detection import ObjectDetectProcess
|
||||||
from frigate.events import EventCleanup, EventProcessor
|
from frigate.events.cleanup import EventCleanup
|
||||||
|
from frigate.events.maintainer import EventProcessor
|
||||||
from frigate.http import create_app
|
from frigate.http import create_app
|
||||||
from frigate.log import log_process, root_configurer
|
from frigate.log import log_process, root_configurer
|
||||||
from frigate.models import Event, Recordings, Timeline
|
from frigate.models import Event, Recordings, Timeline
|
||||||
|
|||||||
175
frigate/events/cleanup.py
Normal file
175
frigate/events/cleanup.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
"""Cleanup events based on configured retention."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from peewee import fn
|
||||||
|
|
||||||
|
from frigate.config import FrigateConfig
|
||||||
|
from frigate.const import CLIPS_DIR
|
||||||
|
from frigate.models import Event
|
||||||
|
|
||||||
|
from multiprocessing.synchronize import Event as MpEvent
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class EventCleanup(threading.Thread):
|
||||||
|
def __init__(self, config: FrigateConfig, stop_event: MpEvent):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.name = "event_cleanup"
|
||||||
|
self.config = config
|
||||||
|
self.stop_event = stop_event
|
||||||
|
self.camera_keys = list(self.config.cameras.keys())
|
||||||
|
|
||||||
|
def expire(self, media_type: str) -> None:
|
||||||
|
# TODO: Refactor media_type to enum
|
||||||
|
## Expire events from unlisted cameras based on the global config
|
||||||
|
if media_type == "clips":
|
||||||
|
retain_config = self.config.record.events.retain
|
||||||
|
file_extension = "mp4"
|
||||||
|
update_params = {"has_clip": False}
|
||||||
|
else:
|
||||||
|
retain_config = self.config.snapshots.retain
|
||||||
|
file_extension = "jpg"
|
||||||
|
update_params = {"has_snapshot": False}
|
||||||
|
|
||||||
|
distinct_labels = (
|
||||||
|
Event.select(Event.label)
|
||||||
|
.where(Event.camera.not_in(self.camera_keys))
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
|
# loop over object types in db
|
||||||
|
for l in distinct_labels:
|
||||||
|
# get expiration time for this label
|
||||||
|
expire_days = retain_config.objects.get(l.label, retain_config.default)
|
||||||
|
expire_after = (
|
||||||
|
datetime.datetime.now() - datetime.timedelta(days=expire_days)
|
||||||
|
).timestamp()
|
||||||
|
# grab all events after specific time
|
||||||
|
expired_events = Event.select().where(
|
||||||
|
Event.camera.not_in(self.camera_keys),
|
||||||
|
Event.start_time < expire_after,
|
||||||
|
Event.label == l.label,
|
||||||
|
Event.retain_indefinitely == False,
|
||||||
|
)
|
||||||
|
# delete the media from disk
|
||||||
|
for event in expired_events:
|
||||||
|
media_name = f"{event.camera}-{event.id}"
|
||||||
|
media_path = Path(
|
||||||
|
f"{os.path.join(CLIPS_DIR, media_name)}.{file_extension}"
|
||||||
|
)
|
||||||
|
media_path.unlink(missing_ok=True)
|
||||||
|
if file_extension == "jpg":
|
||||||
|
media_path = Path(
|
||||||
|
f"{os.path.join(CLIPS_DIR, media_name)}-clean.png"
|
||||||
|
)
|
||||||
|
media_path.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
# update the clips attribute for the db entry
|
||||||
|
update_query = Event.update(update_params).where(
|
||||||
|
Event.camera.not_in(self.camera_keys),
|
||||||
|
Event.start_time < expire_after,
|
||||||
|
Event.label == l.label,
|
||||||
|
Event.retain_indefinitely == False,
|
||||||
|
)
|
||||||
|
update_query.execute()
|
||||||
|
|
||||||
|
## Expire events from cameras based on the camera config
|
||||||
|
for name, camera in self.config.cameras.items():
|
||||||
|
if media_type == "clips":
|
||||||
|
retain_config = camera.record.events.retain
|
||||||
|
else:
|
||||||
|
retain_config = camera.snapshots.retain
|
||||||
|
# get distinct objects in database for this camera
|
||||||
|
distinct_labels = (
|
||||||
|
Event.select(Event.label).where(Event.camera == name).distinct()
|
||||||
|
)
|
||||||
|
|
||||||
|
# loop over object types in db
|
||||||
|
for l in distinct_labels:
|
||||||
|
# get expiration time for this label
|
||||||
|
expire_days = retain_config.objects.get(l.label, retain_config.default)
|
||||||
|
expire_after = (
|
||||||
|
datetime.datetime.now() - datetime.timedelta(days=expire_days)
|
||||||
|
).timestamp()
|
||||||
|
# grab all events after specific time
|
||||||
|
expired_events = Event.select().where(
|
||||||
|
Event.camera == name,
|
||||||
|
Event.start_time < expire_after,
|
||||||
|
Event.label == l.label,
|
||||||
|
Event.retain_indefinitely == False,
|
||||||
|
)
|
||||||
|
# delete the grabbed clips from disk
|
||||||
|
for event in expired_events:
|
||||||
|
media_name = f"{event.camera}-{event.id}"
|
||||||
|
media_path = Path(
|
||||||
|
f"{os.path.join(CLIPS_DIR, media_name)}.{file_extension}"
|
||||||
|
)
|
||||||
|
media_path.unlink(missing_ok=True)
|
||||||
|
if file_extension == "jpg":
|
||||||
|
media_path = Path(
|
||||||
|
f"{os.path.join(CLIPS_DIR, media_name)}-clean.png"
|
||||||
|
)
|
||||||
|
media_path.unlink(missing_ok=True)
|
||||||
|
# update the clips attribute for the db entry
|
||||||
|
update_query = Event.update(update_params).where(
|
||||||
|
Event.camera == name,
|
||||||
|
Event.start_time < expire_after,
|
||||||
|
Event.label == l.label,
|
||||||
|
Event.retain_indefinitely == False,
|
||||||
|
)
|
||||||
|
update_query.execute()
|
||||||
|
|
||||||
|
def purge_duplicates(self) -> None:
|
||||||
|
duplicate_query = """with grouped_events as (
|
||||||
|
select id,
|
||||||
|
label,
|
||||||
|
camera,
|
||||||
|
has_snapshot,
|
||||||
|
has_clip,
|
||||||
|
row_number() over (
|
||||||
|
partition by label, camera, round(start_time/5,0)*5
|
||||||
|
order by end_time-start_time desc
|
||||||
|
) as copy_number
|
||||||
|
from event
|
||||||
|
)
|
||||||
|
|
||||||
|
select distinct id, camera, has_snapshot, has_clip from grouped_events
|
||||||
|
where copy_number > 1;"""
|
||||||
|
|
||||||
|
duplicate_events = Event.raw(duplicate_query)
|
||||||
|
for event in duplicate_events:
|
||||||
|
logger.debug(f"Removing duplicate: {event.id}")
|
||||||
|
media_name = f"{event.camera}-{event.id}"
|
||||||
|
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}.jpg")
|
||||||
|
media_path.unlink(missing_ok=True)
|
||||||
|
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}-clean.png")
|
||||||
|
media_path.unlink(missing_ok=True)
|
||||||
|
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}.mp4")
|
||||||
|
media_path.unlink(missing_ok=True)
|
||||||
|
|
||||||
|
(
|
||||||
|
Event.delete()
|
||||||
|
.where(Event.id << [event.id for event in duplicate_events])
|
||||||
|
.execute()
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
# only expire events every 5 minutes
|
||||||
|
while not self.stop_event.wait(300):
|
||||||
|
self.expire("clips")
|
||||||
|
self.expire("snapshots")
|
||||||
|
self.purge_duplicates()
|
||||||
|
|
||||||
|
# drop events from db where has_clip and has_snapshot are false
|
||||||
|
delete_query = Event.delete().where(
|
||||||
|
Event.has_clip == False, Event.has_snapshot == False
|
||||||
|
)
|
||||||
|
delete_query.execute()
|
||||||
|
|
||||||
|
logger.info(f"Exiting event cleanup...")
|
||||||
@ -10,7 +10,6 @@ from pathlib import Path
|
|||||||
from peewee import fn
|
from peewee import fn
|
||||||
|
|
||||||
from frigate.config import EventsConfig, FrigateConfig
|
from frigate.config import EventsConfig, FrigateConfig
|
||||||
from frigate.const import CLIPS_DIR
|
|
||||||
from frigate.models import Event
|
from frigate.models import Event
|
||||||
from frigate.types import CameraMetricsTypes
|
from frigate.types import CameraMetricsTypes
|
||||||
from frigate.util import to_relative_box
|
from frigate.util import to_relative_box
|
||||||
@ -196,161 +195,3 @@ class EventProcessor(threading.Thread):
|
|||||||
if event_type == "end":
|
if event_type == "end":
|
||||||
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))
|
||||||
|
|
||||||
|
|
||||||
class EventCleanup(threading.Thread):
|
|
||||||
def __init__(self, config: FrigateConfig, stop_event: MpEvent):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
self.name = "event_cleanup"
|
|
||||||
self.config = config
|
|
||||||
self.stop_event = stop_event
|
|
||||||
self.camera_keys = list(self.config.cameras.keys())
|
|
||||||
|
|
||||||
def expire(self, media_type: str) -> None:
|
|
||||||
# TODO: Refactor media_type to enum
|
|
||||||
## Expire events from unlisted cameras based on the global config
|
|
||||||
if media_type == "clips":
|
|
||||||
retain_config = self.config.record.events.retain
|
|
||||||
file_extension = "mp4"
|
|
||||||
update_params = {"has_clip": False}
|
|
||||||
else:
|
|
||||||
retain_config = self.config.snapshots.retain
|
|
||||||
file_extension = "jpg"
|
|
||||||
update_params = {"has_snapshot": False}
|
|
||||||
|
|
||||||
distinct_labels = (
|
|
||||||
Event.select(Event.label)
|
|
||||||
.where(Event.camera.not_in(self.camera_keys))
|
|
||||||
.distinct()
|
|
||||||
)
|
|
||||||
|
|
||||||
# loop over object types in db
|
|
||||||
for l in distinct_labels:
|
|
||||||
# get expiration time for this label
|
|
||||||
expire_days = retain_config.objects.get(l.label, retain_config.default)
|
|
||||||
expire_after = (
|
|
||||||
datetime.datetime.now() - datetime.timedelta(days=expire_days)
|
|
||||||
).timestamp()
|
|
||||||
# grab all events after specific time
|
|
||||||
expired_events = Event.select().where(
|
|
||||||
Event.camera.not_in(self.camera_keys),
|
|
||||||
Event.start_time < expire_after,
|
|
||||||
Event.label == l.label,
|
|
||||||
Event.retain_indefinitely == False,
|
|
||||||
)
|
|
||||||
# delete the media from disk
|
|
||||||
for event in expired_events:
|
|
||||||
media_name = f"{event.camera}-{event.id}"
|
|
||||||
media_path = Path(
|
|
||||||
f"{os.path.join(CLIPS_DIR, media_name)}.{file_extension}"
|
|
||||||
)
|
|
||||||
media_path.unlink(missing_ok=True)
|
|
||||||
if file_extension == "jpg":
|
|
||||||
media_path = Path(
|
|
||||||
f"{os.path.join(CLIPS_DIR, media_name)}-clean.png"
|
|
||||||
)
|
|
||||||
media_path.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
# update the clips attribute for the db entry
|
|
||||||
update_query = Event.update(update_params).where(
|
|
||||||
Event.camera.not_in(self.camera_keys),
|
|
||||||
Event.start_time < expire_after,
|
|
||||||
Event.label == l.label,
|
|
||||||
Event.retain_indefinitely == False,
|
|
||||||
)
|
|
||||||
update_query.execute()
|
|
||||||
|
|
||||||
## Expire events from cameras based on the camera config
|
|
||||||
for name, camera in self.config.cameras.items():
|
|
||||||
if media_type == "clips":
|
|
||||||
retain_config = camera.record.events.retain
|
|
||||||
else:
|
|
||||||
retain_config = camera.snapshots.retain
|
|
||||||
# get distinct objects in database for this camera
|
|
||||||
distinct_labels = (
|
|
||||||
Event.select(Event.label).where(Event.camera == name).distinct()
|
|
||||||
)
|
|
||||||
|
|
||||||
# loop over object types in db
|
|
||||||
for l in distinct_labels:
|
|
||||||
# get expiration time for this label
|
|
||||||
expire_days = retain_config.objects.get(l.label, retain_config.default)
|
|
||||||
expire_after = (
|
|
||||||
datetime.datetime.now() - datetime.timedelta(days=expire_days)
|
|
||||||
).timestamp()
|
|
||||||
# grab all events after specific time
|
|
||||||
expired_events = Event.select().where(
|
|
||||||
Event.camera == name,
|
|
||||||
Event.start_time < expire_after,
|
|
||||||
Event.label == l.label,
|
|
||||||
Event.retain_indefinitely == False,
|
|
||||||
)
|
|
||||||
# delete the grabbed clips from disk
|
|
||||||
for event in expired_events:
|
|
||||||
media_name = f"{event.camera}-{event.id}"
|
|
||||||
media_path = Path(
|
|
||||||
f"{os.path.join(CLIPS_DIR, media_name)}.{file_extension}"
|
|
||||||
)
|
|
||||||
media_path.unlink(missing_ok=True)
|
|
||||||
if file_extension == "jpg":
|
|
||||||
media_path = Path(
|
|
||||||
f"{os.path.join(CLIPS_DIR, media_name)}-clean.png"
|
|
||||||
)
|
|
||||||
media_path.unlink(missing_ok=True)
|
|
||||||
# update the clips attribute for the db entry
|
|
||||||
update_query = Event.update(update_params).where(
|
|
||||||
Event.camera == name,
|
|
||||||
Event.start_time < expire_after,
|
|
||||||
Event.label == l.label,
|
|
||||||
Event.retain_indefinitely == False,
|
|
||||||
)
|
|
||||||
update_query.execute()
|
|
||||||
|
|
||||||
def purge_duplicates(self) -> None:
|
|
||||||
duplicate_query = """with grouped_events as (
|
|
||||||
select id,
|
|
||||||
label,
|
|
||||||
camera,
|
|
||||||
has_snapshot,
|
|
||||||
has_clip,
|
|
||||||
row_number() over (
|
|
||||||
partition by label, camera, round(start_time/5,0)*5
|
|
||||||
order by end_time-start_time desc
|
|
||||||
) as copy_number
|
|
||||||
from event
|
|
||||||
)
|
|
||||||
|
|
||||||
select distinct id, camera, has_snapshot, has_clip from grouped_events
|
|
||||||
where copy_number > 1;"""
|
|
||||||
|
|
||||||
duplicate_events = Event.raw(duplicate_query)
|
|
||||||
for event in duplicate_events:
|
|
||||||
logger.debug(f"Removing duplicate: {event.id}")
|
|
||||||
media_name = f"{event.camera}-{event.id}"
|
|
||||||
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}.jpg")
|
|
||||||
media_path.unlink(missing_ok=True)
|
|
||||||
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}-clean.png")
|
|
||||||
media_path.unlink(missing_ok=True)
|
|
||||||
media_path = Path(f"{os.path.join(CLIPS_DIR, media_name)}.mp4")
|
|
||||||
media_path.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
(
|
|
||||||
Event.delete()
|
|
||||||
.where(Event.id << [event.id for event in duplicate_events])
|
|
||||||
.execute()
|
|
||||||
)
|
|
||||||
|
|
||||||
def run(self) -> None:
|
|
||||||
# only expire events every 5 minutes
|
|
||||||
while not self.stop_event.wait(300):
|
|
||||||
self.expire("clips")
|
|
||||||
self.expire("snapshots")
|
|
||||||
self.purge_duplicates()
|
|
||||||
|
|
||||||
# drop events from db where has_clip and has_snapshot are false
|
|
||||||
delete_query = Event.delete().where(
|
|
||||||
Event.has_clip == False, Event.has_snapshot == False
|
|
||||||
)
|
|
||||||
delete_query.execute()
|
|
||||||
|
|
||||||
logger.info(f"Exiting event cleanup...")
|
|
||||||
144
frigate/events_manual.py
Normal file
144
frigate/events_manual.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
import cv2
|
||||||
|
from frigate.config import CameraConfig
|
||||||
|
|
||||||
|
from frigate.const import CLIPS_DIR
|
||||||
|
from frigate.models import Event
|
||||||
|
from frigate.object_processing import TrackedObjectProcessor
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def create_manual_event(
|
||||||
|
tracked_object_processor: TrackedObjectProcessor,
|
||||||
|
camera_config: CameraConfig,
|
||||||
|
camera_name: str,
|
||||||
|
label: str,
|
||||||
|
) -> str:
|
||||||
|
# get a valid frame time for camera
|
||||||
|
frame_time = tracked_object_processor.get_current_frame_time(camera_name)
|
||||||
|
|
||||||
|
# create event id and start frame time
|
||||||
|
rand_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
|
||||||
|
event_id = f"{frame_time}-{rand_id}"
|
||||||
|
|
||||||
|
# fabricate obj_data
|
||||||
|
obj_data = {
|
||||||
|
"id": event_id,
|
||||||
|
"camera": camera_name,
|
||||||
|
"frame_time": frame_time,
|
||||||
|
"snapshot_time": 0.0,
|
||||||
|
"label": label,
|
||||||
|
"top_score": 1,
|
||||||
|
"false_positive": False,
|
||||||
|
"start_time": frame_time,
|
||||||
|
"end_time": None,
|
||||||
|
"score": 1,
|
||||||
|
"box": [],
|
||||||
|
"area": 0,
|
||||||
|
"ratio": 0,
|
||||||
|
"region": [],
|
||||||
|
"stationary": False,
|
||||||
|
"motionless_count": 0,
|
||||||
|
"position_changes": [],
|
||||||
|
"current_zones": "",
|
||||||
|
"entered_zones": "",
|
||||||
|
"has_clip": False,
|
||||||
|
"has_snapshot": False,
|
||||||
|
"thumbnail": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# insert object into the queue
|
||||||
|
current_obj_data = obj_data.copy()
|
||||||
|
tracked_object_processor.event_queue.put(("start", camera_name, current_obj_data))
|
||||||
|
|
||||||
|
# update object data an send another event
|
||||||
|
obj_data["has_clip"] = camera_config.record.enabled
|
||||||
|
obj_data["has_snapshot"] = True
|
||||||
|
|
||||||
|
# Get current frame for thumb & snapshot
|
||||||
|
(
|
||||||
|
current_frame,
|
||||||
|
updated_frame_time,
|
||||||
|
) = tracked_object_processor.get_current_frame_and_time(camera_name)
|
||||||
|
|
||||||
|
# write jpg snapshot
|
||||||
|
height = int(current_frame.shape[0])
|
||||||
|
width = int(height * current_frame.shape[1] / current_frame.shape[0])
|
||||||
|
|
||||||
|
current_frame = cv2.resize(
|
||||||
|
current_frame, dsize=(width, height), interpolation=cv2.INTER_AREA
|
||||||
|
)
|
||||||
|
|
||||||
|
ret, jpg = cv2.imencode(".jpg", current_frame, [int(cv2.IMWRITE_JPEG_QUALITY), 70])
|
||||||
|
|
||||||
|
with open(
|
||||||
|
os.path.join(CLIPS_DIR, f"{camera_name}-{event_id}.jpg"),
|
||||||
|
"wb",
|
||||||
|
) as j:
|
||||||
|
j.write(jpg.tobytes())
|
||||||
|
|
||||||
|
# write clean snapshot if enabled
|
||||||
|
if camera_config.snapshots.clean_copy:
|
||||||
|
ret, png = cv2.imencode(".png", current_frame)
|
||||||
|
png_bytes = png.tobytes()
|
||||||
|
|
||||||
|
if png_bytes is None:
|
||||||
|
logger.warning(f"Unable to save clean snapshot for {event_id}.")
|
||||||
|
else:
|
||||||
|
with open(
|
||||||
|
os.path.join(
|
||||||
|
CLIPS_DIR,
|
||||||
|
f"{camera_name}-{event_id}-clean.png",
|
||||||
|
),
|
||||||
|
"wb",
|
||||||
|
) as p:
|
||||||
|
p.write(png_bytes)
|
||||||
|
|
||||||
|
# get thumbnail
|
||||||
|
height = 175
|
||||||
|
width = int(height * current_frame.shape[1] / current_frame.shape[0])
|
||||||
|
|
||||||
|
current_frame = cv2.resize(
|
||||||
|
current_frame, dsize=(width, height), interpolation=cv2.INTER_AREA
|
||||||
|
)
|
||||||
|
|
||||||
|
ret, thumb = cv2.imencode(
|
||||||
|
".jpg", current_frame, [int(cv2.IMWRITE_JPEG_QUALITY), 30]
|
||||||
|
)
|
||||||
|
|
||||||
|
obj_data["thumbnail"] = base64.b64encode(thumb).decode("utf-8")
|
||||||
|
obj_data["snapshot_time"] = updated_frame_time
|
||||||
|
|
||||||
|
# update event in queue
|
||||||
|
tracked_object_processor.event_queue.put(("update", camera_name, obj_data))
|
||||||
|
|
||||||
|
return event_id
|
||||||
|
|
||||||
|
|
||||||
|
def finish_manual_event(
|
||||||
|
tracked_object_processor: TrackedObjectProcessor,
|
||||||
|
event_id: str,
|
||||||
|
):
|
||||||
|
# get associated event and data
|
||||||
|
manual_event: Event = Event.get(Event.id == event_id)
|
||||||
|
camera_name = manual_event.camera
|
||||||
|
|
||||||
|
# get frame time
|
||||||
|
frame_time = tracked_object_processor.get_current_frame_time(camera_name)
|
||||||
|
|
||||||
|
# create obj_data
|
||||||
|
obj_data = {
|
||||||
|
"id": event_id,
|
||||||
|
"end_time": frame_time,
|
||||||
|
"has_snapshot": False,
|
||||||
|
"has_clip": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
# end event in queue
|
||||||
|
tracked_object_processor.event_queue.put(("end", camera_name, obj_data))
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import base64
|
import base64
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
import copy
|
import copy
|
||||||
import glob
|
|
||||||
import logging
|
import logging
|
||||||
|
import glob
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
@ -34,6 +34,7 @@ from playhouse.shortcuts import model_to_dict
|
|||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.const import CLIPS_DIR, MAX_SEGMENT_DURATION, RECORD_DIR
|
from frigate.const import CLIPS_DIR, MAX_SEGMENT_DURATION, RECORD_DIR
|
||||||
from frigate.models import Event, Recordings, Timeline
|
from frigate.models import Event, Recordings, Timeline
|
||||||
|
from frigate.events_manual import create_manual_event, finish_manual_event
|
||||||
from frigate.object_processing import TrackedObject
|
from frigate.object_processing import TrackedObject
|
||||||
from frigate.plus import PlusApi
|
from frigate.plus import PlusApi
|
||||||
from frigate.ptz import OnvifController
|
from frigate.ptz import OnvifController
|
||||||
@ -44,6 +45,7 @@ from frigate.util import (
|
|||||||
restart_frigate,
|
restart_frigate,
|
||||||
vainfo_hwaccel,
|
vainfo_hwaccel,
|
||||||
get_tz_modifiers,
|
get_tz_modifiers,
|
||||||
|
to_relative_box,
|
||||||
)
|
)
|
||||||
from frigate.storage import StorageMaintainer
|
from frigate.storage import StorageMaintainer
|
||||||
from frigate.version import VERSION
|
from frigate.version import VERSION
|
||||||
@ -195,7 +197,7 @@ def send_to_plus(id):
|
|||||||
return make_response(jsonify({"success": False, "message": message}), 404)
|
return make_response(jsonify({"success": False, "message": message}), 404)
|
||||||
|
|
||||||
# events from before the conversion to relative dimensions cant include annotations
|
# 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
|
include_annotation = None
|
||||||
|
|
||||||
if event.end_time is None:
|
if event.end_time is None:
|
||||||
@ -251,8 +253,8 @@ def send_to_plus(id):
|
|||||||
event.save()
|
event.save()
|
||||||
|
|
||||||
if not include_annotation is None:
|
if not include_annotation is None:
|
||||||
region = event.data["region"]
|
region = event.region
|
||||||
box = event.data["box"]
|
box = event.box
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_app.plus_api.add_annotation(
|
current_app.plus_api.add_annotation(
|
||||||
@ -293,7 +295,7 @@ def false_positive(id):
|
|||||||
return make_response(jsonify({"success": False, "message": message}), 404)
|
return make_response(jsonify({"success": False, "message": message}), 404)
|
||||||
|
|
||||||
# events from before the conversion to relative dimensions cant include annotations
|
# 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"
|
message = f"Events prior to 0.13 cannot be submitted as false positives"
|
||||||
logger.error(message)
|
logger.error(message)
|
||||||
return make_response(jsonify({"success": False, "message": message}), 400)
|
return make_response(jsonify({"success": False, "message": message}), 400)
|
||||||
@ -310,15 +312,11 @@ def false_positive(id):
|
|||||||
# need to refetch the event now that it has a plus_id
|
# need to refetch the event now that it has a plus_id
|
||||||
event = Event.get(Event.id == id)
|
event = Event.get(Event.id == id)
|
||||||
|
|
||||||
region = event.data["region"]
|
region = event.region
|
||||||
box = event.data["box"]
|
box = event.box
|
||||||
|
|
||||||
# provide top score if score is unavailable
|
# provide top score if score is unavailable
|
||||||
score = (
|
score = event.top_score if event.score is None else event.score
|
||||||
(event.data["top_score"] if event.data["top_score"] else event.top_score)
|
|
||||||
if event.data["score"] is None
|
|
||||||
else event.data["score"]
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
current_app.plus_api.add_false_positive(
|
current_app.plus_api.add_false_positive(
|
||||||
@ -759,7 +757,6 @@ def events():
|
|||||||
Event.top_score,
|
Event.top_score,
|
||||||
Event.false_positive,
|
Event.false_positive,
|
||||||
Event.box,
|
Event.box,
|
||||||
Event.data,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if camera != "all":
|
if camera != "all":
|
||||||
@ -848,6 +845,47 @@ def events():
|
|||||||
return jsonify([model_to_dict(e, exclude=excluded_fields) for e in events])
|
return jsonify([model_to_dict(e, exclude=excluded_fields) for e in events])
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/events/manual/<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
|
||||||
|
)
|
||||||
|
|
||||||
|
camera_config = current_app.frigate_config.cameras.get(camera_name)
|
||||||
|
|
||||||
|
if not label:
|
||||||
|
return jsonify({"success": False, "message": f"{label} must be set."}, 404)
|
||||||
|
|
||||||
|
event_id = create_manual_event(
|
||||||
|
current_app.detected_frames_processor,
|
||||||
|
camera_config,
|
||||||
|
camera_name,
|
||||||
|
label,
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify(
|
||||||
|
{
|
||||||
|
"success": True,
|
||||||
|
"message": f"Event successfully created.",
|
||||||
|
"event_id": event_id,
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/events/manual/<event_id>/end", methods=("POST",))
|
||||||
|
def end_event(event_id):
|
||||||
|
try:
|
||||||
|
finish_manual_event(current_app.detected_frames_processor, 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")
|
@bp.route("/config")
|
||||||
def config():
|
def config():
|
||||||
config = current_app.frigate_config.dict()
|
config = current_app.frigate_config.dict()
|
||||||
@ -866,11 +904,6 @@ def config():
|
|||||||
|
|
||||||
config["plus"] = {"enabled": current_app.plus_api.is_active()}
|
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)
|
return jsonify(config)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ from frigate.config import (
|
|||||||
FrigateConfig,
|
FrigateConfig,
|
||||||
)
|
)
|
||||||
from frigate.const import CLIPS_DIR
|
from frigate.const import CLIPS_DIR
|
||||||
from frigate.events import EventTypeEnum
|
from frigate.events.maintainer import EventTypeEnum
|
||||||
from frigate.util import (
|
from frigate.util import (
|
||||||
SharedMemoryFrameManager,
|
SharedMemoryFrameManager,
|
||||||
calculate_region,
|
calculate_region,
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import threading
|
|||||||
import queue
|
import queue
|
||||||
|
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.events import EventTypeEnum
|
from frigate.events.maintainer import EventTypeEnum
|
||||||
from frigate.models import Timeline
|
from frigate.models import Timeline
|
||||||
|
|
||||||
from multiprocessing.queues import Queue
|
from multiprocessing.queues import Queue
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user