Refactor path handling

- 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
This commit is contained in:
Martin Weinelt 2024-02-01 13:29:44 +01:00
parent 3df8b5829c
commit 6596207fb4
No known key found for this signature in database
GPG Key ID: 87C1E9888F856759
10 changed files with 48 additions and 38 deletions

View File

@ -109,7 +109,7 @@ class FrigateApp:
root_configurer(self.log_queue) root_configurer(self.log_queue)
def init_config(self) -> None: 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 # Check if we can use .yaml instead of .yml
config_file_yaml = config_file.replace(".yml", ".yaml") config_file_yaml = config_file.replace(".yml", ".yaml")

View File

@ -910,7 +910,7 @@ class CameraConfig(FrigateBaseModel):
class DatabaseConfig(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): class LogLevelEnum(str, Enum):

View File

@ -1,14 +1,23 @@
CONFIG_DIR = "/config" from pathlib import Path
DEFAULT_DB_PATH = f"{CONFIG_DIR}/frigate.db"
MODEL_CACHE_DIR = f"{CONFIG_DIR}/model_cache" CACHE_DIR = Path("/tmp/cache")
BASE_DIR = "/media/frigate" BIRDSEYE_PIPE = CACHE_DIR / "birdseye"
CLIPS_DIR = f"{BASE_DIR}/clips"
RECORD_DIR = f"{BASE_DIR}/recordings" CONFIG_DIR = Path("/config")
EXPORT_DIR = f"{BASE_DIR}/exports" DEFAULT_DB_PATH = CONFIG_DIR / "frigate.db"
BIRDSEYE_PIPE = "/tmp/cache/birdseye" MODEL_CACHE_DIR = CONFIG_DIR / "model_cache"
CACHE_DIR = "/tmp/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") YAML_EXT = (".yaml", ".yml")
FRIGATE_LOCALHOST = "http://127.0.0.1:5000" FRIGATE_LOCALHOST = "http://127.0.0.1:5000"
PLUS_ENV_VAR = "PLUS_API_KEY" PLUS_ENV_VAR = "PLUS_API_KEY"
PLUS_API_HOST = "https://api.frigate.video" PLUS_API_HOST = "https://api.frigate.video"

View File

@ -10,6 +10,7 @@ import requests
from pydantic import BaseModel, Extra, Field from pydantic import BaseModel, Extra, Field
from pydantic.fields import PrivateAttr from pydantic.fields import PrivateAttr
from frigate.const import MODEL_CACHE_DIR
from frigate.plus import PlusApi from frigate.plus import PlusApi
from frigate.util.builtin import load_labels from frigate.util.builtin import load_labels
@ -83,7 +84,7 @@ class ModelConfig(BaseModel):
return return
model_id = self.path[7:] 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" model_info_path = f"{self.path}.json"
# download the model if it doesn't exist # download the model if it doesn't exist

View File

@ -15,6 +15,7 @@ except: # noqa: E722
from pydantic import Field from pydantic import Field
from frigate.const import MODEL_CACHE_DIR
from frigate.detectors.detection_api import DetectionApi from frigate.detectors.detection_api import DetectionApi
from frigate.detectors.detector_config import BaseDetectorConfig from frigate.detectors.detector_config import BaseDetectorConfig
@ -86,12 +87,10 @@ class Rknn(DetectionApi):
else: else:
model_suffix = yolov8_suffix[self.model_path] model_suffix = yolov8_suffix[self.model_path]
self.model_path = ( self.model_path = (
"/config/model_cache/rknn/yolov8{suffix}-320x320-{soc}.rknn".format( MODEL_CACHE_DIR / f"rknn/yolov8{model_suffix}-320x320-{soc}.rknn"
suffix=model_suffix, soc=soc
)
) )
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): if not os.path.isfile(self.model_path):
logger.info( logger.info(
"Downloading yolov8{suffix} model.".format(suffix=model_suffix) "Downloading yolov8{suffix} model.".format(suffix=model_suffix)

View File

@ -1,6 +1,5 @@
import base64 import base64
import copy import copy
import glob
import json import json
import logging import logging
import os import os
@ -41,7 +40,9 @@ from frigate.const import (
CLIPS_DIR, CLIPS_DIR,
CONFIG_DIR, CONFIG_DIR,
EXPORT_DIR, EXPORT_DIR,
INSTALL_DIR,
MAX_SEGMENT_DURATION, MAX_SEGMENT_DURATION,
MEDIA_DIR,
RECORD_DIR, RECORD_DIR,
) )
from frigate.events.external import ExternalEventProcessor from frigate.events.external import ExternalEventProcessor
@ -1384,7 +1385,7 @@ def config():
@bp.route("/config/raw") @bp.route("/config/raw")
def 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 # Check if we can use .yaml instead of .yml
config_file_yaml = config_file.replace(".yml", ".yaml") config_file_yaml = config_file.replace(".yml", ".yaml")
@ -1434,7 +1435,7 @@ def config_save():
# Save the config to file # Save the config to file
try: 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 # Check if we can use .yaml instead of .yml
config_file_yaml = config_file.replace(".yml", ".yaml") config_file_yaml = config_file.replace(".yml", ".yaml")
@ -1647,11 +1648,11 @@ def latest_frame(camera_name):
+ retry_interval + retry_interval
): ):
if current_app.camera_error_image is None: 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( 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 frame = current_app.camera_error_image
@ -2097,7 +2098,7 @@ def preview_ts(camera_name, start_ts, end_ts):
clips.append( clips.append(
{ {
"camera": preview["camera"], "camera": preview["camera"],
"src": preview["path"].replace("/media/frigate", ""), "src": preview["path"].replace(MEDIA_DIR, ""),
"type": "video/mp4", "type": "video/mp4",
"start": preview["start_time"], "start": preview["start_time"],
"end": preview["end_time"], "end": preview["end_time"],

View File

@ -1,7 +1,6 @@
"""Handle outputting birdseye frames via jsmpeg and go2rtc.""" """Handle outputting birdseye frames via jsmpeg and go2rtc."""
import datetime import datetime
import glob
import logging import logging
import math import math
import multiprocessing as mp import multiprocessing as mp
@ -15,7 +14,7 @@ import cv2
import numpy as np import numpy as np
from frigate.config import BirdseyeModeEnum, FrigateConfig 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.types import CameraMetricsTypes
from frigate.util.image import ( from frigate.util.image import (
SharedMemoryFrameManager, SharedMemoryFrameManager,
@ -280,16 +279,16 @@ class BirdsEyeFrameManager:
# find and copy the logo on the blank frame # find and copy the logo on the blank frame
birdseye_logo = None 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: if custom_logo_file.is_file():
birdseye_logo = cv2.imread(custom_logo_files[0], cv2.IMREAD_UNCHANGED) birdseye_logo = cv2.imread(str(custom_logo_file), cv2.IMREAD_UNCHANGED)
if birdseye_logo is None: 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: if logo_file.is_file():
birdseye_logo = cv2.imread(logo_files[0], cv2.IMREAD_UNCHANGED) birdseye_logo = cv2.imread(str(logo_file), cv2.IMREAD_UNCHANGED)
if birdseye_logo is not None: if birdseye_logo is not None:
transparent_layer = birdseye_logo[:, :, 3] transparent_layer = birdseye_logo[:, :, 3]

View File

@ -300,7 +300,7 @@ def stats_snapshot(
for path in [RECORD_DIR, CLIPS_DIR, CACHE_DIR, "/dev/shm"]: for path in [RECORD_DIR, CLIPS_DIR, CACHE_DIR, "/dev/shm"]:
try: try:
storage_stats = shutil.disk_usage(path) storage_stats = shutil.disk_usage(str(path))
except FileNotFoundError: except FileNotFoundError:
stats["service"]["storage"][path] = {} stats["service"]["storage"][path] = {}
continue continue
@ -309,7 +309,7 @@ def stats_snapshot(
"total": round(storage_stats.total / pow(2, 20), 1), "total": round(storage_stats.total / pow(2, 20), 1),
"used": round(storage_stats.used / pow(2, 20), 1), "used": round(storage_stats.used / pow(2, 20), 1),
"free": round(storage_stats.free / 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"] = {} stats["processes"] = {}

View File

@ -847,9 +847,9 @@ class TestConfig(unittest.TestCase):
assert runtime_config.model.merged_labelmap[0] == "person" assert runtime_config.model.merged_labelmap[0] == "person"
def test_plus_labelmap(self): 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) 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) json.dump(self.plus_model_info, f)
config = { config = {

View File

@ -11,6 +11,7 @@ from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase from playhouse.sqliteq import SqliteQueueDatabase
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR
from frigate.http import create_app from frigate.http import create_app
from frigate.models import Event, Recordings from frigate.models import Event, Recordings
from frigate.plus import PlusApi from frigate.plus import PlusApi
@ -76,19 +77,19 @@ class TestHttp(unittest.TestCase):
"total": 67.1, "total": 67.1,
"used": 16.6, "used": 16.6,
}, },
"/media/frigate/clips": { str(CLIPS_DIR): {
"free": 42429.9, "free": 42429.9,
"mount_type": "ext4", "mount_type": "ext4",
"total": 244529.7, "total": 244529.7,
"used": 189607.0, "used": 189607.0,
}, },
"/media/frigate/recordings": { str(RECORD_DIR): {
"free": 0.2, "free": 0.2,
"mount_type": "ext4", "mount_type": "ext4",
"total": 8.0, "total": 8.0,
"used": 7.8, "used": 7.8,
}, },
"/tmp/cache": { str(CACHE_DIR): {
"free": 976.8, "free": 976.8,
"mount_type": "tmpfs", "mount_type": "tmpfs",
"total": 1000.0, "total": 1000.0,