mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-01-11 23:08:28 -05:00
Compare commits
6 Commits
v0.17.0-be
...
misc-fixes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27625e75ac | ||
|
|
b1d7187ef8 | ||
|
|
3533578aef | ||
|
|
7b5a1b7284 | ||
|
|
7e5d98dbab | ||
|
|
d952a97bda |
@@ -23,8 +23,28 @@ sys.path.remove("/opt/frigate")
|
||||
yaml = YAML()
|
||||
|
||||
# Check if arbitrary exec sources are allowed (defaults to False for security)
|
||||
ALLOW_ARBITRARY_EXEC = os.environ.get(
|
||||
"GO2RTC_ALLOW_ARBITRARY_EXEC", "false"
|
||||
allow_arbitrary_exec = None
|
||||
if "GO2RTC_ALLOW_ARBITRARY_EXEC" in os.environ:
|
||||
allow_arbitrary_exec = os.environ.get("GO2RTC_ALLOW_ARBITRARY_EXEC")
|
||||
elif (
|
||||
os.path.isdir("/run/secrets")
|
||||
and os.access("/run/secrets", os.R_OK)
|
||||
and "GO2RTC_ALLOW_ARBITRARY_EXEC" in os.listdir("/run/secrets")
|
||||
):
|
||||
allow_arbitrary_exec = (
|
||||
Path(os.path.join("/run/secrets", "GO2RTC_ALLOW_ARBITRARY_EXEC"))
|
||||
.read_text()
|
||||
.strip()
|
||||
)
|
||||
# check for the add-on options file
|
||||
elif os.path.isfile("/data/options.json"):
|
||||
with open("/data/options.json") as f:
|
||||
raw_options = f.read()
|
||||
options = json.loads(raw_options)
|
||||
allow_arbitrary_exec = options.get("go2rtc_allow_arbitrary_exec")
|
||||
|
||||
ALLOW_ARBITRARY_EXEC = allow_arbitrary_exec is not None and str(
|
||||
allow_arbitrary_exec
|
||||
).lower() in ("true", "1", "yes")
|
||||
|
||||
FRIGATE_ENV_VARS = {k: v for k, v in os.environ.items() if k.startswith("FRIGATE_")}
|
||||
|
||||
8
docs/static/_headers
vendored
Normal file
8
docs/static/_headers
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
https://:project.pages.dev/*
|
||||
X-Robots-Tag: noindex
|
||||
|
||||
https://:version.:project.pages.dev/*
|
||||
X-Robots-Tag: noindex
|
||||
|
||||
https://docs-dev.frigate.video/*
|
||||
X-Robots-Tag: noindex
|
||||
BIN
docs/static/img/frigate-autotracking-example.gif
vendored
BIN
docs/static/img/frigate-autotracking-example.gif
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 28 MiB After Width: | Height: | Size: 12 MiB |
@@ -10,7 +10,7 @@ class ReviewQueryParams(BaseModel):
|
||||
cameras: str = "all"
|
||||
labels: str = "all"
|
||||
zones: str = "all"
|
||||
reviewed: int = 0
|
||||
reviewed: Union[int, SkipJsonSchema[None]] = None
|
||||
limit: Union[int, SkipJsonSchema[None]] = None
|
||||
severity: Union[SeverityEnum, SkipJsonSchema[None]] = None
|
||||
before: Union[float, SkipJsonSchema[None]] = None
|
||||
|
||||
@@ -144,6 +144,8 @@ async def review(
|
||||
(UserReviewStatus.has_been_reviewed == False)
|
||||
| (UserReviewStatus.has_been_reviewed.is_null())
|
||||
)
|
||||
elif reviewed == 1:
|
||||
review_query = review_query.where(UserReviewStatus.has_been_reviewed == True)
|
||||
|
||||
# Apply ordering and limit
|
||||
review_query = (
|
||||
|
||||
@@ -171,8 +171,8 @@ class BaseTestHttp(unittest.TestCase):
|
||||
def insert_mock_event(
|
||||
self,
|
||||
id: str,
|
||||
start_time: float = datetime.datetime.now().timestamp(),
|
||||
end_time: float = datetime.datetime.now().timestamp() + 20,
|
||||
start_time: float | None = None,
|
||||
end_time: float | None = None,
|
||||
has_clip: bool = True,
|
||||
top_score: int = 100,
|
||||
score: int = 0,
|
||||
@@ -180,6 +180,11 @@ class BaseTestHttp(unittest.TestCase):
|
||||
camera: str = "front_door",
|
||||
) -> Event:
|
||||
"""Inserts a basic event model with a given id."""
|
||||
if start_time is None:
|
||||
start_time = datetime.datetime.now().timestamp()
|
||||
if end_time is None:
|
||||
end_time = start_time + 20
|
||||
|
||||
return Event.insert(
|
||||
id=id,
|
||||
label="Mock",
|
||||
@@ -229,11 +234,16 @@ class BaseTestHttp(unittest.TestCase):
|
||||
def insert_mock_recording(
|
||||
self,
|
||||
id: str,
|
||||
start_time: float = datetime.datetime.now().timestamp(),
|
||||
end_time: float = datetime.datetime.now().timestamp() + 20,
|
||||
start_time: float | None = None,
|
||||
end_time: float | None = None,
|
||||
motion: int = 0,
|
||||
) -> Event:
|
||||
"""Inserts a recording model with a given id."""
|
||||
if start_time is None:
|
||||
start_time = datetime.datetime.now().timestamp()
|
||||
if end_time is None:
|
||||
end_time = start_time + 20
|
||||
|
||||
return Recordings.insert(
|
||||
id=id,
|
||||
path=id,
|
||||
|
||||
@@ -96,16 +96,17 @@ class TestHttpApp(BaseTestHttp):
|
||||
assert len(events) == 0
|
||||
|
||||
def test_get_event_list_limit(self):
|
||||
now = datetime.now().timestamp()
|
||||
id = "123456.random"
|
||||
id2 = "54321.random"
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
super().insert_mock_event(id)
|
||||
super().insert_mock_event(id, start_time=now + 1)
|
||||
events = client.get("/events").json()
|
||||
assert len(events) == 1
|
||||
assert events[0]["id"] == id
|
||||
|
||||
super().insert_mock_event(id2)
|
||||
super().insert_mock_event(id2, start_time=now)
|
||||
events = client.get("/events").json()
|
||||
assert len(events) == 2
|
||||
|
||||
@@ -144,7 +145,7 @@ class TestHttpApp(BaseTestHttp):
|
||||
assert events[0]["id"] == id2
|
||||
assert events[1]["id"] == id
|
||||
|
||||
events = client.get("/events", params={"sort": "score_des"}).json()
|
||||
events = client.get("/events", params={"sort": "score_desc"}).json()
|
||||
assert len(events) == 2
|
||||
assert events[0]["id"] == id
|
||||
assert events[1]["id"] == id2
|
||||
|
||||
@@ -196,6 +196,50 @@ class TestHttpReview(BaseTestHttp):
|
||||
assert len(response_json) == 1
|
||||
assert response_json[0]["id"] == id
|
||||
|
||||
def test_get_review_with_reviewed_filter_unreviewed(self):
|
||||
"""Test that reviewed=0 returns only unreviewed items."""
|
||||
now = datetime.now().timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
id_unreviewed = "123456.unreviewed"
|
||||
id_reviewed = "123456.reviewed"
|
||||
super().insert_mock_review_segment(id_unreviewed, now, now + 2)
|
||||
super().insert_mock_review_segment(id_reviewed, now, now + 2)
|
||||
self._insert_user_review_status(id_reviewed, reviewed=True)
|
||||
|
||||
params = {
|
||||
"reviewed": 0,
|
||||
"after": now - 1,
|
||||
"before": now + 3,
|
||||
}
|
||||
response = client.get("/review", params=params)
|
||||
assert response.status_code == 200
|
||||
response_json = response.json()
|
||||
assert len(response_json) == 1
|
||||
assert response_json[0]["id"] == id_unreviewed
|
||||
|
||||
def test_get_review_with_reviewed_filter_reviewed(self):
|
||||
"""Test that reviewed=1 returns only reviewed items."""
|
||||
now = datetime.now().timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
id_unreviewed = "123456.unreviewed"
|
||||
id_reviewed = "123456.reviewed"
|
||||
super().insert_mock_review_segment(id_unreviewed, now, now + 2)
|
||||
super().insert_mock_review_segment(id_reviewed, now, now + 2)
|
||||
self._insert_user_review_status(id_reviewed, reviewed=True)
|
||||
|
||||
params = {
|
||||
"reviewed": 1,
|
||||
"after": now - 1,
|
||||
"before": now + 3,
|
||||
}
|
||||
response = client.get("/review", params=params)
|
||||
assert response.status_code == 200
|
||||
response_json = response.json()
|
||||
assert len(response_json) == 1
|
||||
assert response_json[0]["id"] == id_reviewed
|
||||
|
||||
####################################################################################################################
|
||||
################################### GET /review/summary Endpoint #################################################
|
||||
####################################################################################################################
|
||||
|
||||
@@ -43,6 +43,7 @@ def write_training_metadata(model_name: str, image_count: int) -> None:
|
||||
model_name: Name of the classification model
|
||||
image_count: Number of images used in training
|
||||
"""
|
||||
model_name = model_name.strip()
|
||||
clips_model_dir = os.path.join(CLIPS_DIR, model_name)
|
||||
os.makedirs(clips_model_dir, exist_ok=True)
|
||||
|
||||
@@ -70,6 +71,7 @@ def read_training_metadata(model_name: str) -> dict[str, any] | None:
|
||||
Returns:
|
||||
Dictionary with last_training_date and last_training_image_count, or None if not found
|
||||
"""
|
||||
model_name = model_name.strip()
|
||||
clips_model_dir = os.path.join(CLIPS_DIR, model_name)
|
||||
metadata_path = os.path.join(clips_model_dir, TRAINING_METADATA_FILE)
|
||||
|
||||
@@ -95,6 +97,7 @@ def get_dataset_image_count(model_name: str) -> int:
|
||||
Returns:
|
||||
Total count of images across all categories
|
||||
"""
|
||||
model_name = model_name.strip()
|
||||
dataset_dir = os.path.join(CLIPS_DIR, model_name, "dataset")
|
||||
|
||||
if not os.path.exists(dataset_dir):
|
||||
@@ -126,6 +129,7 @@ class ClassificationTrainingProcess(FrigateProcess):
|
||||
"TF_KERAS_MOBILENET_V2_WEIGHTS_URL",
|
||||
"",
|
||||
)
|
||||
model_name = model_name.strip()
|
||||
super().__init__(
|
||||
stop_event=None,
|
||||
priority=PROCESS_PRIORITY_LOW,
|
||||
@@ -292,6 +296,7 @@ class ClassificationTrainingProcess(FrigateProcess):
|
||||
def kickoff_model_training(
|
||||
embeddingRequestor: EmbeddingsRequestor, model_name: str
|
||||
) -> None:
|
||||
model_name = model_name.strip()
|
||||
requestor = InterProcessRequestor()
|
||||
requestor.send_data(
|
||||
UPDATE_MODEL_STATE,
|
||||
@@ -359,6 +364,7 @@ def collect_state_classification_examples(
|
||||
model_name: Name of the classification model
|
||||
cameras: Dict mapping camera names to normalized crop coordinates [x1, y1, x2, y2] (0-1)
|
||||
"""
|
||||
model_name = model_name.strip()
|
||||
dataset_dir = os.path.join(CLIPS_DIR, model_name, "dataset")
|
||||
|
||||
# Step 1: Get review items for the cameras
|
||||
@@ -714,6 +720,7 @@ def collect_object_classification_examples(
|
||||
model_name: Name of the classification model
|
||||
label: Object label to collect (e.g., "person", "car")
|
||||
"""
|
||||
model_name = model_name.strip()
|
||||
dataset_dir = os.path.join(CLIPS_DIR, model_name, "dataset")
|
||||
temp_dir = os.path.join(dataset_dir, "temp")
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
|
||||
@@ -205,7 +205,7 @@ export default function Events() {
|
||||
cameras: reviewSearchParams["cameras"],
|
||||
labels: reviewSearchParams["labels"],
|
||||
zones: reviewSearchParams["zones"],
|
||||
reviewed: 1,
|
||||
reviewed: null, // We want both reviewed and unreviewed items as we filter in the UI
|
||||
before: reviewSearchParams["before"] || last24Hours.before,
|
||||
after: reviewSearchParams["after"] || last24Hours.after,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user