mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-10 07:25:27 +03:00
Compare commits
4 Commits
544d3c6139
...
b147b53522
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b147b53522 | ||
|
|
d2b2faa2d7 | ||
|
|
614a6b39d4 | ||
|
|
f29ee53fb4 |
@ -89,6 +89,14 @@ After closing VS Code, you may still have containers running. To close everythin
|
|||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
|
#### Unit Tests
|
||||||
|
|
||||||
|
GitHub will execute unit tests on new PRs. You must ensure that all tests pass.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python3 -u -m unittest
|
||||||
|
```
|
||||||
|
|
||||||
#### FFMPEG Hardware Acceleration
|
#### FFMPEG Hardware Acceleration
|
||||||
|
|
||||||
The following commands are used inside the container to ensure hardware acceleration is working properly.
|
The following commands are used inside the container to ensure hardware acceleration is working properly.
|
||||||
@ -125,6 +133,28 @@ ffmpeg -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format
|
|||||||
ffmpeg -c:v h264_qsv -re -stream_loop -1 -i https://streams.videolan.org/ffmpeg/incoming/720p60.mp4 -f rawvideo -pix_fmt yuv420p pipe: > /dev/null
|
ffmpeg -c:v h264_qsv -re -stream_loop -1 -i https://streams.videolan.org/ffmpeg/incoming/720p60.mp4 -f rawvideo -pix_fmt yuv420p pipe: > /dev/null
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Submitting a pull request
|
||||||
|
|
||||||
|
Code must be formatted, linted and type-tested. GitHub will run these checks on pull requests, so it is advised to run them yourself prior to opening.
|
||||||
|
|
||||||
|
**Formatting**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ruff format frigate migrations docker *.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linting**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ruff check frigate migrations docker *.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**MyPy Static Typing**
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python3 -u -m mypy --config-file frigate/mypy.ini frigate
|
||||||
|
```
|
||||||
|
|
||||||
## Web Interface
|
## Web Interface
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|||||||
@ -55,6 +55,14 @@ DYNAMIC_OBJECT_THRESHOLDS = StationaryThresholds(
|
|||||||
motion_classifier_enabled=True,
|
motion_classifier_enabled=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Thresholds for objects that are not expected to be stationary
|
||||||
|
NON_STATIONARY_OBJECT_THRESHOLDS = StationaryThresholds(
|
||||||
|
objects=["license_plate"],
|
||||||
|
known_active_iou=0.9,
|
||||||
|
stationary_check_iou=0.9,
|
||||||
|
max_stationary_history=4,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_stationary_threshold(label: str) -> StationaryThresholds:
|
def get_stationary_threshold(label: str) -> StationaryThresholds:
|
||||||
"""Get the stationary thresholds for a given object label."""
|
"""Get the stationary thresholds for a given object label."""
|
||||||
@ -65,6 +73,9 @@ def get_stationary_threshold(label: str) -> StationaryThresholds:
|
|||||||
if label in DYNAMIC_OBJECT_THRESHOLDS.objects:
|
if label in DYNAMIC_OBJECT_THRESHOLDS.objects:
|
||||||
return DYNAMIC_OBJECT_THRESHOLDS
|
return DYNAMIC_OBJECT_THRESHOLDS
|
||||||
|
|
||||||
|
if label in NON_STATIONARY_OBJECT_THRESHOLDS.objects:
|
||||||
|
return NON_STATIONARY_OBJECT_THRESHOLDS
|
||||||
|
|
||||||
return StationaryThresholds()
|
return StationaryThresholds()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -125,17 +125,23 @@ export default function ClassificationSelectionDialog({
|
|||||||
isMobile && "gap-2 pb-4",
|
isMobile && "gap-2 pb-4",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{classes.sort().map((category) => (
|
{classes
|
||||||
<SelectorItem
|
.sort((a, b) => {
|
||||||
key={category}
|
if (a === "none") return 1;
|
||||||
className="flex cursor-pointer gap-2 smart-capitalize"
|
if (b === "none") return -1;
|
||||||
onClick={() => onCategorizeImage(category)}
|
return a.localeCompare(b);
|
||||||
>
|
})
|
||||||
{category === "none"
|
.map((category) => (
|
||||||
? t("details.none")
|
<SelectorItem
|
||||||
: category.replaceAll("_", " ")}
|
key={category}
|
||||||
</SelectorItem>
|
className="flex cursor-pointer gap-2 smart-capitalize"
|
||||||
))}
|
onClick={() => onCategorizeImage(category)}
|
||||||
|
>
|
||||||
|
{category === "none"
|
||||||
|
? t("details.none")
|
||||||
|
: category.replaceAll("_", " ")}
|
||||||
|
</SelectorItem>
|
||||||
|
))}
|
||||||
<Separator />
|
<Separator />
|
||||||
<SelectorItem
|
<SelectorItem
|
||||||
className="flex cursor-pointer gap-2 smart-capitalize"
|
className="flex cursor-pointer gap-2 smart-capitalize"
|
||||||
|
|||||||
@ -598,18 +598,18 @@ function LibrarySelector({
|
|||||||
{Object.values(faces).map((face) => (
|
{Object.values(faces).map((face) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={face}
|
key={face}
|
||||||
className="group flex items-center justify-between"
|
className="group flex items-center justify-between p-0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex-grow cursor-pointer"
|
className="flex-grow cursor-pointer"
|
||||||
onClick={() => setPageToggle(face)}
|
onClick={() => setPageToggle(face)}
|
||||||
>
|
>
|
||||||
{face}
|
{face}
|
||||||
<span className="ml-2 text-muted-foreground">
|
<span className="ml-2 px-2 py-1.5 text-muted-foreground">
|
||||||
({faceData?.[face].length})
|
({faceData?.[face].length})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-0.5">
|
<div className="flex gap-0.5 px-2">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -700,66 +700,72 @@ function LibrarySelector({
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{Object.keys(dataset).map((id) => (
|
{Object.keys(dataset)
|
||||||
<DropdownMenuItem
|
.sort((a, b) => {
|
||||||
key={id}
|
if (a === "none") return 1;
|
||||||
className="group flex items-center justify-between"
|
if (b === "none") return -1;
|
||||||
>
|
return a.localeCompare(b);
|
||||||
<div
|
})
|
||||||
className="flex-grow cursor-pointer capitalize"
|
.map((id) => (
|
||||||
onClick={() => setPageToggle(id)}
|
<DropdownMenuItem
|
||||||
|
key={id}
|
||||||
|
className="group flex items-center justify-between p-0"
|
||||||
>
|
>
|
||||||
{id === "none" ? t("details.none") : id.replaceAll("_", " ")}
|
<div
|
||||||
<span className="ml-2 text-muted-foreground">
|
className="flex-grow cursor-pointer px-2 py-1.5 capitalize"
|
||||||
({dataset?.[id].length})
|
onClick={() => setPageToggle(id)}
|
||||||
</span>
|
>
|
||||||
</div>
|
{id === "none" ? t("details.none") : id.replaceAll("_", " ")}
|
||||||
{id != "none" && (
|
<span className="ml-2 text-muted-foreground">
|
||||||
<div className="flex gap-0.5">
|
({dataset?.[id].length})
|
||||||
<Tooltip>
|
</span>
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setRenameClass(id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LuPencil className="size-4 text-primary" />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipPortal>
|
|
||||||
<TooltipContent>
|
|
||||||
{t("button.renameCategory")}
|
|
||||||
</TooltipContent>
|
|
||||||
</TooltipPortal>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setConfirmDelete(id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LuTrash2 className="size-4 text-destructive" />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipPortal>
|
|
||||||
<TooltipContent>
|
|
||||||
{t("button.deleteCategory")}
|
|
||||||
</TooltipContent>
|
|
||||||
</TooltipPortal>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{id != "none" && (
|
||||||
</DropdownMenuItem>
|
<div className="flex gap-0.5 px-2">
|
||||||
))}
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setRenameClass(id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LuPencil className="size-4 text-primary" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPortal>
|
||||||
|
<TooltipContent>
|
||||||
|
{t("button.renameCategory")}
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPortal>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="size-7 lg:opacity-0 lg:transition-opacity lg:group-hover:opacity-100"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setConfirmDelete(id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LuTrash2 className="size-4 text-destructive" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPortal>
|
||||||
|
<TooltipContent>
|
||||||
|
{t("button.deleteCategory")}
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPortal>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user