forked from Github/frigate
Compare commits
7 Commits
v0.13.0-be
...
pass_filte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a74228cf9 | ||
|
|
ba603c1937 | ||
|
|
e89dafa82e | ||
|
|
9d717b371c | ||
|
|
3d70d29672 | ||
|
|
f1efd8dbe2 | ||
|
|
159fb51518 |
@@ -32,6 +32,8 @@ http {
|
||||
gzip_proxied no-cache no-store private expired auth;
|
||||
gzip_vary on;
|
||||
|
||||
proxy_cache_path /dev/shm/nginx_cache levels=1:2 keys_zone=api_cache:10m max_size=10m inactive=1m use_temp_path=off;
|
||||
|
||||
upstream frigate_api {
|
||||
server 127.0.0.1:5001;
|
||||
keepalive 1024;
|
||||
@@ -185,6 +187,19 @@ http {
|
||||
proxy_pass http://frigate_api/;
|
||||
include proxy.conf;
|
||||
|
||||
proxy_cache api_cache;
|
||||
proxy_cache_lock on;
|
||||
proxy_cache_use_stale updating;
|
||||
proxy_cache_valid 200 5s;
|
||||
proxy_cache_bypass $http_x_cache_bypass;
|
||||
add_header X-Cache-Status $upstream_cache_status;
|
||||
|
||||
location /api/vod/ {
|
||||
proxy_pass http://frigate_api/vod/;
|
||||
include proxy.conf;
|
||||
proxy_cache off;
|
||||
}
|
||||
|
||||
location /api/stats {
|
||||
access_log off;
|
||||
rewrite ^/api/(.*)$ $1 break;
|
||||
|
||||
@@ -231,6 +231,8 @@ detect:
|
||||
fps: 5
|
||||
# Optional: enables detection for the camera (default: True)
|
||||
enabled: True
|
||||
# Optional: Number of consecutive detection hits required for an object to be initialized in the tracker. (default: 1/2 the frame rate)
|
||||
min_initialized: 2
|
||||
# Optional: Number of frames without a detection before Frigate considers an object to be gone. (default: 5x the frame rate)
|
||||
max_disappeared: 25
|
||||
# Optional: Configuration for stationary object tracking
|
||||
|
||||
@@ -352,6 +352,9 @@ class DetectConfig(FrigateBaseModel):
|
||||
default=5, title="Number of frames per second to process through detection."
|
||||
)
|
||||
enabled: bool = Field(default=True, title="Detection Enabled.")
|
||||
min_initialized: Optional[int] = Field(
|
||||
title="Minimum number of consecutive hits for an object to be initialized by the tracker."
|
||||
)
|
||||
max_disappeared: Optional[int] = Field(
|
||||
title="Maximum number of frames the object can dissapear before detection ends."
|
||||
)
|
||||
@@ -1143,6 +1146,11 @@ class FrigateConfig(FrigateBaseModel):
|
||||
else DEFAULT_DETECT_DIMENSIONS["height"]
|
||||
)
|
||||
|
||||
# Default min_initialized configuration
|
||||
min_initialized = camera_config.detect.fps / 2
|
||||
if camera_config.detect.min_initialized is None:
|
||||
camera_config.detect.min_initialized = min_initialized
|
||||
|
||||
# Default max_disappeared configuration
|
||||
max_disappeared = camera_config.detect.fps * 5
|
||||
if camera_config.detect.max_disappeared is None:
|
||||
|
||||
@@ -106,10 +106,10 @@ class ExternalEventProcessor:
|
||||
# write jpg snapshot with optional annotations
|
||||
if draw.get("boxes") and isinstance(draw.get("boxes"), list):
|
||||
for box in draw.get("boxes"):
|
||||
x = box["box"][0] * camera_config.detect.width
|
||||
y = box["box"][1] * camera_config.detect.height
|
||||
width = box["box"][2] * camera_config.detect.width
|
||||
height = box["box"][3] * camera_config.detect.height
|
||||
x = int(box["box"][0] * camera_config.detect.width)
|
||||
y = int(box["box"][1] * camera_config.detect.height)
|
||||
width = int(box["box"][2] * camera_config.detect.width)
|
||||
height = int(box["box"][3] * camera_config.detect.height)
|
||||
|
||||
draw_box_with_label(
|
||||
img_frame,
|
||||
|
||||
@@ -463,7 +463,7 @@ class BirdsEyeFrameManager:
|
||||
def calculate_layout(self, cameras_to_add: list[str], coefficient) -> tuple[any]:
|
||||
"""Calculate the optimal layout for 2+ cameras."""
|
||||
|
||||
def map_layout(row_height: int):
|
||||
def map_layout(camera_layout: list[list[any]], row_height: int):
|
||||
"""Map the calculated layout."""
|
||||
candidate_layout = []
|
||||
starting_x = 0
|
||||
@@ -492,7 +492,7 @@ class BirdsEyeFrameManager:
|
||||
x + scaled_width > self.canvas.width
|
||||
or y + scaled_height > self.canvas.height
|
||||
):
|
||||
return 0, 0, None
|
||||
return x + scaled_width, y + scaled_height, None
|
||||
|
||||
final_row.append((cameras[0], (x, y, scaled_width, scaled_height)))
|
||||
x += scaled_width
|
||||
@@ -564,10 +564,24 @@ class BirdsEyeFrameManager:
|
||||
return None
|
||||
|
||||
row_height = int(self.canvas.height / coefficient)
|
||||
total_width, total_height, standard_candidate_layout = map_layout(row_height)
|
||||
total_width, total_height, standard_candidate_layout = map_layout(
|
||||
camera_layout, row_height
|
||||
)
|
||||
|
||||
if not standard_candidate_layout:
|
||||
return None
|
||||
# if standard layout didn't work
|
||||
# try reducing row_height by the % overflow
|
||||
scale_down_percent = max(
|
||||
total_width / self.canvas.width,
|
||||
total_height / self.canvas.height,
|
||||
)
|
||||
row_height = int(row_height / scale_down_percent)
|
||||
total_width, total_height, standard_candidate_layout = map_layout(
|
||||
camera_layout, row_height
|
||||
)
|
||||
|
||||
if not standard_candidate_layout:
|
||||
return None
|
||||
|
||||
# layout can't be optimized more
|
||||
if total_width / self.canvas.width >= 0.99:
|
||||
@@ -578,7 +592,7 @@ class BirdsEyeFrameManager:
|
||||
1 / (total_height / self.canvas.height),
|
||||
)
|
||||
row_height = int(row_height * scale_up_percent)
|
||||
_, _, scaled_layout = map_layout(row_height)
|
||||
_, _, scaled_layout = map_layout(camera_layout, row_height)
|
||||
|
||||
if scaled_layout:
|
||||
return scaled_layout
|
||||
|
||||
@@ -6,6 +6,7 @@ import os
|
||||
import subprocess as sp
|
||||
import threading
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import EXPORT_DIR, MAX_PLAYLIST_SECONDS
|
||||
@@ -121,6 +122,7 @@ class RecordingExporter(threading.Thread):
|
||||
f"Failed to export recording for command {' '.join(ffmpeg_cmd)}"
|
||||
)
|
||||
logger.error(p.stderr)
|
||||
Path(file_name).unlink(missing_ok=True)
|
||||
return
|
||||
|
||||
logger.debug(f"Updating finalized export {file_name}")
|
||||
|
||||
@@ -260,8 +260,10 @@ class RecordingMaintainer(threading.Thread):
|
||||
most_recently_processed_frame_time = (
|
||||
camera_info[-1][0] if len(camera_info) > 0 else 0
|
||||
)
|
||||
retain_cutoff = most_recently_processed_frame_time - pre_capture
|
||||
if end_time.timestamp() < retain_cutoff:
|
||||
retain_cutoff = datetime.datetime.fromtimestamp(
|
||||
most_recently_processed_frame_time - pre_capture
|
||||
).astimezone(datetime.timezone.utc)
|
||||
if end_time.astimezone(datetime.timezone.utc) < retain_cutoff:
|
||||
Path(cache_path).unlink(missing_ok=True)
|
||||
self.end_time_cache.pop(cache_path, None)
|
||||
# else retain days includes this segment
|
||||
@@ -273,7 +275,11 @@ class RecordingMaintainer(threading.Thread):
|
||||
)
|
||||
|
||||
# ensure delayed segment info does not lead to lost segments
|
||||
if most_recently_processed_frame_time >= end_time.timestamp():
|
||||
if datetime.datetime.fromtimestamp(
|
||||
most_recently_processed_frame_time
|
||||
).astimezone(datetime.timezone.utc) >= end_time.astimezone(
|
||||
datetime.timezone.utc
|
||||
):
|
||||
record_mode = self.config.cameras[camera].record.retain.mode
|
||||
return await self.move_segment(
|
||||
camera, start_time, end_time, duration, cache_path, record_mode
|
||||
|
||||
@@ -68,7 +68,6 @@ class NorfairTracker(ObjectTracker):
|
||||
self.untracked_object_boxes: list[list[int]] = []
|
||||
self.disappeared = {}
|
||||
self.positions = {}
|
||||
self.max_disappeared = config.detect.max_disappeared
|
||||
self.camera_config = config
|
||||
self.detect_config = config.detect
|
||||
self.ptz_metrics = ptz_metrics
|
||||
@@ -81,8 +80,8 @@ class NorfairTracker(ObjectTracker):
|
||||
self.tracker = Tracker(
|
||||
distance_function=frigate_distance,
|
||||
distance_threshold=2.5,
|
||||
initialization_delay=self.detect_config.fps / 2,
|
||||
hit_counter_max=self.max_disappeared,
|
||||
initialization_delay=self.detect_config.min_initialized,
|
||||
hit_counter_max=self.detect_config.max_disappeared,
|
||||
)
|
||||
if self.ptz_autotracker_enabled.value:
|
||||
self.ptz_motion_estimator = PtzMotionEstimator(
|
||||
|
||||
@@ -114,10 +114,8 @@ def load_config_with_no_duplicates(raw_config) -> dict:
|
||||
|
||||
def clean_camera_user_pass(line: str) -> str:
|
||||
"""Removes user and password from line."""
|
||||
if "rtsp://" in line:
|
||||
return re.sub(REGEX_RTSP_CAMERA_USER_PASS, "://*:*@", line)
|
||||
else:
|
||||
return re.sub(REGEX_HTTP_CAMERA_USER_PASS, "user=*&password=*", line)
|
||||
rtsp_cleaned = re.sub(REGEX_RTSP_CAMERA_USER_PASS, "://*:*@", line)
|
||||
return re.sub(REGEX_HTTP_CAMERA_USER_PASS, "user=*&password=*", rtsp_cleaned)
|
||||
|
||||
|
||||
def escape_special_characters(path: str) -> str:
|
||||
|
||||
@@ -233,14 +233,15 @@ class CameraWatchdog(threading.Thread):
|
||||
poll = p["process"].poll()
|
||||
|
||||
if self.config.record.enabled and "record" in p["roles"]:
|
||||
latest_segment_time = self.get_latest_segment_timestamp(
|
||||
latest_segment_time = self.get_latest_segment_datetime(
|
||||
p.get(
|
||||
"latest_segment_time", datetime.datetime.now().timestamp()
|
||||
"latest_segment_time",
|
||||
datetime.datetime.now().astimezone(datetime.timezone.utc),
|
||||
)
|
||||
)
|
||||
|
||||
if datetime.datetime.now().timestamp() > (
|
||||
latest_segment_time + 120
|
||||
if datetime.datetime.now().astimezone(datetime.timezone.utc) > (
|
||||
latest_segment_time + datetime.timedelta(seconds=120)
|
||||
):
|
||||
self.logger.error(
|
||||
f"No new recording segments were created for {self.camera_name} in the last 120s. restarting the ffmpeg record process..."
|
||||
@@ -288,7 +289,7 @@ class CameraWatchdog(threading.Thread):
|
||||
)
|
||||
self.capture_thread.start()
|
||||
|
||||
def get_latest_segment_timestamp(self, latest_timestamp) -> int:
|
||||
def get_latest_segment_datetime(self, latest_segment: datetime.datetime) -> int:
|
||||
"""Checks if ffmpeg is still writing recording segments to cache."""
|
||||
cache_files = sorted(
|
||||
[
|
||||
@@ -299,13 +300,15 @@ class CameraWatchdog(threading.Thread):
|
||||
and not d.startswith("clip_")
|
||||
]
|
||||
)
|
||||
newest_segment_timestamp = latest_timestamp
|
||||
newest_segment_timestamp = latest_segment
|
||||
|
||||
for file in cache_files:
|
||||
if self.camera_name in file:
|
||||
basename = os.path.splitext(file)[0]
|
||||
_, date = basename.rsplit("-", maxsplit=1)
|
||||
ts = datetime.datetime.strptime(date, "%Y%m%d%H%M%S").timestamp()
|
||||
ts = datetime.datetime.strptime(date, "%Y%m%d%H%M%S").astimezone(
|
||||
datetime.timezone.utc
|
||||
)
|
||||
if ts > newest_segment_timestamp:
|
||||
newest_segment_timestamp = ts
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import axios from 'axios';
|
||||
axios.defaults.baseURL = `${baseUrl}api/`;
|
||||
axios.defaults.headers.common = {
|
||||
'X-CSRF-TOKEN': 1,
|
||||
'X-CACHE-BYPASS': 1,
|
||||
};
|
||||
|
||||
export function ApiProvider({ children, options }) {
|
||||
|
||||
Reference in New Issue
Block a user