mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-01-22 20:18:30 +03:00
Merge bfe46d9f4a into cfeb86646f
This commit is contained in:
commit
c8d288e8d1
5
.github/copilot-instructions.md
vendored
5
.github/copilot-instructions.md
vendored
@ -1,2 +1,3 @@
|
|||||||
Never write strings in the frontend directly, always write to and reference the relevant translations file.
|
- For Frigate NVR, never write strings in the frontend directly. Since the project uses `react-i18next`, use `t()` and write the English string in the relevant translations file in `web/public/locales/en`.
|
||||||
Always conform new and refactored code to the existing coding style in the project.
|
- Always conform new and refactored code to the existing coding style in the project.
|
||||||
|
- Always have a way to test your work and confirm your changes. When running backend tests, use `python3 -u -m unittest`.
|
||||||
|
|||||||
@ -68,8 +68,8 @@ Fine-tune the LPR feature using these optional parameters at the global level of
|
|||||||
- Default: `1000` pixels. Note: this is intentionally set very low as it is an _area_ measurement (length x width). For reference, 1000 pixels represents a ~32x32 pixel square in your camera image.
|
- Default: `1000` pixels. Note: this is intentionally set very low as it is an _area_ measurement (length x width). For reference, 1000 pixels represents a ~32x32 pixel square in your camera image.
|
||||||
- Depending on the resolution of your camera's `detect` stream, you can increase this value to ignore small or distant plates.
|
- Depending on the resolution of your camera's `detect` stream, you can increase this value to ignore small or distant plates.
|
||||||
- **`device`**: Device to use to run license plate detection _and_ recognition models.
|
- **`device`**: Device to use to run license plate detection _and_ recognition models.
|
||||||
- Default: `CPU`
|
- Default: `None`
|
||||||
- This can be `CPU`, `GPU`, or the GPU's device number. For users without a model that detects license plates natively, using a GPU may increase performance of the YOLOv9 license plate detector model. See the [Hardware Accelerated Enrichments](/configuration/hardware_acceleration_enrichments.md) documentation. However, for users who run a model that detects `license_plate` natively, there is little to no performance gain reported with running LPR on GPU compared to the CPU.
|
- This is auto-selected by Frigate and can be `CPU`, `GPU`, or the GPU's device number. For users without a model that detects license plates natively, using a GPU may increase performance of the YOLOv9 license plate detector model. See the [Hardware Accelerated Enrichments](/configuration/hardware_acceleration_enrichments.md) documentation. However, for users who run a model that detects `license_plate` natively, there is little to no performance gain reported with running LPR on GPU compared to the CPU.
|
||||||
- **`model_size`**: The size of the model used to identify regions of text on plates.
|
- **`model_size`**: The size of the model used to identify regions of text on plates.
|
||||||
- Default: `small`
|
- Default: `small`
|
||||||
- This can be `small` or `large`.
|
- This can be `small` or `large`.
|
||||||
@ -432,6 +432,6 @@ If you are using a model that natively detects `license_plate`, add an _object m
|
|||||||
|
|
||||||
If you are not using a model that natively detects `license_plate` or you are using dedicated LPR camera mode, only a _motion mask_ over your text is required.
|
If you are not using a model that natively detects `license_plate` or you are using dedicated LPR camera mode, only a _motion mask_ over your text is required.
|
||||||
|
|
||||||
### I see "Error running ... model" in my logs. How can I fix this?
|
### I see "Error running ... model" in my logs, or my inference time is very high. How can I fix this?
|
||||||
|
|
||||||
This usually happens when your GPU is unable to compile or use one of the LPR models. Set your `device` to `CPU` and try again. GPU acceleration only provides a slight performance increase, and the models are lightweight enough to run without issue on most CPUs.
|
This usually happens when your GPU is unable to compile or use one of the LPR models. Set your `device` to `CPU` and try again. GPU acceleration only provides a slight performance increase, and the models are lightweight enough to run without issue on most CPUs.
|
||||||
|
|||||||
@ -23,7 +23,12 @@ from markupsafe import escape
|
|||||||
from peewee import SQL, fn, operator
|
from peewee import SQL, fn, operator
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from frigate.api.auth import allow_any_authenticated, allow_public, require_role
|
from frigate.api.auth import (
|
||||||
|
allow_any_authenticated,
|
||||||
|
allow_public,
|
||||||
|
get_allowed_cameras_for_filter,
|
||||||
|
require_role,
|
||||||
|
)
|
||||||
from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters
|
from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters
|
||||||
from frigate.api.defs.request.app_body import AppConfigSetBody
|
from frigate.api.defs.request.app_body import AppConfigSetBody
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
@ -687,13 +692,19 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
|
|||||||
@router.get(
|
@router.get(
|
||||||
"/recognized_license_plates", dependencies=[Depends(allow_any_authenticated())]
|
"/recognized_license_plates", dependencies=[Depends(allow_any_authenticated())]
|
||||||
)
|
)
|
||||||
def get_recognized_license_plates(split_joined: Optional[int] = None):
|
def get_recognized_license_plates(
|
||||||
|
split_joined: Optional[int] = None,
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
query = (
|
query = (
|
||||||
Event.select(
|
Event.select(
|
||||||
SQL("json_extract(data, '$.recognized_license_plate') AS plate")
|
SQL("json_extract(data, '$.recognized_license_plate') AS plate")
|
||||||
)
|
)
|
||||||
.where(SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL"))
|
.where(
|
||||||
|
(SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL"))
|
||||||
|
& (Event.camera << allowed_cameras)
|
||||||
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
recognized_license_plates = [row[0] for row in query.tuples()]
|
recognized_license_plates = [row[0] for row in query.tuples()]
|
||||||
|
|||||||
@ -662,6 +662,13 @@ class FrigateConfig(FrigateBaseModel):
|
|||||||
# generate zone contours
|
# generate zone contours
|
||||||
if len(camera_config.zones) > 0:
|
if len(camera_config.zones) > 0:
|
||||||
for zone in camera_config.zones.values():
|
for zone in camera_config.zones.values():
|
||||||
|
if zone.filters:
|
||||||
|
for object_name, filter_config in zone.filters.items():
|
||||||
|
zone.filters[object_name] = RuntimeFilterConfig(
|
||||||
|
frame_shape=camera_config.frame_shape,
|
||||||
|
**filter_config.model_dump(exclude_unset=True),
|
||||||
|
)
|
||||||
|
|
||||||
zone.generate_contour(camera_config.frame_shape)
|
zone.generate_contour(camera_config.frame_shape)
|
||||||
|
|
||||||
# Set live view stream if none is set
|
# Set live view stream if none is set
|
||||||
|
|||||||
@ -24,6 +24,14 @@ class GeminiClient(GenAIClient):
|
|||||||
http_options_dict = {
|
http_options_dict = {
|
||||||
"api_version": "v1",
|
"api_version": "v1",
|
||||||
"timeout": int(self.timeout * 1000), # requires milliseconds
|
"timeout": int(self.timeout * 1000), # requires milliseconds
|
||||||
|
"retry_options": types.HttpRetryOptions(
|
||||||
|
attempts=3,
|
||||||
|
initial_delay=1.0,
|
||||||
|
max_delay=60.0,
|
||||||
|
exp_base=2.0,
|
||||||
|
jitter=1.0,
|
||||||
|
http_status_codes=[429, 500, 502, 503, 504],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
if isinstance(self.genai_config.provider_options, dict):
|
if isinstance(self.genai_config.provider_options, dict):
|
||||||
|
|||||||
@ -632,6 +632,49 @@ class TestConfig(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
assert frigate_config.cameras["back"].zones["test"].color != (0, 0, 0)
|
assert frigate_config.cameras["back"].zones["test"].color != (0, 0, 0)
|
||||||
|
|
||||||
|
def test_zone_filter_area_percent_converts_to_pixels(self):
|
||||||
|
config = {
|
||||||
|
"mqtt": {"host": "mqtt"},
|
||||||
|
"record": {
|
||||||
|
"alerts": {
|
||||||
|
"retain": {
|
||||||
|
"days": 20,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cameras": {
|
||||||
|
"back": {
|
||||||
|
"ffmpeg": {
|
||||||
|
"inputs": [
|
||||||
|
{"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"detect": {
|
||||||
|
"height": 1080,
|
||||||
|
"width": 1920,
|
||||||
|
"fps": 5,
|
||||||
|
},
|
||||||
|
"zones": {
|
||||||
|
"notification": {
|
||||||
|
"coordinates": "0.03,1,0.025,0,0.626,0,0.643,1",
|
||||||
|
"objects": ["person"],
|
||||||
|
"filters": {"person": {"min_area": 0.1}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
frigate_config = FrigateConfig(**config)
|
||||||
|
expected_min_area = int(1080 * 1920 * 0.1)
|
||||||
|
assert (
|
||||||
|
frigate_config.cameras["back"]
|
||||||
|
.zones["notification"]
|
||||||
|
.filters["person"]
|
||||||
|
.min_area
|
||||||
|
== expected_min_area
|
||||||
|
)
|
||||||
|
|
||||||
def test_zone_relative_matches_explicit(self):
|
def test_zone_relative_matches_explicit(self):
|
||||||
config = {
|
config = {
|
||||||
"mqtt": {"host": "mqtt"},
|
"mqtt": {"host": "mqtt"},
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"untilForTime": "Until {{time}}",
|
"untilForTime": "Until {{time}}",
|
||||||
"untilForRestart": "Until Frigate restarts.",
|
"untilForRestart": "Until Frigate restarts.",
|
||||||
"untilRestart": "Until restart",
|
"untilRestart": "Until restart",
|
||||||
|
"never": "Never",
|
||||||
"ago": "{{timeAgo}} ago",
|
"ago": "{{timeAgo}} ago",
|
||||||
"justNow": "Just now",
|
"justNow": "Just now",
|
||||||
"today": "Today",
|
"today": "Today",
|
||||||
|
|||||||
@ -268,7 +268,7 @@ export default function CreateTriggerDialog({
|
|||||||
<FormItem className="flex flex-row items-center justify-between">
|
<FormItem className="flex flex-row items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel className="text-base">
|
<FormLabel className="text-base">
|
||||||
{t("enabled", { ns: "common" })}
|
{t("button.enabled", { ns: "common" })}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
{t("triggers.dialog.form.enabled.description")}
|
{t("triggers.dialog.form.enabled.description")}
|
||||||
@ -394,7 +394,10 @@ export default function CreateTriggerDialog({
|
|||||||
</FormLabel>
|
</FormLabel>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{availableActions.map((action) => (
|
{availableActions.map((action) => (
|
||||||
<div key={action} className="flex items-center space-x-2">
|
<label
|
||||||
|
key={action}
|
||||||
|
className="flex cursor-pointer items-center space-x-2"
|
||||||
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={form
|
checked={form
|
||||||
@ -416,10 +419,10 @@ export default function CreateTriggerDialog({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel className="text-sm font-normal">
|
<span className="text-sm font-normal">
|
||||||
{t(`triggers.actions.${action}`)}
|
{t(`triggers.actions.${action}`)}
|
||||||
</FormLabel>
|
</span>
|
||||||
</div>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
|
|||||||
@ -142,7 +142,10 @@ export default function Step3ThresholdAndActions({
|
|||||||
<FormLabel>{t("triggers.dialog.form.actions.title")}</FormLabel>
|
<FormLabel>{t("triggers.dialog.form.actions.title")}</FormLabel>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{availableActions.map((action) => (
|
{availableActions.map((action) => (
|
||||||
<div key={action} className="flex items-center space-x-2">
|
<label
|
||||||
|
key={action}
|
||||||
|
className="flex cursor-pointer items-center space-x-2"
|
||||||
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={form
|
checked={form
|
||||||
@ -164,10 +167,10 @@ export default function Step3ThresholdAndActions({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel className="text-sm font-normal">
|
<span className="text-sm font-normal">
|
||||||
{t(`triggers.actions.${action}`)}
|
{t(`triggers.actions.${action}`)}
|
||||||
</FormLabel>
|
</span>
|
||||||
</div>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
@ -197,9 +200,7 @@ export default function Step3ThresholdAndActions({
|
|||||||
{isLoading && <ActivityIndicator className="mr-2 size-5" />}
|
{isLoading && <ActivityIndicator className="mr-2 size-5" />}
|
||||||
{isLoading
|
{isLoading
|
||||||
? t("button.saving", { ns: "common" })
|
? t("button.saving", { ns: "common" })
|
||||||
: t("triggers.dialog.form.save", {
|
: t("button.save", { ns: "common" })}
|
||||||
defaultValue: "Save Trigger",
|
|
||||||
})}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -206,7 +206,7 @@ function Exports() {
|
|||||||
>
|
>
|
||||||
{Object.values(exports).map((item) => (
|
{Object.values(exports).map((item) => (
|
||||||
<ExportCard
|
<ExportCard
|
||||||
key={item.name}
|
key={item.id}
|
||||||
className={
|
className={
|
||||||
search == "" || filteredExports.includes(item) ? "" : "hidden"
|
search == "" || filteredExports.includes(item) ? "" : "hidden"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import { Toaster, toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@ -598,7 +599,7 @@ export default function TriggerView({
|
|||||||
date_style: "medium",
|
date_style: "medium",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: "Never"}
|
: t("never", { ns: "common" })}
|
||||||
</span>
|
</span>
|
||||||
{trigger_status?.triggers[trigger.name]
|
{trigger_status?.triggers[trigger.name]
|
||||||
?.triggering_event_id && (
|
?.triggering_event_id && (
|
||||||
@ -663,7 +664,9 @@ 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>{t("name", { ns: "common" })}</TableHead>
|
<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")}
|
||||||
@ -759,7 +762,7 @@ export default function TriggerView({
|
|||||||
date_style: "medium",
|
date_style: "medium",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: "Never"}
|
: t("time.never", { ns: "common" })}
|
||||||
</span>
|
</span>
|
||||||
{trigger_status?.triggers[trigger.name]
|
{trigger_status?.triggers[trigger.name]
|
||||||
?.triggering_event_id && (
|
?.triggering_event_id && (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user