diff --git a/frigate/config/camera/camera.py b/frigate/config/camera/camera.py index 21397065b..9960abdce 100644 --- a/frigate/config/camera/camera.py +++ b/frigate/config/camera/camera.py @@ -242,6 +242,14 @@ class CameraConfig(FrigateBaseModel): def create_ffmpeg_cmds(self): if "_ffmpeg_cmds" in self: return + self._build_ffmpeg_cmds() + + def recreate_ffmpeg_cmds(self): + """Force regeneration of ffmpeg commands from current config.""" + self._build_ffmpeg_cmds() + + def _build_ffmpeg_cmds(self): + """Build ffmpeg commands from the current ffmpeg config.""" ffmpeg_cmds = [] for ffmpeg_input in self.ffmpeg.inputs: ffmpeg_cmd = self._get_ffmpeg_cmd(ffmpeg_input) diff --git a/frigate/config/camera/updater.py b/frigate/config/camera/updater.py index 44aea527d..0c49ec465 100644 --- a/frigate/config/camera/updater.py +++ b/frigate/config/camera/updater.py @@ -17,6 +17,7 @@ class CameraConfigUpdateEnum(str, Enum): birdseye = "birdseye" detect = "detect" enabled = "enabled" + ffmpeg = "ffmpeg" motion = "motion" # includes motion and motion masks notifications = "notifications" objects = "objects" @@ -91,6 +92,9 @@ class CameraConfigUpdateSubscriber: if update_type == CameraConfigUpdateEnum.audio: config.audio = updated_config + elif update_type == CameraConfigUpdateEnum.ffmpeg: + config.ffmpeg = updated_config + config.recreate_ffmpeg_cmds() elif update_type == CameraConfigUpdateEnum.audio_transcription: config.audio_transcription = updated_config elif update_type == CameraConfigUpdateEnum.birdseye: diff --git a/frigate/video.py b/frigate/video.py index 5e42619dd..67da0a664 100755 --- a/frigate/video.py +++ b/frigate/video.py @@ -214,7 +214,11 @@ class CameraWatchdog(threading.Thread): self.config_subscriber = CameraConfigUpdateSubscriber( None, {config.name: config}, - [CameraConfigUpdateEnum.enabled, CameraConfigUpdateEnum.record], + [ + CameraConfigUpdateEnum.enabled, + CameraConfigUpdateEnum.ffmpeg, + CameraConfigUpdateEnum.record, + ], ) self.requestor = InterProcessRequestor() self.was_enabled = self.config.enabled @@ -254,9 +258,13 @@ class CameraWatchdog(threading.Thread): self._last_record_status = status self._last_status_update_time = now + def _check_config_updates(self) -> dict[str, list[str]]: + """Check for config updates and return the update dict.""" + return self.config_subscriber.check_for_updates() + def _update_enabled_state(self) -> bool: """Fetch the latest config and update enabled state.""" - self.config_subscriber.check_for_updates() + self._check_config_updates() return self.config.enabled def reset_capture_thread( @@ -317,7 +325,24 @@ class CameraWatchdog(threading.Thread): # 1 second watchdog loop while not self.stop_event.wait(1): - enabled = self._update_enabled_state() + updates = self._check_config_updates() + + # Handle ffmpeg config changes by restarting all ffmpeg processes + if "ffmpeg" in updates and self.config.enabled: + self.logger.debug( + "FFmpeg config updated for %s, restarting ffmpeg processes", + self.config.name, + ) + self.stop_all_ffmpeg() + self.start_all_ffmpeg() + self.latest_valid_segment_time = 0 + self.latest_invalid_segment_time = 0 + self.latest_cache_segment_time = 0 + self.record_enable_time = datetime.now().astimezone(timezone.utc) + last_restart_time = datetime.now().timestamp() + continue + + enabled = self.config.enabled if enabled != self.was_enabled: if enabled: self.logger.debug(f"Enabling camera {self.config.name}") diff --git a/web/src/components/config-form/section-configs/ffmpeg.ts b/web/src/components/config-form/section-configs/ffmpeg.ts index ccbca5609..5ad2f7b2a 100644 --- a/web/src/components/config-form/section-configs/ffmpeg.ts +++ b/web/src/components/config-form/section-configs/ffmpeg.ts @@ -162,17 +162,7 @@ const ffmpeg: SectionConfigOverrides = { fieldGroups: { cameraFfmpeg: ["input_args", "hwaccel_args", "output_args"], }, - restartRequired: [ - "inputs", - "path", - "global_args", - "hwaccel_args", - "input_args", - "output_args", - "retry_interval", - "apple_compatibility", - "gpu", - ], + restartRequired: [], }, };