mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-08-04 13:47:37 +02:00
allow defining required zones for snapshots/clips/mqtt
This commit is contained in:
parent
9592d95599
commit
121ea37825
@ -139,6 +139,8 @@ clips:
|
|||||||
# Optional: Objects to save clips for. (default: all tracked objects)
|
# Optional: Objects to save clips for. (default: all tracked objects)
|
||||||
objects:
|
objects:
|
||||||
- person
|
- person
|
||||||
|
# Optional: Restrict clips to objects that entered any of the listed zones (default: no required zones)
|
||||||
|
required_zones: []
|
||||||
# Optional: Camera override for retention settings (default: global values)
|
# Optional: Camera override for retention settings (default: global values)
|
||||||
retain:
|
retain:
|
||||||
# Required: Default retention days (default: shown below)
|
# Required: Default retention days (default: shown below)
|
||||||
@ -166,6 +168,8 @@ snapshots:
|
|||||||
crop: False
|
crop: False
|
||||||
# Optional: height to resize the snapshot to (default: original size)
|
# Optional: height to resize the snapshot to (default: original size)
|
||||||
height: 175
|
height: 175
|
||||||
|
# Optional: Restrict snapshots to objects that entered any of the listed zones (default: no required zones)
|
||||||
|
required_zones: []
|
||||||
# Optional: Camera override for retention settings (default: global values)
|
# Optional: Camera override for retention settings (default: global values)
|
||||||
retain:
|
retain:
|
||||||
# Required: Default retention days (default: shown below)
|
# Required: Default retention days (default: shown below)
|
||||||
@ -226,12 +230,6 @@ cameras:
|
|||||||
# Optional: stream specific input args (default: inherit)
|
# Optional: stream specific input args (default: inherit)
|
||||||
input_args:
|
input_args:
|
||||||
|
|
||||||
# Optional: camera specific global args (default: inherit)
|
|
||||||
global_args:
|
|
||||||
# Optional: camera specific hwaccel args (default: inherit)
|
|
||||||
hwaccel_args:
|
|
||||||
# Optional: camera specific input args (default: inherit)
|
|
||||||
input_args:
|
|
||||||
# Optional: camera specific output args (default: inherit)
|
# Optional: camera specific output args (default: inherit)
|
||||||
output_args:
|
output_args:
|
||||||
|
|
||||||
@ -291,6 +289,8 @@ cameras:
|
|||||||
# Optional: Objects to save clips for. (default: all tracked objects)
|
# Optional: Objects to save clips for. (default: all tracked objects)
|
||||||
objects:
|
objects:
|
||||||
- person
|
- person
|
||||||
|
# Optional: Restrict clips to objects that entered any of the listed zones (default: no required zones)
|
||||||
|
required_zones: []
|
||||||
# Optional: Camera override for retention settings (default: global values)
|
# Optional: Camera override for retention settings (default: global values)
|
||||||
retain:
|
retain:
|
||||||
# Required: Default retention days (default: shown below)
|
# Required: Default retention days (default: shown below)
|
||||||
@ -324,6 +324,8 @@ cameras:
|
|||||||
crop: False
|
crop: False
|
||||||
# Optional: height to resize the snapshot to (default: original size)
|
# Optional: height to resize the snapshot to (default: original size)
|
||||||
height: 175
|
height: 175
|
||||||
|
# Optional: Restrict snapshots to objects that entered any of the listed zones (default: no required zones)
|
||||||
|
required_zones: []
|
||||||
# Optional: Camera override for retention settings (default: global values)
|
# Optional: Camera override for retention settings (default: global values)
|
||||||
retain:
|
retain:
|
||||||
# Required: Default retention days (default: shown below)
|
# Required: Default retention days (default: shown below)
|
||||||
@ -346,6 +348,8 @@ cameras:
|
|||||||
crop: True
|
crop: True
|
||||||
# Optional: height to resize the snapshot to (default: shown below)
|
# Optional: height to resize the snapshot to (default: shown below)
|
||||||
height: 270
|
height: 270
|
||||||
|
# Optional: Restrict mqtt messages to objects that entered any of the listed zones (default: no required zones)
|
||||||
|
required_zones: []
|
||||||
|
|
||||||
# Optional: Camera level object filters config.
|
# Optional: Camera level object filters config.
|
||||||
objects:
|
objects:
|
||||||
|
@ -198,6 +198,7 @@ CAMERAS_SCHEMA = vol.Schema(vol.All(
|
|||||||
vol.Optional('enabled', default=False): bool,
|
vol.Optional('enabled', default=False): bool,
|
||||||
vol.Optional('pre_capture', default=5): int,
|
vol.Optional('pre_capture', default=5): int,
|
||||||
vol.Optional('post_capture', default=5): int,
|
vol.Optional('post_capture', default=5): int,
|
||||||
|
vol.Optional('required_zones', default=[]): [str],
|
||||||
'objects': [str],
|
'objects': [str],
|
||||||
vol.Optional('retain', default={}): RETAIN_SCHEMA,
|
vol.Optional('retain', default={}): RETAIN_SCHEMA,
|
||||||
},
|
},
|
||||||
@ -213,6 +214,7 @@ CAMERAS_SCHEMA = vol.Schema(vol.All(
|
|||||||
vol.Optional('timestamp', default=False): bool,
|
vol.Optional('timestamp', default=False): bool,
|
||||||
vol.Optional('bounding_box', default=False): bool,
|
vol.Optional('bounding_box', default=False): bool,
|
||||||
vol.Optional('crop', default=False): bool,
|
vol.Optional('crop', default=False): bool,
|
||||||
|
vol.Optional('required_zones', default=[]): [str],
|
||||||
'height': int,
|
'height': int,
|
||||||
vol.Optional('retain', default={}): RETAIN_SCHEMA,
|
vol.Optional('retain', default={}): RETAIN_SCHEMA,
|
||||||
},
|
},
|
||||||
@ -221,7 +223,8 @@ CAMERAS_SCHEMA = vol.Schema(vol.All(
|
|||||||
vol.Optional('timestamp', default=True): bool,
|
vol.Optional('timestamp', default=True): bool,
|
||||||
vol.Optional('bounding_box', default=True): bool,
|
vol.Optional('bounding_box', default=True): bool,
|
||||||
vol.Optional('crop', default=True): bool,
|
vol.Optional('crop', default=True): bool,
|
||||||
vol.Optional('height', default=270): int
|
vol.Optional('height', default=270): int,
|
||||||
|
vol.Optional('required_zones', default=[]): [str],
|
||||||
},
|
},
|
||||||
vol.Optional('objects', default={}): OBJECTS_SCHEMA,
|
vol.Optional('objects', default={}): OBJECTS_SCHEMA,
|
||||||
vol.Optional('motion', default={}): MOTION_SCHEMA,
|
vol.Optional('motion', default={}): MOTION_SCHEMA,
|
||||||
@ -570,6 +573,7 @@ class CameraSnapshotsConfig():
|
|||||||
self._crop = config['crop']
|
self._crop = config['crop']
|
||||||
self._height = config.get('height')
|
self._height = config.get('height')
|
||||||
self._retain = RetainConfig(global_config['snapshots']['retain'], config['retain'])
|
self._retain = RetainConfig(global_config['snapshots']['retain'], config['retain'])
|
||||||
|
self._required_zones = config['required_zones']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self):
|
def enabled(self):
|
||||||
@ -595,6 +599,10 @@ class CameraSnapshotsConfig():
|
|||||||
def retain(self):
|
def retain(self):
|
||||||
return self._retain
|
return self._retain
|
||||||
|
|
||||||
|
@property
|
||||||
|
def required_zones(self):
|
||||||
|
return self._required_zones
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
'enabled': self.enabled,
|
'enabled': self.enabled,
|
||||||
@ -602,7 +610,8 @@ class CameraSnapshotsConfig():
|
|||||||
'bounding_box': self.bounding_box,
|
'bounding_box': self.bounding_box,
|
||||||
'crop': self.crop,
|
'crop': self.crop,
|
||||||
'height': self.height,
|
'height': self.height,
|
||||||
'retain': self.retain.to_dict()
|
'retain': self.retain.to_dict(),
|
||||||
|
'required_zones': self.required_zones
|
||||||
}
|
}
|
||||||
|
|
||||||
class CameraMqttConfig():
|
class CameraMqttConfig():
|
||||||
@ -612,6 +621,7 @@ class CameraMqttConfig():
|
|||||||
self._bounding_box = config['bounding_box']
|
self._bounding_box = config['bounding_box']
|
||||||
self._crop = config['crop']
|
self._crop = config['crop']
|
||||||
self._height = config.get('height')
|
self._height = config.get('height')
|
||||||
|
self._required_zones = config['required_zones']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self):
|
def enabled(self):
|
||||||
@ -633,13 +643,18 @@ class CameraMqttConfig():
|
|||||||
def height(self):
|
def height(self):
|
||||||
return self._height
|
return self._height
|
||||||
|
|
||||||
|
@property
|
||||||
|
def required_zones(self):
|
||||||
|
return self._required_zones
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
'enabled': self.enabled,
|
'enabled': self.enabled,
|
||||||
'timestamp': self.timestamp,
|
'timestamp': self.timestamp,
|
||||||
'bounding_box': self.bounding_box,
|
'bounding_box': self.bounding_box,
|
||||||
'crop': self.crop,
|
'crop': self.crop,
|
||||||
'height': self.height
|
'height': self.height,
|
||||||
|
'required_zones': self.required_zones
|
||||||
}
|
}
|
||||||
|
|
||||||
class CameraClipsConfig():
|
class CameraClipsConfig():
|
||||||
@ -649,6 +664,7 @@ class CameraClipsConfig():
|
|||||||
self._post_capture = config['post_capture']
|
self._post_capture = config['post_capture']
|
||||||
self._objects = config.get('objects')
|
self._objects = config.get('objects')
|
||||||
self._retain = RetainConfig(global_config['clips']['retain'], config['retain'])
|
self._retain = RetainConfig(global_config['clips']['retain'], config['retain'])
|
||||||
|
self._required_zones = config['required_zones']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self):
|
def enabled(self):
|
||||||
@ -670,13 +686,18 @@ class CameraClipsConfig():
|
|||||||
def retain(self):
|
def retain(self):
|
||||||
return self._retain
|
return self._retain
|
||||||
|
|
||||||
|
@property
|
||||||
|
def required_zones(self):
|
||||||
|
return self._required_zones
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
'enabled': self.enabled,
|
'enabled': self.enabled,
|
||||||
'pre_capture': self.pre_capture,
|
'pre_capture': self.pre_capture,
|
||||||
'post_capture': self.post_capture,
|
'post_capture': self.post_capture,
|
||||||
'objects': self.objects,
|
'objects': self.objects,
|
||||||
'retain': self.retain.to_dict()
|
'retain': self.retain.to_dict(),
|
||||||
|
'required_zones': self.required_zones
|
||||||
}
|
}
|
||||||
|
|
||||||
class CameraRtmpConfig():
|
class CameraRtmpConfig():
|
||||||
|
@ -32,6 +32,18 @@ class EventProcessor(threading.Thread):
|
|||||||
self.events_in_process = {}
|
self.events_in_process = {}
|
||||||
self.stop_event = stop_event
|
self.stop_event = stop_event
|
||||||
|
|
||||||
|
def should_create_clip(self, camera, event_data):
|
||||||
|
if event_data['false_positive']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# if there are required zones and there is no overlap
|
||||||
|
required_zones = self.config.cameras[camera].clips.required_zones
|
||||||
|
if len(required_zones) > 0 and not set(event_data['entered_zones']) & set(required_zones):
|
||||||
|
logger.debug(f"Not creating clip for {event_data['id']} because it did not enter required zones")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def refresh_cache(self):
|
def refresh_cache(self):
|
||||||
cached_files = os.listdir(CACHE_DIR)
|
cached_files = os.listdir(CACHE_DIR)
|
||||||
|
|
||||||
@ -193,7 +205,7 @@ class EventProcessor(threading.Thread):
|
|||||||
if event_type == 'end':
|
if event_type == 'end':
|
||||||
clips_config = self.config.cameras[camera].clips
|
clips_config = self.config.cameras[camera].clips
|
||||||
|
|
||||||
if not event_data['false_positive']:
|
if self.should_create_clip(camera, event_data):
|
||||||
clip_created = False
|
clip_created = False
|
||||||
if clips_config.enabled and (clips_config.objects is None or event_data['label'] in clips_config.objects):
|
if clips_config.enabled and (clips_config.objects is None or event_data['label'] in clips_config.objects):
|
||||||
clip_created = self.create_clip(camera, event_data, clips_config.pre_capture, clips_config.post_capture)
|
clip_created = self.create_clip(camera, event_data, clips_config.pre_capture, clips_config.post_capture)
|
||||||
|
@ -454,7 +454,7 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
message = { 'before': obj.previous, 'after': obj.to_dict(), 'type': 'end' }
|
message = { 'before': obj.previous, 'after': obj.to_dict(), 'type': 'end' }
|
||||||
self.client.publish(f"{self.topic_prefix}/events", json.dumps(message), retain=False)
|
self.client.publish(f"{self.topic_prefix}/events", json.dumps(message), retain=False)
|
||||||
# write snapshot to disk if enabled
|
# write snapshot to disk if enabled
|
||||||
if snapshot_config.enabled:
|
if snapshot_config.enabled and self.should_save_snapshot(camera, obj):
|
||||||
jpg_bytes = obj.get_jpg_bytes(
|
jpg_bytes = obj.get_jpg_bytes(
|
||||||
timestamp=snapshot_config.timestamp,
|
timestamp=snapshot_config.timestamp,
|
||||||
bounding_box=snapshot_config.bounding_box,
|
bounding_box=snapshot_config.bounding_box,
|
||||||
@ -468,7 +468,7 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
|
|
||||||
def snapshot(camera, obj: TrackedObject, current_frame_time):
|
def snapshot(camera, obj: TrackedObject, current_frame_time):
|
||||||
mqtt_config = self.config.cameras[camera].mqtt
|
mqtt_config = self.config.cameras[camera].mqtt
|
||||||
if mqtt_config.enabled:
|
if mqtt_config.enabled and self.should_mqtt_snapshot(camera, obj):
|
||||||
jpg_bytes = obj.get_jpg_bytes(
|
jpg_bytes = obj.get_jpg_bytes(
|
||||||
timestamp=mqtt_config.timestamp,
|
timestamp=mqtt_config.timestamp,
|
||||||
bounding_box=mqtt_config.bounding_box,
|
bounding_box=mqtt_config.bounding_box,
|
||||||
@ -499,6 +499,24 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
# }
|
# }
|
||||||
self.zone_data = defaultdict(lambda: defaultdict(lambda: {}))
|
self.zone_data = defaultdict(lambda: defaultdict(lambda: {}))
|
||||||
|
|
||||||
|
def should_save_snapshot(self, camera, obj: TrackedObject):
|
||||||
|
# if there are required zones and there is no overlap
|
||||||
|
required_zones = self.config.cameras[camera].snapshots.required_zones
|
||||||
|
if len(required_zones) > 0 and not obj.entered_zones & set(required_zones):
|
||||||
|
logger.debug(f"Not creating snapshot for {obj.obj_data['id']} because it did not enter required zones")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def should_mqtt_snapshot(self, camera, obj: TrackedObject):
|
||||||
|
# if there are required zones and there is no overlap
|
||||||
|
required_zones = self.config.cameras[camera].mqtt.required_zones
|
||||||
|
if len(required_zones) > 0 and not obj.entered_zones & set(required_zones):
|
||||||
|
logger.debug(f"Not sending mqtt for {obj.obj_data['id']} because it did not enter required zones")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def get_best(self, camera, label):
|
def get_best(self, camera, label):
|
||||||
# TODO: need a lock here
|
# TODO: need a lock here
|
||||||
camera_state = self.camera_states[camera]
|
camera_state = self.camera_states[camera]
|
||||||
|
Loading…
Reference in New Issue
Block a user