From 26041e66c07c851d25ec94532cac2128e9d01ef6 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Tue, 6 Feb 2024 00:56:36 +0100 Subject: [PATCH] Refactor path handling (#9611) - Use pathlib for path joining sugar using slashes - Rename BASE_DIR to MEDIA_DIR, which is more meaningful - Introduce INSTALL_DIR for the installation base dir - Replace glob.glob usage with no wildcard to file existance check --- frigate/app.py | 2 +- frigate/config.py | 2 +- frigate/const.py | 27 ++++++++++++++++++--------- frigate/detectors/detector_config.py | 3 ++- frigate/detectors/plugins/rknn.py | 7 +++---- frigate/http.py | 15 ++++++++------- frigate/output/birdseye.py | 15 +++++++-------- frigate/stats.py | 4 ++-- frigate/test/test_config.py | 4 ++-- frigate/test/test_http.py | 7 ++++--- 10 files changed, 48 insertions(+), 38 deletions(-) diff --git a/frigate/app.py b/frigate/app.py index 5aa738d93..568b76563 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -109,7 +109,7 @@ class FrigateApp: root_configurer(self.log_queue) def init_config(self) -> None: - config_file = os.environ.get("CONFIG_FILE", "/config/config.yml") + config_file = os.environ.get("CONFIG_FILE", str(CONFIG_DIR / "config.yml")) # Check if we can use .yaml instead of .yml config_file_yaml = config_file.replace(".yml", ".yaml") diff --git a/frigate/config.py b/frigate/config.py index 9373cbcaf..808188e8a 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -910,7 +910,7 @@ class CameraConfig(FrigateBaseModel): class DatabaseConfig(FrigateBaseModel): - path: str = Field(default=DEFAULT_DB_PATH, title="Database path.") + path: str = Field(default=str(DEFAULT_DB_PATH), title="Database path.") class LogLevelEnum(str, Enum): diff --git a/frigate/const.py b/frigate/const.py index 97e33b689..37873b748 100644 --- a/frigate/const.py +++ b/frigate/const.py @@ -1,14 +1,23 @@ -CONFIG_DIR = "/config" -DEFAULT_DB_PATH = f"{CONFIG_DIR}/frigate.db" -MODEL_CACHE_DIR = f"{CONFIG_DIR}/model_cache" -BASE_DIR = "/media/frigate" -CLIPS_DIR = f"{BASE_DIR}/clips" -RECORD_DIR = f"{BASE_DIR}/recordings" -EXPORT_DIR = f"{BASE_DIR}/exports" -BIRDSEYE_PIPE = "/tmp/cache/birdseye" -CACHE_DIR = "/tmp/cache" +from pathlib import Path + +CACHE_DIR = Path("/tmp/cache") +BIRDSEYE_PIPE = CACHE_DIR / "birdseye" + +CONFIG_DIR = Path("/config") +DEFAULT_DB_PATH = CONFIG_DIR / "frigate.db" +MODEL_CACHE_DIR = CONFIG_DIR / "model_cache" + +INSTALL_DIR = Path("/opt/frigate") + +MEDIA_DIR = Path("/media/frigate") +CLIPS_DIR = MEDIA_DIR / "clips" +RECORD_DIR = MEDIA_DIR / "recordings" +EXPORT_DIR = MEDIA_DIR / "exports" + YAML_EXT = (".yaml", ".yml") + FRIGATE_LOCALHOST = "http://127.0.0.1:5000" + PLUS_ENV_VAR = "PLUS_API_KEY" PLUS_API_HOST = "https://api.frigate.video" diff --git a/frigate/detectors/detector_config.py b/frigate/detectors/detector_config.py index ca1915449..83d474e3c 100644 --- a/frigate/detectors/detector_config.py +++ b/frigate/detectors/detector_config.py @@ -10,6 +10,7 @@ import requests from pydantic import BaseModel, Extra, Field from pydantic.fields import PrivateAttr +from frigate.const import MODEL_CACHE_DIR from frigate.plus import PlusApi from frigate.util.builtin import load_labels @@ -83,7 +84,7 @@ class ModelConfig(BaseModel): return model_id = self.path[7:] - self.path = f"/config/model_cache/{model_id}" + self.path = str(MODEL_CACHE_DIR / model_id) model_info_path = f"{self.path}.json" # download the model if it doesn't exist diff --git a/frigate/detectors/plugins/rknn.py b/frigate/detectors/plugins/rknn.py index 01c58b94d..4f1de967f 100644 --- a/frigate/detectors/plugins/rknn.py +++ b/frigate/detectors/plugins/rknn.py @@ -15,6 +15,7 @@ except: # noqa: E722 from pydantic import Field +from frigate.const import MODEL_CACHE_DIR from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detector_config import BaseDetectorConfig @@ -86,12 +87,10 @@ class Rknn(DetectionApi): else: model_suffix = yolov8_suffix[self.model_path] self.model_path = ( - "/config/model_cache/rknn/yolov8{suffix}-320x320-{soc}.rknn".format( - suffix=model_suffix, soc=soc - ) + MODEL_CACHE_DIR / f"rknn/yolov8{model_suffix}-320x320-{soc}.rknn" ) - os.makedirs("/config/model_cache/rknn", exist_ok=True) + os.makedirs(MODEL_CACHE_DIR / "rknn", exist_ok=True) if not os.path.isfile(self.model_path): logger.info( "Downloading yolov8{suffix} model.".format(suffix=model_suffix) diff --git a/frigate/http.py b/frigate/http.py index 0a671e92c..9067ba960 100644 --- a/frigate/http.py +++ b/frigate/http.py @@ -1,6 +1,5 @@ import base64 import copy -import glob import json import logging import os @@ -41,7 +40,9 @@ from frigate.const import ( CLIPS_DIR, CONFIG_DIR, EXPORT_DIR, + INSTALL_DIR, MAX_SEGMENT_DURATION, + MEDIA_DIR, RECORD_DIR, ) from frigate.events.external import ExternalEventProcessor @@ -1384,7 +1385,7 @@ def config(): @bp.route("/config/raw") def config_raw(): - config_file = os.environ.get("CONFIG_FILE", "/config/config.yml") + config_file = os.environ.get("CONFIG_FILE", CONFIG_DIR / "config.yml") # Check if we can use .yaml instead of .yml config_file_yaml = config_file.replace(".yml", ".yaml") @@ -1434,7 +1435,7 @@ def config_save(): # Save the config to file try: - config_file = os.environ.get("CONFIG_FILE", "/config/config.yml") + config_file = os.environ.get("CONFIG_FILE", CONFIG_DIR / "config.yml") # Check if we can use .yaml instead of .yml config_file_yaml = config_file.replace(".yml", ".yaml") @@ -1647,11 +1648,11 @@ def latest_frame(camera_name): + retry_interval ): if current_app.camera_error_image is None: - error_image = glob.glob("/opt/frigate/frigate/images/camera-error.jpg") + error_image = INSTALL_DIR / "frigate/images/camera-error.jpg" - if len(error_image) > 0: + if error_image.is_file(): current_app.camera_error_image = cv2.imread( - error_image[0], cv2.IMREAD_UNCHANGED + str(error_image), cv2.IMREAD_UNCHANGED ) frame = current_app.camera_error_image @@ -2097,7 +2098,7 @@ def preview_ts(camera_name, start_ts, end_ts): clips.append( { "camera": preview["camera"], - "src": preview["path"].replace("/media/frigate", ""), + "src": preview["path"].replace(MEDIA_DIR, ""), "type": "video/mp4", "start": preview["start_time"], "end": preview["end_time"], diff --git a/frigate/output/birdseye.py b/frigate/output/birdseye.py index dbaafa313..c670d270c 100644 --- a/frigate/output/birdseye.py +++ b/frigate/output/birdseye.py @@ -1,7 +1,6 @@ """Handle outputting birdseye frames via jsmpeg and go2rtc.""" import datetime -import glob import logging import math import multiprocessing as mp @@ -15,7 +14,7 @@ import cv2 import numpy as np from frigate.config import BirdseyeModeEnum, FrigateConfig -from frigate.const import BASE_DIR, BIRDSEYE_PIPE +from frigate.const import BIRDSEYE_PIPE, INSTALL_DIR, MEDIA_DIR from frigate.types import CameraMetricsTypes from frigate.util.image import ( SharedMemoryFrameManager, @@ -280,16 +279,16 @@ class BirdsEyeFrameManager: # find and copy the logo on the blank frame birdseye_logo = None - custom_logo_files = glob.glob(f"{BASE_DIR}/custom.png") + custom_logo_file = MEDIA_DIR / "custom.png" - if len(custom_logo_files) > 0: - birdseye_logo = cv2.imread(custom_logo_files[0], cv2.IMREAD_UNCHANGED) + if custom_logo_file.is_file(): + birdseye_logo = cv2.imread(str(custom_logo_file), cv2.IMREAD_UNCHANGED) if birdseye_logo is None: - logo_files = glob.glob("/opt/frigate/frigate/images/birdseye.png") + logo_file = INSTALL_DIR / "frigate/images/birdseye.png" - if len(logo_files) > 0: - birdseye_logo = cv2.imread(logo_files[0], cv2.IMREAD_UNCHANGED) + if logo_file.is_file(): + birdseye_logo = cv2.imread(str(logo_file), cv2.IMREAD_UNCHANGED) if birdseye_logo is not None: transparent_layer = birdseye_logo[:, :, 3] diff --git a/frigate/stats.py b/frigate/stats.py index addcd4a5b..e44dc856f 100644 --- a/frigate/stats.py +++ b/frigate/stats.py @@ -300,7 +300,7 @@ def stats_snapshot( for path in [RECORD_DIR, CLIPS_DIR, CACHE_DIR, "/dev/shm"]: try: - storage_stats = shutil.disk_usage(path) + storage_stats = shutil.disk_usage(str(path)) except FileNotFoundError: stats["service"]["storage"][path] = {} continue @@ -309,7 +309,7 @@ def stats_snapshot( "total": round(storage_stats.total / pow(2, 20), 1), "used": round(storage_stats.used / pow(2, 20), 1), "free": round(storage_stats.free / pow(2, 20), 1), - "mount_type": get_fs_type(path), + "mount_type": get_fs_type(str(path)), } stats["processes"] = {} diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index 94863709c..58c04d374 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -847,9 +847,9 @@ class TestConfig(unittest.TestCase): assert runtime_config.model.merged_labelmap[0] == "person" def test_plus_labelmap(self): - with open("/config/model_cache/test", "w") as f: + with open(MODEL_CACHE_DIR / "test", "w") as f: json.dump(self.plus_model_info, f) - with open("/config/model_cache/test.json", "w") as f: + with open(MODEL_CACHE_DIR / "test.json", "w") as f: json.dump(self.plus_model_info, f) config = { diff --git a/frigate/test/test_http.py b/frigate/test/test_http.py index 932a468a3..7d1518c9c 100644 --- a/frigate/test/test_http.py +++ b/frigate/test/test_http.py @@ -11,6 +11,7 @@ from playhouse.sqlite_ext import SqliteExtDatabase from playhouse.sqliteq import SqliteQueueDatabase from frigate.config import FrigateConfig +from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR from frigate.http import create_app from frigate.models import Event, Recordings from frigate.plus import PlusApi @@ -76,19 +77,19 @@ class TestHttp(unittest.TestCase): "total": 67.1, "used": 16.6, }, - "/media/frigate/clips": { + str(CLIPS_DIR): { "free": 42429.9, "mount_type": "ext4", "total": 244529.7, "used": 189607.0, }, - "/media/frigate/recordings": { + str(RECORD_DIR): { "free": 0.2, "mount_type": "ext4", "total": 8.0, "used": 7.8, }, - "/tmp/cache": { + str(CACHE_DIR): { "free": 976.8, "mount_type": "tmpfs", "total": 1000.0,