* face library i18n fixes

* face library i18n fixes

* add ability to use ctrl/cmd S to save in the config editor

* Use datetime as ID

* Update metrics inference speed to start with 0 ms

* fix android formatted thumbnail

* ensure role is comma separated and stripped correctly

* improve face library deletion

- add a confirmation dialog
- add ability to select all / delete faces in collections

* Implement lazy loading for video previews

* Force GPU for large embedding model

* GPU is required

* settings i18n fixes

* Don't delete train tab

* webpush debugging logs

* Fix incorrectly copying zones

* copy path data

* Ensure that cache dir exists for Frigate+

* face docs update

* Add description to upload image step to clarify the image

* Clean up

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
Josh Hawkins
2025-05-09 08:36:44 -05:00
committed by GitHub
parent 52d94231c7
commit 8094dd4075
27 changed files with 402 additions and 195 deletions

View File

@@ -128,13 +128,18 @@ export default function CreateFaceWizardDialog({
</TextEntry>
)}
{step == 1 && (
<ImageEntry onSave={onUploadImage}>
<div className="flex justify-end py-2">
<Button variant="select" type="submit">
{t("button.next", { ns: "common" })}
</Button>
<>
<div className="px-8 py-2 text-center text-sm text-secondary-foreground">
{t("steps.description.uploadFace", { name })}
</div>
</ImageEntry>
<ImageEntry onSave={onUploadImage}>
<div className="flex justify-end py-2">
<Button variant="select" type="submit">
{t("button.next", { ns: "common" })}
</Button>
</div>
</ImageEntry>
</>
)}
{step == 2 && (
<div className="mt-2">

View File

@@ -23,6 +23,7 @@ import {
import { useTranslation } from "react-i18next";
type PreviewPlayerProps = {
previewRef?: (ref: HTMLDivElement | null) => void;
className?: string;
camera: string;
timeRange: TimeRange;
@@ -30,16 +31,19 @@ type PreviewPlayerProps = {
startTime?: number;
isScrubbing: boolean;
forceAspect?: number;
isVisible?: boolean;
onControllerReady: (controller: PreviewController) => void;
onClick?: () => void;
};
export default function PreviewPlayer({
previewRef,
className,
camera,
timeRange,
cameraPreviews,
startTime,
isScrubbing,
isVisible = true,
onControllerReady,
onClick,
}: PreviewPlayerProps) {
@@ -54,6 +58,7 @@ export default function PreviewPlayer({
if (currentPreview) {
return (
<PreviewVideoPlayer
visibilityRef={previewRef}
className={className}
camera={camera}
timeRange={timeRange}
@@ -61,6 +66,7 @@ export default function PreviewPlayer({
initialPreview={currentPreview}
startTime={startTime}
isScrubbing={isScrubbing}
isVisible={isVisible}
currentHourFrame={currentHourFrame}
onControllerReady={onControllerReady}
onClick={onClick}
@@ -110,6 +116,7 @@ export abstract class PreviewController {
}
type PreviewVideoPlayerProps = {
visibilityRef?: (ref: HTMLDivElement | null) => void;
className?: string;
camera: string;
timeRange: TimeRange;
@@ -117,12 +124,14 @@ type PreviewVideoPlayerProps = {
initialPreview?: Preview;
startTime?: number;
isScrubbing: boolean;
isVisible: boolean;
currentHourFrame?: string;
onControllerReady: (controller: PreviewVideoController) => void;
onClick?: () => void;
setCurrentHourFrame: (src: string | undefined) => void;
};
function PreviewVideoPlayer({
visibilityRef,
className,
camera,
timeRange,
@@ -130,6 +139,7 @@ function PreviewVideoPlayer({
initialPreview,
startTime,
isScrubbing,
isVisible,
currentHourFrame,
onControllerReady,
onClick,
@@ -267,11 +277,13 @@ function PreviewVideoPlayer({
return (
<div
ref={visibilityRef}
className={cn(
"relative flex w-full justify-center overflow-hidden rounded-lg bg-black md:rounded-2xl",
onClick && "cursor-pointer",
className,
)}
data-camera={camera}
onClick={onClick}
>
<img
@@ -286,45 +298,48 @@ function PreviewVideoPlayer({
previewRef.current?.load();
}}
/>
<video
ref={previewRef}
className={`absolute size-full ${currentHourFrame ? "invisible" : "visible"}`}
preload="auto"
autoPlay
playsInline
muted
disableRemotePlayback
onSeeked={onPreviewSeeked}
onLoadedData={() => {
if (firstLoad) {
setFirstLoad(false);
}
if (controller) {
controller.previewReady();
} else {
previewRef.current?.pause();
}
if (previewRef.current) {
setVideoSize([
previewRef.current.videoWidth,
previewRef.current.videoHeight,
]);
if (startTime && currentPreview) {
previewRef.current.currentTime = startTime - currentPreview.start;
{isVisible && (
<video
ref={previewRef}
className={`absolute size-full ${currentHourFrame ? "invisible" : "visible"}`}
preload="auto"
autoPlay
playsInline
muted
disableRemotePlayback
onSeeked={onPreviewSeeked}
onLoadedData={() => {
if (firstLoad) {
setFirstLoad(false);
}
}
}}
>
{currentPreview != undefined && (
<source
src={`${baseUrl}${currentPreview.src.substring(1)}`}
type={currentPreview.type}
/>
)}
</video>
if (controller) {
controller.previewReady();
} else {
previewRef.current?.pause();
}
if (previewRef.current) {
setVideoSize([
previewRef.current.videoWidth,
previewRef.current.videoHeight,
]);
if (startTime && currentPreview) {
previewRef.current.currentTime =
startTime - currentPreview.start;
}
}
}}
>
{currentPreview != undefined && (
<source
src={`${baseUrl}${currentPreview.src.substring(1)}`}
type={currentPreview.type}
/>
)}
</video>
)}
{cameraPreviews && !currentPreview && (
<div className="absolute inset-0 flex items-center justify-center rounded-lg bg-background_alt text-primary dark:bg-black md:rounded-2xl">
{t("noPreviewFoundFor", { camera: camera.replaceAll("_", " ") })}