Add rename api

This commit is contained in:
Weitheng Haw 2025-01-28 15:32:47 +00:00
parent 39fbd8a76f
commit 754ae4db37
3 changed files with 74 additions and 72 deletions

View File

@ -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},
)

View File

@ -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:

View File

@ -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) {