Miscellaneous Fixes (#21193)

* Fix saving zone friendly name when it wasn't set

* Fix UTF-8 handling for Onvif

* Don't remove none directory for classes

* Lookup all event IDs for review item immediately

* Cleanup typing

* Only fetch events when review group is open

* Cleanup

* disable debug paths switch for autotracking cameras

* fix clickable birdseye

---------

Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
Nicolas Mowen 2025-12-09 11:08:44 -07:00 committed by GitHub
parent dfd837cfb0
commit 4cf4520ea7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 62 additions and 21 deletions

View File

@ -710,7 +710,7 @@ def delete_classification_dataset_images(
if os.path.isfile(file_path): if os.path.isfile(file_path):
os.unlink(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) os.rmdir(folder)
return JSONResponse( return JSONResponse(

View File

@ -607,23 +607,27 @@ class Dispatcher:
) )
self.publish(f"{camera_name}/snapshots/state", payload, retain=True) 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.""" """Callback for ptz topic."""
try: 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 command = OnvifCommandEnum.preset
param = payload.lower()[payload.index("_") + 1 :] param = preset[preset.index("_") + 1 :]
elif "move_relative" in payload.lower(): elif "move_relative" in preset:
command = OnvifCommandEnum.move_relative command = OnvifCommandEnum.move_relative
param = payload.lower()[payload.index("_") + 1 :] param = preset[preset.index("_") + 1 :]
else: else:
command = OnvifCommandEnum[payload.lower()] command = OnvifCommandEnum[preset]
param = "" param = ""
self.onvif.handle_command(camera_name, command, param) self.onvif.handle_command(camera_name, command, param)
logger.info(f"Setting ptz command to {command} for {camera_name}") logger.info(f"Setting ptz command to {command} for {camera_name}")
except KeyError as k: 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: def _on_birdseye_command(self, camera_name: str, payload: str) -> None:
"""Callback for birdseye topic.""" """Callback for birdseye topic."""

View File

@ -95,12 +95,21 @@ class OnvifController:
cam = self.camera_configs[cam_name] cam = self.camera_configs[cam_name]
try: 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] = { self.cams[cam_name] = {
"onvif": ONVIFCamera( "onvif": ONVIFCamera(
cam.onvif.host, cam.onvif.host,
cam.onvif.port, cam.onvif.port,
cam.onvif.user, user,
cam.onvif.password, password,
wsdl_dir=str(Path(find_spec("onvif").origin).parent / "wsdl"), wsdl_dir=str(Path(find_spec("onvif").origin).parent / "wsdl"),
adjust_time=cam.onvif.ignore_time_mismatch, adjust_time=cam.onvif.ignore_time_mismatch,
encrypt=not cam.onvif.tls_insecure, encrypt=not cam.onvif.tls_insecure,
@ -325,9 +334,15 @@ class OnvifController:
presets = [] presets = []
for preset in presets: for preset in presets:
self.cams[camera_name]["presets"][ # Ensure preset name is a Unicode string and handle UTF-8 characters correctly
(getattr(preset, "Name") or f"preset {preset['token']}").lower() preset_name = getattr(preset, "Name") or f"preset {preset['token']}"
] = 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 # get list of supported features
supported_features = [] supported_features = []
@ -563,6 +578,11 @@ class OnvifController:
self.cams[camera_name]["active"] = False self.cams[camera_name]["active"] = False
async def _move_to_preset(self, camera_name: str, preset: str) -> None: 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"]: if preset not in self.cams[camera_name]["presets"]:
logger.error(f"{preset} is not a valid preset for {camera_name}") logger.error(f"{preset} is not a valid preset for {camera_name}")
return return

View File

@ -441,7 +441,7 @@ export default function ZoneEditPane({
} }
let friendlyNameQuery = ""; let friendlyNameQuery = "";
if (friendly_name) { if (friendly_name && friendly_name !== zoneName) {
friendlyNameQuery = `&cameras.${polygon?.camera}.zones.${zoneName}.friendly_name=${encodeURIComponent(friendly_name)}`; friendlyNameQuery = `&cameras.${polygon?.camera}.zones.${zoneName}.friendly_name=${encodeURIComponent(friendly_name)}`;
} }

View File

@ -316,7 +316,7 @@ function ReviewGroup({
date_style: "medium", date_style: "medium",
}); });
const shouldFetchEvents = review?.data?.detections?.length > 0; const shouldFetchEvents = open && review?.data?.detections?.length > 0;
const { data: fetchedEvents, isValidating } = useSWR<Event[]>( const { data: fetchedEvents, isValidating } = useSWR<Event[]>(
shouldFetchEvents shouldFetchEvents

View File

@ -134,10 +134,20 @@ function Live() {
.sort((aConf, bConf) => aConf.ui.order - bConf.ui.order); .sort((aConf, bConf) => aConf.ui.order - bConf.ui.order);
}, [config, cameraGroup, allowedCameras]); }, [config, cameraGroup, allowedCameras]);
const selectedCamera = useMemo( const selectedCamera = useMemo(() => {
() => cameras.find((cam) => cam.name == selectedCameraName), if (!config || !selectedCameraName || selectedCameraName === "birdseye") {
[cameras, selectedCameraName], return undefined;
); }
const camera = config.cameras[selectedCameraName];
if (
camera &&
allowedCameras.includes(selectedCameraName) &&
camera.enabled_in_config
) {
return camera;
}
return undefined;
}, [config, selectedCameraName, allowedCameras]);
return ( return (
<div className="size-full" ref={mainRef}> <div className="size-full" ref={mainRef}>
@ -146,6 +156,7 @@ function Live() {
supportsFullscreen={supportsFullScreen} supportsFullscreen={supportsFullScreen}
fullscreen={fullscreen} fullscreen={fullscreen}
toggleFullscreen={toggleFullscreen} toggleFullscreen={toggleFullscreen}
onSelectCamera={setSelectedCameraName}
/> />
) : selectedCamera ? ( ) : selectedCamera ? (
<LiveCameraView <LiveCameraView

View File

@ -28,12 +28,14 @@ type LiveBirdseyeViewProps = {
supportsFullscreen: boolean; supportsFullscreen: boolean;
fullscreen: boolean; fullscreen: boolean;
toggleFullscreen: () => void; toggleFullscreen: () => void;
onSelectCamera?: (cameraName: string) => void;
}; };
export default function LiveBirdseyeView({ export default function LiveBirdseyeView({
supportsFullscreen, supportsFullscreen,
fullscreen, fullscreen,
toggleFullscreen, toggleFullscreen,
onSelectCamera,
}: LiveBirdseyeViewProps) { }: LiveBirdseyeViewProps) {
const { t } = useTranslation(["views/live"]); const { t } = useTranslation(["views/live"]);
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -181,13 +183,13 @@ export default function LiveBirdseyeView({
canvasY >= parsedCoords.y && canvasY >= parsedCoords.y &&
canvasY < parsedCoords.y + parsedCoords.height canvasY < parsedCoords.y + parsedCoords.height
) { ) {
navigate(`/#${cameraName}`); onSelectCamera?.(cameraName);
break; break;
} }
} }
} }
}, },
[playerRef, config, birdseyeLayout, navigate], [playerRef, config, birdseyeLayout, onSelectCamera],
); );
if (!config) { if (!config) {

View File

@ -252,6 +252,10 @@ export default function ObjectSettingsView({
className="ml-1" className="ml-1"
id={param} id={param}
checked={options && options[param]} checked={options && options[param]}
disabled={
param === "paths" &&
cameraConfig?.onvif?.autotracking?.enabled_in_config
}
onCheckedChange={(isChecked) => { onCheckedChange={(isChecked) => {
handleSetOption(param, isChecked); handleSetOption(param, isChecked);
}} }}