mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-06-09 01:16:08 +02: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