mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 16:38:24 -06:00
Various fixes (#14786)
* Catch openvino error * Remove clip deletion * Update deletion text * Fix timeline not respecting timezone config * Tweaks * More timezone fixes * Fix * More timezone fixes * Fix shm docs
This commit is contained in:
parent
156e7cc628
commit
a13b9815f6
@ -81,15 +81,15 @@ You can calculate the **minimum** shm size for each camera with the following fo
|
|||||||
|
|
||||||
```console
|
```console
|
||||||
# Replace <width> and <height>
|
# Replace <width> and <height>
|
||||||
$ python -c 'print("{:.2f}MB".format((<width> * <height> * 1.5 * 10 + 270480) / 1048576))'
|
$ python -c 'print("{:.2f}MB".format((<width> * <height> * 1.5 * 20 + 270480) / 1048576))'
|
||||||
|
|
||||||
# Example for 1280x720
|
# Example for 1280x720, including logs
|
||||||
$ python -c 'print("{:.2f}MB".format((1280 * 720 * 1.5 * 10 + 270480) / 1048576))'
|
$ python -c 'print("{:.2f}MB".format((1280 * 720 * 1.5 * 20 + 270480) / 1048576)) + 40'
|
||||||
13.44MB
|
46.63MB
|
||||||
|
|
||||||
# Example for eight cameras detecting at 1280x720, including logs
|
# Example for eight cameras detecting at 1280x720, including logs
|
||||||
$ python -c 'print("{:.2f}MB".format(((1280 * 720 * 1.5 * 10 + 270480) / 1048576) * 8 + 40))'
|
$ python -c 'print("{:.2f}MB".format(((1280 * 720 * 1.5 * 20 + 270480) / 1048576) * 8 + 40))'
|
||||||
136.99MB
|
253MB
|
||||||
```
|
```
|
||||||
|
|
||||||
The shm size cannot be set per container for Home Assistant add-ons. However, this is probably not required since by default Home Assistant Supervisor allocates `/dev/shm` with half the size of your total memory. If your machine has 8GB of memory, chances are that Frigate will have access to up to 4GB without any additional configuration.
|
The shm size cannot be set per container for Home Assistant add-ons. However, this is probably not required since by default Home Assistant Supervisor allocates `/dev/shm` with half the size of your total memory. If your machine has 8GB of memory, chances are that Frigate will have access to up to 4GB without any additional configuration.
|
||||||
@ -194,7 +194,7 @@ services:
|
|||||||
privileged: true # this may not be necessary for all setups
|
privileged: true # this may not be necessary for all setups
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: ghcr.io/blakeblackshear/frigate:stable
|
image: ghcr.io/blakeblackshear/frigate:stable
|
||||||
shm_size: "64mb" # update for your cameras based on calculation above
|
shm_size: "512mb" # update for your cameras based on calculation above
|
||||||
devices:
|
devices:
|
||||||
- /dev/bus/usb:/dev/bus/usb # Passes the USB Coral, needs to be modified for other versions
|
- /dev/bus/usb:/dev/bus/usb # Passes the USB Coral, needs to be modified for other versions
|
||||||
- /dev/apex_0:/dev/apex_0 # Passes a PCIe Coral, follow driver instructions here https://coral.ai/docs/m2/get-started/#2a-on-linux
|
- /dev/apex_0:/dev/apex_0 # Passes a PCIe Coral, follow driver instructions here https://coral.ai/docs/m2/get-started/#2a-on-linux
|
||||||
|
@ -1042,9 +1042,6 @@ def delete_event(request: Request, event_id: str):
|
|||||||
media.unlink(missing_ok=True)
|
media.unlink(missing_ok=True)
|
||||||
media = Path(f"{os.path.join(CLIPS_DIR, media_name)}-clean.png")
|
media = Path(f"{os.path.join(CLIPS_DIR, media_name)}-clean.png")
|
||||||
media.unlink(missing_ok=True)
|
media.unlink(missing_ok=True)
|
||||||
if event.has_clip:
|
|
||||||
media = Path(f"{os.path.join(CLIPS_DIR, media_name)}.mp4")
|
|
||||||
media.unlink(missing_ok=True)
|
|
||||||
|
|
||||||
event.delete_instance()
|
event.delete_instance()
|
||||||
Timeline.delete().where(Timeline.source_id == event_id).execute()
|
Timeline.delete().where(Timeline.source_id == event_id).execute()
|
||||||
|
@ -521,9 +521,9 @@ class FrigateApp:
|
|||||||
f"Calculated total camera size {available_shm} / {cam_total_frame_size} :: {shm_frame_count} frames for each camera in SHM"
|
f"Calculated total camera size {available_shm} / {cam_total_frame_size} :: {shm_frame_count} frames for each camera in SHM"
|
||||||
)
|
)
|
||||||
|
|
||||||
if shm_frame_count < 10:
|
if shm_frame_count < 20:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"The current SHM size of {total_shm}MB is too small, recommend increasing it to at least {round(min_req_shm + cam_total_frame_size * 10)}MB."
|
f"The current SHM size of {total_shm}MB is too small, recommend increasing it to at least {round(min_req_shm + cam_total_frame_size * 20)}MB."
|
||||||
)
|
)
|
||||||
|
|
||||||
return shm_frame_count
|
return shm_frame_count
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Model Utils"""
|
"""Model Utils"""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -11,6 +12,8 @@ except ImportError:
|
|||||||
# openvino is not included
|
# openvino is not included
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_ort_providers(
|
def get_ort_providers(
|
||||||
force_cpu: bool = False, device: str = "AUTO", requires_fp16: bool = False
|
force_cpu: bool = False, device: str = "AUTO", requires_fp16: bool = False
|
||||||
@ -89,19 +92,27 @@ class ONNXModelRunner:
|
|||||||
self.ort: ort.InferenceSession = None
|
self.ort: ort.InferenceSession = None
|
||||||
self.ov: ov.Core = None
|
self.ov: ov.Core = None
|
||||||
providers, options = get_ort_providers(device == "CPU", device, requires_fp16)
|
providers, options = get_ort_providers(device == "CPU", device, requires_fp16)
|
||||||
|
self.interpreter = None
|
||||||
|
|
||||||
if "OpenVINOExecutionProvider" in providers:
|
if "OpenVINOExecutionProvider" in providers:
|
||||||
# use OpenVINO directly
|
try:
|
||||||
self.type = "ov"
|
# use OpenVINO directly
|
||||||
self.ov = ov.Core()
|
self.type = "ov"
|
||||||
self.ov.set_property(
|
self.ov = ov.Core()
|
||||||
{ov.properties.cache_dir: "/config/model_cache/openvino"}
|
self.ov.set_property(
|
||||||
)
|
{ov.properties.cache_dir: "/config/model_cache/openvino"}
|
||||||
self.interpreter = self.ov.compile_model(
|
)
|
||||||
model=model_path, device_name=device
|
self.interpreter = self.ov.compile_model(
|
||||||
)
|
model=model_path, device_name=device
|
||||||
else:
|
)
|
||||||
# Use ONNXRuntime
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"OpenVINO failed to build model, using CPU instead: {e}"
|
||||||
|
)
|
||||||
|
self.interpreter = None
|
||||||
|
|
||||||
|
# Use ONNXRuntime
|
||||||
|
if self.interpreter is None:
|
||||||
self.type = "ort"
|
self.type = "ort"
|
||||||
self.ort = ort.InferenceSession(
|
self.ort = ort.InferenceSession(
|
||||||
model_path,
|
model_path,
|
||||||
|
@ -1,191 +0,0 @@
|
|||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
|
||||||
import { GraphDataPoint } from "@/types/graph";
|
|
||||||
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
|
||||||
import useSWR from "swr";
|
|
||||||
import ActivityIndicator from "../indicators/activity-indicator";
|
|
||||||
|
|
||||||
type TimelineBarProps = {
|
|
||||||
startTime: number;
|
|
||||||
graphData:
|
|
||||||
| {
|
|
||||||
objects: number[];
|
|
||||||
motion: GraphDataPoint[];
|
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
onClick?: () => void;
|
|
||||||
};
|
|
||||||
export default function TimelineBar({
|
|
||||||
startTime,
|
|
||||||
graphData,
|
|
||||||
onClick,
|
|
||||||
}: TimelineBarProps) {
|
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
return <ActivityIndicator />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="h-18 my-1 w-full cursor-pointer rounded border p-1 hover:bg-secondary hover:bg-opacity-30"
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{graphData != undefined && (
|
|
||||||
<div className="relative flex h-8 w-full">
|
|
||||||
{getHourBlocks().map((idx) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={idx}
|
|
||||||
className={`h-2 flex-auto ${
|
|
||||||
(graphData.motion.at(idx)?.y || 0) == 0
|
|
||||||
? ""
|
|
||||||
: graphData.objects.includes(idx)
|
|
||||||
? "bg-object"
|
|
||||||
: "bg-motion"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<div className="absolute bottom-0 left-0 top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:00" : "%I:00%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[8.3%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:05" : "%I:05%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[16.7%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:10" : "%I:10%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[25%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:15" : "%I:15%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[33.3%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:20" : "%I:20%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[41.7%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:25" : "%I:25%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[50%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:30" : "%I:30%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[58.3%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:35" : "%I:35%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[66.7%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:40" : "%I:40%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[75%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:45" : "%I:45%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[83.3%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:50" : "%I:50%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute bottom-0 left-[91.7%] top-0 border-l border-gray-500 align-bottom">
|
|
||||||
<div className="absolute bottom-0 ml-1 text-sm text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config?.ui.time_format == "24hour" ? "%H:55" : "%I:55%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="text-gray-500">
|
|
||||||
{formatUnixTimestampToDateTime(startTime, {
|
|
||||||
strftime_fmt:
|
|
||||||
config.ui.time_format == "24hour" ? "%m/%d %H:%M" : "%m/%d %I:%M%P",
|
|
||||||
time_style: "medium",
|
|
||||||
date_style: "medium",
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHourBlocks() {
|
|
||||||
const arr = [];
|
|
||||||
|
|
||||||
for (let x = 0; x <= 59; x++) {
|
|
||||||
arr.push(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr;
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
import { useTheme } from "@/context/theme-provider";
|
import { useTheme } from "@/context/theme-provider";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||||
import { useCallback, useEffect, useMemo } from "react";
|
import { useCallback, useEffect, useMemo } from "react";
|
||||||
import Chart from "react-apexcharts";
|
import Chart from "react-apexcharts";
|
||||||
import { isMobileOnly } from "react-device-detect";
|
import { isMobileOnly } from "react-device-detect";
|
||||||
@ -42,12 +43,14 @@ export function CameraLineGraph({
|
|||||||
|
|
||||||
const formatTime = useCallback(
|
const formatTime = useCallback(
|
||||||
(val: unknown) => {
|
(val: unknown) => {
|
||||||
const date = new Date(updateTimes[Math.round(val as number)] * 1000);
|
return formatUnixTimestampToDateTime(
|
||||||
return date.toLocaleTimeString([], {
|
updateTimes[Math.round(val as number)],
|
||||||
hour12: config?.ui.time_format != "24hour",
|
{
|
||||||
hour: "2-digit",
|
timezone: config?.ui.timezone,
|
||||||
minute: "2-digit",
|
strftime_fmt:
|
||||||
});
|
config?.ui.time_format == "24hour" ? "%H:%M" : "%I:%M %p",
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[config, updateTimes],
|
[config, updateTimes],
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useTheme } from "@/context/theme-provider";
|
import { useTheme } from "@/context/theme-provider";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import { Threshold } from "@/types/graph";
|
import { Threshold } from "@/types/graph";
|
||||||
|
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||||
import { useCallback, useEffect, useMemo } from "react";
|
import { useCallback, useEffect, useMemo } from "react";
|
||||||
import Chart from "react-apexcharts";
|
import Chart from "react-apexcharts";
|
||||||
import { isMobileOnly } from "react-device-detect";
|
import { isMobileOnly } from "react-device-detect";
|
||||||
@ -50,17 +51,17 @@ export function ThresholdBarGraph({
|
|||||||
|
|
||||||
let timeOffset = 0;
|
let timeOffset = 0;
|
||||||
if (dateIndex < 0) {
|
if (dateIndex < 0) {
|
||||||
timeOffset = 5000 * Math.abs(dateIndex);
|
timeOffset = 5 * Math.abs(dateIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
const date = new Date(
|
return formatUnixTimestampToDateTime(
|
||||||
updateTimes[Math.max(1, dateIndex) - 1] * 1000 - timeOffset,
|
updateTimes[Math.max(1, dateIndex) - 1] - timeOffset,
|
||||||
|
{
|
||||||
|
timezone: config?.ui.timezone,
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour" ? "%H:%M" : "%I:%M %p",
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return date.toLocaleTimeString([], {
|
|
||||||
hour12: config?.ui.time_format != "24hour",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[config, updateTimes],
|
[config, updateTimes],
|
||||||
);
|
);
|
||||||
|
@ -159,7 +159,13 @@ export default function SearchResultActions({
|
|||||||
<AlertDialogTitle>Confirm Delete</AlertDialogTitle>
|
<AlertDialogTitle>Confirm Delete</AlertDialogTitle>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
Are you sure you want to delete this tracked object?
|
Deleting this tracked object removes the snapshot, any saved
|
||||||
|
embeddings, and any associated object lifecycle entries. Recorded
|
||||||
|
footage of this tracked object in History view will <em>NOT</em> be
|
||||||
|
deleted.
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
Are you sure you want to proceed?
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
@ -427,6 +427,7 @@ export default function ObjectLifecycle({
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-primary-variant">
|
<div className="text-sm text-primary-variant">
|
||||||
{formatUnixTimestampToDateTime(item.timestamp, {
|
{formatUnixTimestampToDateTime(item.timestamp, {
|
||||||
|
timezone: config.ui.timezone,
|
||||||
strftime_fmt:
|
strftime_fmt:
|
||||||
config.ui.time_format == "24hour"
|
config.ui.time_format == "24hour"
|
||||||
? "%d %b %H:%M:%S"
|
? "%d %b %H:%M:%S"
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||||
|
import { useMemo } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
type MinimapSegmentProps = {
|
type MinimapSegmentProps = {
|
||||||
@ -40,22 +42,22 @@ export function MinimapBounds({
|
|||||||
className="pointer-events-none absolute inset-0 -bottom-7 z-20 flex w-full select-none scroll-mt-8 items-center justify-center text-center text-[10px] font-medium text-primary"
|
className="pointer-events-none absolute inset-0 -bottom-7 z-20 flex w-full select-none scroll-mt-8 items-center justify-center text-center text-[10px] font-medium text-primary"
|
||||||
ref={firstMinimapSegmentRef}
|
ref={firstMinimapSegmentRef}
|
||||||
>
|
>
|
||||||
{new Date(alignedMinimapStartTime * 1000).toLocaleTimeString([], {
|
{formatUnixTimestampToDateTime(alignedMinimapStartTime, {
|
||||||
hour12: config?.ui.time_format != "24hour",
|
timezone: config?.ui.timezone,
|
||||||
hour: "2-digit",
|
strftime_fmt: !dense
|
||||||
minute: "2-digit",
|
? `%b %d, ${config?.ui.time_format == "24hour" ? "%H:%M" : "%I:%M %p"}`
|
||||||
...(!dense && { month: "short", day: "2-digit" }),
|
: `${config?.ui.time_format == "24hour" ? "%H:%M" : "%I:%M %p"}`,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isLastSegmentInMinimap && (
|
{isLastSegmentInMinimap && (
|
||||||
<div className="pointer-events-none absolute inset-0 -top-3 z-20 flex w-full select-none items-center justify-center text-center text-[10px] font-medium text-primary">
|
<div className="pointer-events-none absolute inset-0 -top-3 z-20 flex w-full select-none items-center justify-center text-center text-[10px] font-medium text-primary">
|
||||||
{new Date(alignedMinimapEndTime * 1000).toLocaleTimeString([], {
|
{formatUnixTimestampToDateTime(alignedMinimapEndTime, {
|
||||||
hour12: config?.ui.time_format != "24hour",
|
timezone: config?.ui.timezone,
|
||||||
hour: "2-digit",
|
strftime_fmt: !dense
|
||||||
minute: "2-digit",
|
? `%b %d, ${config?.ui.time_format == "24hour" ? "%H:%M" : "%I:%M %p"}`
|
||||||
...(!dense && { month: "short", day: "2-digit" }),
|
: `${config?.ui.time_format == "24hour" ? "%H:%M" : "%I:%M %p"}`,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -92,6 +94,22 @@ export function Timestamp({
|
|||||||
}: TimestampSegmentProps) {
|
}: TimestampSegmentProps) {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
|
const formattedTimestamp = useMemo(() => {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
timestamp.getMinutes() % timestampSpread === 0 &&
|
||||||
|
timestamp.getSeconds() === 0
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatUnixTimestampToDateTime(timestamp.getTime() / 1000, {
|
||||||
|
timezone: config?.ui.timezone,
|
||||||
|
strftime_fmt: config?.ui.time_format == "24hour" ? "%H:%M" : "%I:%M %p",
|
||||||
|
});
|
||||||
|
}, [config, timestamp, timestampSpread]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute left-[15px] z-10 h-[8px]">
|
<div className="absolute left-[15px] z-10 h-[8px]">
|
||||||
{!isFirstSegmentInMinimap && !isLastSegmentInMinimap && (
|
{!isFirstSegmentInMinimap && !isLastSegmentInMinimap && (
|
||||||
@ -99,13 +117,7 @@ export function Timestamp({
|
|||||||
key={`${segmentKey}_timestamp`}
|
key={`${segmentKey}_timestamp`}
|
||||||
className="pointer-events-none select-none text-[8px] text-neutral_variant dark:text-neutral"
|
className="pointer-events-none select-none text-[8px] text-neutral_variant dark:text-neutral"
|
||||||
>
|
>
|
||||||
{timestamp.getMinutes() % timestampSpread === 0 &&
|
{formattedTimestamp}
|
||||||
timestamp.getSeconds() === 0 &&
|
|
||||||
timestamp.toLocaleTimeString([], {
|
|
||||||
hour12: config?.ui.time_format != "24hour",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,6 +10,7 @@ import scrollIntoView from "scroll-into-view-if-needed";
|
|||||||
import { useTimelineUtils } from "./use-timeline-utils";
|
import { useTimelineUtils } from "./use-timeline-utils";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
|
||||||
|
|
||||||
type DraggableElementProps = {
|
type DraggableElementProps = {
|
||||||
contentRef: React.RefObject<HTMLElement>;
|
contentRef: React.RefObject<HTMLElement>;
|
||||||
@ -168,6 +169,19 @@ function useDraggableElement({
|
|||||||
[segmentDuration, timelineStartAligned, segmentHeight],
|
[segmentDuration, timelineStartAligned, segmentHeight],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getFormattedTimestamp = useCallback(
|
||||||
|
(segmentStartTime: number) => {
|
||||||
|
return formatUnixTimestampToDateTime(segmentStartTime, {
|
||||||
|
timezone: config?.ui.timezone,
|
||||||
|
strftime_fmt:
|
||||||
|
config?.ui.time_format == "24hour"
|
||||||
|
? `%H:%M${segmentDuration < 60 && !dense ? ":%S" : ""}`
|
||||||
|
: `%I:%M${segmentDuration < 60 && !dense ? ":%S" : ""} %p`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[config, dense, segmentDuration],
|
||||||
|
);
|
||||||
|
|
||||||
const updateDraggableElementPosition = useCallback(
|
const updateDraggableElementPosition = useCallback(
|
||||||
(
|
(
|
||||||
newElementPosition: number,
|
newElementPosition: number,
|
||||||
@ -184,14 +198,8 @@ function useDraggableElement({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (draggableElementTimeRef.current) {
|
if (draggableElementTimeRef.current) {
|
||||||
draggableElementTimeRef.current.textContent = new Date(
|
draggableElementTimeRef.current.textContent =
|
||||||
segmentStartTime * 1000,
|
getFormattedTimestamp(segmentStartTime);
|
||||||
).toLocaleTimeString([], {
|
|
||||||
hour12: config?.ui.time_format != "24hour",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
...(segmentDuration < 60 && !dense && { second: "2-digit" }),
|
|
||||||
});
|
|
||||||
if (scrollTimeline && !userInteracting) {
|
if (scrollTimeline && !userInteracting) {
|
||||||
scrollIntoView(thumb, {
|
scrollIntoView(thumb, {
|
||||||
block: "center",
|
block: "center",
|
||||||
@ -208,13 +216,11 @@ function useDraggableElement({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
segmentDuration,
|
|
||||||
draggableElementTimeRef,
|
draggableElementTimeRef,
|
||||||
draggableElementRef,
|
draggableElementRef,
|
||||||
setDraggableElementTime,
|
setDraggableElementTime,
|
||||||
setDraggableElementPosition,
|
setDraggableElementPosition,
|
||||||
dense,
|
getFormattedTimestamp,
|
||||||
config,
|
|
||||||
userInteracting,
|
userInteracting,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user