mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Fixes (#18275)
This commit is contained in:
		
							parent
							
								
									ebae6cb1ed
								
							
						
					
					
						commit
						5d13925d2b
					
				@ -264,6 +264,7 @@ tensorrt
 | 
				
			|||||||
tflite
 | 
					tflite
 | 
				
			||||||
thresholded
 | 
					thresholded
 | 
				
			||||||
timelapse
 | 
					timelapse
 | 
				
			||||||
 | 
					titlecase
 | 
				
			||||||
tmpfs
 | 
					tmpfs
 | 
				
			||||||
tobytes
 | 
					tobytes
 | 
				
			||||||
toggleable
 | 
					toggleable
 | 
				
			||||||
 | 
				
			|||||||
@ -31,6 +31,7 @@ norfair == 2.2.*
 | 
				
			|||||||
setproctitle == 1.3.*
 | 
					setproctitle == 1.3.*
 | 
				
			||||||
ws4py == 0.5.*
 | 
					ws4py == 0.5.*
 | 
				
			||||||
unidecode == 1.3.*
 | 
					unidecode == 1.3.*
 | 
				
			||||||
 | 
					titlecase == 2.4.*
 | 
				
			||||||
# Image Manipulation
 | 
					# Image Manipulation
 | 
				
			||||||
numpy == 1.26.*
 | 
					numpy == 1.26.*
 | 
				
			||||||
opencv-python-headless == 4.11.0.*
 | 
					opencv-python-headless == 4.11.0.*
 | 
				
			||||||
 | 
				
			|||||||
@ -172,6 +172,7 @@ class CameraState:
 | 
				
			|||||||
                # draw any attributes
 | 
					                # draw any attributes
 | 
				
			||||||
                for attribute in obj["current_attributes"]:
 | 
					                for attribute in obj["current_attributes"]:
 | 
				
			||||||
                    box = attribute["box"]
 | 
					                    box = attribute["box"]
 | 
				
			||||||
 | 
					                    box_area = int((box[2] - box[0]) * (box[3] - box[1]))
 | 
				
			||||||
                    draw_box_with_label(
 | 
					                    draw_box_with_label(
 | 
				
			||||||
                        frame_copy,
 | 
					                        frame_copy,
 | 
				
			||||||
                        box[0],
 | 
					                        box[0],
 | 
				
			||||||
@ -179,7 +180,7 @@ class CameraState:
 | 
				
			|||||||
                        box[2],
 | 
					                        box[2],
 | 
				
			||||||
                        box[3],
 | 
					                        box[3],
 | 
				
			||||||
                        attribute["label"],
 | 
					                        attribute["label"],
 | 
				
			||||||
                        f"{attribute['score']:.0%}",
 | 
					                        f"{attribute['score']:.0%} {str(box_area)}",
 | 
				
			||||||
                        thickness=thickness,
 | 
					                        thickness=thickness,
 | 
				
			||||||
                        color=color,
 | 
					                        color=color,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ from typing import Any, Callable
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from py_vapid import Vapid01
 | 
					from py_vapid import Vapid01
 | 
				
			||||||
from pywebpush import WebPusher
 | 
					from pywebpush import WebPusher
 | 
				
			||||||
 | 
					from titlecase import titlecase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from frigate.comms.base_communicator import Communicator
 | 
					from frigate.comms.base_communicator import Communicator
 | 
				
			||||||
from frigate.comms.config_updater import ConfigSubscriber
 | 
					from frigate.comms.config_updater import ConfigSubscriber
 | 
				
			||||||
@ -325,8 +326,8 @@ class WebPushClient(Communicator):  # type: ignore[misc]
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        sorted_objects.update(payload["after"]["data"]["sub_labels"])
 | 
					        sorted_objects.update(payload["after"]["data"]["sub_labels"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        title = f"{', '.join(sorted_objects).replace('_', ' ').title()}{' was' if state == 'end' else ''} detected in {', '.join(payload['after']['data']['zones']).replace('_', ' ').title()}"
 | 
					        title = f"{titlecase(', '.join(sorted_objects).replace('_', ' '))}{' was' if state == 'end' else ''} detected in {titlecase(', '.join(payload['after']['data']['zones']).replace('_', ' '))}"
 | 
				
			||||||
        message = f"Detected on {camera.replace('_', ' ').title()}"
 | 
					        message = f"Detected on {titlecase(camera.replace('_', ' '))}"
 | 
				
			||||||
        image = f"{payload['after']['thumb_path'].replace('/media/frigate', '')}"
 | 
					        image = f"{payload['after']['thumb_path'].replace('/media/frigate', '')}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # if event is ongoing open to live view otherwise open to recordings view
 | 
					        # if event is ongoing open to live view otherwise open to recordings view
 | 
				
			||||||
 | 
				
			|||||||
@ -5,12 +5,12 @@ import json
 | 
				
			|||||||
import logging
 | 
					import logging
 | 
				
			||||||
import multiprocessing as mp
 | 
					import multiprocessing as mp
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import signal
 | 
					import signal
 | 
				
			||||||
import threading
 | 
					import threading
 | 
				
			||||||
from types import FrameType
 | 
					from types import FrameType
 | 
				
			||||||
from typing import Any, Optional, Union
 | 
					from typing import Any, Optional, Union
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import regex
 | 
				
			||||||
from pathvalidate import ValidationError, sanitize_filename
 | 
					from pathvalidate import ValidationError, sanitize_filename
 | 
				
			||||||
from setproctitle import setproctitle
 | 
					from setproctitle import setproctitle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -243,7 +243,7 @@ class EmbeddingsContext:
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def rename_face(self, old_name: str, new_name: str) -> None:
 | 
					    def rename_face(self, old_name: str, new_name: str) -> None:
 | 
				
			||||||
        valid_name_pattern = r"^[a-zA-Z0-9\s_-]{1,50}$"
 | 
					        valid_name_pattern = r"^[\p{L}\p{N}\s'_-]{1,50}$"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            sanitized_old_name = sanitize_filename(old_name, replacement_text="_")
 | 
					            sanitized_old_name = sanitize_filename(old_name, replacement_text="_")
 | 
				
			||||||
@ -251,9 +251,9 @@ class EmbeddingsContext:
 | 
				
			|||||||
        except ValidationError as e:
 | 
					        except ValidationError as e:
 | 
				
			||||||
            raise ValueError(f"Invalid face name: {str(e)}")
 | 
					            raise ValueError(f"Invalid face name: {str(e)}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not re.match(valid_name_pattern, old_name):
 | 
					        if not regex.match(valid_name_pattern, old_name):
 | 
				
			||||||
            raise ValueError(f"Invalid old face name: {old_name}")
 | 
					            raise ValueError(f"Invalid old face name: {old_name}")
 | 
				
			||||||
        if not re.match(valid_name_pattern, new_name):
 | 
					        if not regex.match(valid_name_pattern, new_name):
 | 
				
			||||||
            raise ValueError(f"Invalid new face name: {new_name}")
 | 
					            raise ValueError(f"Invalid new face name: {new_name}")
 | 
				
			||||||
        if sanitized_old_name != old_name:
 | 
					        if sanitized_old_name != old_name:
 | 
				
			||||||
            raise ValueError(f"Old face name contains invalid characters: {old_name}")
 | 
					            raise ValueError(f"Old face name contains invalid characters: {old_name}")
 | 
				
			||||||
 | 
				
			|||||||
@ -474,6 +474,7 @@ class TrackedObject:
 | 
				
			|||||||
            # draw any attributes
 | 
					            # draw any attributes
 | 
				
			||||||
            for attribute in self.thumbnail_data["attributes"]:
 | 
					            for attribute in self.thumbnail_data["attributes"]:
 | 
				
			||||||
                box = attribute["box"]
 | 
					                box = attribute["box"]
 | 
				
			||||||
 | 
					                box_area = int((box[2] - box[0]) * (box[3] - box[1]))
 | 
				
			||||||
                draw_box_with_label(
 | 
					                draw_box_with_label(
 | 
				
			||||||
                    best_frame,
 | 
					                    best_frame,
 | 
				
			||||||
                    box[0],
 | 
					                    box[0],
 | 
				
			||||||
@ -481,7 +482,7 @@ class TrackedObject:
 | 
				
			|||||||
                    box[2],
 | 
					                    box[2],
 | 
				
			||||||
                    box[3],
 | 
					                    box[3],
 | 
				
			||||||
                    attribute["label"],
 | 
					                    attribute["label"],
 | 
				
			||||||
                    f"{attribute['score']:.0%}",
 | 
					                    f"{attribute['score']:.0%} {str(box_area)}",
 | 
				
			||||||
                    thickness=thickness,
 | 
					                    thickness=thickness,
 | 
				
			||||||
                    color=color,
 | 
					                    color=color,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,8 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "description": {
 | 
					  "description": {
 | 
				
			||||||
    "addFace": "Walk through adding a new collection to the Face Library.",
 | 
					    "addFace": "Walk through adding a new collection to the Face Library.",
 | 
				
			||||||
    "placeholder": "Enter a name for this collection"
 | 
					    "placeholder": "Enter a name for this collection",
 | 
				
			||||||
 | 
					    "invalidName": "Invalid name. Names can only include letters, numbers, spaces, apostrophes, underscores, and hyphens."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "details": {
 | 
					  "details": {
 | 
				
			||||||
    "person": "Person",
 | 
					    "person": "Person",
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ export default function StepIndicator({
 | 
				
			|||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="flex flex-row justify-evenly">
 | 
					    <div className="flex flex-row justify-evenly">
 | 
				
			||||||
      {steps.map((name, idx) => (
 | 
					      {steps.map((name, idx) => (
 | 
				
			||||||
        <div className="flex flex-col items-center gap-2">
 | 
					        <div key={idx} className="flex flex-col items-center gap-2">
 | 
				
			||||||
          <div
 | 
					          <div
 | 
				
			||||||
            className={cn(
 | 
					            className={cn(
 | 
				
			||||||
              "flex size-16 items-center justify-center rounded-full",
 | 
					              "flex size-16 items-center justify-center rounded-full",
 | 
				
			||||||
 | 
				
			|||||||
@ -18,18 +18,33 @@ type TextEntryProps = {
 | 
				
			|||||||
  allowEmpty?: boolean;
 | 
					  allowEmpty?: boolean;
 | 
				
			||||||
  onSave: (text: string) => void;
 | 
					  onSave: (text: string) => void;
 | 
				
			||||||
  children?: React.ReactNode;
 | 
					  children?: React.ReactNode;
 | 
				
			||||||
 | 
					  regexPattern?: RegExp;
 | 
				
			||||||
 | 
					  regexErrorMessage?: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function TextEntry({
 | 
					export default function TextEntry({
 | 
				
			||||||
  defaultValue = "",
 | 
					  defaultValue = "",
 | 
				
			||||||
  placeholder,
 | 
					  placeholder,
 | 
				
			||||||
  allowEmpty = false,
 | 
					  allowEmpty = false,
 | 
				
			||||||
  onSave,
 | 
					  onSave,
 | 
				
			||||||
  children,
 | 
					  children,
 | 
				
			||||||
 | 
					  regexPattern,
 | 
				
			||||||
 | 
					  regexErrorMessage = "Input does not match the required format",
 | 
				
			||||||
}: TextEntryProps) {
 | 
					}: TextEntryProps) {
 | 
				
			||||||
  const formSchema = z.object({
 | 
					  const formSchema = z.object({
 | 
				
			||||||
    text: allowEmpty
 | 
					    text: z
 | 
				
			||||||
      ? z.string().optional()
 | 
					      .string()
 | 
				
			||||||
      : z.string().min(1, "Field is required"),
 | 
					      .optional()
 | 
				
			||||||
 | 
					      .refine(
 | 
				
			||||||
 | 
					        (val) => {
 | 
				
			||||||
 | 
					          if (!allowEmpty && !val) return false;
 | 
				
			||||||
 | 
					          if (val && regexPattern) return regexPattern.test(val);
 | 
				
			||||||
 | 
					          return true;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          message: regexPattern ? regexErrorMessage : "Field is required",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const form = useForm<z.infer<typeof formSchema>>({
 | 
					  const form = useForm<z.infer<typeof formSchema>>({
 | 
				
			||||||
 | 
				
			|||||||
@ -119,6 +119,8 @@ export default function CreateFaceWizardDialog({
 | 
				
			|||||||
              setName(name);
 | 
					              setName(name);
 | 
				
			||||||
              setStep(1);
 | 
					              setStep(1);
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
 | 
					            regexPattern={/^[\p{L}\p{N}\s'_-]{1,50}$/u}
 | 
				
			||||||
 | 
					            regexErrorMessage={t("description.invalidName")}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <div className="flex justify-end py-2">
 | 
					            <div className="flex justify-end py-2">
 | 
				
			||||||
              <Button variant="select" type="submit">
 | 
					              <Button variant="select" type="submit">
 | 
				
			||||||
 | 
				
			|||||||
@ -20,6 +20,8 @@ type TextEntryDialogProps = {
 | 
				
			|||||||
  onSave: (text: string) => void;
 | 
					  onSave: (text: string) => void;
 | 
				
			||||||
  defaultValue?: string;
 | 
					  defaultValue?: string;
 | 
				
			||||||
  allowEmpty?: boolean;
 | 
					  allowEmpty?: boolean;
 | 
				
			||||||
 | 
					  regexPattern?: RegExp;
 | 
				
			||||||
 | 
					  regexErrorMessage?: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function TextEntryDialog({
 | 
					export default function TextEntryDialog({
 | 
				
			||||||
@ -30,6 +32,8 @@ export default function TextEntryDialog({
 | 
				
			|||||||
  onSave,
 | 
					  onSave,
 | 
				
			||||||
  defaultValue = "",
 | 
					  defaultValue = "",
 | 
				
			||||||
  allowEmpty = false,
 | 
					  allowEmpty = false,
 | 
				
			||||||
 | 
					  regexPattern,
 | 
				
			||||||
 | 
					  regexErrorMessage,
 | 
				
			||||||
}: TextEntryDialogProps) {
 | 
					}: TextEntryDialogProps) {
 | 
				
			||||||
  const { t } = useTranslation("common");
 | 
					  const { t } = useTranslation("common");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,6 +48,8 @@ export default function TextEntryDialog({
 | 
				
			|||||||
          defaultValue={defaultValue}
 | 
					          defaultValue={defaultValue}
 | 
				
			||||||
          allowEmpty={allowEmpty}
 | 
					          allowEmpty={allowEmpty}
 | 
				
			||||||
          onSave={onSave}
 | 
					          onSave={onSave}
 | 
				
			||||||
 | 
					          regexPattern={regexPattern}
 | 
				
			||||||
 | 
					          regexErrorMessage={regexErrorMessage}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <DialogFooter className={cn("pt-4", isMobile && "gap-2")}>
 | 
					          <DialogFooter className={cn("pt-4", isMobile && "gap-2")}>
 | 
				
			||||||
            <Button type="button" onClick={() => setOpen(false)}>
 | 
					            <Button type="button" onClick={() => setOpen(false)}>
 | 
				
			||||||
 | 
				
			|||||||
@ -499,6 +499,8 @@ function LibrarySelector({
 | 
				
			|||||||
          setRenameFace(null);
 | 
					          setRenameFace(null);
 | 
				
			||||||
        }}
 | 
					        }}
 | 
				
			||||||
        defaultValue={renameFace || ""}
 | 
					        defaultValue={renameFace || ""}
 | 
				
			||||||
 | 
					        regexPattern={/^[\p{L}\p{N}\s'_-]{1,50}$/u}
 | 
				
			||||||
 | 
					        regexErrorMessage={t("description.invalidName")}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <DropdownMenu>
 | 
					      <DropdownMenu>
 | 
				
			||||||
@ -538,7 +540,7 @@ function LibrarySelector({
 | 
				
			|||||||
              className="group flex items-center justify-between"
 | 
					              className="group flex items-center justify-between"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              <div
 | 
					              <div
 | 
				
			||||||
                className="flex-grow cursor-pointer smart-capitalize"
 | 
					                className="flex-grow cursor-pointer"
 | 
				
			||||||
                onClick={() => setPageToggle(face)}
 | 
					                onClick={() => setPageToggle(face)}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                {face}
 | 
					                {face}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user