mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-01-22 20:18:30 +03:00
Merge 72eb0352ad into 0a8f499640
This commit is contained in:
commit
49c5f71632
@ -11,6 +11,12 @@ Cameras configured to output H.264 video and AAC audio will offer the most compa
|
|||||||
|
|
||||||
- **Stream Viewing**: This stream will be rebroadcast as is to Home Assistant for viewing with the stream component. Setting this resolution too high will use significant bandwidth when viewing streams in Home Assistant, and they may not load reliably over slower connections.
|
- **Stream Viewing**: This stream will be rebroadcast as is to Home Assistant for viewing with the stream component. Setting this resolution too high will use significant bandwidth when viewing streams in Home Assistant, and they may not load reliably over slower connections.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
For the best experience in Frigate's UI, configure your camera so that the detection and recording streams use the same aspect ratio. For example, if your main stream is 3840x2160 (16:9), set your substream to 640x360 (also 16:9) instead of 640x480 (4:3). While not strictly required, matching aspect ratios helps ensure seamless live stream display and preview/recordings playback.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
### Choosing a detect resolution
|
### Choosing a detect resolution
|
||||||
|
|
||||||
The ideal resolution for detection is one where the objects you want to detect fit inside the dimensions of the model used by Frigate (320x320). Frigate does not pass the entire camera frame to object detection. It will crop an area of motion from the full frame and look in that portion of the frame. If the area being inspected is larger than 320x320, Frigate must resize it before running object detection. Higher resolutions do not improve the detection accuracy because the additional detail is lost in the resize. Below you can see a reference for how large a 320x320 area is against common resolutions.
|
The ideal resolution for detection is one where the objects you want to detect fit inside the dimensions of the model used by Frigate (320x320). Frigate does not pass the entire camera frame to object detection. It will crop an area of motion from the full frame and look in that portion of the frame. If the area being inspected is larger than 320x320, Frigate must resize it before running object detection. Higher resolutions do not improve the detection accuracy because the additional detail is lost in the resize. Below you can see a reference for how large a 320x320 area is against common resolutions.
|
||||||
|
|||||||
@ -64,10 +64,12 @@ def stop_ffmpeg(ffmpeg_process: sp.Popen[Any], logger: logging.Logger):
|
|||||||
try:
|
try:
|
||||||
logger.info("Waiting for ffmpeg to exit gracefully...")
|
logger.info("Waiting for ffmpeg to exit gracefully...")
|
||||||
ffmpeg_process.communicate(timeout=30)
|
ffmpeg_process.communicate(timeout=30)
|
||||||
|
logger.info("FFmpeg has exited")
|
||||||
except sp.TimeoutExpired:
|
except sp.TimeoutExpired:
|
||||||
logger.info("FFmpeg didn't exit. Force killing...")
|
logger.info("FFmpeg didn't exit. Force killing...")
|
||||||
ffmpeg_process.kill()
|
ffmpeg_process.kill()
|
||||||
ffmpeg_process.communicate()
|
ffmpeg_process.communicate()
|
||||||
|
logger.info("FFmpeg has been killed")
|
||||||
ffmpeg_process = None
|
ffmpeg_process = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -181,6 +181,16 @@
|
|||||||
"restricted": {
|
"restricted": {
|
||||||
"title": "No Cameras Available",
|
"title": "No Cameras Available",
|
||||||
"description": "You don't have permission to view any cameras in this group."
|
"description": "You don't have permission to view any cameras in this group."
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"title": "No Cameras Configured",
|
||||||
|
"description": "Get started by connecting a camera to Frigate.",
|
||||||
|
"buttonText": "Add Camera"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"title": "No Cameras in Group",
|
||||||
|
"description": "This camera group has no assigned or enabled cameras.",
|
||||||
|
"buttonText": "Manage Groups"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,9 @@ export function EmptyCard({
|
|||||||
{icon}
|
{icon}
|
||||||
{TitleComponent}
|
{TitleComponent}
|
||||||
{description && (
|
{description && (
|
||||||
<div className="mb-3 text-secondary-foreground">{description}</div>
|
<div className="mb-3 text-center text-secondary-foreground">
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{buttonText?.length && (
|
{buttonText?.length && (
|
||||||
<Button size="sm" variant="select">
|
<Button size="sm" variant="select">
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import HlsVideoPlayer from "@/components/player/HlsVideoPlayer";
|
|||||||
import { baseUrl } from "@/api/baseUrl";
|
import { baseUrl } from "@/api/baseUrl";
|
||||||
import { REVIEW_PADDING } from "@/types/review";
|
import { REVIEW_PADDING } from "@/types/review";
|
||||||
import {
|
import {
|
||||||
ASPECT_VERTICAL_LAYOUT,
|
ASPECT_PORTRAIT_LAYOUT,
|
||||||
ASPECT_WIDE_LAYOUT,
|
ASPECT_WIDE_LAYOUT,
|
||||||
Recording,
|
Recording,
|
||||||
} from "@/types/record";
|
} from "@/types/record";
|
||||||
@ -39,6 +39,7 @@ import { useApiHost } from "@/api";
|
|||||||
import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator";
|
import ImageLoadingIndicator from "@/components/indicators/ImageLoadingIndicator";
|
||||||
import ObjectTrackOverlay from "../ObjectTrackOverlay";
|
import ObjectTrackOverlay from "../ObjectTrackOverlay";
|
||||||
import { useIsAdmin } from "@/hooks/use-is-admin";
|
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||||
|
import { VideoResolutionType } from "@/types/live";
|
||||||
|
|
||||||
type TrackingDetailsProps = {
|
type TrackingDetailsProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -253,16 +254,25 @@ export function TrackingDetails({
|
|||||||
|
|
||||||
const [timelineSize] = useResizeObserver(timelineContainerRef);
|
const [timelineSize] = useResizeObserver(timelineContainerRef);
|
||||||
|
|
||||||
|
const [fullResolution, setFullResolution] = useState<VideoResolutionType>({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const aspectRatio = useMemo(() => {
|
const aspectRatio = useMemo(() => {
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return 16 / 9;
|
return 16 / 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fullResolution.width && fullResolution.height) {
|
||||||
|
return fullResolution.width / fullResolution.height;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
config.cameras[event.camera].detect.width /
|
config.cameras[event.camera].detect.width /
|
||||||
config.cameras[event.camera].detect.height
|
config.cameras[event.camera].detect.height
|
||||||
);
|
);
|
||||||
}, [config, event]);
|
}, [config, event, fullResolution]);
|
||||||
|
|
||||||
const label = event.sub_label
|
const label = event.sub_label
|
||||||
? event.sub_label
|
? event.sub_label
|
||||||
@ -460,7 +470,7 @@ export function TrackingDetails({
|
|||||||
return "normal";
|
return "normal";
|
||||||
} else if (aspectRatio > ASPECT_WIDE_LAYOUT) {
|
} else if (aspectRatio > ASPECT_WIDE_LAYOUT) {
|
||||||
return "wide";
|
return "wide";
|
||||||
} else if (aspectRatio < ASPECT_VERTICAL_LAYOUT) {
|
} else if (aspectRatio < ASPECT_PORTRAIT_LAYOUT) {
|
||||||
return "tall";
|
return "tall";
|
||||||
} else {
|
} else {
|
||||||
return "normal";
|
return "normal";
|
||||||
@ -556,6 +566,7 @@ export function TrackingDetails({
|
|||||||
onSeekToTime={handleSeekToTime}
|
onSeekToTime={handleSeekToTime}
|
||||||
onUploadFrame={onUploadFrameToPlus}
|
onUploadFrame={onUploadFrameToPlus}
|
||||||
onPlaying={() => setIsVideoLoading(false)}
|
onPlaying={() => setIsVideoLoading(false)}
|
||||||
|
setFullResolution={setFullResolution}
|
||||||
isDetailMode={true}
|
isDetailMode={true}
|
||||||
camera={event.camera}
|
camera={event.camera}
|
||||||
currentTimeOverride={currentTime}
|
currentTimeOverride={currentTime}
|
||||||
@ -623,7 +634,7 @@ export function TrackingDetails({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
isDesktop && "justify-start overflow-hidden",
|
isDesktop && "justify-start overflow-hidden",
|
||||||
aspectRatio > 1 && aspectRatio < 1.5
|
aspectRatio > 1 && aspectRatio < ASPECT_PORTRAIT_LAYOUT
|
||||||
? "lg:basis-3/5"
|
? "lg:basis-3/5"
|
||||||
: "lg:basis-2/5",
|
: "lg:basis-2/5",
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -44,4 +44,5 @@ export type RecordingStartingPoint = {
|
|||||||
export type RecordingPlayerError = "stalled" | "startup";
|
export type RecordingPlayerError = "stalled" | "startup";
|
||||||
|
|
||||||
export const ASPECT_VERTICAL_LAYOUT = 1.5;
|
export const ASPECT_VERTICAL_LAYOUT = 1.5;
|
||||||
|
export const ASPECT_PORTRAIT_LAYOUT = 1.333;
|
||||||
export const ASPECT_WIDE_LAYOUT = 2;
|
export const ASPECT_WIDE_LAYOUT = 2;
|
||||||
|
|||||||
@ -447,7 +447,7 @@ export default function LiveDashboardView({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{cameras.length == 0 && !includeBirdseye ? (
|
{cameras.length == 0 && !includeBirdseye ? (
|
||||||
<NoCameraView />
|
<NoCameraView cameraGroup={cameraGroup} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{!fullscreen && events && events.length > 0 && (
|
{!fullscreen && events && events.length > 0 && (
|
||||||
@ -666,28 +666,39 @@ export default function LiveDashboardView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NoCameraView() {
|
function NoCameraView({ cameraGroup }: { cameraGroup?: string }) {
|
||||||
const { t } = useTranslation(["views/live"]);
|
const { t } = useTranslation(["views/live"]);
|
||||||
const { auth } = useContext(AuthContext);
|
const { auth } = useContext(AuthContext);
|
||||||
const isAdmin = useIsAdmin();
|
const isAdmin = useIsAdmin();
|
||||||
|
|
||||||
// Check if this is a restricted user with no cameras in this group
|
const isDefault = cameraGroup === "default";
|
||||||
const isRestricted = !isAdmin && auth.isAuthenticated;
|
const isRestricted = !isAdmin && auth.isAuthenticated;
|
||||||
|
|
||||||
|
let type: "default" | "group" | "restricted";
|
||||||
|
if (isRestricted) {
|
||||||
|
type = "restricted";
|
||||||
|
} else if (isDefault) {
|
||||||
|
type = "default";
|
||||||
|
} else {
|
||||||
|
type = "group";
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex size-full items-center justify-center">
|
<div className="flex size-full items-center justify-center">
|
||||||
<EmptyCard
|
<EmptyCard
|
||||||
icon={<BsFillCameraVideoOffFill className="size-8" />}
|
icon={<BsFillCameraVideoOffFill className="size-8" />}
|
||||||
title={
|
title={t(`noCameras.${type}.title`)}
|
||||||
isRestricted ? t("noCameras.restricted.title") : t("noCameras.title")
|
description={t(`noCameras.${type}.description`)}
|
||||||
|
buttonText={
|
||||||
|
type !== "restricted" && isDefault
|
||||||
|
? t(`noCameras.${type}.buttonText`)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
description={
|
link={
|
||||||
isRestricted
|
type !== "restricted" && isDefault
|
||||||
? t("noCameras.restricted.description")
|
? "/settings?page=cameraManagement"
|
||||||
: t("noCameras.description")
|
: undefined
|
||||||
}
|
}
|
||||||
buttonText={!isRestricted ? t("noCameras.buttonText") : undefined}
|
|
||||||
link={!isRestricted ? "/settings?page=cameraManagement" : undefined}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -664,9 +664,7 @@ export default function TriggerView({
|
|||||||
<TableHeader className="sticky top-0 bg-muted/50">
|
<TableHeader className="sticky top-0 bg-muted/50">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-4"></TableHead>
|
<TableHead className="w-4"></TableHead>
|
||||||
<TableHead>
|
<TableHead>{t("triggers.table.name")}</TableHead>
|
||||||
{t("name", { ns: "triggers.table.name" })}
|
|
||||||
</TableHead>
|
|
||||||
<TableHead>{t("triggers.table.type")}</TableHead>
|
<TableHead>{t("triggers.table.type")}</TableHead>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
{t("triggers.table.lastTriggered")}
|
{t("triggers.table.lastTriggered")}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user