mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-18 17:14:26 +03:00
Add rename api
This commit is contained in:
parent
39fbd8a76f
commit
754ae4db37
@ -109,16 +109,22 @@ def deregister_faces(request: Request, name: str, body: dict = None):
|
||||
)
|
||||
|
||||
json: dict[str, any] = body or {}
|
||||
list_of_ids = json.get("ids", "")
|
||||
list_of_ids = json.get("ids", [])
|
||||
|
||||
if not list_of_ids or len(list_of_ids) == 0:
|
||||
if not list_of_ids:
|
||||
return JSONResponse(
|
||||
content=({"success": False, "message": "Not a valid list of ids"}),
|
||||
content={"success": False, "message": "Not a valid list of ids"},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
face_dir = os.path.join(FACE_DIR, name)
|
||||
|
||||
if not os.path.exists(face_dir):
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={"message": f"Face '{name}' not found", "success": False},
|
||||
)
|
||||
|
||||
context: EmbeddingsContext = request.app.embeddings
|
||||
context.delete_face_ids(
|
||||
name, map(lambda file: sanitize_filename(file), list_of_ids)
|
||||
@ -130,12 +136,12 @@ def deregister_faces(request: Request, name: str, body: dict = None):
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to remove directory {face_dir}: {str(e)}")
|
||||
return JSONResponse(
|
||||
content=({"success": False, "message": f"Failed to remove directory: {str(e)}"}),
|
||||
content={"success": False, "message": f"Failed to remove directory: {str(e)}"},
|
||||
status_code=500,
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
content=({"success": True, "message": "Successfully deleted faces."}),
|
||||
content={"success": True, "message": "Successfully deleted faces."},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
@ -155,3 +161,60 @@ def create_face(name: str):
|
||||
status_code=200,
|
||||
content={"message": "Successfully created face", "success": True},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/faces/{name}/rename")
|
||||
def rename_face(request: Request, name: str, body: dict = None):
|
||||
"""Rename a face directory."""
|
||||
if not request.app.frigate_config.face_recognition.enabled:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"message": "Face recognition is not enabled.", "success": False},
|
||||
)
|
||||
|
||||
json: dict[str, any] = body or {}
|
||||
new_name = json.get("new_name")
|
||||
|
||||
if not new_name:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"message": "New name is required", "success": False},
|
||||
)
|
||||
|
||||
old_folder = os.path.join(FACE_DIR, name)
|
||||
new_folder = os.path.join(FACE_DIR, new_name)
|
||||
|
||||
if not os.path.exists(old_folder):
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={"message": f"Face '{name}' not found", "success": False},
|
||||
)
|
||||
|
||||
if os.path.exists(new_folder):
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"message": f"Face '{new_name}' already exists", "success": False},
|
||||
)
|
||||
|
||||
try:
|
||||
# Use atomic operation when possible
|
||||
try:
|
||||
os.rename(old_folder, new_folder)
|
||||
except OSError:
|
||||
# Fallback to copy+delete if rename fails
|
||||
shutil.copytree(old_folder, new_folder)
|
||||
shutil.rmtree(old_folder)
|
||||
|
||||
context: EmbeddingsContext = request.app.embeddings
|
||||
context.clear_face_classifier()
|
||||
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={"message": "Successfully renamed face", "success": True},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to rename face: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content={"message": f"Failed to rename face: {str(e)}", "success": False},
|
||||
)
|
||||
|
||||
@ -423,49 +423,6 @@ class FaceProcessor(RealTimeProcessorApi):
|
||||
"message": "Internal server error",
|
||||
"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,
|
||||
}
|
||||
|
||||
shutil.copytree(old_folder, new_folder)
|
||||
shutil.rmtree(old_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):
|
||||
if object_id in self.detected_faces:
|
||||
|
||||
@ -164,28 +164,8 @@ export default function FaceLibrary() {
|
||||
|
||||
setIsRenaming(true);
|
||||
try {
|
||||
await axios.post(`/faces/${renameData.newName}/create`);
|
||||
|
||||
const oldFaceImages = faceData[renameData.oldName] || [];
|
||||
const copyPromises = oldFaceImages.map(async (image: string) => {
|
||||
const response = await fetch(`${baseUrl}clips/faces/${renameData.oldName}/${image}`);
|
||||
const blob = await response.blob();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', new File([blob], image));
|
||||
formData.append('cropped', 'true');
|
||||
|
||||
return axios.post(`/faces/${renameData.newName}`, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(copyPromises);
|
||||
|
||||
await axios.post(`/faces/${renameData.oldName}/delete`, {
|
||||
ids: oldFaceImages.length ? oldFaceImages : ['dummy']
|
||||
await axios.post(`/faces/${renameData.oldName}/rename`, {
|
||||
new_name: renameData.newName
|
||||
});
|
||||
|
||||
setRenameDialog(false);
|
||||
@ -201,7 +181,7 @@ export default function FaceLibrary() {
|
||||
} finally {
|
||||
setIsRenaming(false);
|
||||
}
|
||||
}, [renameData, faceData, refreshFaces]);
|
||||
}, [renameData, refreshFaces]);
|
||||
|
||||
const deleteFace = useCallback(async () => {
|
||||
try {
|
||||
@ -210,7 +190,9 @@ export default function FaceLibrary() {
|
||||
ids: images.length ? images : ['dummy']
|
||||
});
|
||||
setRenameDialog(false);
|
||||
setPageToggle(faces[0]);
|
||||
|
||||
const nextFace = faces.find(face => face !== renameData.oldName) || null;
|
||||
setPageToggle(nextFace);
|
||||
await refreshFaces();
|
||||
toast.success("Successfully deleted face", { position: "top-center" });
|
||||
} catch (error) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user