mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-16 18:16:44 +03:00
Compare commits
7 Commits
6c171a2041
...
38a507b6a7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38a507b6a7 | ||
|
|
644c7fa6b4 | ||
|
|
88a8de0b1c | ||
|
|
513bede475 | ||
|
|
7d5317959d | ||
|
|
f85a1fe3e8 | ||
|
|
4d0456dcf0 |
2
.github/copilot-instructions.md
vendored
Normal file
2
.github/copilot-instructions.md
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
Never write strings in the frontend directly, always write to and reference the relevant translations file.
|
||||
Always conform new and refactored code to the existing coding style in the project.
|
||||
@ -116,4 +116,4 @@ Along with individual review item summaries, Generative AI provides the ability
|
||||
|
||||
Review reports can be requested via the [API](/integrations/api#review-summarization) by sending a POST request to `/api/review/summarize/start/{start_ts}/end/{end_ts}` with Unix timestamps.
|
||||
|
||||
For Home Assistant users, there is a built-in service (`frigate.generate_review_summary`) that makes it easy to request review reports as part of automations or scripts. This allows you to automatically generate daily summaries, vacation reports, or custom time period reports based on your specific needs.
|
||||
For Home Assistant users, there is a built-in service (`frigate.review_summarize`) that makes it easy to request review reports as part of automations or scripts. This allows you to automatically generate daily summaries, vacation reports, or custom time period reports based on your specific needs.
|
||||
|
||||
@ -28,7 +28,6 @@ To create a poly mask:
|
||||
5. Click the plus icon under the type of mask or zone you would like to create
|
||||
6. Click on the camera's latest image to create the points for a masked area. Click the first point again to close the polygon.
|
||||
7. When you've finished creating your mask, press Save.
|
||||
8. Restart Frigate to apply your changes.
|
||||
|
||||
Your config file will be updated with the relative coordinates of the mask/zone:
|
||||
|
||||
|
||||
@ -1002,10 +1002,6 @@ ui:
|
||||
# full: 8:15:22 PM Mountain Standard Time
|
||||
# (default: shown below).
|
||||
time_style: medium
|
||||
# Optional: Ability to manually override the date / time styling to use strftime format
|
||||
# https://www.gnu.org/software/libc/manual/html_node/Formatting-Calendar-Time.html
|
||||
# possible values are shown above (default: not set)
|
||||
strftime_fmt: "%Y/%m/%d %H:%M"
|
||||
# Optional: Set the unit system to either "imperial" or "metric" (default: metric)
|
||||
# Used in the UI and in MQTT topics
|
||||
unit_system: metric
|
||||
|
||||
@ -390,7 +390,20 @@ class WebPushClient(Communicator):
|
||||
|
||||
message = payload["after"]["data"]["metadata"]["scene"]
|
||||
else:
|
||||
title = f"{titlecase(', '.join(sorted_objects).replace('_', ' '))}{' was' if state == 'end' else ''} detected in {titlecase(', '.join(payload['after']['data']['zones']).replace('_', ' '))}"
|
||||
zone_names = payload["after"]["data"]["zones"]
|
||||
formatted_zone_names = []
|
||||
|
||||
for zone_name in zone_names:
|
||||
if zone_name in self.config.cameras[camera].zones:
|
||||
formatted_zone_names.append(
|
||||
self.config.cameras[camera]
|
||||
.zones[zone_name]
|
||||
.get_formatted_name(zone_name)
|
||||
)
|
||||
else:
|
||||
formatted_zone_names.append(titlecase(zone_name.replace("_", " ")))
|
||||
|
||||
title = f"{titlecase(', '.join(sorted_objects).replace('_', ' '))}{' was' if state == 'end' else ''} detected in {', '.join(formatted_zone_names)}"
|
||||
message = f"Detected on {camera_name}"
|
||||
|
||||
if ended:
|
||||
|
||||
@ -37,9 +37,6 @@ class UIConfig(FrigateBaseModel):
|
||||
time_style: DateTimeStyleEnum = Field(
|
||||
default=DateTimeStyleEnum.medium, title="Override UI timeStyle."
|
||||
)
|
||||
strftime_fmt: Optional[str] = Field(
|
||||
default=None, title="Override date and time format using strftime syntax."
|
||||
)
|
||||
unit_system: UnitSystemEnum = Field(
|
||||
default=UnitSystemEnum.metric, title="The unit system to use for measurements."
|
||||
)
|
||||
|
||||
@ -639,14 +639,14 @@ def write_classification_attempt(
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
cv2.imwrite(file, frame)
|
||||
|
||||
# delete oldest face image if maximum is reached
|
||||
try:
|
||||
files = sorted(
|
||||
filter(lambda f: (f.endswith(".webp")), os.listdir(folder)),
|
||||
key=lambda f: os.path.getctime(os.path.join(folder, f)),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
# delete oldest face image if maximum is reached
|
||||
try:
|
||||
if len(files) > max_files:
|
||||
os.unlink(os.path.join(folder, files[-1]))
|
||||
except FileNotFoundError:
|
||||
|
||||
@ -22,6 +22,7 @@ from frigate.util.services import (
|
||||
get_bandwidth_stats,
|
||||
get_cpu_stats,
|
||||
get_fs_type,
|
||||
get_hailo_temps,
|
||||
get_intel_gpu_stats,
|
||||
get_jetson_stats,
|
||||
get_nvidia_gpu_stats,
|
||||
@ -91,6 +92,9 @@ def get_temperatures() -> dict[str, float]:
|
||||
if temp is not None:
|
||||
temps[apex] = temp
|
||||
|
||||
# Get temperatures for Hailo devices
|
||||
temps.update(get_hailo_temps())
|
||||
|
||||
return temps
|
||||
|
||||
|
||||
|
||||
@ -549,6 +549,53 @@ def get_jetson_stats() -> Optional[dict[int, dict]]:
|
||||
return results
|
||||
|
||||
|
||||
def get_hailo_temps() -> dict[str, float]:
|
||||
"""Get temperatures for Hailo devices."""
|
||||
try:
|
||||
from hailo_platform import Device
|
||||
except ModuleNotFoundError:
|
||||
return {}
|
||||
|
||||
temps = {}
|
||||
|
||||
try:
|
||||
device_ids = Device.scan()
|
||||
for i, device_id in enumerate(device_ids):
|
||||
try:
|
||||
with Device(device_id) as device:
|
||||
temp_info = device.control.get_chip_temperature()
|
||||
|
||||
# Get board name and normalise it
|
||||
identity = device.control.identify()
|
||||
board_name = None
|
||||
for line in str(identity).split("\n"):
|
||||
if line.startswith("Board Name:"):
|
||||
board_name = (
|
||||
line.split(":", 1)[1].strip().lower().replace("-", "")
|
||||
)
|
||||
break
|
||||
|
||||
if not board_name:
|
||||
board_name = f"hailo{i}"
|
||||
|
||||
# Use indexed name if multiple devices, otherwise just the board name
|
||||
device_name = (
|
||||
f"{board_name}-{i}" if len(device_ids) > 1 else board_name
|
||||
)
|
||||
|
||||
# ts1_temperature is also available, but appeared to be the same as ts0 in testing.
|
||||
temps[device_name] = round(temp_info.ts0_temperature, 1)
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
f"Failed to get temperature for Hailo device {device_id}: {e}"
|
||||
)
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to scan for Hailo devices: {e}")
|
||||
|
||||
return temps
|
||||
|
||||
|
||||
def ffprobe_stream(ffmpeg, path: str, detailed: bool = False) -> sp.CompletedProcess:
|
||||
"""Run ffprobe on stream."""
|
||||
clean_path = escape_special_characters(path)
|
||||
|
||||
@ -13,9 +13,6 @@
|
||||
"time_style": {
|
||||
"label": "Override UI timeStyle."
|
||||
},
|
||||
"strftime_fmt": {
|
||||
"label": "Override date and time format using strftime syntax."
|
||||
},
|
||||
"unit_system": {
|
||||
"label": "The unit system to use for measurements."
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"documentTitle": "Classification Models",
|
||||
"documentTitle": "Classification Models - Frigate",
|
||||
"details": {
|
||||
"scoreInfo": "Score represents the average classification confidence across all detections of this object."
|
||||
},
|
||||
@ -83,6 +83,7 @@
|
||||
"aria": "Select Recent Classifications"
|
||||
},
|
||||
"categories": "Classes",
|
||||
"none": "None",
|
||||
"createCategory": {
|
||||
"new": "Create New Class"
|
||||
},
|
||||
|
||||
@ -77,7 +77,7 @@
|
||||
"millisecondsToOffset": "Milliseconds to offset detect annotations by. <em>Default: 0</em>",
|
||||
"tips": "Lower the value if the video playback is ahead of the boxes and path points, and increase the value if the video playback is behind them. This value can be negative.",
|
||||
"toast": {
|
||||
"success": "Annotation offset for {{camera}} has been saved to the config file. Restart Frigate to apply your changes."
|
||||
"success": "Annotation offset for {{camera}} has been saved to the config file."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -534,7 +534,7 @@
|
||||
}
|
||||
},
|
||||
"toast": {
|
||||
"success": "Zone ({{zoneName}}) has been saved. Restart Frigate to apply changes."
|
||||
"success": "Zone ({{zoneName}}) has been saved."
|
||||
}
|
||||
},
|
||||
"motionMasks": {
|
||||
@ -558,8 +558,8 @@
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"title": "{{polygonName}} has been saved. Restart Frigate to apply changes.",
|
||||
"noName": "Motion Mask has been saved. Restart Frigate to apply changes."
|
||||
"title": "{{polygonName}} has been saved.",
|
||||
"noName": "Motion Mask has been saved."
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -583,8 +583,8 @@
|
||||
},
|
||||
"toast": {
|
||||
"success": {
|
||||
"title": "{{polygonName}} has been saved. Restart Frigate to apply changes.",
|
||||
"noName": "Object Mask has been saved. Restart Frigate to apply changes."
|
||||
"title": "{{polygonName}} has been saved.",
|
||||
"noName": "Object Mask has been saved."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,7 +131,9 @@ export default function ClassificationSelectionDialog({
|
||||
className="flex cursor-pointer gap-2 smart-capitalize"
|
||||
onClick={() => onCategorizeImage(category)}
|
||||
>
|
||||
{category.replaceAll("_", " ")}
|
||||
{category === "none"
|
||||
? t("none")
|
||||
: category.replaceAll("_", " ")}
|
||||
</SelectorItem>
|
||||
))}
|
||||
<Separator />
|
||||
|
||||
@ -180,7 +180,9 @@ export function ClassFilterContent({
|
||||
{allClasses.map((item) => (
|
||||
<FilterSwitch
|
||||
key={item}
|
||||
label={item.replaceAll("_", " ")}
|
||||
label={
|
||||
item === "none" ? t("none") : item.replaceAll("_", " ")
|
||||
}
|
||||
isChecked={classes?.includes(item) ?? false}
|
||||
onCheckedChange={(isChecked) => {
|
||||
if (isChecked) {
|
||||
|
||||
@ -371,7 +371,12 @@ export default function FaceLibrary() {
|
||||
{selectedFaces?.length > 0 ? (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<div className="mx-1 flex w-48 items-center justify-center text-sm text-muted-foreground">
|
||||
<div className="p-1">{`${selectedFaces.length} selected`}</div>
|
||||
<div className="p-1">
|
||||
{t("selected", {
|
||||
ns: "views/event",
|
||||
count: selectedFaces.length,
|
||||
})}
|
||||
</div>
|
||||
<div className="p-1">{"|"}</div>
|
||||
<div
|
||||
className="cursor-pointer p-2 text-primary hover:rounded-lg hover:bg-secondary"
|
||||
|
||||
@ -6,7 +6,6 @@ export interface UiConfig {
|
||||
time_format?: "browser" | "12hour" | "24hour";
|
||||
date_style?: "full" | "long" | "medium" | "short";
|
||||
time_style?: "full" | "long" | "medium" | "short";
|
||||
strftime_fmt?: string;
|
||||
dashboard: boolean;
|
||||
order: number;
|
||||
unit_system?: "metric" | "imperial";
|
||||
|
||||
@ -84,6 +84,12 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
|
||||
const [page, setPage] = useState<string>("train");
|
||||
const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100);
|
||||
|
||||
// title
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `${model.name} - ${t("documentTitle")}`;
|
||||
}, [model.name, t]);
|
||||
|
||||
// model state
|
||||
|
||||
const [wasTraining, setWasTraining] = useState(false);
|
||||
@ -416,7 +422,12 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
|
||||
)}
|
||||
>
|
||||
<div className="flex w-48 items-center justify-center text-sm text-muted-foreground">
|
||||
<div className="p-1">{`${selectedImages.length} selected`}</div>
|
||||
<div className="p-1">
|
||||
{t("selected", {
|
||||
ns: "views/event",
|
||||
count: selectedImages.length,
|
||||
})}
|
||||
</div>
|
||||
<div className="p-1">{"|"}</div>
|
||||
<div
|
||||
className="cursor-pointer p-2 text-primary hover:rounded-lg hover:bg-secondary"
|
||||
@ -676,7 +687,7 @@ function LibrarySelector({
|
||||
className="flex-grow cursor-pointer capitalize"
|
||||
onClick={() => setPageToggle(id)}
|
||||
>
|
||||
{id.replaceAll("_", " ")}
|
||||
{id === "none" ? t("none") : id.replaceAll("_", " ")}
|
||||
<span className="ml-2 text-muted-foreground">
|
||||
({dataset?.[id].length})
|
||||
</span>
|
||||
|
||||
@ -144,7 +144,7 @@ export default function GeneralMetrics({
|
||||
}
|
||||
|
||||
Object.entries(stats.detectors).forEach(([key], cIdx) => {
|
||||
if (!key.includes("coral")) {
|
||||
if (!key.includes("coral") && !key.includes("hailo")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user