This commit is contained in:
Josh Hawkins 2025-05-17 17:11:19 -05:00 committed by GitHub
parent ebae6cb1ed
commit 5d13925d2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 45 additions and 14 deletions

View File

@ -264,6 +264,7 @@ tensorrt
tflite
thresholded
timelapse
titlecase
tmpfs
tobytes
toggleable

View File

@ -31,6 +31,7 @@ norfair == 2.2.*
setproctitle == 1.3.*
ws4py == 0.5.*
unidecode == 1.3.*
titlecase == 2.4.*
# Image Manipulation
numpy == 1.26.*
opencv-python-headless == 4.11.0.*

View File

@ -172,6 +172,7 @@ class CameraState:
# draw any attributes
for attribute in obj["current_attributes"]:
box = attribute["box"]
box_area = int((box[2] - box[0]) * (box[3] - box[1]))
draw_box_with_label(
frame_copy,
box[0],
@ -179,7 +180,7 @@ class CameraState:
box[2],
box[3],
attribute["label"],
f"{attribute['score']:.0%}",
f"{attribute['score']:.0%} {str(box_area)}",
thickness=thickness,
color=color,
)

View File

@ -12,6 +12,7 @@ from typing import Any, Callable
from py_vapid import Vapid01
from pywebpush import WebPusher
from titlecase import titlecase
from frigate.comms.base_communicator import Communicator
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"])
title = f"{', '.join(sorted_objects).replace('_', ' ').title()}{' was' if state == 'end' else ''} detected in {', '.join(payload['after']['data']['zones']).replace('_', ' ').title()}"
message = f"Detected on {camera.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 {titlecase(camera.replace('_', ' '))}"
image = f"{payload['after']['thumb_path'].replace('/media/frigate', '')}"
# if event is ongoing open to live view otherwise open to recordings view

View File

@ -5,12 +5,12 @@ import json
import logging
import multiprocessing as mp
import os
import re
import signal
import threading
from types import FrameType
from typing import Any, Optional, Union
import regex
from pathvalidate import ValidationError, sanitize_filename
from setproctitle import setproctitle
@ -243,7 +243,7 @@ class EmbeddingsContext:
)
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:
sanitized_old_name = sanitize_filename(old_name, replacement_text="_")
@ -251,9 +251,9 @@ class EmbeddingsContext:
except ValidationError as 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}")
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}")
if sanitized_old_name != old_name:
raise ValueError(f"Old face name contains invalid characters: {old_name}")

View File

@ -474,6 +474,7 @@ class TrackedObject:
# draw any attributes
for attribute in self.thumbnail_data["attributes"]:
box = attribute["box"]
box_area = int((box[2] - box[0]) * (box[3] - box[1]))
draw_box_with_label(
best_frame,
box[0],
@ -481,7 +482,7 @@ class TrackedObject:
box[2],
box[3],
attribute["label"],
f"{attribute['score']:.0%}",
f"{attribute['score']:.0%} {str(box_area)}",
thickness=thickness,
color=color,
)

View File

@ -1,7 +1,8 @@
{
"description": {
"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": {
"person": "Person",

View File

@ -16,7 +16,7 @@ export default function StepIndicator({
return (
<div className="flex flex-row justify-evenly">
{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
className={cn(
"flex size-16 items-center justify-center rounded-full",

View File

@ -18,18 +18,33 @@ type TextEntryProps = {
allowEmpty?: boolean;
onSave: (text: string) => void;
children?: React.ReactNode;
regexPattern?: RegExp;
regexErrorMessage?: string;
};
export default function TextEntry({
defaultValue = "",
placeholder,
allowEmpty = false,
onSave,
children,
regexPattern,
regexErrorMessage = "Input does not match the required format",
}: TextEntryProps) {
const formSchema = z.object({
text: allowEmpty
? z.string().optional()
: z.string().min(1, "Field is required"),
text: z
.string()
.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>>({

View File

@ -119,6 +119,8 @@ export default function CreateFaceWizardDialog({
setName(name);
setStep(1);
}}
regexPattern={/^[\p{L}\p{N}\s'_-]{1,50}$/u}
regexErrorMessage={t("description.invalidName")}
>
<div className="flex justify-end py-2">
<Button variant="select" type="submit">

View File

@ -20,6 +20,8 @@ type TextEntryDialogProps = {
onSave: (text: string) => void;
defaultValue?: string;
allowEmpty?: boolean;
regexPattern?: RegExp;
regexErrorMessage?: string;
};
export default function TextEntryDialog({
@ -30,6 +32,8 @@ export default function TextEntryDialog({
onSave,
defaultValue = "",
allowEmpty = false,
regexPattern,
regexErrorMessage,
}: TextEntryDialogProps) {
const { t } = useTranslation("common");
@ -44,6 +48,8 @@ export default function TextEntryDialog({
defaultValue={defaultValue}
allowEmpty={allowEmpty}
onSave={onSave}
regexPattern={regexPattern}
regexErrorMessage={regexErrorMessage}
>
<DialogFooter className={cn("pt-4", isMobile && "gap-2")}>
<Button type="button" onClick={() => setOpen(false)}>

View File

@ -499,6 +499,8 @@ function LibrarySelector({
setRenameFace(null);
}}
defaultValue={renameFace || ""}
regexPattern={/^[\p{L}\p{N}\s'_-]{1,50}$/u}
regexErrorMessage={t("description.invalidName")}
/>
<DropdownMenu>
@ -538,7 +540,7 @@ function LibrarySelector({
className="group flex items-center justify-between"
>
<div
className="flex-grow cursor-pointer smart-capitalize"
className="flex-grow cursor-pointer"
onClick={() => setPageToggle(face)}
>
{face}