From 71deb719ea6925bdcfff5b5d8709b8253d1a2a45 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 13 Jul 2026 14:26:01 +0200 Subject: [PATCH 1/5] frigate: 0.17.2 -> 0.18.0 --- nixos/modules/services/video/frigate.nix | 33 +- pkgs/by-name/fr/frigate/ai-edge-litert.patch | 100 --- pkgs/by-name/fr/frigate/ffmpeg.patch | 68 +- .../frigate/fix-tests-media-auth-paths.patch | 32 + pkgs/by-name/fr/frigate/package.nix | 212 +++-- pkgs/by-name/fr/frigate/pr23641.patch | 770 ++++++++++++++++++ .../fr/frigate/proc-cmdline-strip.patch | 22 - pkgs/by-name/fr/frigate/web.nix | 2 +- 8 files changed, 940 insertions(+), 299 deletions(-) delete mode 100644 pkgs/by-name/fr/frigate/ai-edge-litert.patch create mode 100644 pkgs/by-name/fr/frigate/fix-tests-media-auth-paths.patch create mode 100644 pkgs/by-name/fr/frigate/pr23641.patch delete mode 100644 pkgs/by-name/fr/frigate/proc-cmdline-strip.patch diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix index 43941e64ac0d..c358f0cd63b2 100644 --- a/nixos/modules/services/video/frigate.nix +++ b/nixos/modules/services/video/frigate.nix @@ -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"; diff --git a/pkgs/by-name/fr/frigate/ai-edge-litert.patch b/pkgs/by-name/fr/frigate/ai-edge-litert.patch deleted file mode 100644 index 3a8c3f8a566f..000000000000 --- a/pkgs/by-name/fr/frigate/ai-edge-litert.patch +++ /dev/null @@ -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__) diff --git a/pkgs/by-name/fr/frigate/ffmpeg.patch b/pkgs/by-name/fr/frigate/ffmpeg.patch index 2b9d08cf2d06..d2b4a5eea85a 100644 --- a/pkgs/by-name/fr/frigate/ffmpeg.patch +++ b/pkgs/by-name/fr/frigate/ffmpeg.patch @@ -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. diff --git a/pkgs/by-name/fr/frigate/fix-tests-media-auth-paths.patch b/pkgs/by-name/fr/frigate/fix-tests-media-auth-paths.patch new file mode 100644 index 000000000000..ac0f75afda85 --- /dev/null +++ b/pkgs/by-name/fr/frigate/fix-tests-media-auth-paths.patch @@ -0,0 +1,32 @@ +From 91711507df5ad880bfcba1e11619441f7d0f9a58 Mon Sep 17 00:00:00 2001 +From: Martin Weinelt +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() + diff --git a/pkgs/by-name/fr/frigate/package.nix b/pkgs/by-name/fr/frigate/package.nix index f395c2a74eb1..e60770f91fbc 100644 --- a/pkgs/by-name/fr/frigate/package.nix +++ b/pkgs/by-name/fr/frigate/package.nix @@ -12,36 +12,11 @@ sqlite-vec, frigate, nixosTests, - go2rtc, }: 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: @@ -114,12 +89,18 @@ let 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 +111,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 \ @@ -189,75 +173,77 @@ python3Packages.buildPythonApplication rec { 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 @@ -283,9 +269,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 +286,20 @@ python3Packages.buildPythonApplication rec { ]; passthru = { - web = frigate-web; inherit python; - pythonPath = (python3Packages.makePythonPath dependencies) + ":${frigate}/${python.sitePackages}"; + pythonPath = + (python3Packages.makePythonPath finalAttrs.finalPackage.dependencies) + + ":${frigate}/${python.sitePackages}"; + web = callPackage ./web.nix { + inherit (finalAttrs) version src; + }; 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 +310,4 @@ python3Packages.buildPythonApplication rec { license = lib.licenses.mit; maintainers = with lib.maintainers; [ hexa ]; }; -} +}) diff --git a/pkgs/by-name/fr/frigate/pr23641.patch b/pkgs/by-name/fr/frigate/pr23641.patch new file mode 100644 index 000000000000..281c651fcdde --- /dev/null +++ b/pkgs/by-name/fr/frigate/pr23641.patch @@ -0,0 +1,770 @@ +From e4bba6f9c03af6fe20c8bd090bb1bc94cbd9ef1f Mon Sep 17 00:00:00 2001 +From: Martin Weinelt +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 +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 +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 +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 +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") diff --git a/pkgs/by-name/fr/frigate/proc-cmdline-strip.patch b/pkgs/by-name/fr/frigate/proc-cmdline-strip.patch deleted file mode 100644 index 2dd131ebf419..000000000000 --- a/pkgs/by-name/fr/frigate/proc-cmdline-strip.patch +++ /dev/null @@ -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 diff --git a/pkgs/by-name/fr/frigate/web.nix b/pkgs/by-name/fr/frigate/web.nix index 7e284818b975..7ea2aecce45d 100644 --- a/pkgs/by-name/fr/frigate/web.nix +++ b/pkgs/by-name/fr/frigate/web.nix @@ -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 From aa1b83e8ead366758a13823065e5799d1e26a879 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sat, 18 Jul 2026 23:53:47 +0200 Subject: [PATCH 2/5] frigate: extract models into models attribute tree This creates dedicated attributes per model and exposes a `model` and a optionally a `labelmap` attribute where useful. The idea is to collect model build plans to make them easily accessible and referencable in your configurations. The explicit idea is not to cache on cache.nixos.org, because they are usually readily availalbe as FODs or just require light conversions that everyone should be able to handle on their own machine or build pipeline. This now also packages the converted ssdlite mobilnet v2 model for OpenVINO based setups, though I would recommend looking into YOLO models at this time. --- .../package.nix | 14 ++++ .../ssdlite_mobilenet_v2_coco/package.nix | 64 ++++++++++++++ .../package.nix | 14 ++++ .../yamnet_classification_v1/package.nix | 32 +++++++ pkgs/by-name/fr/frigate/package.nix | 83 +++++++++---------- 5 files changed, 164 insertions(+), 43 deletions(-) create mode 100644 pkgs/by-name/fr/frigate/models/edgetpu/ssdlite_mobiledet_coco_qat_postprocess/package.nix create mode 100644 pkgs/by-name/fr/frigate/models/openvino/ssdlite_mobilenet_v2_coco/package.nix create mode 100644 pkgs/by-name/fr/frigate/models/tflite/ssdlite_mobiledet_coco_qat_postprocess/package.nix create mode 100644 pkgs/by-name/fr/frigate/models/tflite/yamnet_classification_v1/package.nix diff --git a/pkgs/by-name/fr/frigate/models/edgetpu/ssdlite_mobiledet_coco_qat_postprocess/package.nix b/pkgs/by-name/fr/frigate/models/edgetpu/ssdlite_mobiledet_coco_qat_postprocess/package.nix new file mode 100644 index 000000000000..925323b6fbcb --- /dev/null +++ b/pkgs/by-name/fr/frigate/models/edgetpu/ssdlite_mobiledet_coco_qat_postprocess/package.nix @@ -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; +}) diff --git a/pkgs/by-name/fr/frigate/models/openvino/ssdlite_mobilenet_v2_coco/package.nix b/pkgs/by-name/fr/frigate/models/openvino/ssdlite_mobilenet_v2_coco/package.nix new file mode 100644 index 000000000000..c961d1291e8a --- /dev/null +++ b/pkgs/by-name/fr/frigate/models/openvino/ssdlite_mobilenet_v2_coco/package.nix @@ -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/"; + }; +}) diff --git a/pkgs/by-name/fr/frigate/models/tflite/ssdlite_mobiledet_coco_qat_postprocess/package.nix b/pkgs/by-name/fr/frigate/models/tflite/ssdlite_mobiledet_coco_qat_postprocess/package.nix new file mode 100644 index 000000000000..a9399e969a32 --- /dev/null +++ b/pkgs/by-name/fr/frigate/models/tflite/ssdlite_mobiledet_coco_qat_postprocess/package.nix @@ -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; +}) diff --git a/pkgs/by-name/fr/frigate/models/tflite/yamnet_classification_v1/package.nix b/pkgs/by-name/fr/frigate/models/tflite/yamnet_classification_v1/package.nix new file mode 100644 index 000000000000..255d8f4d1b2e --- /dev/null +++ b/pkgs/by-name/fr/frigate/models/tflite/yamnet_classification_v1/package.nix @@ -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; + }; +}) diff --git a/pkgs/by-name/fr/frigate/package.nix b/pkgs/by-name/fr/frigate/package.nix index e60770f91fbc..bdf4fb1931d4 100644 --- a/pkgs/by-name/fr/frigate/package.nix +++ b/pkgs/by-name/fr/frigate/package.nix @@ -1,17 +1,16 @@ { - lib, - stdenv, - replaceVars, addDriverRunpath, callPackage, - python313Packages, fetchFromGitHub, rustPlatform, - fetchurl, ffmpeg-headless, - sqlite-vec, frigate, + lib, nixosTests, + python313Packages, + replaceVars, + sqlite-vec, + stdenv, }: let @@ -61,33 +60,6 @@ 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 (finalAttrs: { pname = "frigate"; @@ -158,16 +130,18 @@ python3Packages.buildPythonApplication (finalAttrs: { 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" ''; @@ -254,11 +228,7 @@ python3Packages.buildPythonApplication (finalAttrs: { 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 ''; @@ -286,13 +256,40 @@ python3Packages.buildPythonApplication (finalAttrs: { ]; passthru = { - inherit python; + 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; }; From b453b6ed6cfea9b0a2f3df740be00ee06a8f362c Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sun, 19 Jul 2026 16:22:08 +0200 Subject: [PATCH 3/5] nixos/frigate: harden runtime execution environment --- nixos/modules/services/video/frigate.nix | 44 ++++++++++++++++++++++++ nixos/tests/frigate.nix | 2 ++ 2 files changed, 46 insertions(+) diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix index c358f0cd63b2..6ee20f7cf582 100644 --- a/nixos/modules/services/video/frigate.nix +++ b/nixos/modules/services/video/frigate.nix @@ -792,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"; }; }; diff --git a/nixos/tests/frigate.nix b/nixos/tests/frigate.nix index 10712387f421..ca6fc5a5c621 100644 --- a/nixos/tests/frigate.nix +++ b/nixos/tests/frigate.nix @@ -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]) ''; } From 182e420eccd48bea2deb5d7ff9cb450b920a2b85 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Mon, 13 Jul 2026 15:01:44 +0200 Subject: [PATCH 4/5] python3Packages.filterpy: fix scipy 1.12 deprecation warnings --- .../python-modules/filterpy/default.nix | 7 ++++++- .../filterpy/scipy-1.12-deprecation.patch | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 pkgs/development/python-modules/filterpy/scipy-1.12-deprecation.patch diff --git a/pkgs/development/python-modules/filterpy/default.nix b/pkgs/development/python-modules/filterpy/default.nix index 110d3b136b0d..c5417046b1f6 100644 --- a/pkgs/development/python-modules/filterpy/default.nix +++ b/pkgs/development/python-modules/filterpy/default.nix @@ -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 ]; diff --git a/pkgs/development/python-modules/filterpy/scipy-1.12-deprecation.patch b/pkgs/development/python-modules/filterpy/scipy-1.12-deprecation.patch new file mode 100644 index 000000000000..ef26dbefafa2 --- /dev/null +++ b/pkgs/development/python-modules/filterpy/scipy-1.12-deprecation.patch @@ -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): From 61d7bf9e9796a5c451dd8c7c53dd2f5c950aacad Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Fri, 18 Sep 2026 12:57:14 +0200 Subject: [PATCH 5/5] openvino: build cpu plugin on aarch64-linux We need to substitute the gcc-ar and gcc-ranlib path as otherwise the build scripts for ARM ComputeLibrary are finding the wrong ar/ranlib executables which breaks at link time. --- pkgs/by-name/op/openvino/package.nix | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pkgs/by-name/op/openvino/package.nix b/pkgs/by-name/op/openvino/package.nix index 8946009c1340..65acf6a9453e 100644 --- a/pkgs/by-name/op/openvino/package.nix +++ b/pkgs/by-name/op/openvino/package.nix @@ -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)