mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-21 11:19:02 +03:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba4f4304ec | ||
|
|
e84ef53536 | ||
|
|
b052035046 | ||
|
|
657fb6da62 | ||
|
|
63d3227457 | ||
|
|
eefa0977e3 | ||
|
|
86f3244b6a |
Vendored
+1
-1
@@ -25,7 +25,7 @@ paths:
|
||||
description: Authentication Accepted (no response body, different headers depending on auth method)
|
||||
headers:
|
||||
remote-user:
|
||||
description: Authenticated username or "anonymous" in proxy-only mode
|
||||
description: Authenticated username or "viewer" in proxy-only mode
|
||||
schema:
|
||||
type: string
|
||||
remote-role:
|
||||
|
||||
+6
-6
@@ -167,7 +167,7 @@ def allow_any_authenticated():
|
||||
Allows:
|
||||
- Port 5000 internal requests (remote-user: "anonymous", remote-role: "admin")
|
||||
- Authenticated users with JWT tokens (remote-user: username)
|
||||
- Unauthenticated requests when auth is disabled (remote-user: "anonymous")
|
||||
- Unauthenticated requests when auth is disabled (remote-user: "viewer")
|
||||
|
||||
Rejects:
|
||||
- Requests with no remote-user header (did not pass through /auth endpoint)
|
||||
@@ -550,7 +550,7 @@ def resolve_role(
|
||||
"description": "Authentication Accepted (no response body)",
|
||||
"headers": {
|
||||
"remote-user": {
|
||||
"description": 'Authenticated username or "anonymous" in proxy-only mode',
|
||||
"description": 'Authenticated username or "viewer" in proxy-only mode',
|
||||
"schema": {"type": "string"},
|
||||
},
|
||||
"remote-role": {
|
||||
@@ -592,12 +592,12 @@ def auth(request: Request):
|
||||
# if auth is disabled, just apply the proxy header map and return success
|
||||
if not auth_config.enabled:
|
||||
# pass the user header value from the upstream proxy if a mapping is specified
|
||||
# or use anonymous if none are specified
|
||||
# or use viewer if none are specified
|
||||
user_header = proxy_config.header_map.user
|
||||
success_response.headers["remote-user"] = (
|
||||
request.headers.get(user_header, default="anonymous")
|
||||
request.headers.get(user_header, default="viewer")
|
||||
if user_header
|
||||
else "anonymous"
|
||||
else "viewer"
|
||||
)
|
||||
|
||||
# parse header and resolve a valid role
|
||||
@@ -712,7 +712,7 @@ def auth(request: Request):
|
||||
description="Returns the current authenticated user's profile including username, role, and allowed cameras. This endpoint requires authentication and returns information about the user's permissions.",
|
||||
)
|
||||
def profile(request: Request):
|
||||
username = request.headers.get("remote-user", "anonymous")
|
||||
username = request.headers.get("remote-user", "viewer")
|
||||
role = request.headers.get("remote-role", "viewer")
|
||||
|
||||
all_camera_names = set(request.app.frigate_config.cameras.keys())
|
||||
|
||||
@@ -225,7 +225,8 @@ class MqttClient(Communicator):
|
||||
"birdseye_mode",
|
||||
"review_alerts",
|
||||
"review_detections",
|
||||
"genai",
|
||||
"object_descriptions",
|
||||
"review_descriptions",
|
||||
]
|
||||
|
||||
for name in self.config.cameras.keys():
|
||||
|
||||
@@ -77,6 +77,9 @@ FFMPEG_HWACCEL_RKMPP = "preset-rkmpp"
|
||||
FFMPEG_HWACCEL_AMF = "preset-amd-amf"
|
||||
FFMPEG_HVC1_ARGS = ["-tag:v", "hvc1"]
|
||||
|
||||
# RKNN constants
|
||||
SUPPORTED_RK_SOCS = ["rk3562", "rk3566", "rk3568", "rk3576", "rk3588"]
|
||||
|
||||
# Regex constants
|
||||
|
||||
REGEX_CAMERA_NAME = r"^[a-zA-Z0-9_-]+$"
|
||||
|
||||
@@ -8,7 +8,7 @@ import cv2
|
||||
import numpy as np
|
||||
from pydantic import Field
|
||||
|
||||
from frigate.const import MODEL_CACHE_DIR
|
||||
from frigate.const import MODEL_CACHE_DIR, SUPPORTED_RK_SOCS
|
||||
from frigate.detectors.detection_api import DetectionApi
|
||||
from frigate.detectors.detection_runners import RKNNModelRunner
|
||||
from frigate.detectors.detector_config import BaseDetectorConfig, ModelTypeEnum
|
||||
@@ -19,8 +19,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
DETECTOR_KEY = "rknn"
|
||||
|
||||
supported_socs = ["rk3562", "rk3566", "rk3568", "rk3576", "rk3588"]
|
||||
|
||||
supported_models = {
|
||||
ModelTypeEnum.yologeneric: "^frigate-fp16-yolov9-[cemst]$",
|
||||
ModelTypeEnum.yolonas: "^deci-fp16-yolonas_[sml]$",
|
||||
@@ -82,9 +80,9 @@ class Rknn(DetectionApi):
|
||||
except FileNotFoundError:
|
||||
raise Exception("Make sure to run docker in privileged mode.")
|
||||
|
||||
if soc not in supported_socs:
|
||||
if soc not in SUPPORTED_RK_SOCS:
|
||||
raise Exception(
|
||||
f"Your SoC is not supported. Your SoC is: {soc}. Currently these SoCs are supported: {supported_socs}."
|
||||
f"Your SoC is not supported. Your SoC is: {soc}. Currently these SoCs are supported: {SUPPORTED_RK_SOCS}."
|
||||
)
|
||||
|
||||
return soc
|
||||
|
||||
@@ -8,6 +8,7 @@ import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from frigate.const import SUPPORTED_RK_SOCS
|
||||
from frigate.util.file import FileLock
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -68,9 +69,20 @@ def is_rknn_compatible(model_path: str, model_type: str | None = None) -> bool:
|
||||
True if the model is RKNN-compatible, False otherwise
|
||||
"""
|
||||
soc = get_soc_type()
|
||||
|
||||
if soc is None:
|
||||
return False
|
||||
|
||||
# Check if the SoC is actually a supported RK device
|
||||
# This prevents false positives on non-RK devices (e.g., macOS Docker)
|
||||
# where /proc/device-tree/compatible might exist but contain non-RK content
|
||||
if soc not in SUPPORTED_RK_SOCS:
|
||||
logger.debug(
|
||||
f"SoC '{soc}' is not a supported RK device for RKNN conversion. "
|
||||
f"Supported SoCs: {SUPPORTED_RK_SOCS}"
|
||||
)
|
||||
return False
|
||||
|
||||
if not model_type:
|
||||
model_type = get_rknn_model_type(model_path)
|
||||
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
"nameOnlyNumbers": "Model name cannot contain only numbers",
|
||||
"classRequired": "At least 1 class is required",
|
||||
"classesUnique": "Class names must be unique",
|
||||
"noneNotAllowed": "The class 'none' is not allowed",
|
||||
"stateRequiresTwoClasses": "State models require at least 2 classes",
|
||||
"objectLabelRequired": "Please select an object label",
|
||||
"objectTypeRequired": "Please select a classification type"
|
||||
|
||||
@@ -40,6 +40,7 @@ type ClassificationCardProps = {
|
||||
data: ClassificationItemData;
|
||||
threshold?: ClassificationThreshold;
|
||||
selected: boolean;
|
||||
clickable: boolean;
|
||||
i18nLibrary: string;
|
||||
showArea?: boolean;
|
||||
count?: number;
|
||||
@@ -56,6 +57,7 @@ export const ClassificationCard = forwardRef<
|
||||
data,
|
||||
threshold,
|
||||
selected,
|
||||
clickable,
|
||||
i18nLibrary,
|
||||
showArea = true,
|
||||
count,
|
||||
@@ -101,11 +103,12 @@ export const ClassificationCard = forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex size-full cursor-pointer flex-col overflow-hidden rounded-lg outline outline-[3px]",
|
||||
"relative flex size-full flex-col overflow-hidden rounded-lg outline outline-[3px]",
|
||||
className,
|
||||
selected
|
||||
? "shadow-selected outline-selected"
|
||||
: "outline-transparent duration-500",
|
||||
clickable && "cursor-pointer",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
const isMeta = e.metaKey || e.ctrlKey;
|
||||
@@ -289,6 +292,7 @@ export function GroupedClassificationCard({
|
||||
data={bestItem}
|
||||
threshold={threshold}
|
||||
selected={selectedItems.includes(bestItem.filename)}
|
||||
clickable={true}
|
||||
i18nLibrary={i18nLibrary}
|
||||
count={group.length}
|
||||
onClick={(_, meta) => {
|
||||
@@ -413,6 +417,7 @@ export function GroupedClassificationCard({
|
||||
data={data}
|
||||
threshold={threshold}
|
||||
selected={false}
|
||||
clickable={false}
|
||||
i18nLibrary={i18nLibrary}
|
||||
onClick={() => {}}
|
||||
>
|
||||
|
||||
@@ -94,7 +94,14 @@ export default function Step1NameAndDefine({
|
||||
objectLabel: z.string().optional(),
|
||||
objectType: z.enum(["sub_label", "attribute"]).optional(),
|
||||
classes: z
|
||||
.array(z.string())
|
||||
.array(
|
||||
z
|
||||
.string()
|
||||
.refine(
|
||||
(val) => val.trim().toLowerCase() !== "none",
|
||||
t("wizard.step1.errors.noneNotAllowed"),
|
||||
),
|
||||
)
|
||||
.min(1, t("wizard.step1.errors.classRequired"))
|
||||
.refine(
|
||||
(classes) => {
|
||||
@@ -467,6 +474,7 @@ export default function Step1NameAndDefine({
|
||||
)}
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -1026,6 +1026,7 @@ function FaceGrid({
|
||||
filepath: `clips/faces/${pageToggle}/${image}`,
|
||||
}}
|
||||
selected={selectedFaces.includes(image)}
|
||||
clickable={selectedFaces.length > 0}
|
||||
i18nLibrary="views/faceLibrary"
|
||||
onClick={(data, meta) => onClickFaces([data.filename], meta)}
|
||||
>
|
||||
|
||||
@@ -804,6 +804,7 @@ function DatasetGrid({
|
||||
name: "",
|
||||
}}
|
||||
showArea={false}
|
||||
clickable={selectedImages.length > 0}
|
||||
selected={selectedImages.includes(image)}
|
||||
i18nLibrary="views/classificationModel"
|
||||
onClick={(data, _) => onClickImages([data.filename], true)}
|
||||
@@ -962,6 +963,7 @@ function StateTrainGrid({
|
||||
data={data}
|
||||
threshold={threshold}
|
||||
selected={selectedImages.includes(data.filename)}
|
||||
clickable={selectedImages.length > 0}
|
||||
i18nLibrary="views/classificationModel"
|
||||
showArea={false}
|
||||
onClick={(data, meta) => onClickImages([data.filename], meta)}
|
||||
|
||||
Reference in New Issue
Block a user