diff --git a/frigate/api/classification.py b/frigate/api/classification.py index 6a738409e..deafaf956 100644 --- a/frigate/api/classification.py +++ b/frigate/api/classification.py @@ -710,7 +710,7 @@ def delete_classification_dataset_images( if os.path.isfile(file_path): os.unlink(file_path) - if os.path.exists(folder) and not os.listdir(folder): + if os.path.exists(folder) and not os.listdir(folder) and category.lower() != "none": os.rmdir(folder) return JSONResponse( diff --git a/frigate/comms/dispatcher.py b/frigate/comms/dispatcher.py index 0c2ba5a89..6e45ac175 100644 --- a/frigate/comms/dispatcher.py +++ b/frigate/comms/dispatcher.py @@ -607,23 +607,27 @@ class Dispatcher: ) self.publish(f"{camera_name}/snapshots/state", payload, retain=True) - def _on_ptz_command(self, camera_name: str, payload: str) -> None: + def _on_ptz_command(self, camera_name: str, payload: str | bytes) -> None: """Callback for ptz topic.""" try: - if "preset" in payload.lower(): + preset: str = ( + payload.decode("utf-8") if isinstance(payload, bytes) else payload + ).lower() + + if "preset" in preset: command = OnvifCommandEnum.preset - param = payload.lower()[payload.index("_") + 1 :] - elif "move_relative" in payload.lower(): + param = preset[preset.index("_") + 1 :] + elif "move_relative" in preset: command = OnvifCommandEnum.move_relative - param = payload.lower()[payload.index("_") + 1 :] + param = preset[preset.index("_") + 1 :] else: - command = OnvifCommandEnum[payload.lower()] + command = OnvifCommandEnum[preset] param = "" self.onvif.handle_command(camera_name, command, param) logger.info(f"Setting ptz command to {command} for {camera_name}") except KeyError as k: - logger.error(f"Invalid PTZ command {payload}: {k}") + logger.error(f"Invalid PTZ command {preset}: {k}") def _on_birdseye_command(self, camera_name: str, payload: str) -> None: """Callback for birdseye topic.""" diff --git a/frigate/ptz/onvif.py b/frigate/ptz/onvif.py index e7539b1d6..488dbd278 100644 --- a/frigate/ptz/onvif.py +++ b/frigate/ptz/onvif.py @@ -95,12 +95,21 @@ class OnvifController: cam = self.camera_configs[cam_name] try: + user = cam.onvif.user + password = cam.onvif.password + + if user is not None and isinstance(user, bytes): + user = user.decode("utf-8") + + if password is not None and isinstance(password, bytes): + password = password.decode("utf-8") + self.cams[cam_name] = { "onvif": ONVIFCamera( cam.onvif.host, cam.onvif.port, - cam.onvif.user, - cam.onvif.password, + user, + password, wsdl_dir=str(Path(find_spec("onvif").origin).parent / "wsdl"), adjust_time=cam.onvif.ignore_time_mismatch, encrypt=not cam.onvif.tls_insecure, @@ -325,9 +334,15 @@ class OnvifController: presets = [] for preset in presets: - self.cams[camera_name]["presets"][ - (getattr(preset, "Name") or f"preset {preset['token']}").lower() - ] = preset["token"] + # Ensure preset name is a Unicode string and handle UTF-8 characters correctly + preset_name = getattr(preset, "Name") or f"preset {preset['token']}" + + if isinstance(preset_name, bytes): + preset_name = preset_name.decode("utf-8") + + # Convert to lowercase while preserving UTF-8 characters + preset_name_lower = preset_name.lower() + self.cams[camera_name]["presets"][preset_name_lower] = preset["token"] # get list of supported features supported_features = [] @@ -563,6 +578,11 @@ class OnvifController: self.cams[camera_name]["active"] = False async def _move_to_preset(self, camera_name: str, preset: str) -> None: + if isinstance(preset, bytes): + preset = preset.decode("utf-8") + + preset = preset.lower() + if preset not in self.cams[camera_name]["presets"]: logger.error(f"{preset} is not a valid preset for {camera_name}") return diff --git a/web/src/components/settings/ZoneEditPane.tsx b/web/src/components/settings/ZoneEditPane.tsx index f68546aa2..ebcb5d2ed 100644 --- a/web/src/components/settings/ZoneEditPane.tsx +++ b/web/src/components/settings/ZoneEditPane.tsx @@ -441,7 +441,7 @@ export default function ZoneEditPane({ } let friendlyNameQuery = ""; - if (friendly_name) { + if (friendly_name && friendly_name !== zoneName) { friendlyNameQuery = `&cameras.${polygon?.camera}.zones.${zoneName}.friendly_name=${encodeURIComponent(friendly_name)}`; } diff --git a/web/src/components/timeline/DetailStream.tsx b/web/src/components/timeline/DetailStream.tsx index ec2bc3b27..dc87e1f0f 100644 --- a/web/src/components/timeline/DetailStream.tsx +++ b/web/src/components/timeline/DetailStream.tsx @@ -316,7 +316,7 @@ function ReviewGroup({ date_style: "medium", }); - const shouldFetchEvents = review?.data?.detections?.length > 0; + const shouldFetchEvents = open && review?.data?.detections?.length > 0; const { data: fetchedEvents, isValidating } = useSWR( shouldFetchEvents