mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-09-29 03:10:19 +00:00
frigate: 0.17.2 -> 0.18.0 (#541441)
This commit is contained in:
@@ -405,7 +405,9 @@ in
|
||||
extraConfig = nginxAuthRequest + ''
|
||||
types {
|
||||
video/mp4 mp4;
|
||||
image/jpeg jpg;
|
||||
image/jpeg jpg jpeg;
|
||||
image/png png;
|
||||
image/webp webp;
|
||||
}
|
||||
|
||||
expires 7d;
|
||||
@@ -493,19 +495,6 @@ in
|
||||
}
|
||||
'';
|
||||
};
|
||||
# frontend uses this to fetch the version
|
||||
"/api/go2rtc/api" = {
|
||||
proxyPass = "http://frigate-go2rtc/api";
|
||||
recommendedProxySettings = true;
|
||||
extraConfig =
|
||||
nginxAuthRequest
|
||||
+ nginxProxySettings
|
||||
+ ''
|
||||
limit_except GET {
|
||||
deny all;
|
||||
}
|
||||
'';
|
||||
};
|
||||
# integrationn uses this to add webrtc candidate
|
||||
"/api/go2rtc/webrtc" = {
|
||||
proxyPass = "http://frigate-go2rtc/api/webrtc";
|
||||
@@ -541,6 +530,7 @@ in
|
||||
expires off;
|
||||
|
||||
proxy_cache frigate_api_cache;
|
||||
proxy_cache_key "$scheme$proxy_host$request_uri|$role|$groups|$user";
|
||||
proxy_cache_lock on;
|
||||
proxy_cache_use_stale updating;
|
||||
proxy_cache_valid 200 5s;
|
||||
@@ -563,6 +553,13 @@ in
|
||||
${nginxProxySettings}
|
||||
}
|
||||
|
||||
location /api/logout {
|
||||
auth_request off;
|
||||
rewrite ^/api(/.*)$ $1 break;
|
||||
proxy_pass http://frigate-api;
|
||||
${nginxProxySettings}
|
||||
}
|
||||
|
||||
location /api/auth/first_time_login {
|
||||
auth_request off;
|
||||
limit_except GET {
|
||||
@@ -747,7 +744,6 @@ in
|
||||
]
|
||||
++ optionals (!stdenv.hostPlatform.isAarch64) [
|
||||
# not available on aarch64-linux
|
||||
intel-gpu-tools
|
||||
rocmPackages.rocminfo
|
||||
];
|
||||
serviceConfig = {
|
||||
@@ -775,11 +771,10 @@ in
|
||||
Group = "frigate";
|
||||
SupplementaryGroups = [ "render" ] ++ optionals withCoral [ "coral" ];
|
||||
|
||||
AmbientCapabilities = optionals (elem cfg.vaapiDriver [
|
||||
"i965"
|
||||
"iHD"
|
||||
]) [ "CAP_PERFMON" ]; # for intel_gpu_top
|
||||
# No capabilities
|
||||
CapabilityBoundingSet = [ "" ];
|
||||
|
||||
# Allow delegating access
|
||||
UMask = "0027";
|
||||
|
||||
StateDirectory = "frigate";
|
||||
@@ -797,9 +792,53 @@ in
|
||||
|
||||
# Sockets/IPC
|
||||
RuntimeDirectory = "frigate";
|
||||
RemoveIPC = true;
|
||||
|
||||
# Reduce visible process scope to cgroup
|
||||
ProtectProc = "invisible";
|
||||
|
||||
# Allow wide /proc inspection, e.g. for cpuinfo
|
||||
ProcSubset = "all";
|
||||
|
||||
# Protect various system locations/interfaces
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectSystem = "strict";
|
||||
|
||||
# No JIT compilation
|
||||
MemoryDenyWriteExecute = true;
|
||||
|
||||
# No ABI personality changes
|
||||
LockPersonality = true;
|
||||
|
||||
# Only IP/Unix sockets
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_UNIX"
|
||||
];
|
||||
|
||||
# Deny namespace creation
|
||||
RestrictNamespaces = true;
|
||||
|
||||
# No privilege escalation
|
||||
NoNewPrivileges = true;
|
||||
RestrictSUIDSGID = true;
|
||||
|
||||
# No realtime schedulign
|
||||
RestrictRealtime = true;
|
||||
|
||||
# Restrict allowed syscalls
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@privileged"
|
||||
];
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -77,5 +77,7 @@
|
||||
|
||||
# wait for a recording to appear
|
||||
machine.wait_for_file("/var/cache/frigate/test@*.mp4")
|
||||
|
||||
machine.log(machine.execute("systemd-analyze security frigate.service | grep -v ✓")[1])
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
diff --git a/frigate/data_processing/real_time/bird.py b/frigate/data_processing/real_time/bird.py
|
||||
index 7851c0997..520440005 100644
|
||||
--- a/frigate/data_processing/real_time/bird.py
|
||||
+++ b/frigate/data_processing/real_time/bird.py
|
||||
@@ -22,7 +22,7 @@ from .api import RealTimeProcessorApi
|
||||
try:
|
||||
from tflite_runtime.interpreter import Interpreter
|
||||
except ModuleNotFoundError:
|
||||
- from tensorflow.lite.python.interpreter import Interpreter
|
||||
+ from ai_edge_litert.interpreter import Interpreter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
diff --git a/frigate/data_processing/real_time/custom_classification.py b/frigate/data_processing/real_time/custom_classification.py
|
||||
index 229383d9f..2c74a6575 100644
|
||||
--- a/frigate/data_processing/real_time/custom_classification.py
|
||||
+++ b/frigate/data_processing/real_time/custom_classification.py
|
||||
@@ -32,7 +32,7 @@ from .api import RealTimeProcessorApi
|
||||
try:
|
||||
from tflite_runtime.interpreter import Interpreter
|
||||
except ModuleNotFoundError:
|
||||
- from tensorflow.lite.python.interpreter import Interpreter
|
||||
+ from ai_edge_litert.interpreter import Interpreter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -76,7 +76,7 @@ class CustomStateClassificationProcessor(RealTimeProcessorApi):
|
||||
try:
|
||||
from tflite_runtime.interpreter import Interpreter
|
||||
except ModuleNotFoundError:
|
||||
- from tensorflow.lite.python.interpreter import Interpreter
|
||||
+ from ai_edge_litert.interpreter import Interpreter
|
||||
|
||||
model_path = os.path.join(self.model_dir, "model.tflite")
|
||||
labelmap_path = os.path.join(self.model_dir, "labelmap.txt")
|
||||
diff --git a/frigate/detectors/detector_utils.py b/frigate/detectors/detector_utils.py
|
||||
index d732de871..d8930b2ae 100644
|
||||
--- a/frigate/detectors/detector_utils.py
|
||||
+++ b/frigate/detectors/detector_utils.py
|
||||
@@ -6,7 +6,7 @@ import numpy as np
|
||||
try:
|
||||
from tflite_runtime.interpreter import Interpreter, load_delegate
|
||||
except ModuleNotFoundError:
|
||||
- from tensorflow.lite.python.interpreter import Interpreter, load_delegate
|
||||
+ from ai_edge_litert.interpreter import Interpreter, load_delegate
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
diff --git a/frigate/detectors/plugins/cpu_tfl.py b/frigate/detectors/plugins/cpu_tfl.py
|
||||
index 00351f519..6d336bb6b 100644
|
||||
--- a/frigate/detectors/plugins/cpu_tfl.py
|
||||
+++ b/frigate/detectors/plugins/cpu_tfl.py
|
||||
@@ -12,7 +12,7 @@ from ..detector_utils import tflite_detect_raw, tflite_init
|
||||
try:
|
||||
from tflite_runtime.interpreter import Interpreter
|
||||
except ModuleNotFoundError:
|
||||
- from tensorflow.lite.python.interpreter import Interpreter
|
||||
+ from ai_edge_litert.interpreter import Interpreter
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
diff --git a/frigate/detectors/plugins/edgetpu_tfl.py b/frigate/detectors/plugins/edgetpu_tfl.py
|
||||
index 2b94fde39..36c769b4b 100644
|
||||
--- a/frigate/detectors/plugins/edgetpu_tfl.py
|
||||
+++ b/frigate/detectors/plugins/edgetpu_tfl.py
|
||||
@@ -13,7 +13,7 @@ from frigate.detectors.detector_config import BaseDetectorConfig, ModelTypeEnum
|
||||
try:
|
||||
from tflite_runtime.interpreter import Interpreter, load_delegate
|
||||
except ModuleNotFoundError:
|
||||
- from tensorflow.lite.python.interpreter import Interpreter, load_delegate
|
||||
+ from ai_edge_litert.interpreter import Interpreter, load_delegate
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
diff --git a/frigate/embeddings/onnx/face_embedding.py b/frigate/embeddings/onnx/face_embedding.py
|
||||
index 04d756897..75dfedc94 100644
|
||||
--- a/frigate/embeddings/onnx/face_embedding.py
|
||||
+++ b/frigate/embeddings/onnx/face_embedding.py
|
||||
@@ -17,7 +17,7 @@ from .base_embedding import BaseEmbedding
|
||||
try:
|
||||
from tflite_runtime.interpreter import Interpreter
|
||||
except ModuleNotFoundError:
|
||||
- from tensorflow.lite.python.interpreter import Interpreter
|
||||
+ from ai_edge_litert.interpreter import Interpreter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
diff --git a/frigate/events/audio.py b/frigate/events/audio.py
|
||||
index e88f2ae71..ad87d19c1 100644
|
||||
--- a/frigate/events/audio.py
|
||||
+++ b/frigate/events/audio.py
|
||||
@@ -43,7 +43,7 @@ from frigate.video import start_or_restart_ffmpeg, stop_ffmpeg
|
||||
try:
|
||||
from tflite_runtime.interpreter import Interpreter
|
||||
except ModuleNotFoundError:
|
||||
- from tensorflow.lite.python.interpreter import Interpreter
|
||||
+ from ai_edge_litert.interpreter import Interpreter
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -1,47 +1,23 @@
|
||||
diff --git a/frigate/config/camera/ffmpeg.py b/frigate/config/camera/ffmpeg.py
|
||||
index 2c1e4cdca..d1a1f7065 100644
|
||||
--- a/frigate/config/camera/ffmpeg.py
|
||||
+++ b/frigate/config/camera/ffmpeg.py
|
||||
@@ -1,4 +1,5 @@
|
||||
from enum import Enum
|
||||
+from os.path import join
|
||||
from typing import Union
|
||||
diff --git a/frigate/util/config.py b/frigate/util/config.py
|
||||
index 3e093a978..c9a5d7462 100644
|
||||
--- a/frigate/util/config.py
|
||||
+++ b/frigate/util/config.py
|
||||
@@ -32,17 +32,7 @@ def resolve_ffmpeg_path(path: str, binary: str = "ffmpeg") -> str:
|
||||
bundled version so existing configs keep working across an upgrade or a
|
||||
revert. Custom install paths (anything absolute) are used as-is.
|
||||
"""
|
||||
- if path == "default" or (
|
||||
- not path.startswith("/") and path not in INCLUDED_FFMPEG_VERSIONS
|
||||
- ):
|
||||
- version = DEFAULT_FFMPEG_VERSION
|
||||
- elif path in INCLUDED_FFMPEG_VERSIONS:
|
||||
- version = path
|
||||
- else:
|
||||
- return f"{path}/bin/{binary}"
|
||||
-
|
||||
- return f"/usr/lib/ffmpeg/{version}/bin/{binary}"
|
||||
-
|
||||
+ return os.path.join(path, "bin", binary)
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
@@ -71,21 +72,11 @@ class FfmpegConfig(FrigateBaseModel):
|
||||
|
||||
@property
|
||||
def ffmpeg_path(self) -> str:
|
||||
- if self.path == "default":
|
||||
- return f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffmpeg"
|
||||
- elif self.path in INCLUDED_FFMPEG_VERSIONS:
|
||||
- return f"/usr/lib/ffmpeg/{self.path}/bin/ffmpeg"
|
||||
- else:
|
||||
- return f"{self.path}/bin/ffmpeg"
|
||||
+ return join(self.path, "bin/ffmpeg")
|
||||
|
||||
@property
|
||||
def ffprobe_path(self) -> str:
|
||||
- if self.path == "default":
|
||||
- return f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffprobe"
|
||||
- elif self.path in INCLUDED_FFMPEG_VERSIONS:
|
||||
- return f"/usr/lib/ffmpeg/{self.path}/bin/ffprobe"
|
||||
- else:
|
||||
- return f"{self.path}/bin/ffprobe"
|
||||
+ return join(self.path, "bin/ffprobe")
|
||||
|
||||
|
||||
class CameraRoleEnum(str, Enum):
|
||||
diff --git a/frigate/record/export.py b/frigate/record/export.py
|
||||
index d4b49bb4b..bbcccff61 100644
|
||||
--- a/frigate/record/export.py
|
||||
+++ b/frigate/record/export.py
|
||||
@@ -127,7 +127,7 @@ class RecordingExporter(threading.Thread):
|
||||
minutes = int(diff / 60)
|
||||
seconds = int(diff % 60)
|
||||
ffmpeg_cmd = [
|
||||
- "/usr/lib/ffmpeg/7.0/bin/ffmpeg", # hardcode path for exports thumbnail due to missing libwebp support
|
||||
+ self.config.ffmpeg.ffmpeg_path, # hardcode path for exports thumbnail due to missing libwebp support
|
||||
"-hide_banner",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
def redact_credential(obj: dict[str, Any], key: str) -> None:
|
||||
"""Replace obj[key] with the redaction sentinel if a value is saved, else drop.
|
||||
|
||||
32
pkgs/by-name/fr/frigate/fix-tests-media-auth-paths.patch
Normal file
32
pkgs/by-name/fr/frigate/fix-tests-media-auth-paths.patch
Normal file
@@ -0,0 +1,32 @@
|
||||
From 91711507df5ad880bfcba1e11619441f7d0f9a58 Mon Sep 17 00:00:00 2001
|
||||
From: Martin Weinelt <hexa@darmstadt.ccc.de>
|
||||
Date: Mon, 13 Jul 2026 14:11:18 +0200
|
||||
Subject: [PATCH] Reuse constants in media auth tests
|
||||
|
||||
---
|
||||
frigate/test/test_media_auth.py | 5 +++--
|
||||
1 file changed, 3 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/frigate/test/test_media_auth.py b/frigate/test/test_media_auth.py
|
||||
index d025fea614..0b106047b2 100644
|
||||
--- a/frigate/test/test_media_auth.py
|
||||
+++ b/frigate/test/test_media_auth.py
|
||||
@@ -20,6 +20,7 @@
|
||||
resolve_media_uri,
|
||||
)
|
||||
from frigate.config import FrigateConfig
|
||||
+from frigate.const import EXPORT_DIR
|
||||
from frigate.models import Event, Export, Recordings, ReviewSegment
|
||||
from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
|
||||
|
||||
@@ -231,8 +232,8 @@ def _insert_export(self, export_id, camera, filename):
|
||||
camera=camera,
|
||||
name=f"export-{export_id}",
|
||||
date=int(datetime.datetime.now().timestamp()),
|
||||
- video_path=f"/media/frigate/exports/{filename}",
|
||||
- thumb_path=f"/media/frigate/exports/{filename}.jpg",
|
||||
+ video_path=os.path.join(EXPORT_DIR, filename),
|
||||
+ thumb_path=os.path.join(EXPORT_DIR, f"{filename}.jpg"),
|
||||
in_progress=False,
|
||||
).execute()
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
fetchurl,
|
||||
}:
|
||||
|
||||
let
|
||||
model = fetchurl {
|
||||
url = "https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite";
|
||||
hash = "sha256-Siviu7YU5XbVbcuRT6UnUr8PE0EVEnENNV2X+qGzVkE=";
|
||||
};
|
||||
in
|
||||
|
||||
model.overrideAttrs (_: {
|
||||
passthru.model = model;
|
||||
})
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
fetchurl,
|
||||
frigate,
|
||||
lib,
|
||||
stdenv,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (frigate) python3Packages;
|
||||
in
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "ssdlite_mobilenet_v2_coco";
|
||||
version = "2018_05_09";
|
||||
|
||||
src = fetchurl {
|
||||
url = "http://download.tensorflow.org/models/object_detection/ssdlite_mobilenet_v2_coco_2018_05_09.tar.gz";
|
||||
hash = "sha256-VCRFzOg02/u33xmRQl1HXoWi1+xoxgpPJiuxiqwQyLI=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
python3Packages.openvino
|
||||
];
|
||||
|
||||
postPatch = ''
|
||||
cp --no-preserve=mode ${frigate.src}/docker/main/build_ov_model.py .
|
||||
substituteInPlace build_ov_model.py \
|
||||
--replace-fail "/models/${finalAttrs.pname}_${finalAttrs.version}" "./" \
|
||||
--replace-fail "/models/" "dist/"
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
mkdir dist
|
||||
python3 build_ov_model.py
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out
|
||||
cp dist/*.{bin,xml} $out/
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
model = "${finalAttrs.finalPackage}/ssdlite_mobilnet_v2.xml";
|
||||
labelmap = fetchurl {
|
||||
url = "https://github.com/openvinotoolkit/open_model_zoo/raw/master/data/dataset_classes/coco_91cl_bkgr.txt";
|
||||
hash = "sha256-cQBhlxYm2yS6s7rm/06c5uZmUFZZkBI/P4bxLThbN9E=";
|
||||
# https://github.com/blakeblackshear/frigate/commit/8ac3114f9a014356272b8a44b752e82df342f245
|
||||
postFetch = ''
|
||||
sed -i "s/truck/car/g" $out
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
description = "Object detection model trained on the COCO dataset.";
|
||||
license = lib.licenses.asl20;
|
||||
homepage = "https://www.kaggle.com/models/tensorflow/ssdlite-mobilenet-v2/";
|
||||
};
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
fetchurl,
|
||||
}:
|
||||
|
||||
let
|
||||
model = fetchurl {
|
||||
url = "https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess.tflite";
|
||||
hash = "sha256-kLszpjTgQZFMwYGapd+ZgY5sOWxNLblSwP16nP/Eck8=";
|
||||
};
|
||||
in
|
||||
|
||||
model.overrideAttrs (_: {
|
||||
passthru.model = model;
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
fetchzip,
|
||||
lib,
|
||||
stdenv,
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "yamnet_classification_v1";
|
||||
version = "0-unstable";
|
||||
|
||||
src = fetchzip {
|
||||
url = "https://www.kaggle.com/api/v1/models/google/yamnet/tfLite/classification-tflite/1/download";
|
||||
extension = "tar.gz";
|
||||
hash = "sha256-FRL0lpbjgAV7HlaZIR1Hf/hr1bcfSMSeYdeGzMYpDf0=";
|
||||
};
|
||||
|
||||
dontBuild = true;
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir $out
|
||||
mv 1.tflite $out/yamnet_classification_v1.tflite
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru.model = "${finalAttrs.finalPackage}/${finalAttrs.pname}.tflite";
|
||||
|
||||
meta = {
|
||||
homepage = "https://www.kaggle.com/models/google/yamnet/tfLite/classification-tflite";
|
||||
license = lib.licenses.asl20;
|
||||
};
|
||||
})
|
||||
@@ -1,47 +1,21 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
replaceVars,
|
||||
addDriverRunpath,
|
||||
callPackage,
|
||||
python313Packages,
|
||||
fetchFromGitHub,
|
||||
rustPlatform,
|
||||
fetchurl,
|
||||
ffmpeg-headless,
|
||||
sqlite-vec,
|
||||
frigate,
|
||||
lib,
|
||||
nixosTests,
|
||||
go2rtc,
|
||||
python313Packages,
|
||||
replaceVars,
|
||||
sqlite-vec,
|
||||
stdenv,
|
||||
}:
|
||||
|
||||
let
|
||||
version = "0.17.2";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
name = "frigate-${version}-source";
|
||||
owner = "blakeblackshear";
|
||||
repo = "frigate";
|
||||
tag = "v${version}";
|
||||
hash = "sha256-8ujG5rVGqIJxM+IiQKvudrA0xqfz+3Uisl/zXwARPpY=";
|
||||
};
|
||||
|
||||
frigate-web = callPackage ./web.nix {
|
||||
inherit version src;
|
||||
};
|
||||
|
||||
python3Packages = python313Packages.overrideScope (
|
||||
self: super: {
|
||||
joserfc = super.joserfc.overridePythonAttrs (oldAttrs: {
|
||||
version = "1.1.0";
|
||||
src = fetchFromGitHub {
|
||||
owner = "authlib";
|
||||
repo = "joserfc";
|
||||
tag = version;
|
||||
hash = "sha256-95xtUzzIxxvDtpHX/5uCHnTQTB8Fc08DZGUOR/SdKLs=";
|
||||
};
|
||||
});
|
||||
|
||||
# transformers 4.* is not compatible with the latest tokenizers
|
||||
tokenizers = super.tokenizers.overridePythonAttrs (
|
||||
oldAttrs:
|
||||
@@ -86,40 +60,19 @@ let
|
||||
);
|
||||
|
||||
inherit (python3Packages) python;
|
||||
|
||||
# Tensorflow audio model
|
||||
# https://github.com/blakeblackshear/frigate/blob/v0.15.0/docker/main/Dockerfile#L125
|
||||
tflite_audio_model = fetchurl {
|
||||
url = "https://www.kaggle.com/api/v1/models/google/yamnet/tfLite/classification-tflite/1/download";
|
||||
hash = "sha256-G5cbITJ2AnOl+49dxQToZ4OyeFO7MTXVVa4G8eHjZfM=";
|
||||
};
|
||||
|
||||
# Tensorflow Lite models
|
||||
# https://github.com/blakeblackshear/frigate/blob/v0.15.0/docker/main/Dockerfile#L115-L117
|
||||
tflite_cpu_model = fetchurl {
|
||||
url = "https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess.tflite";
|
||||
hash = "sha256-kLszpjTgQZFMwYGapd+ZgY5sOWxNLblSwP16nP/Eck8=";
|
||||
};
|
||||
tflite_edgetpu_model = fetchurl {
|
||||
url = "https://github.com/google-coral/test_data/raw/release-frogfish/ssdlite_mobiledet_coco_qat_postprocess_edgetpu.tflite";
|
||||
hash = "sha256-Siviu7YU5XbVbcuRT6UnUr8PE0EVEnENNV2X+qGzVkE=";
|
||||
};
|
||||
|
||||
# TODO: OpenVino model
|
||||
# https://github.com/blakeblackshear/frigate/blob/v0.15.0/docker/main/Dockerfile#L64-L77
|
||||
# https://github.com/blakeblackshear/frigate/blob/v0.15.0/docker/main/Dockerfile#L120-L123
|
||||
# Convert https://www.kaggle.com/models/tensorflow/ssdlite-mobilenet-v2 with https://github.com/blakeblackshear/frigate/blob/v0.15.0/docker/main/build_ov_model.py into OpenVino IR format
|
||||
coco_91cl_bkgr = fetchurl {
|
||||
url = "https://github.com/openvinotoolkit/open_model_zoo/raw/master/data/dataset_classes/coco_91cl_bkgr.txt";
|
||||
hash = "sha256-5Cj2vEiWR8Z9d2xBmVoLZuNRv4UOuxHSGZQWTJorXUQ=";
|
||||
};
|
||||
in
|
||||
python3Packages.buildPythonApplication rec {
|
||||
python3Packages.buildPythonApplication (finalAttrs: {
|
||||
pname = "frigate";
|
||||
inherit version;
|
||||
version = "0.18.0";
|
||||
pyproject = false;
|
||||
|
||||
inherit src;
|
||||
src = fetchFromGitHub {
|
||||
name = "frigate-${finalAttrs.version}-source";
|
||||
owner = "blakeblackshear";
|
||||
repo = "frigate";
|
||||
tag = "v${finalAttrs.version}";
|
||||
hash = "sha256-2FJG7tEZCuCQ6VJga9BMiuLTeswmlG8oN1MO6t0eroM=";
|
||||
};
|
||||
|
||||
patches = [
|
||||
# Always lookup ffmpeg from config setting
|
||||
@@ -130,24 +83,27 @@ python3Packages.buildPythonApplication rec {
|
||||
inherit (addDriverRunpath) driverLink;
|
||||
})
|
||||
|
||||
# Use ai-edge-litert as tensorflow interpreter
|
||||
# https://github.com/blakeblackshear/frigate/pull/21876
|
||||
./ai-edge-litert.patch
|
||||
|
||||
# Disable failing optimization in onnxruntime
|
||||
# https://github.com/microsoft/onnxruntime/issues/26717
|
||||
# https://github.com/microsoft/onnxruntime/pull/29196
|
||||
./onnxruntime-compat.patch
|
||||
|
||||
# Fix excessive trailing whitespaces in process commandlines
|
||||
# https://github.com/blakeblackshear/frigate/pull/22089
|
||||
./proc-cmdline-strip.patch
|
||||
# Reuse EXPORT_DIR in tests
|
||||
./fix-tests-media-auth-paths.patch
|
||||
|
||||
# Fix more granular dtype resolution in Pandas 3.0
|
||||
./pandas3-compat.patch
|
||||
|
||||
# Various fixes for newer library packages, migrates to
|
||||
# - FastAPI lifespan,
|
||||
# - native tz-aware objects,
|
||||
# - Pydantic TypeAdapter
|
||||
# https://github.com/blakeblackshear/frigate/pull/23641
|
||||
./pr23641.patch
|
||||
];
|
||||
|
||||
postPatch = ''
|
||||
echo 'VERSION = "${version}"' > frigate/version.py
|
||||
echo 'VERSION = "${finalAttrs.version}"' > frigate/version.py
|
||||
|
||||
substituteInPlace \
|
||||
frigate/app.py \
|
||||
@@ -174,90 +130,94 @@ python3Packages.buildPythonApplication rec {
|
||||
substituteInPlace frigate/db/sqlitevecq.py \
|
||||
--replace-fail "/usr/local/lib/vec0" "${lib.getLib sqlite-vec}/lib/vec0${stdenv.hostPlatform.extensions.sharedLibrary}"
|
||||
|
||||
# provide default paths for models and maps that are shipped with frigate
|
||||
# hardcoded default models shipped with Frigate
|
||||
substituteInPlace frigate/config/config.py \
|
||||
--replace-fail "/cpu_model.tflite" "${tflite_cpu_model}" \
|
||||
--replace-fail "/edgetpu_model.tflite" "${tflite_edgetpu_model}"
|
||||
--replace-fail "/cpu_model.tflite" "${frigate.models.tflite.ssdlite_mobiledet_coco_qat_postprocess}" \
|
||||
--replace-fail "/edgetpu_model.tflite" "${frigate.models.edgetpu.ssdlite_mobiledet_coco_qat_postprocess}" \
|
||||
--replace-fail "/openvino-model/ssdlite_mobilenet_v2.xml" "${frigate.models.openvino.ssdlite_mobilenet_v2_coco.model}" \
|
||||
--replace-fail "/openvino-model/coco_91cl_bkgr.txt" "${frigate.models.openvino.ssdlite_mobilenet_v2_coco.labelmap}"
|
||||
|
||||
substituteInPlace frigate/detectors/detector_config.py \
|
||||
--replace-fail "/labelmap.txt" "${placeholder "out"}/share/frigate/labelmap.txt"
|
||||
|
||||
substituteInPlace frigate/events/audio.py \
|
||||
--replace-fail "/cpu_audio_model.tflite" "${placeholder "out"}/share/frigate/cpu_audio_model.tflite" \
|
||||
--replace-fail "/cpu_audio_model.tflite" "${frigate.models.tflite.yamnet_classification_v1.model}" \
|
||||
--replace-fail "/audio-labelmap.txt" "${placeholder "out"}/share/frigate/audio-labelmap.txt"
|
||||
'';
|
||||
|
||||
dontBuild = true;
|
||||
|
||||
dependencies = with python3Packages; [
|
||||
# docker/main/requirements-wheel.txt
|
||||
# TODO: degirum (no source repo, binary wheels only)
|
||||
ai-edge-litert
|
||||
aiofiles
|
||||
aiohttp
|
||||
appdirs
|
||||
argcomplete
|
||||
click
|
||||
contextlib2
|
||||
cryptography
|
||||
distlib
|
||||
fastapi
|
||||
faster-whisper
|
||||
filelock
|
||||
google-genai
|
||||
importlib-metadata
|
||||
importlib-resources
|
||||
joserfc
|
||||
keras # via tensorflow.keras
|
||||
librosa
|
||||
markupsafe
|
||||
memray
|
||||
netaddr
|
||||
netifaces
|
||||
norfair
|
||||
numpy
|
||||
ollama
|
||||
onnxruntime
|
||||
onvif-zeep-async
|
||||
openai
|
||||
opencv4
|
||||
openvino
|
||||
paho-mqtt
|
||||
pandas
|
||||
pathvalidate
|
||||
peewee
|
||||
peewee-migrate
|
||||
prometheus-client
|
||||
psutil
|
||||
py3nvml
|
||||
pyclipper
|
||||
pydantic
|
||||
python-multipart
|
||||
pytz
|
||||
py-vapid
|
||||
pywebpush
|
||||
pyzmq
|
||||
rapidfuzz
|
||||
requests
|
||||
ruamel-yaml
|
||||
scipy
|
||||
setproctitle
|
||||
shapely
|
||||
sherpa-onnx
|
||||
slowapi
|
||||
soundfile
|
||||
starlette
|
||||
starlette-context
|
||||
tensorflow
|
||||
titlecase
|
||||
transformers
|
||||
tzlocal
|
||||
unidecode
|
||||
uvicorn
|
||||
verboselogs
|
||||
virtualenv
|
||||
ws4py
|
||||
];
|
||||
dependencies =
|
||||
with python3Packages;
|
||||
[
|
||||
# docker/main/requirements-wheel.txt
|
||||
ai-edge-litert
|
||||
aiofiles
|
||||
aiohttp
|
||||
appdirs
|
||||
argcomplete
|
||||
click
|
||||
contextlib2
|
||||
cryptography
|
||||
distlib
|
||||
fastapi
|
||||
faster-whisper
|
||||
filelock
|
||||
google-genai
|
||||
httpx
|
||||
importlib-metadata
|
||||
importlib-resources
|
||||
joserfc
|
||||
keras # via tensorflow.keras
|
||||
librosa
|
||||
markupsafe
|
||||
memray
|
||||
netaddr
|
||||
netifaces
|
||||
norfair
|
||||
numpy
|
||||
ollama
|
||||
onnxruntime
|
||||
onvif-zeep-async
|
||||
openai
|
||||
opencv4
|
||||
openvino
|
||||
paho-mqtt
|
||||
pandas
|
||||
pathvalidate
|
||||
peewee
|
||||
peewee-migrate
|
||||
prometheus-client
|
||||
psutil
|
||||
py3nvml
|
||||
pyclipper
|
||||
pydantic
|
||||
python-multipart
|
||||
py-vapid
|
||||
pywebpush
|
||||
pyzmq
|
||||
rapidfuzz
|
||||
requests
|
||||
ruamel-yaml
|
||||
scipy
|
||||
setproctitle
|
||||
shapely
|
||||
sherpa-onnx
|
||||
slowapi
|
||||
soundfile
|
||||
starlette
|
||||
starlette-context
|
||||
tensorflow
|
||||
titlecase
|
||||
transformers
|
||||
tzlocal
|
||||
unidecode
|
||||
uvicorn
|
||||
verboselogs
|
||||
virtualenv
|
||||
ws4py
|
||||
]
|
||||
++ httpx.optional-dependencies.http2;
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
@@ -268,11 +228,7 @@ python3Packages.buildPythonApplication rec {
|
||||
mkdir -p $out/share/frigate
|
||||
cp -R {migrations,labelmap.txt,audio-labelmap.txt} $out/share/frigate/
|
||||
|
||||
tar --extract --gzip --file ${tflite_audio_model}
|
||||
cp --no-preserve=mode ./1.tflite $out/share/frigate/cpu_audio_model.tflite
|
||||
|
||||
cp --no-preserve=mode ${coco_91cl_bkgr} $out/share/frigate/coco_91cl_bkgr.txt
|
||||
sed -i 's/truck/car/g' $out/share/frigate/coco_91cl_bkgr.txt
|
||||
ln -s ${frigate.models.tflite.yamnet_classification_v1.model} $out/share/frigate/cpu_audio_model.tflite
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
@@ -283,9 +239,9 @@ python3Packages.buildPythonApplication rec {
|
||||
];
|
||||
|
||||
preCheck = ''
|
||||
# Unavailable in the build sandbox
|
||||
# FHS paths are unreachable in the build sandbox
|
||||
substituteInPlace frigate/const.py \
|
||||
--replace-fail "/var/lib/frigate" "$TMPDIR/" \
|
||||
--replace-fail "/var/lib/frigate" "$TMPDIR" \
|
||||
--replace-fail "/var/cache/frigate" "$TMPDIR"
|
||||
'';
|
||||
|
||||
@@ -300,16 +256,47 @@ python3Packages.buildPythonApplication rec {
|
||||
];
|
||||
|
||||
passthru = {
|
||||
web = frigate-web;
|
||||
inherit python;
|
||||
pythonPath = (python3Packages.makePythonPath dependencies) + ":${frigate}/${python.sitePackages}";
|
||||
inherit python python3Packages;
|
||||
pythonPath =
|
||||
(python3Packages.makePythonPath finalAttrs.finalPackage.dependencies)
|
||||
+ ":${frigate}/${python.sitePackages}";
|
||||
web = callPackage ./web.nix {
|
||||
inherit (finalAttrs) version src;
|
||||
};
|
||||
models = {
|
||||
# Google Coral Accelerators as Tensorflow Delegate
|
||||
# https://docs.frigate.video/configuration/object_detectors/#edge-tpu-detector
|
||||
edgetpu = lib.recurseIntoAttrs (
|
||||
lib.packagesFromDirectoryRecursive {
|
||||
inherit callPackage;
|
||||
directory = ./models/edgetpu;
|
||||
}
|
||||
);
|
||||
# Intel OpenVINO for CPU/GPU/NPU
|
||||
# https://docs.frigate.video/configuration/object_detectors/#openvino-detector
|
||||
openvino = lib.recurseIntoAttrs (
|
||||
lib.packagesFromDirectoryRecursive {
|
||||
inherit callPackage;
|
||||
directory = ./models/openvino;
|
||||
}
|
||||
);
|
||||
# Tensorflow Lite CPU models
|
||||
# https://docs.frigate.video/configuration/object_detectors/#cpu-detector-not-recommended
|
||||
# https://docs.frigate.video/configuration/audio_detectors/
|
||||
tflite = lib.recurseIntoAttrs (
|
||||
lib.packagesFromDirectoryRecursive {
|
||||
inherit callPackage;
|
||||
directory = ./models/tflite;
|
||||
}
|
||||
);
|
||||
};
|
||||
tests = {
|
||||
inherit (nixosTests) frigate;
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
changelog = "https://github.com/blakeblackshear/frigate/releases/tag/${src.tag}";
|
||||
changelog = "https://github.com/blakeblackshear/frigate/releases/tag/${finalAttrs.src.tag}";
|
||||
description = "NVR with realtime local object detection for IP cameras";
|
||||
longDescription = ''
|
||||
A complete and local NVR designed for Home Assistant with AI
|
||||
@@ -320,4 +307,4 @@ python3Packages.buildPythonApplication rec {
|
||||
license = lib.licenses.mit;
|
||||
maintainers = with lib.maintainers; [ hexa ];
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
770
pkgs/by-name/fr/frigate/pr23641.patch
Normal file
770
pkgs/by-name/fr/frigate/pr23641.patch
Normal file
@@ -0,0 +1,770 @@
|
||||
From e4bba6f9c03af6fe20c8bd090bb1bc94cbd9ef1f Mon Sep 17 00:00:00 2001
|
||||
From: Martin Weinelt <hexa@darmstadt.ccc.de>
|
||||
Date: Mon, 6 Jul 2026 13:14:54 +0200
|
||||
Subject: [PATCH 1/5] Migrate to fastapi lifespan for startup events
|
||||
|
||||
The `on_event` style decorator is deprecated
|
||||
|
||||
https://fastapi.tiangolo.com/advanced/events/
|
||||
---
|
||||
frigate/api/fastapi_app.py | 21 ++++++++++++---------
|
||||
1 file changed, 12 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/frigate/api/fastapi_app.py b/frigate/api/fastapi_app.py
|
||||
index 387f0473fc..86058bfdaa 100644
|
||||
--- a/frigate/api/fastapi_app.py
|
||||
+++ b/frigate/api/fastapi_app.py
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
+from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import Depends, FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
@@ -77,9 +78,20 @@ def create_fastapi_app(
|
||||
enforce_default_admin: bool = True,
|
||||
config_holder: ConfigHolder | None = None,
|
||||
):
|
||||
+ @asynccontextmanager
|
||||
+ async def lifespan(app: FastAPI):
|
||||
+ logger.info("FastAPI started")
|
||||
+ asyncio.create_task(
|
||||
+ debug_replay_auto_stop_watchdog(
|
||||
+ replay_manager, frigate_config, config_publisher
|
||||
+ )
|
||||
+ )
|
||||
+ yield
|
||||
+
|
||||
logger.info("Starting FastAPI app")
|
||||
app = FastAPI(
|
||||
debug=False,
|
||||
+ lifespan=lifespan,
|
||||
swagger_ui_parameters={"apisSorter": "alpha", "operationsSorter": "alpha"},
|
||||
dependencies=[Depends(require_admin_by_default())]
|
||||
if enforce_default_admin
|
||||
@@ -115,15 +127,6 @@ async def frigate_middleware(request: Request, call_next):
|
||||
database.close()
|
||||
return response
|
||||
|
||||
- @app.on_event("startup")
|
||||
- async def startup():
|
||||
- logger.info("FastAPI started")
|
||||
- asyncio.create_task(
|
||||
- debug_replay_auto_stop_watchdog(
|
||||
- replay_manager, frigate_config, config_publisher
|
||||
- )
|
||||
- )
|
||||
-
|
||||
# Rate limiter (used for login endpoint)
|
||||
if frigate_config.auth.failed_login_rate_limit is None:
|
||||
limiter.enabled = False
|
||||
|
||||
From ef8fcf2328bd59b6d30c7ca523e39909a8b8c01e Mon Sep 17 00:00:00 2001
|
||||
From: Martin Weinelt <hexa@darmstadt.ccc.de>
|
||||
Date: Mon, 6 Jul 2026 13:16:58 +0200
|
||||
Subject: [PATCH 2/5] Migrate to pydantic TypeAdapter
|
||||
|
||||
https://pydantic.dev/docs/validation/2.12/get-started/migration/#introduction-of-typeadapter
|
||||
---
|
||||
frigate/test/test_object_detector.py | 18 ++++++++++++------
|
||||
1 file changed, 12 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/frigate/test/test_object_detector.py b/frigate/test/test_object_detector.py
|
||||
index dc15b23516..33748c2807 100644
|
||||
--- a/frigate/test/test_object_detector.py
|
||||
+++ b/frigate/test/test_object_detector.py
|
||||
@@ -2,7 +2,7 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import numpy as np
|
||||
-from pydantic import parse_obj_as
|
||||
+from pydantic import TypeAdapter
|
||||
|
||||
import frigate.detectors as detectors
|
||||
import frigate.object_detection.base
|
||||
@@ -19,8 +19,8 @@ def test_localdetectorprocess_should_only_create_specified_detector_type(self):
|
||||
"frigate.detectors.api_types",
|
||||
{det_type: Mock() for det_type in DetectorTypeEnum},
|
||||
):
|
||||
- test_cfg = parse_obj_as(
|
||||
- DetectorConfig, ({"type": det_type, "model": {}})
|
||||
+ test_cfg = TypeAdapter(DetectorConfig).validate_python(
|
||||
+ {"type": det_type, "model": {}}
|
||||
)
|
||||
test_cfg.model.path = "/test/modelpath"
|
||||
test_obj = frigate.object_detection.base.LocalObjectDetector(
|
||||
@@ -44,7 +44,9 @@ def test_detect_raw_given_tensor_input_should_return_api_detect_raw_result(self)
|
||||
TEST_DATA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
TEST_DETECT_RESULT = np.ndarray([1, 2, 4, 8, 16, 32])
|
||||
test_obj_detect = frigate.object_detection.base.LocalObjectDetector(
|
||||
- detector_config=parse_obj_as(DetectorConfig, {"type": "cpu", "model": {}})
|
||||
+ detector_config=TypeAdapter(DetectorConfig).validate_python(
|
||||
+ {"type": "cpu", "model": {}}
|
||||
+ )
|
||||
)
|
||||
|
||||
mock_det_api = mock_cputfl.return_value
|
||||
@@ -67,7 +69,9 @@ def test_detect_raw_given_tensor_input_should_call_api_detect_raw_with_transpose
|
||||
TEST_DATA = np.zeros((1, 32, 32, 3), np.uint8)
|
||||
TEST_DETECT_RESULT = np.ndarray([1, 2, 4, 8, 16, 32])
|
||||
|
||||
- test_cfg = parse_obj_as(DetectorConfig, {"type": "cpu", "model": {}})
|
||||
+ test_cfg = TypeAdapter(DetectorConfig).validate_python(
|
||||
+ {"type": "cpu", "model": {}}
|
||||
+ )
|
||||
test_cfg.model.input_tensor = InputTensorEnum.nchw
|
||||
|
||||
test_obj_detect = frigate.object_detection.base.LocalObjectDetector(
|
||||
@@ -116,7 +120,9 @@ def test_detect_given_tensor_input_should_return_lfiltered_detections(
|
||||
"label-5",
|
||||
]
|
||||
|
||||
- test_cfg = parse_obj_as(DetectorConfig, {"type": "cpu", "model": {}})
|
||||
+ test_cfg = TypeAdapter(DetectorConfig).validate_python(
|
||||
+ {"type": "cpu", "model": {}}
|
||||
+ )
|
||||
test_cfg.model = ModelConfig()
|
||||
test_obj_detect = frigate.object_detection.base.LocalObjectDetector(
|
||||
detector_config=test_cfg,
|
||||
|
||||
From e9f1435eeb337bf7e269af1b09a0b1b87de719d5 Mon Sep 17 00:00:00 2001
|
||||
From: Martin Weinelt <hexa@darmstadt.ccc.de>
|
||||
Date: Mon, 6 Jul 2026 13:17:16 +0200
|
||||
Subject: [PATCH 3/5] Replace pytz with native tz-aware objects and zoneinfo
|
||||
library
|
||||
|
||||
Supported from Python 3.9, see https://peps.python.org/pep-0615/. Also
|
||||
https://blog.ganssle.io/articles/2019/11/utcnow.html.
|
||||
---
|
||||
docker/main/requirements-wheels.txt | 1 -
|
||||
docs/static/frigate-api.yaml | 14 +++----
|
||||
.../api/defs/query/app_query_parameters.py | 2 +-
|
||||
.../api/defs/query/events_query_parameters.py | 6 +--
|
||||
.../defs/query/recordings_query_parameters.py | 2 +-
|
||||
.../api/defs/query/review_query_parameters.py | 2 +-
|
||||
frigate/api/media.py | 4 +-
|
||||
frigate/api/preview.py | 4 +-
|
||||
frigate/api/record.py | 2 +-
|
||||
frigate/record/export.py | 10 ++---
|
||||
frigate/test/http_api/test_http_media.py | 39 +++++++++----------
|
||||
frigate/test/http_api/test_http_review.py | 2 +-
|
||||
frigate/util/time.py | 17 ++++----
|
||||
13 files changed, 50 insertions(+), 55 deletions(-)
|
||||
|
||||
diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt
|
||||
index 5ed9664fc2..164ab3b853 100644
|
||||
--- a/docker/main/requirements-wheels.txt
|
||||
+++ b/docker/main/requirements-wheels.txt
|
||||
@@ -25,7 +25,6 @@ peewee_migrate == 1.14.*
|
||||
psutil == 7.1.*
|
||||
pydantic == 2.10.*
|
||||
git+https://github.com/fbcotter/py3nvml#egg=py3nvml
|
||||
-pytz == 2025.*
|
||||
pyzmq == 26.2.*
|
||||
ruamel.yaml == 0.18.*
|
||||
tzlocal == 5.2
|
||||
diff --git a/docs/static/frigate-api.yaml b/docs/static/frigate-api.yaml
|
||||
index fe467e0c62..3ce79e4ae6 100644
|
||||
--- a/docs/static/frigate-api.yaml
|
||||
+++ b/docs/static/frigate-api.yaml
|
||||
@@ -2087,7 +2087,7 @@ paths:
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- default: utc
|
||||
+ default: UTC
|
||||
title: Timezone
|
||||
responses:
|
||||
'200':
|
||||
@@ -3129,7 +3129,7 @@ paths:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: 'null'
|
||||
- default: utc
|
||||
+ default: UTC
|
||||
title: Timezone
|
||||
responses:
|
||||
'200':
|
||||
@@ -4227,7 +4227,7 @@ paths:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: 'null'
|
||||
- default: utc
|
||||
+ default: UTC
|
||||
title: Timezone
|
||||
responses:
|
||||
'200':
|
||||
@@ -4479,7 +4479,7 @@ paths:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: 'null'
|
||||
- default: utc
|
||||
+ default: UTC
|
||||
title: Timezone
|
||||
- name: min_score
|
||||
in: query
|
||||
@@ -4559,7 +4559,7 @@ paths:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: 'null'
|
||||
- default: utc
|
||||
+ default: UTC
|
||||
title: Timezone
|
||||
- name: has_clip
|
||||
in: query
|
||||
@@ -6854,7 +6854,7 @@ paths:
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- default: utc
|
||||
+ default: UTC
|
||||
title: Timezone
|
||||
- name: cameras
|
||||
in: query
|
||||
@@ -6904,7 +6904,7 @@ paths:
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- default: utc
|
||||
+ default: UTC
|
||||
title: Timezone
|
||||
responses:
|
||||
'200':
|
||||
diff --git a/frigate/api/defs/query/app_query_parameters.py b/frigate/api/defs/query/app_query_parameters.py
|
||||
index 626d39679b..26974d59b9 100644
|
||||
--- a/frigate/api/defs/query/app_query_parameters.py
|
||||
+++ b/frigate/api/defs/query/app_query_parameters.py
|
||||
@@ -7,4 +7,4 @@ class AppTimelineHourlyQueryParameters(BaseModel):
|
||||
after: float | None = None
|
||||
before: float | None = None
|
||||
limit: int | None = 200
|
||||
- timezone: str | None = "utc"
|
||||
+ timezone: str | None = "UTC"
|
||||
diff --git a/frigate/api/defs/query/events_query_parameters.py b/frigate/api/defs/query/events_query_parameters.py
|
||||
index 06d0dfc3af..30d0fcac50 100644
|
||||
--- a/frigate/api/defs/query/events_query_parameters.py
|
||||
+++ b/frigate/api/defs/query/events_query_parameters.py
|
||||
@@ -39,7 +39,7 @@ class EventsQueryParams(BaseModel):
|
||||
max_length: float | None = None
|
||||
event_id: str | None = None
|
||||
sort: str | None = None
|
||||
- timezone: str | None = "utc"
|
||||
+ timezone: str | None = "UTC"
|
||||
|
||||
|
||||
class EventsSearchQueryParams(BaseModel):
|
||||
@@ -66,7 +66,7 @@ class EventsSearchQueryParams(BaseModel):
|
||||
has_clip: bool | None = None
|
||||
has_snapshot: bool | None = None
|
||||
is_submitted: bool | None = None
|
||||
- timezone: str | None = "utc"
|
||||
+ timezone: str | None = "UTC"
|
||||
min_score: float | None = None
|
||||
max_score: float | None = None
|
||||
min_speed: float | None = None
|
||||
@@ -76,6 +76,6 @@ class EventsSearchQueryParams(BaseModel):
|
||||
|
||||
|
||||
class EventsSummaryQueryParams(BaseModel):
|
||||
- timezone: str | None = "utc"
|
||||
+ timezone: str | None = "UTC"
|
||||
has_clip: int | None = None
|
||||
has_snapshot: int | None = None
|
||||
diff --git a/frigate/api/defs/query/recordings_query_parameters.py b/frigate/api/defs/query/recordings_query_parameters.py
|
||||
index 770da96bb8..cd7b4221c2 100644
|
||||
--- a/frigate/api/defs/query/recordings_query_parameters.py
|
||||
+++ b/frigate/api/defs/query/recordings_query_parameters.py
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
class MediaRecordingsSummaryQueryParams(BaseModel):
|
||||
- timezone: str = "utc"
|
||||
+ timezone: str = "UTC"
|
||||
cameras: str | None = "all"
|
||||
|
||||
|
||||
diff --git a/frigate/api/defs/query/review_query_parameters.py b/frigate/api/defs/query/review_query_parameters.py
|
||||
index b16146a9bc..16f759f42a 100644
|
||||
--- a/frigate/api/defs/query/review_query_parameters.py
|
||||
+++ b/frigate/api/defs/query/review_query_parameters.py
|
||||
@@ -19,7 +19,7 @@ class ReviewSummaryQueryParams(BaseModel):
|
||||
cameras: str = "all"
|
||||
labels: str = "all"
|
||||
zones: str = "all"
|
||||
- timezone: str = "utc"
|
||||
+ timezone: str = "UTC"
|
||||
|
||||
|
||||
class ReviewActivityMotionQueryParams(BaseModel):
|
||||
diff --git a/frigate/api/media.py b/frigate/api/media.py
|
||||
index c6fa90c068..a5f5e1bab8 100644
|
||||
--- a/frigate/api/media.py
|
||||
+++ b/frigate/api/media.py
|
||||
@@ -11,10 +11,10 @@
|
||||
from pathlib import Path as FilePath
|
||||
from typing import Any
|
||||
from urllib.parse import unquote
|
||||
+from zoneinfo import ZoneInfo
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
-import pytz
|
||||
from fastapi import APIRouter, Depends, Path, Query, Request, Response
|
||||
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
||||
from pathvalidate import sanitize_filename
|
||||
@@ -714,7 +714,7 @@ async def vod_hour(
|
||||
parts = year_month.split("-")
|
||||
start_date = (
|
||||
datetime(int(parts[0]), int(parts[1]), day, hour, tzinfo=UTC)
|
||||
- - datetime.now(pytz.timezone(tz_name.replace(",", "/"))).utcoffset()
|
||||
+ - datetime.now(ZoneInfo(tz_name.replace(",", "/"))).utcoffset()
|
||||
)
|
||||
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
|
||||
start_ts = start_date.timestamp()
|
||||
diff --git a/frigate/api/preview.py b/frigate/api/preview.py
|
||||
index 1047591434..af84117fd6 100644
|
||||
--- a/frigate/api/preview.py
|
||||
+++ b/frigate/api/preview.py
|
||||
@@ -5,8 +5,8 @@
|
||||
import os
|
||||
import threading
|
||||
from datetime import UTC, datetime, timedelta
|
||||
+from zoneinfo import ZoneInfo
|
||||
|
||||
-import pytz
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
@@ -126,7 +126,7 @@ def preview_hour(
|
||||
parts = year_month.split("-")
|
||||
start_date = (
|
||||
datetime(int(parts[0]), int(parts[1]), int(day), int(hour), tzinfo=UTC)
|
||||
- - datetime.now(pytz.timezone(tz_name.replace(",", "/"))).utcoffset()
|
||||
+ - datetime.now(ZoneInfo(tz_name.replace(",", "/"))).utcoffset()
|
||||
)
|
||||
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
|
||||
start_ts = start_date.timestamp()
|
||||
diff --git a/frigate/api/record.py b/frigate/api/record.py
|
||||
index 5db257f482..3352ec039b 100644
|
||||
--- a/frigate/api/record.py
|
||||
+++ b/frigate/api/record.py
|
||||
@@ -120,7 +120,7 @@ def all_recordings_summary(
|
||||
@router.get(
|
||||
"/{camera_name}/recordings/summary", dependencies=[Depends(require_camera_access)]
|
||||
)
|
||||
-async def recordings_summary(camera_name: str, timezone: str = "utc"):
|
||||
+async def recordings_summary(camera_name: str, timezone: str = "UTC"):
|
||||
"""Returns hourly summary for recordings of given camera"""
|
||||
|
||||
time_range_query = (
|
||||
diff --git a/frigate/record/export.py b/frigate/record/export.py
|
||||
index 5d307077c9..0564dee04b 100644
|
||||
--- a/frigate/record/export.py
|
||||
+++ b/frigate/record/export.py
|
||||
@@ -12,8 +12,8 @@
|
||||
from collections.abc import Callable
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
+from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
-import pytz # type: ignore[import-untyped]
|
||||
from peewee import DoesNotExist
|
||||
|
||||
from frigate.config import FfmpegConfig, FrigateConfig
|
||||
@@ -371,8 +371,8 @@ def get_datetime_from_timestamp(self, timestamp: int) -> str:
|
||||
tz_name = self.config.ui.timezone
|
||||
if tz_name:
|
||||
try:
|
||||
- tz = pytz.timezone(tz_name)
|
||||
- except pytz.UnknownTimeZoneError:
|
||||
+ tz = ZoneInfo(tz_name)
|
||||
+ except (ValueError, ZoneInfoNotFoundError):
|
||||
tz = None
|
||||
if tz is not None:
|
||||
return datetime.datetime.fromtimestamp(timestamp, tz=tz).strftime(
|
||||
@@ -533,8 +533,8 @@ def _build_recording_segment_chapter_metadata_file(
|
||||
tz: datetime.tzinfo | None = None
|
||||
if tz_name:
|
||||
try:
|
||||
- tz = pytz.timezone(tz_name)
|
||||
- except pytz.UnknownTimeZoneError:
|
||||
+ tz = ZoneInfo(tz_name)
|
||||
+ except (ValueError, ZoneInfoNotFoundError):
|
||||
tz = None
|
||||
if tz is None:
|
||||
tz = datetime.UTC
|
||||
diff --git a/frigate/test/http_api/test_http_media.py b/frigate/test/http_api/test_http_media.py
|
||||
index b2d83cbc8a..8a3835c324 100644
|
||||
--- a/frigate/test/http_api/test_http_media.py
|
||||
+++ b/frigate/test/http_api/test_http_media.py
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Unit tests for recordings/media API endpoints."""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
+from zoneinfo import ZoneInfo
|
||||
|
||||
-import pytz
|
||||
from fastapi import Request
|
||||
|
||||
from frigate.api.auth import get_allowed_cameras_for_filter, get_current_user
|
||||
@@ -51,16 +51,16 @@ def test_recordings_summary_across_dst_spring_forward(self):
|
||||
In 2024, DST in America/New_York transitions on March 10, 2024 at 2:00 AM
|
||||
Clocks spring forward from 2:00 AM to 3:00 AM (EST to EDT)
|
||||
"""
|
||||
- tz = pytz.timezone("America/New_York")
|
||||
+ tz = ZoneInfo("America/New_York")
|
||||
|
||||
# March 9, 2024 at 12:00 PM EST (before DST)
|
||||
- march_9_noon = tz.localize(datetime(2024, 3, 9, 12, 0, 0)).timestamp()
|
||||
+ march_9_noon = datetime(2024, 3, 9, 12, 0, 0, tzinfo=tz).timestamp()
|
||||
|
||||
# March 10, 2024 at 12:00 PM EDT (after DST transition)
|
||||
- march_10_noon = tz.localize(datetime(2024, 3, 10, 12, 0, 0)).timestamp()
|
||||
+ march_10_noon = datetime(2024, 3, 10, 12, 0, 0, tzinfo=tz).timestamp()
|
||||
|
||||
# March 11, 2024 at 12:00 PM EDT (after DST)
|
||||
- march_11_noon = tz.localize(datetime(2024, 3, 11, 12, 0, 0)).timestamp()
|
||||
+ march_11_noon = datetime(2024, 3, 11, 12, 0, 0, tzinfo=tz).timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
# Insert recordings for each day
|
||||
@@ -124,19 +124,16 @@ def test_recordings_summary_across_dst_fall_back(self):
|
||||
In 2024, DST in America/New_York transitions on November 3, 2024 at 2:00 AM
|
||||
Clocks fall back from 2:00 AM to 1:00 AM (EDT to EST)
|
||||
"""
|
||||
- tz = pytz.timezone("America/New_York")
|
||||
+ tz = ZoneInfo("America/New_York")
|
||||
|
||||
# November 2, 2024 at 12:00 PM EDT (before DST transition)
|
||||
- nov_2_noon = tz.localize(datetime(2024, 11, 2, 12, 0, 0)).timestamp()
|
||||
+ nov_2_noon = datetime(2024, 11, 2, 12, 0, 0, tzinfo=tz).timestamp()
|
||||
|
||||
# November 3, 2024 at 12:00 PM EST (after DST transition)
|
||||
- # Need to specify is_dst=False to get the time after fall back
|
||||
- nov_3_noon = tz.localize(
|
||||
- datetime(2024, 11, 3, 12, 0, 0), is_dst=False
|
||||
- ).timestamp()
|
||||
+ nov_3_noon = datetime(2024, 11, 3, 12, 0, 0, tzinfo=tz).timestamp()
|
||||
|
||||
# November 4, 2024 at 12:00 PM EST (after DST)
|
||||
- nov_4_noon = tz.localize(datetime(2024, 11, 4, 12, 0, 0)).timestamp()
|
||||
+ nov_4_noon = datetime(2024, 11, 4, 12, 0, 0, tzinfo=tz).timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
# Insert recordings for each day
|
||||
@@ -197,13 +194,13 @@ def test_recordings_summary_multiple_cameras_across_dst(self):
|
||||
"""
|
||||
Test recordings summary with multiple cameras across DST boundary.
|
||||
"""
|
||||
- tz = pytz.timezone("America/New_York")
|
||||
+ tz = ZoneInfo("America/New_York")
|
||||
|
||||
# March 9, 2024 at 10:00 AM EST (before DST)
|
||||
- march_9_morning = tz.localize(datetime(2024, 3, 9, 10, 0, 0)).timestamp()
|
||||
+ march_9_morning = datetime(2024, 3, 9, 10, 0, 0, tzinfo=tz).timestamp()
|
||||
|
||||
# March 10, 2024 at 3:00 PM EDT (after DST transition)
|
||||
- march_10_afternoon = tz.localize(datetime(2024, 3, 10, 15, 0, 0)).timestamp()
|
||||
+ march_10_afternoon = datetime(2024, 3, 10, 15, 0, 0, tzinfo=tz).timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
# Override allowed cameras for this test to include both
|
||||
@@ -266,15 +263,15 @@ def test_recordings_summary_at_dst_transition_time(self):
|
||||
"""
|
||||
Test recordings that span the exact DST transition time.
|
||||
"""
|
||||
- tz = pytz.timezone("America/New_York")
|
||||
+ tz = ZoneInfo("America/New_York")
|
||||
|
||||
# March 10, 2024 at 1:00 AM EST (1 hour before DST transition)
|
||||
# At 2:00 AM, clocks jump to 3:00 AM
|
||||
- before_transition = tz.localize(datetime(2024, 3, 10, 1, 0, 0)).timestamp()
|
||||
+ before_transition = datetime(2024, 3, 10, 1, 0, 0, tzinfo=tz).timestamp()
|
||||
|
||||
# Recording that spans the transition (1:00 AM to 3:30 AM EDT)
|
||||
# This is 1.5 hours of actual time but spans the "missing" hour
|
||||
- after_transition = tz.localize(datetime(2024, 3, 10, 3, 30, 0)).timestamp()
|
||||
+ after_transition = datetime(2024, 3, 10, 3, 30, 0, tzinfo=tz).timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
Recordings.insert(
|
||||
@@ -334,7 +331,7 @@ def test_recordings_summary_utc_timezone(self):
|
||||
|
||||
# Test with UTC timezone
|
||||
response = client.get(
|
||||
- "/recordings/summary", params={"timezone": "utc", "cameras": "all"}
|
||||
+ "/recordings/summary", params={"timezone": "UTC", "cameras": "all"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -365,8 +362,8 @@ def test_recordings_summary_single_camera_filter(self):
|
||||
"""
|
||||
Test recordings summary filtered to a single camera.
|
||||
"""
|
||||
- tz = pytz.timezone("America/New_York")
|
||||
- march_10_noon = tz.localize(datetime(2024, 3, 10, 12, 0, 0)).timestamp()
|
||||
+ tz = ZoneInfo("America/New_York")
|
||||
+ march_10_noon = datetime(2024, 3, 10, 12, 0, 0, tzinfo=tz).timestamp()
|
||||
|
||||
with AuthTestClient(self.app) as client:
|
||||
# Insert recordings for both cameras
|
||||
diff --git a/frigate/test/http_api/test_http_review.py b/frigate/test/http_api/test_http_review.py
|
||||
index d13a7bd273..62d4f1608d 100644
|
||||
--- a/frigate/test/http_api/test_http_review.py
|
||||
+++ b/frigate/test/http_api/test_http_review.py
|
||||
@@ -250,7 +250,7 @@ def test_get_review_summary_all_filters(self):
|
||||
"cameras": "front_door",
|
||||
"labels": "all",
|
||||
"zones": "all",
|
||||
- "timezone": "utc",
|
||||
+ "timezone": "UTC",
|
||||
}
|
||||
response = client.get("/review/summary", params=params)
|
||||
assert response.status_code == 200
|
||||
diff --git a/frigate/util/time.py b/frigate/util/time.py
|
||||
index 861bec17f6..777ae9cc11 100644
|
||||
--- a/frigate/util/time.py
|
||||
+++ b/frigate/util/time.py
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
-from zoneinfo import ZoneInfoNotFoundError
|
||||
+from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
-import pytz
|
||||
from tzlocal import get_localzone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -12,7 +11,7 @@
|
||||
|
||||
def get_tz_modifiers(tz_name: str) -> tuple[str, str, float]:
|
||||
seconds_offset = (
|
||||
- datetime.datetime.now(pytz.timezone(tz_name)).utcoffset().total_seconds()
|
||||
+ datetime.datetime.now(ZoneInfo(tz_name)).utcoffset().total_seconds()
|
||||
)
|
||||
hours_offset = int(seconds_offset / 60 / 60)
|
||||
minutes_offset = int(seconds_offset / 60 - hours_offset * 60)
|
||||
@@ -25,7 +24,7 @@ def get_tomorrow_at_time(hour: int) -> datetime.datetime:
|
||||
"""Returns the datetime of the following day at 2am."""
|
||||
try:
|
||||
tomorrow = datetime.datetime.now(get_localzone()) + datetime.timedelta(days=1)
|
||||
- except ZoneInfoNotFoundError:
|
||||
+ except (ValueError, ZoneInfoNotFoundError):
|
||||
tomorrow = datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=1)
|
||||
logger.warning(
|
||||
"Using utc for maintenance due to missing or incorrect timezone set"
|
||||
@@ -45,7 +44,7 @@ def is_current_hour(timestamp: int) -> bool:
|
||||
|
||||
def get_dst_transitions(
|
||||
tz_name: str, start_time: float, end_time: float
|
||||
-) -> list[tuple[float, float]]:
|
||||
+) -> list[tuple[float, float, int]]:
|
||||
"""
|
||||
Find DST transition points and return time periods with consistent offsets.
|
||||
|
||||
@@ -59,8 +58,8 @@ def get_dst_transitions(
|
||||
continuous periods with the same UTC offset
|
||||
"""
|
||||
try:
|
||||
- tz = pytz.timezone(tz_name)
|
||||
- except pytz.UnknownTimeZoneError:
|
||||
+ tz = ZoneInfo(tz_name)
|
||||
+ except (ValueError, ZoneInfoNotFoundError):
|
||||
# If timezone is invalid, return single period with no offset
|
||||
return [(start_time, end_time, 0)]
|
||||
|
||||
@@ -68,14 +67,14 @@ def get_dst_transitions(
|
||||
current = start_time
|
||||
|
||||
# Get initial offset
|
||||
- dt = datetime.datetime.utcfromtimestamp(current).replace(tzinfo=pytz.UTC)
|
||||
+ dt = datetime.datetime.fromtimestamp(current, tz=datetime.UTC)
|
||||
local_dt = dt.astimezone(tz)
|
||||
prev_offset = local_dt.utcoffset().total_seconds()
|
||||
period_start = start_time
|
||||
|
||||
# Check each day for offset changes
|
||||
while current <= end_time:
|
||||
- dt = datetime.datetime.utcfromtimestamp(current).replace(tzinfo=pytz.UTC)
|
||||
+ dt = datetime.datetime.fromtimestamp(current, tz=datetime.UTC)
|
||||
local_dt = dt.astimezone(tz)
|
||||
current_offset = local_dt.utcoffset().total_seconds()
|
||||
|
||||
|
||||
From af86312f3dd57c068c3f6b3ae68d8fa6f77c6e30 Mon Sep 17 00:00:00 2001
|
||||
From: Martin Weinelt <hexa@darmstadt.ccc.de>
|
||||
Date: Mon, 6 Jul 2026 16:21:01 +0200
|
||||
Subject: [PATCH 4/5] Provide lookup for normalized timezone names
|
||||
|
||||
This provides a helper to look up the correct timezone name independent
|
||||
of the casing of the given timezone name.
|
||||
---
|
||||
frigate/api/media.py | 5 ++++-
|
||||
frigate/api/preview.py | 5 ++++-
|
||||
frigate/record/export.py | 6 +++---
|
||||
frigate/util/time.py | 22 +++++++++++++++++++---
|
||||
4 files changed, 30 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/frigate/api/media.py b/frigate/api/media.py
|
||||
index a5f5e1bab8..14ae263053 100644
|
||||
--- a/frigate/api/media.py
|
||||
+++ b/frigate/api/media.py
|
||||
@@ -54,6 +54,7 @@
|
||||
from frigate.util.image import get_image_from_recording, get_image_quality_params
|
||||
from frigate.util.media import get_keyframe_before
|
||||
from frigate.util.object import create_empty_regions_grid
|
||||
+from frigate.util.time import get_normalized_tz_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -714,7 +715,9 @@ async def vod_hour(
|
||||
parts = year_month.split("-")
|
||||
start_date = (
|
||||
datetime(int(parts[0]), int(parts[1]), day, hour, tzinfo=UTC)
|
||||
- - datetime.now(ZoneInfo(tz_name.replace(",", "/"))).utcoffset()
|
||||
+ - datetime.now(
|
||||
+ ZoneInfo(get_normalized_tz_name(tz_name.replace(",", "/")))
|
||||
+ ).utcoffset()
|
||||
)
|
||||
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
|
||||
start_ts = start_date.timestamp()
|
||||
diff --git a/frigate/api/preview.py b/frigate/api/preview.py
|
||||
index af84117fd6..81de23d003 100644
|
||||
--- a/frigate/api/preview.py
|
||||
+++ b/frigate/api/preview.py
|
||||
@@ -22,6 +22,7 @@
|
||||
from frigate.api.defs.tags import Tags
|
||||
from frigate.const import BASE_DIR, CACHE_DIR, PREVIEW_FRAME_TYPE
|
||||
from frigate.models import Previews
|
||||
+from frigate.util.time import get_normalized_tz_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -126,7 +127,9 @@ def preview_hour(
|
||||
parts = year_month.split("-")
|
||||
start_date = (
|
||||
datetime(int(parts[0]), int(parts[1]), int(day), int(hour), tzinfo=UTC)
|
||||
- - datetime.now(ZoneInfo(tz_name.replace(",", "/"))).utcoffset()
|
||||
+ - datetime.now(
|
||||
+ ZoneInfo(get_normalized_tz_name(tz_name.replace(",", "/")))
|
||||
+ ).utcoffset()
|
||||
)
|
||||
end_date = start_date + timedelta(hours=1) - timedelta(milliseconds=1)
|
||||
start_ts = start_date.timestamp()
|
||||
diff --git a/frigate/record/export.py b/frigate/record/export.py
|
||||
index 0564dee04b..ff33063bc3 100644
|
||||
--- a/frigate/record/export.py
|
||||
+++ b/frigate/record/export.py
|
||||
@@ -31,7 +31,7 @@
|
||||
)
|
||||
from frigate.models import Export, Previews, Recordings, ReviewSegment
|
||||
from frigate.util.ffmpeg import run_ffmpeg_with_progress
|
||||
-from frigate.util.time import is_current_hour
|
||||
+from frigate.util.time import get_normalized_tz_name, is_current_hour
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -371,7 +371,7 @@ def get_datetime_from_timestamp(self, timestamp: int) -> str:
|
||||
tz_name = self.config.ui.timezone
|
||||
if tz_name:
|
||||
try:
|
||||
- tz = ZoneInfo(tz_name)
|
||||
+ tz = ZoneInfo(get_normalized_tz_name(tz_name))
|
||||
except (ValueError, ZoneInfoNotFoundError):
|
||||
tz = None
|
||||
if tz is not None:
|
||||
@@ -533,7 +533,7 @@ def _build_recording_segment_chapter_metadata_file(
|
||||
tz: datetime.tzinfo | None = None
|
||||
if tz_name:
|
||||
try:
|
||||
- tz = ZoneInfo(tz_name)
|
||||
+ tz = ZoneInfo(get_normalized_tz_name(tz_name))
|
||||
except (ValueError, ZoneInfoNotFoundError):
|
||||
tz = None
|
||||
if tz is None:
|
||||
diff --git a/frigate/util/time.py b/frigate/util/time.py
|
||||
index 777ae9cc11..a4395c5d5d 100644
|
||||
--- a/frigate/util/time.py
|
||||
+++ b/frigate/util/time.py
|
||||
@@ -2,16 +2,32 @@
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
-from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
+from typing import Final
|
||||
+from zoneinfo import ZoneInfo, ZoneInfoNotFoundError, available_timezones
|
||||
|
||||
from tzlocal import get_localzone
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
+TIMEZONE_CASEFOLD_INDEX: Final = {
|
||||
+ timezone.casefold(): timezone for timezone in available_timezones()
|
||||
+}
|
||||
+
|
||||
+
|
||||
+def get_normalized_tz_name(tz_name: str) -> str:
|
||||
+ """Return the canonical name for a case-insensitive IANA timezone."""
|
||||
+ tz = TIMEZONE_CASEFOLD_INDEX.get(tz_name.casefold())
|
||||
+ if tz:
|
||||
+ return tz
|
||||
+
|
||||
+ raise ValueError(f"Unknown timezone: {tz_name}")
|
||||
+
|
||||
|
||||
def get_tz_modifiers(tz_name: str) -> tuple[str, str, float]:
|
||||
seconds_offset = (
|
||||
- datetime.datetime.now(ZoneInfo(tz_name)).utcoffset().total_seconds()
|
||||
+ datetime.datetime.now(ZoneInfo(get_normalized_tz_name(tz_name)))
|
||||
+ .utcoffset()
|
||||
+ .total_seconds()
|
||||
)
|
||||
hours_offset = int(seconds_offset / 60 / 60)
|
||||
minutes_offset = int(seconds_offset / 60 - hours_offset * 60)
|
||||
@@ -58,7 +74,7 @@ def get_dst_transitions(
|
||||
continuous periods with the same UTC offset
|
||||
"""
|
||||
try:
|
||||
- tz = ZoneInfo(tz_name)
|
||||
+ tz = ZoneInfo(get_normalized_tz_name(tz_name))
|
||||
except (ValueError, ZoneInfoNotFoundError):
|
||||
# If timezone is invalid, return single period with no offset
|
||||
return [(start_time, end_time, 0)]
|
||||
|
||||
From 490f1b93f8cc2aa450e74e882aa31516d6c1078c Mon Sep 17 00:00:00 2001
|
||||
From: Martin Weinelt <hexa@darmstadt.ccc.de>
|
||||
Date: Mon, 6 Jul 2026 18:12:37 +0200
|
||||
Subject: [PATCH 5/5] Test timezone naming normalization and error handling
|
||||
|
||||
---
|
||||
frigate/test/test_tz.py | 19 +++++++++++++++++++
|
||||
1 file changed, 19 insertions(+)
|
||||
create mode 100644 frigate/test/test_tz.py
|
||||
|
||||
diff --git a/frigate/test/test_tz.py b/frigate/test/test_tz.py
|
||||
new file mode 100644
|
||||
index 0000000000..5be8010dda
|
||||
--- /dev/null
|
||||
+++ b/frigate/test/test_tz.py
|
||||
@@ -0,0 +1,19 @@
|
||||
+import unittest
|
||||
+
|
||||
+from frigate.util.time import get_normalized_tz_name
|
||||
+
|
||||
+
|
||||
+class TestGetNormalizedTzName(unittest.TestCase):
|
||||
+ def test_valid(self):
|
||||
+ cases = [
|
||||
+ ("utc", "UTC"),
|
||||
+ ("america/new_york", "America/New_York"),
|
||||
+ ]
|
||||
+
|
||||
+ for tz_name, expected in cases:
|
||||
+ with self.subTest(tz_name=tz_name):
|
||||
+ self.assertEqual(get_normalized_tz_name(tz_name), expected)
|
||||
+
|
||||
+ def test_invalid(self):
|
||||
+ with self.assertRaisesRegex(ValueError, r"Unknown timezone: foo/bar"):
|
||||
+ get_normalized_tz_name("foo/bar")
|
||||
@@ -1,22 +0,0 @@
|
||||
diff --git a/frigate/util/services.py b/frigate/util/services.py
|
||||
index 64d83833d..912ce67bd 100644
|
||||
--- a/frigate/util/services.py
|
||||
+++ b/frigate/util/services.py
|
||||
@@ -121,7 +121,7 @@ def get_cpu_stats() -> dict[str, dict]:
|
||||
pid = str(process.info["pid"])
|
||||
try:
|
||||
cpu_percent = process.info["cpu_percent"]
|
||||
- cmdline = process.info["cmdline"]
|
||||
+ cmdline = " ".join(process.info["cmdline"]).rstrip()
|
||||
|
||||
with open(f"/proc/{pid}/stat", "r") as f:
|
||||
stats = f.readline().split()
|
||||
@@ -155,7 +155,7 @@ def get_cpu_stats() -> dict[str, dict]:
|
||||
"cpu": str(cpu_percent),
|
||||
"cpu_average": str(round(cpu_average_usage, 2)),
|
||||
"mem": f"{mem_pct}",
|
||||
- "cmdline": clean_camera_user_pass(" ".join(cmdline)),
|
||||
+ "cmdline": clean_camera_user_pass(cmdline),
|
||||
}
|
||||
except Exception:
|
||||
continue
|
||||
@@ -31,7 +31,7 @@ buildNpmPackage {
|
||||
--replace-fail "/tmp/cache" "/var/cache/frigate"
|
||||
'';
|
||||
|
||||
npmDepsHash = "sha256-iIqP3pspkDbaXZkZ5MIT/GVGiKBJCkFXQ7Av5h1rWKk=";
|
||||
npmDepsHash = "sha256-k21UBr4agbJDZD0PYVjylRcyuRCFeBE2YBqZzE8v+jU=";
|
||||
|
||||
installPhase = ''
|
||||
cp -rv dist/ $out
|
||||
|
||||
@@ -94,6 +94,17 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
./cmake-install-paths.patch
|
||||
];
|
||||
|
||||
# Fix arm computelib ar/ranlib toolchain paths for LTO awareness
|
||||
postPatch = ''
|
||||
substituteInPlace src/plugins/intel_cpu/thirdparty/ComputeLibrary/SConstruct \
|
||||
--replace-fail \
|
||||
"env['AR'] = toolchain_prefix + \"ar\"" \
|
||||
"env['AR'] = \"${lib.getExe' stdenv.cc.cc "gcc-ar"}\"" \
|
||||
--replace-fail \
|
||||
"env['RANLIB'] = toolchain_prefix + \"ranlib\"" \
|
||||
"env['RANLIB'] = \"${lib.getExe' stdenv.cc.cc "gcc-ranlib"}\""
|
||||
'';
|
||||
|
||||
dontUseSconsCheck = true;
|
||||
dontUseSconsBuild = true;
|
||||
dontUseSconsInstall = true;
|
||||
@@ -127,7 +138,7 @@ stdenv.mkDerivation (finalAttrs: {
|
||||
(cmakeBool "ENABLE_SAMPLES" false)
|
||||
|
||||
# features
|
||||
(cmakeBool "ENABLE_INTEL_CPU" stdenv.hostPlatform.isx86_64)
|
||||
(cmakeBool "ENABLE_INTEL_CPU" true)
|
||||
(cmakeBool "ENABLE_INTEL_GPU" true)
|
||||
(cmakeBool "ENABLE_INTEL_NPU" stdenv.hostPlatform.isx86_64)
|
||||
(cmakeBool "ENABLE_JS" false)
|
||||
|
||||
@@ -24,7 +24,12 @@ buildPythonPackage {
|
||||
hash = "sha256-KuuVu0tqrmQuNKYmDmdy+TU6BnnhDxh4G8n9BGzjGag=";
|
||||
};
|
||||
|
||||
patches = [ ./numpy-2.4-compat.patch ];
|
||||
patches = [
|
||||
./numpy-2.4-compat.patch
|
||||
|
||||
# https://github.com/rlabbe/filterpy/pull/331
|
||||
./scipy-1.12-deprecation.patch
|
||||
];
|
||||
|
||||
build-system = [ setuptools ];
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
diff --git a/filterpy/discrete_bayes/discrete_bayes.py b/filterpy/discrete_bayes/discrete_bayes.py
|
||||
index 084ba8c..c465835 100644
|
||||
--- a/filterpy/discrete_bayes/discrete_bayes.py
|
||||
+++ b/filterpy/discrete_bayes/discrete_bayes.py
|
||||
@@ -19,8 +19,7 @@ from __future__ import (absolute_import, division, print_function,
|
||||
unicode_literals)
|
||||
|
||||
import numpy as np
|
||||
-from scipy.ndimage.filters import convolve
|
||||
-from scipy.ndimage.interpolation import shift
|
||||
+from scipy.ndimage import convolve, shift
|
||||
|
||||
|
||||
def normalize(pdf):
|
||||
Reference in New Issue
Block a user