diff --git a/frigate/api/classification.py b/frigate/api/classification.py index 4a6969cd3..333c50a0e 100644 --- a/frigate/api/classification.py +++ b/frigate/api/classification.py @@ -125,10 +125,13 @@ def train_face(request: Request, name: str, body: dict = None): sanitized_name = sanitize_filename(name) rand_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6)) new_name = f"{sanitized_name}-{rand_id}.webp" - new_file = os.path.join(FACE_DIR, f"{sanitized_name}/{new_name}") + new_file_folder = os.path.join(FACE_DIR, f"{sanitized_name}") + + if not os.path.exists(new_file_folder): + os.mkdir(new_file_folder) if training_file_name: - shutil.move(training_file, new_file) + shutil.move(training_file, os.path.join(new_file_folder, new_name)) else: try: event: Event = Event.get(Event.id == event_id) @@ -155,7 +158,7 @@ def train_face(request: Request, name: str, body: dict = None): x2 = x1 + int(face_box[2] * detect_config.width) - 4 y2 = y1 + int(face_box[3] * detect_config.height) - 4 face = snapshot[y1:y2, x1:x2] - cv2.imwrite(new_file, face) + cv2.imwrite(os.path.join(new_file_folder, new_name), face) context: EmbeddingsContext = request.app.embeddings context.clear_face_classifier() diff --git a/web/src/pages/FaceLibrary.tsx b/web/src/pages/FaceLibrary.tsx index 066f12f8c..67c7a2abe 100644 --- a/web/src/pages/FaceLibrary.tsx +++ b/web/src/pages/FaceLibrary.tsx @@ -3,6 +3,7 @@ import TimeAgo from "@/components/dynamic/TimeAgo"; import AddFaceIcon from "@/components/icons/AddFaceIcon"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import CreateFaceWizardDialog from "@/components/overlay/detail/FaceCreateWizardDialog"; +import TextEntryDialog from "@/components/overlay/dialog/TextEntryDialog"; import UploadImageDialog from "@/components/overlay/dialog/UploadImageDialog"; import { Button } from "@/components/ui/button"; import { @@ -42,6 +43,7 @@ import { isDesktop, isMobile } from "react-device-detect"; import { useTranslation } from "react-i18next"; import { LuImagePlus, + LuPlus, LuRefreshCw, LuScanFace, LuSearch, @@ -605,6 +607,8 @@ function FaceAttempt({ // interaction + const [newFace, setNewFace] = useState(false); + const imgRef = useRef(null); useContextMenu(imgRef, () => { @@ -663,76 +667,99 @@ function FaceAttempt({ }, [data, onRefresh, t]); return ( -
-
- onClick(data, e.metaKey || e.ctrlKey)} + <> + {newFace && ( + onTrainAttempt(newName)} /> -
- + )} + +
+
+ onClick(data, e.metaKey || e.ctrlKey)} + /> +
+ +
-
-
-
-
-
{data.name}
-
- {Math.round(data.score * 100)}% +
+
+
+
{data.name}
+
+ {Math.round(data.score * 100)}% +
+
+
+ + + + + + + + + {t("trainFaceAs")} + setNewFace(true)} + > + + Create New Face + + {faceNames.map((faceName) => ( + onTrainAttempt(faceName)} + > + + {faceName} + + ))} + + + {t("trainFace")} + + + + onReprocess()} + /> + + {t("button.reprocessFace")} +
-
- - - - - - - - - {t("trainFaceAs")} - {faceNames.map((faceName) => ( - onTrainAttempt(faceName)} - > - {faceName} - - ))} - - - {t("trainFace")} - - - - onReprocess()} - /> - - {t("button.reprocessFace")} - -
-
+ ); }