mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-18 17:14:26 +03:00
Add ability to rename faces
This commit is contained in:
parent
b84201c030
commit
a91768d480
@ -422,6 +422,49 @@ class FaceProcessor(RealTimeProcessorApi):
|
|||||||
"message": "Internal server error",
|
"message": "Internal server error",
|
||||||
"success": False,
|
"success": False,
|
||||||
}
|
}
|
||||||
|
elif topic == "rename_face":
|
||||||
|
old_name = request_data.get("old_name")
|
||||||
|
new_name = request_data.get("new_name")
|
||||||
|
|
||||||
|
if not self.__validate_face_name(new_name):
|
||||||
|
return {
|
||||||
|
"message": "Invalid new face name",
|
||||||
|
"success": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
old_folder = os.path.join(FACE_DIR, old_name)
|
||||||
|
new_folder = os.path.join(FACE_DIR, new_name)
|
||||||
|
|
||||||
|
if not os.path.exists(old_folder):
|
||||||
|
return {
|
||||||
|
"message": f"Face '{old_name}' not found",
|
||||||
|
"success": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.path.exists(new_folder):
|
||||||
|
return {
|
||||||
|
"message": f"Face name '{new_name}' already exists",
|
||||||
|
"success": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rename the directory
|
||||||
|
os.rename(old_folder, new_folder)
|
||||||
|
|
||||||
|
# Clear and rebuild classifier with new names
|
||||||
|
self.__clear_classifier()
|
||||||
|
self.__build_classifier()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": "Successfully renamed face",
|
||||||
|
"success": True,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to rename face: {str(e)}")
|
||||||
|
return {
|
||||||
|
"message": f"Failed to rename face: {str(e)}",
|
||||||
|
"success": False,
|
||||||
|
}
|
||||||
|
|
||||||
def expire_object(self, object_id: str):
|
def expire_object(self, object_id: str):
|
||||||
if object_id in self.detected_faces:
|
if object_id in self.detected_faces:
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { LuImagePlus, LuTrash2, LuUserPlus } from "react-icons/lu";
|
import { LuImagePlus, LuTrash2, LuUserPlus, LuEdit } from "react-icons/lu";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@ -150,6 +150,37 @@ export default function FaceLibrary() {
|
|||||||
}
|
}
|
||||||
}, [newFaceName, refreshFaces]);
|
}, [newFaceName, refreshFaces]);
|
||||||
|
|
||||||
|
const [renameDialog, setRenameDialog] = useState(false);
|
||||||
|
const [isRenaming, setIsRenaming] = useState(false);
|
||||||
|
const [renameData, setRenameData] = useState<{ oldName: string; newName: string }>({ oldName: '', newName: '' });
|
||||||
|
|
||||||
|
const renameFace = useCallback(async () => {
|
||||||
|
if (!renameData.newName.trim()) {
|
||||||
|
toast.error("Face name cannot be empty", { position: "top-center" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsRenaming(true);
|
||||||
|
try {
|
||||||
|
const resp = await axios.post(`/faces/${renameData.oldName}/rename`, {
|
||||||
|
new_name: renameData.newName
|
||||||
|
});
|
||||||
|
if (resp.status === 200) {
|
||||||
|
setRenameDialog(false);
|
||||||
|
setRenameData({ oldName: '', newName: '' });
|
||||||
|
refreshFaces();
|
||||||
|
toast.success("Successfully renamed face", { position: "top-center" });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(
|
||||||
|
`Failed to rename face: ${error.response?.data?.message || error.message}`,
|
||||||
|
{ position: "top-center" }
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsRenaming(false);
|
||||||
|
}
|
||||||
|
}, [renameData, refreshFaces]);
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
@ -178,6 +209,26 @@ export default function FaceLibrary() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog open={renameDialog} onOpenChange={setRenameDialog}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Rename Face</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Input
|
||||||
|
placeholder="Enter new name"
|
||||||
|
value={renameData.newName}
|
||||||
|
onChange={(e) => setRenameData(prev => ({ ...prev, newName: e.target.value }))}
|
||||||
|
onKeyPress={(e) => e.key === 'Enter' && renameFace()}
|
||||||
|
disabled={isRenaming}
|
||||||
|
/>
|
||||||
|
<Button onClick={renameFace} disabled={isRenaming}>
|
||||||
|
{isRenaming ? "Renaming..." : "Rename"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<UploadImageDialog
|
<UploadImageDialog
|
||||||
open={upload}
|
open={upload}
|
||||||
title="Upload Face Image"
|
title="Upload Face Image"
|
||||||
@ -217,7 +268,9 @@ export default function FaceLibrary() {
|
|||||||
{Object.values(faces).map((item) => (
|
{Object.values(faces).map((item) => (
|
||||||
<ToggleGroupItem
|
<ToggleGroupItem
|
||||||
key={item}
|
key={item}
|
||||||
className={`flex scroll-mx-10 items-center justify-between gap-2 ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
|
className={`flex scroll-mx-10 items-center justify-between gap-2 ${
|
||||||
|
pageToggle == item ? "" : "*:text-muted-foreground"
|
||||||
|
}`}
|
||||||
value={item}
|
value={item}
|
||||||
data-nav-item={item}
|
data-nav-item={item}
|
||||||
aria-label={`Select ${item}`}
|
aria-label={`Select ${item}`}
|
||||||
@ -225,6 +278,23 @@ export default function FaceLibrary() {
|
|||||||
<div className="capitalize">
|
<div className="capitalize">
|
||||||
{item} ({faceData[item].length})
|
{item} ({faceData[item].length})
|
||||||
</div>
|
</div>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-4 w-4"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setRenameData({ oldName: item, newName: item });
|
||||||
|
setRenameDialog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LuEdit className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Rename Face</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
))}
|
))}
|
||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user