diff --git a/frigate/api/auth.py b/frigate/api/auth.py index ee0c58e34d..fdf235e7b7 100644 --- a/frigate/api/auth.py +++ b/frigate/api/auth.py @@ -324,11 +324,17 @@ def get_remote_addr(request: Request): network = ipaddress.ip_network(proxy) except ValueError: logger.warning(f"Unable to parse trusted network: {proxy}") + continue trusted_proxies.append(network) # return the first remote address that is not trusted for addr in route: - ip = ipaddress.ip_address(addr.strip()) + try: + ip = ipaddress.ip_address(addr.strip()) + except ValueError: + logger.debug("Invalid address in X-Forwarded-For header") + return direct_addr or "127.0.0.1" + logger.debug(f"Checking {ip} (v{ip.version})") trusted = False for trusted_proxy in trusted_proxies: @@ -473,12 +479,11 @@ def create_encoded_jwt(user, role, expiration, secret): def set_jwt_cookie(response: Response, cookie_name, encoded_jwt, max_age, secure): # TODO: ideally this would set secure as well, but that requires TLS - # SameSite is intentionally left unset (browsers default to Lax). Setting - # SameSite=Lax/Strict would stop the cookie from being sent in cross-origin - # iframes, breaking embedded views such as the Home Assistant Frigate card. - # CSRF is instead mitigated by requiring a custom X-CSRF-TOKEN header, which - # cross-origin pages cannot set without a CORS preflight that Frigate never - # grants (see check_csrf in api/fastapi_app.py). + # Starlette sets SameSite=Lax by default. The cookie is still sent to + # same-site iframes (e.g. Home Assistant on the same host or domain), but + # not to cross-site ones. CSRF is also mitigated by requiring a custom + # X-CSRF-TOKEN header, which cross-origin pages cannot set without a CORS + # preflight that Frigate never grants (see check_csrf in api/fastapi_app.py). response.set_cookie( key=cookie_name, value=encoded_jwt, diff --git a/frigate/comms/dispatcher.py b/frigate/comms/dispatcher.py index 189e842cf7..4d9cbc0470 100644 --- a/frigate/comms/dispatcher.py +++ b/frigate/comms/dispatcher.py @@ -782,7 +782,9 @@ class Dispatcher: try: payload = int(payload) except ValueError: - f"Received unsupported value for motion contour area: {payload}" + logger.warning( + f"Received unsupported value for motion contour area: {payload}" + ) return motion_settings = self.config.cameras[camera_name].motion @@ -799,7 +801,9 @@ class Dispatcher: try: payload = int(payload) except ValueError: - f"Received unsupported value for motion threshold: {payload}" + logger.warning( + f"Received unsupported value for motion threshold: {payload}" + ) return motion_settings = self.config.cameras[camera_name].motion @@ -814,7 +818,9 @@ class Dispatcher: def _on_global_notification_command(self, payload: str) -> None: """Callback for global notification topic.""" if payload != "ON" and payload != "OFF": - f"Received unsupported value for all notification: {payload}" + logger.warning( + f"Received unsupported value for all notification: {payload}" + ) return notification_settings = self.config.notifications diff --git a/frigate/data_processing/common/license_plate/mixin.py b/frigate/data_processing/common/license_plate/mixin.py index 28aa52f5bf..39d34402aa 100644 --- a/frigate/data_processing/common/license_plate/mixin.py +++ b/frigate/data_processing/common/license_plate/mixin.py @@ -176,6 +176,7 @@ class LicensePlateProcessingMixin: """ input_shape = [3, 48, 320] num_images = len(images) + outputs: list[np.ndarray] = [] for index in range(0, num_images, self.batch_size): input_h, input_w = input_shape[1], input_shape[2] @@ -195,11 +196,11 @@ class LicensePlateProcessingMixin: norm_image = norm_image[np.newaxis, :] norm_images.append(norm_image) - try: - outputs = self.model_runner.recognition_model(norm_images) # type: ignore[arg-type] - except Exception as e: - logger.warning(f"Error running LPR recognition model: {e}") - return [], [] + try: + outputs.extend(self.model_runner.recognition_model(norm_images)) # type: ignore[arg-type] + except Exception as e: + logger.warning(f"Error running LPR recognition model: {e}") + return [], [] return self.ctc_decoder(outputs) diff --git a/frigate/embeddings/embeddings.py b/frigate/embeddings/embeddings.py index 91144c3fa5..bf255c6bcc 100644 --- a/frigate/embeddings/embeddings.py +++ b/frigate/embeddings/embeddings.py @@ -357,6 +357,9 @@ class Embeddings: self.requestor.send_data(UPDATE_EMBEDDINGS_REINDEX_PROGRESS, totals) + # a single batch sends no progress, so the first message shows it nearly done + totals["processed_objects"] = 0 + events = ( Event.select() .order_by(Event.start_time.desc()) diff --git a/frigate/ptz/autotrack.py b/frigate/ptz/autotrack.py index efe8fd5642..c0f78cbb64 100644 --- a/frigate/ptz/autotrack.py +++ b/frigate/ptz/autotrack.py @@ -1397,9 +1397,6 @@ class PtzAutoTracker: def is_autotracking(self, camera: str): return self.tracked_object[camera] is not None - def autotracked_object_region(self, camera: str): - return self.tracked_object[camera]["region"] - def autotrack_object(self, camera: str, obj: TrackedObject): if camera not in self.config.cameras: return @@ -1538,8 +1535,6 @@ class PtzAutoTracker: # returns camera to preset after timeout when tracking is over autotracker_config = self.config.cameras[camera].onvif.autotracking - if not self.autotracker_init[camera]: - self._autotracker_setup(self.config.cameras[camera], camera) # regularly update camera status if not self.ptz_metrics[camera].motor_stopped.is_set(): await self.onvif.get_camera_status(camera) diff --git a/frigate/ptz/onvif.py b/frigate/ptz/onvif.py index 83094ce4a9..7e2206e122 100644 --- a/frigate/ptz/onvif.py +++ b/frigate/ptz/onvif.py @@ -966,6 +966,10 @@ class OnvifController: } else: logger.warning(f"ONVIF initialization failed for {camera_name}") + self.failed_cams[camera_name] = { + "retry_attempts": attempts + 1, + "last_attempt": time.time(), + } except Exception as e: logger.error( f"Error during ONVIF initialization for {camera_name}: {e}" diff --git a/frigate/record/export.py b/frigate/record/export.py index 7dfc2d739f..7455d56b05 100644 --- a/frigate/record/export.py +++ b/frigate/record/export.py @@ -1375,7 +1375,7 @@ class RecordingExporter(threading.Thread): if preview.end_time > self.end_time: playlist_lines.append( - f"outpoint {int(preview.end_time - self.end_time)}" + f"outpoint {int(self.end_time - preview.start_time)}" ) ffmpeg_input = ( diff --git a/frigate/test/test_config_migration.py b/frigate/test/test_config_migration.py index 1ad4814eaf..c33aa95425 100644 --- a/frigate/test/test_config_migration.py +++ b/frigate/test/test_config_migration.py @@ -257,6 +257,31 @@ class TestMigrateConfigFile(unittest.TestCase): self.assertEqual(migrated["models"][0]["devices"], ["openvino:GPU"]) self.assertNotIn("detectors", migrated) + def test_top_level_changes_survive_later_steps(self): + # 0.16 adds detect and 0.17 splits genai, both at the top level + migrated = self._migrate( + "mqtt:\n" + " enabled: false\n" + "genai:\n" + " provider: ollama\n" + " model: llava\n" + " prompt: describe it\n" + "cameras: {}\n" + "version: 0.15-1\n" + ) + + self.assertEqual(migrated["version"], CURRENT_CONFIG_VERSION) + self.assertTrue(migrated["detect"]["enabled"]) + self.assertEqual(migrated["objects"]["genai"], {"prompt": "describe it"}) + self.assertEqual( + migrated["genai"]["default"], + { + "provider": "ollama", + "model": "llava", + "roles": ["descriptions", "chat"], + }, + ) + def test_a_migrated_config_is_left_alone(self): migrated = self._migrate( "mqtt:\n" diff --git a/frigate/util/config.py b/frigate/util/config.py index 16d0c8ba2e..9fe72f8cd5 100644 --- a/frigate/util/config.py +++ b/frigate/util/config.py @@ -191,7 +191,7 @@ def migrate_frigate_config(config_file: str): if previous_version < "0.14": logger.info(f"Migrating frigate config from {previous_version} to 0.14...") - new_config = migrate_014(config) + new_config = migrate_014(new_config) with open(config_file, "w") as f: yaml.dump(new_config, f) previous_version = "0.14" @@ -209,35 +209,35 @@ def migrate_frigate_config(config_file: str): if previous_version < "0.15-0": logger.info(f"Migrating frigate config from {previous_version} to 0.15-0...") - new_config = migrate_015_0(config) + new_config = migrate_015_0(new_config) with open(config_file, "w") as f: yaml.dump(new_config, f) previous_version = "0.15-0" if previous_version < "0.15-1": logger.info(f"Migrating frigate config from {previous_version} to 0.15-1...") - new_config = migrate_015_1(config) + new_config = migrate_015_1(new_config) with open(config_file, "w") as f: yaml.dump(new_config, f) previous_version = "0.15-1" if previous_version < "0.16-0": logger.info(f"Migrating frigate config from {previous_version} to 0.16-0...") - new_config = migrate_016_0(config) + new_config = migrate_016_0(new_config) with open(config_file, "w") as f: yaml.dump(new_config, f) previous_version = "0.16-0" if previous_version < "0.17-0": logger.info(f"Migrating frigate config from {previous_version} to 0.17-0...") - new_config = migrate_017_0(config) + new_config = migrate_017_0(new_config) with open(config_file, "w") as f: yaml.dump(new_config, f) previous_version = "0.17-0" if previous_version < "0.18-0": logger.info(f"Migrating frigate config from {previous_version} to 0.18-0...") - new_config = migrate_018_0(config) + new_config = migrate_018_0(new_config) with open(config_file, "w") as f: yaml.dump(new_config, f) previous_version = "0.18-0" diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index 303e3fd541..4437960d34 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -1162,7 +1162,9 @@ "actions": "Actions", "noRoles": "No custom roles found.", "editCameras": "Edit Cameras", - "deleteRole": "Delete Role" + "deleteRole": "Delete Role", + "cameraCount_one": "{{count}} camera", + "cameraCount_other": "{{count}} cameras" }, "toast": { "success": { diff --git a/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx b/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx index 2644ff89a5..c2f75dd383 100644 --- a/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx +++ b/web/src/components/config-form/sectionExtras/NotificationsSettingsExtras.tsx @@ -627,7 +627,11 @@ export default function NotificationsSettingsExtras({