diff --git a/README.md b/README.md index 7a6ff58ae0..56dd706e40 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Use of a [Google Coral Accelerator](https://coral.ai/products/) is optional, but - [Object Filters](#object-filters) - [Masks](#masks) - [Zones](#zones) -- [Recording Clips (save_clips)](#recording-clips) +- [Recording Clips (clips)](#recording-clips) - [Snapshots (snapshots)](#snapshots) - [24/7 Recordings (record)](#247-recordings) - [RTMP Streams (rtmp)](#rtmp-streams) @@ -238,7 +238,7 @@ mqtt: password: password # Optional: Global configuration for saving clips -save_clips: +clips: # Optional: Maximum length of time to retain video during long events. (default: shown below) # NOTE: If an object is being tracked for longer than this amount of time, the cache # will begin to expire and the resulting clip will be the last x seconds of the event. @@ -399,7 +399,7 @@ cameras: # NOTE: This feature does not work if you have added "-vsync drop" in your input params. # This will only work for camera feeds that can be copied into the mp4 container format without # encoding such as h264. It may not work for some types of streams. - save_clips: + clips: # Required: enables clips for the camera (default: shown below) enabled: False # Optional: Number of seconds before the event to include in the clips (default: shown below) diff --git a/frigate/app.py b/frigate/app.py index 77f0332182..528e1497bd 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -45,7 +45,7 @@ def ensure_dirs(self): else: logger.debug(f"Skipping directory: {d}") - tmpfs_size = self.config.save_clips.tmpfs_cache_size + tmpfs_size = self.config.clips.tmpfs_cache_size if tmpfs_size: logger.info(f"Creating tmpfs of size {tmpfs_size}") rc = os.system(f"mount -t tmpfs -o size={tmpfs_size} tmpfs {CACHE_DIR}") @@ -78,10 +78,10 @@ def init_config(self): def check_config(self): for name, camera in self.config.cameras.items(): assigned_roles = list(set([r for i in camera.ffmpeg.inputs for r in i.roles])) - if not camera.save_clips.enabled and 'clips' in assigned_roles: - logger.warning(f"Camera {name} has clips assigned to an input, but save_clips is not enabled.") - elif camera.save_clips.enabled and not 'clips' in assigned_roles: - logger.warning(f"Camera {name} has save_clips enabled, but clips is not assigned to an input.") + if not camera.clips.enabled and 'clips' in assigned_roles: + logger.warning(f"Camera {name} has clips assigned to an input, but clips is not enabled.") + elif camera.clips.enabled and not 'clips' in assigned_roles: + logger.warning(f"Camera {name} has clips enabled, but clips is not assigned to an input.") if not camera.record.enabled and 'record' in assigned_roles: logger.warning(f"Camera {name} has record assigned to an input, but record is not enabled.") diff --git a/frigate/config.py b/frigate/config.py index 807f795549..369e3a5122 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -42,7 +42,7 @@ } ) -SAVE_CLIPS_RETAIN_SCHEMA = vol.Schema( +CLIPS_RETAIN_SCHEMA = vol.Schema( { vol.Required('default',default=10): int, 'objects': { @@ -51,11 +51,11 @@ } ) -SAVE_CLIPS_SCHEMA = vol.Schema( +CLIPS_SCHEMA = vol.Schema( { vol.Optional('max_seconds', default=300): int, 'tmpfs_cache_size': str, - vol.Optional('retain', default={}): SAVE_CLIPS_RETAIN_SCHEMA + vol.Optional('retain', default={}): CLIPS_RETAIN_SCHEMA } ) @@ -177,12 +177,12 @@ def ensure_zones_and_cameras_have_different_names(cameras): vol.Optional('filters', default={}): FILTER_SCHEMA } }, - vol.Optional('save_clips', default={}): { + vol.Optional('clips', default={}): { vol.Optional('enabled', default=False): bool, vol.Optional('pre_capture', default=5): int, vol.Optional('post_capture', default=5): int, 'objects': [str], - vol.Optional('retain', default={}): SAVE_CLIPS_RETAIN_SCHEMA, + vol.Optional('retain', default={}): CLIPS_RETAIN_SCHEMA, }, vol.Optional('record', default={}): { 'enabled': bool, @@ -227,7 +227,7 @@ def ensure_zones_and_cameras_have_different_names(cameras): vol.Optional('default', default='info'): vol.In(['info', 'debug', 'warning', 'error', 'critical']), vol.Optional('logs', default={}): {str: vol.In(['info', 'debug', 'warning', 'error', 'critical']) } }, - vol.Optional('save_clips', default={}): SAVE_CLIPS_SCHEMA, + vol.Optional('clips', default={}): CLIPS_SCHEMA, vol.Optional('record', default={}): { vol.Optional('enabled', default=False): bool, vol.Optional('retain_days', default=30): int, @@ -399,7 +399,7 @@ def inputs(self): def output_args(self): return {k: v if isinstance(v, list) else v.split(' ') for k, v in self._output_args.items()} -class SaveClipsRetainConfig(): +class ClipsRetainConfig(): def __init__(self, global_config, config): self._default = config.get('default', global_config.get('default')) self._objects = config.get('objects', global_config.get('objects', {})) @@ -418,11 +418,11 @@ def to_dict(self): 'objects': self.objects } -class SaveClipsConfig(): +class ClipsConfig(): def __init__(self, config): self._max_seconds = config['max_seconds'] self._tmpfs_cache_size = config.get('tmpfs_cache_size', '').strip() - self._retain = SaveClipsRetainConfig(config['retain'], config['retain']) + self._retain = ClipsRetainConfig(config['retain'], config['retain']) @property def max_seconds(self): @@ -589,13 +589,13 @@ def to_dict(self): 'height': self.height } -class CameraSaveClipsConfig(): +class CameraClipsConfig(): def __init__(self, global_config, config): self._enabled = config['enabled'] self._pre_capture = config['pre_capture'] self._post_capture = config['post_capture'] self._objects = config.get('objects', global_config['objects']['track']) - self._retain = SaveClipsRetainConfig(global_config['save_clips']['retain'], config['retain']) + self._retain = ClipsRetainConfig(global_config['clips']['retain'], config['retain']) @property def enabled(self): @@ -748,7 +748,7 @@ def __init__(self, name, config, global_config): self._mask = self._create_mask(config.get('mask')) self._best_image_timeout = config['best_image_timeout'] self._zones = { name: ZoneConfig(name, z) for name, z in config['zones'].items() } - self._save_clips = CameraSaveClipsConfig(global_config, config['save_clips']) + self._clips = CameraClipsConfig(global_config, config['clips']) self._record = RecordConfig(global_config['record'], config['record']) self._rtmp = CameraRtmpConfig(global_config, config['rtmp']) self._snapshots = CameraSnapshotsConfig(config['snapshots']) @@ -806,7 +806,7 @@ def _get_ffmpeg_cmd(self, ffmpeg_input): ffmpeg_output_args = self.ffmpeg.output_args['rtmp'] + [ f"rtmp://127.0.0.1/live/{self.name}" ] + ffmpeg_output_args - if 'clips' in ffmpeg_input.roles and self.save_clips.enabled: + if 'clips' in ffmpeg_input.roles and self.clips.enabled: ffmpeg_output_args = self.ffmpeg.output_args['clips'] + [ f"{os.path.join(CACHE_DIR, self.name)}-%Y%m%d%H%M%S.mp4" ] + ffmpeg_output_args @@ -872,8 +872,8 @@ def zones(self)-> Dict[str, ZoneConfig]: return self._zones @property - def save_clips(self): - return self._save_clips + def clips(self): + return self._clips @property def record(self): @@ -923,7 +923,7 @@ def to_dict(self): 'fps': self.fps, 'best_image_timeout': self.best_image_timeout, 'zones': {k: z.to_dict() for k, z in self.zones.items()}, - 'save_clips': self.save_clips.to_dict(), + 'clips': self.clips.to_dict(), 'record': self.record.to_dict(), 'rtmp': self.rtmp.to_dict(), 'snapshots': self.snapshots.to_dict(), @@ -951,7 +951,7 @@ def __init__(self, config_file=None, config=None): self._model = ModelConfig(config['model']) self._detectors = { name: DetectorConfig(d) for name, d in config['detectors'].items() } self._mqtt = MqttConfig(config['mqtt']) - self._save_clips = SaveClipsConfig(config['save_clips']) + self._clips = ClipsConfig(config['clips']) self._cameras = { name: CameraConfig(name, c, config) for name, c in config['cameras'].items() } self._logger = LoggerConfig(config['logger']) @@ -984,7 +984,7 @@ def to_dict(self): 'model': self.model.to_dict(), 'detectors': {k: d.to_dict() for k, d in self.detectors.items()}, 'mqtt': self.mqtt.to_dict(), - 'save_clips': self.save_clips.to_dict(), + 'clips': self.clips.to_dict(), 'cameras': {k: c.to_dict() for k, c in self.cameras.items()}, 'logger': self.logger.to_dict() } @@ -1010,8 +1010,8 @@ def mqtt(self): return self._mqtt @property - def save_clips(self): - return self._save_clips + def clips(self): + return self._clips @property def cameras(self) -> Dict[str, CameraConfig]: diff --git a/frigate/events.py b/frigate/events.py index 75ad94649f..0383455f95 100644 --- a/frigate/events.py +++ b/frigate/events.py @@ -88,7 +88,7 @@ def refresh_cache(self): earliest_event = datetime.datetime.now().timestamp() # if the earliest event exceeds the max seconds, cap it - max_seconds = self.config.save_clips.max_seconds + max_seconds = self.config.clips.max_seconds if datetime.datetime.now().timestamp()-earliest_event > max_seconds: earliest_event = datetime.datetime.now().timestamp()-max_seconds @@ -164,16 +164,16 @@ def run(self): self.refresh_cache() - save_clips_config = self.config.cameras[camera].save_clips + clips_config = self.config.cameras[camera].clips # if save clips is not enabled for this camera, just continue - if not save_clips_config.enabled: + if not clips_config.enabled: if event_type == 'end': self.event_processed_queue.put((event_data['id'], camera)) continue # if specific objects are listed for this camera, only save clips for them - if not event_data['label'] in save_clips_config.objects: + if not event_data['label'] in clips_config.objects: if event_type == 'end': self.event_processed_queue.put((event_data['id'], camera)) continue @@ -183,7 +183,7 @@ def run(self): if event_type == 'end': if len(self.cached_clips) > 0 and not event_data['false_positive']: - self.create_clip(camera, event_data, save_clips_config.pre_capture, save_clips_config.post_capture) + self.create_clip(camera, event_data, clips_config.pre_capture, clips_config.post_capture) Event.create( id=event_data['id'], label=event_data['label'], @@ -222,7 +222,7 @@ def run(self): camera_keys = list(self.config.cameras.keys()) # Expire events from unlisted cameras based on the global config - retain_config = self.config.save_clips.retain + retain_config = self.config.clips.retain distinct_labels = (Event.select(Event.label) .where(Event.camera.not_in(camera_keys)) @@ -256,7 +256,7 @@ def run(self): # Expire events from cameras based on the camera config for name, camera in self.config.cameras.items(): - retain_config = camera.save_clips.retain + retain_config = camera.clips.retain # get distinct objects in database for this camera distinct_labels = (Event.select(Event.label) .where(Event.camera == name) diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index 9a96e9393e..c73f9b7636 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -191,12 +191,12 @@ def test_ffmpeg_params(self): frigate_config = FrigateConfig(config=config) assert('-re' in frigate_config.cameras['back'].ffmpeg_cmds[0]['cmd']) - def test_inherit_save_clips_retention(self): + def test_inherit_clips_retention(self): config = { 'mqtt': { 'host': 'mqtt' }, - 'save_clips': { + 'clips': { 'retain': { 'default': 20, 'objects': { @@ -217,14 +217,14 @@ def test_inherit_save_clips_retention(self): } } frigate_config = FrigateConfig(config=config) - assert(frigate_config.cameras['back'].save_clips.retain.objects['person'] == 30) + assert(frigate_config.cameras['back'].clips.retain.objects['person'] == 30) def test_roles_listed_twice_throws_error(self): config = { 'mqtt': { 'host': 'mqtt' }, - 'save_clips': { + 'clips': { 'retain': { 'default': 20, 'objects': { @@ -252,7 +252,7 @@ def test_zone_matching_camera_name_throws_error(self): 'mqtt': { 'host': 'mqtt' }, - 'save_clips': { + 'clips': { 'retain': { 'default': 20, 'objects': { @@ -279,12 +279,12 @@ def test_zone_matching_camera_name_throws_error(self): } self.assertRaises(vol.MultipleInvalid, lambda: FrigateConfig(config=config)) - def test_save_clips_should_default_to_global_objects(self): + def test_clips_should_default_to_global_objects(self): config = { 'mqtt': { 'host': 'mqtt' }, - 'save_clips': { + 'clips': { 'retain': { 'default': 20, 'objects': { @@ -304,16 +304,16 @@ def test_save_clips_should_default_to_global_objects(self): }, 'height': 1080, 'width': 1920, - 'save_clips': { + 'clips': { 'enabled': True } } } } config = FrigateConfig(config=config) - assert(len(config.cameras['back'].save_clips.objects) == 2) - assert('dog' in config.cameras['back'].save_clips.objects) - assert('person' in config.cameras['back'].save_clips.objects) + assert(len(config.cameras['back'].clips.objects) == 2) + assert('dog' in config.cameras['back'].clips.objects) + assert('person' in config.cameras['back'].clips.objects) def test_role_assigned_but_not_enabled(self): json_config = {