mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-26 19:06:11 +01:00
Add support for uploading images
This commit is contained in:
parent
41cb52c4cb
commit
3684c441a5
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathvalidate import sanitize_filename
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, UploadFile
|
from fastapi import APIRouter, Request, UploadFile
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
from pathvalidate import sanitize_filename
|
||||||
|
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
from frigate.const import FACE_DIR
|
from frigate.const import FACE_DIR
|
||||||
@ -30,15 +30,6 @@ def get_faces():
|
|||||||
|
|
||||||
@router.post("/faces/{name}")
|
@router.post("/faces/{name}")
|
||||||
async def register_face(request: Request, name: str, file: UploadFile):
|
async def register_face(request: Request, name: str, file: UploadFile):
|
||||||
# if not file.content_type.startswith("image"):
|
|
||||||
# return JSONResponse(
|
|
||||||
# status_code=400,
|
|
||||||
# content={
|
|
||||||
# "success": False,
|
|
||||||
# "message": "Only an image can be used to register a face.",
|
|
||||||
# },
|
|
||||||
# )
|
|
||||||
|
|
||||||
context: EmbeddingsContext = request.app.embeddings
|
context: EmbeddingsContext = request.app.embeddings
|
||||||
context.register_face(name, await file.read())
|
context.register_face(name, await file.read())
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
|
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useCallback } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@ -18,23 +19,36 @@ type UploadImageDialogProps = {
|
|||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
|
onSave: (file: File) => void;
|
||||||
};
|
};
|
||||||
export default function UploadImageDialog({
|
export default function UploadImageDialog({
|
||||||
open,
|
open,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
setOpen,
|
setOpen,
|
||||||
|
onSave,
|
||||||
}: UploadImageDialogProps) {
|
}: UploadImageDialogProps) {
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
image: z
|
file: z.instanceof(FileList, { message: "Please select an image file." }),
|
||||||
.instanceof(File, { message: "Please select an image file." })
|
|
||||||
.refine((file) => file.size < 1000000, "Image size is too large."),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
mode: "onChange",
|
|
||||||
});
|
});
|
||||||
|
const fileRef = form.register("file");
|
||||||
|
|
||||||
|
// upload handler
|
||||||
|
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
(data: z.infer<typeof formSchema>) => {
|
||||||
|
if (!data["file"]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSave(data["file"]["0"]);
|
||||||
|
},
|
||||||
|
[onSave],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} defaultOpen={false} onOpenChange={setOpen}>
|
<Dialog open={open} defaultOpen={false} onOpenChange={setOpen}>
|
||||||
@ -44,31 +58,30 @@ export default function UploadImageDialog({
|
|||||||
{description && <DialogDescription>{description}</DialogDescription>}
|
{description && <DialogDescription>{description}</DialogDescription>}
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form>
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="image"
|
name="file"
|
||||||
render={({ field }) => (
|
render={() => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
{
|
<Input
|
||||||
// @ts-expect-error ignore
|
className="aspect-video h-40 w-full"
|
||||||
<Input
|
type="file"
|
||||||
className="aspect-video h-40 w-full"
|
{...fileRef}
|
||||||
type="file"
|
/>
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<DialogFooter className="pt-4">
|
||||||
|
<Button onClick={() => setOpen(false)}>Cancel</Button>
|
||||||
|
<Button variant="select" type="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
<DialogFooter>
|
|
||||||
<Button onClick={() => setOpen(false)}>Cancel</Button>
|
|
||||||
<Button variant="select">Save</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -18,10 +18,6 @@ export default function FaceLibrary() {
|
|||||||
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
|
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
|
||||||
const tabsRef = useRef<HTMLDivElement | null>(null);
|
const tabsRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
// upload
|
|
||||||
|
|
||||||
const [upload, setUpload] = useState(false);
|
|
||||||
|
|
||||||
// face data
|
// face data
|
||||||
|
|
||||||
const { data: faceData } = useSWR("faces");
|
const { data: faceData } = useSWR("faces");
|
||||||
@ -43,6 +39,23 @@ export default function FaceLibrary() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [faces]);
|
}, [faces]);
|
||||||
|
|
||||||
|
// upload
|
||||||
|
|
||||||
|
const [upload, setUpload] = useState(false);
|
||||||
|
|
||||||
|
const onUploadImage = useCallback(
|
||||||
|
(file: File) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
axios.post(`faces/${pageToggle}`, formData, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[pageToggle],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex size-full flex-col p-2">
|
<div className="flex size-full flex-col p-2">
|
||||||
<Toaster />
|
<Toaster />
|
||||||
@ -52,6 +65,7 @@ export default function FaceLibrary() {
|
|||||||
title="Upload Face Image"
|
title="Upload Face Image"
|
||||||
description={`Upload an image to scan for faces and include for ${pageToggle}`}
|
description={`Upload an image to scan for faces and include for ${pageToggle}`}
|
||||||
setOpen={setUpload}
|
setOpen={setUpload}
|
||||||
|
onSave={onUploadImage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative flex h-11 w-full items-center justify-between">
|
<div className="relative flex h-11 w-full items-center justify-between">
|
||||||
|
Loading…
Reference in New Issue
Block a user