Merge pull request #41 from ibs0d/codex/modify-preview-storage-logic-for-multi-path

Write preview mp4s to camera recordings path and add preview dir helper
This commit is contained in:
ibs0d 2026-03-09 20:46:33 +11:00 committed by GitHub
commit 38d6052aa4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 36 additions and 8 deletions

View File

@ -100,7 +100,9 @@ class OutputProcess(FrigateProcess):
jsmpeg_cameras[camera] = JsmpegCamera(
camera_config, self.stop_event, websocket_server
)
preview_recorders[camera] = PreviewRecorder(camera_config)
preview_recorders[camera] = PreviewRecorder(
camera_config, self.config.get_camera_recordings_path(camera)
)
preview_write_times[camera] = 0
if (

View File

@ -15,7 +15,7 @@ import numpy as np
from frigate.comms.inter_process import InterProcessRequestor
from frigate.config import CameraConfig, RecordQualityEnum
from frigate.const import CACHE_DIR, CLIPS_DIR, INSERT_PREVIEW, PREVIEW_FRAME_TYPE
from frigate.const import CACHE_DIR, INSERT_PREVIEW, PREVIEW_FRAME_TYPE
from frigate.ffmpeg_presets import (
FPS_VFR_PARAM,
EncodeTypeEnum,
@ -58,6 +58,11 @@ PREVIEW_QMAX_PARAM = {
}
def get_preview_dir(recordings_root: str, camera: str) -> str:
"""Get the output directory for generated preview mp4 files."""
return os.path.join(recordings_root, "preview", camera)
def get_cache_image_name(camera: str, frame_time: float) -> str:
"""Get the image name in cache."""
return os.path.join(
@ -119,14 +124,15 @@ class FFMpegConverter(threading.Thread):
config: CameraConfig,
frame_times: list[float],
requestor: InterProcessRequestor,
preview_dir: str,
):
super().__init__(name=f"{config.name}_preview_converter")
self.config = config
self.frame_times = frame_times
self.requestor = requestor
self.path = os.path.join(
CLIPS_DIR,
f"previews/{self.config.name}/{self.frame_times[0]}-{self.frame_times[-1]}.mp4",
preview_dir,
f"{self.frame_times[0]}-{self.frame_times[-1]}.mp4",
)
# write a PREVIEW at fps and 1 key frame per clip
@ -203,12 +209,13 @@ class FFMpegConverter(threading.Thread):
class PreviewRecorder:
def __init__(self, config: CameraConfig) -> None:
def __init__(self, config: CameraConfig, recordings_root: str) -> None:
self.config = config
self.start_time = 0
self.last_output_time = 0
self.offline = False
self.output_frames = []
self.preview_dir = get_preview_dir(recordings_root, config.name)
if config.detect.width > config.detect.height:
self.out_height = PREVIEW_HEIGHT
@ -254,9 +261,7 @@ class PreviewRecorder:
)
Path(PREVIEW_CACHE_DIR).mkdir(exist_ok=True)
Path(os.path.join(CLIPS_DIR, f"previews/{config.name}")).mkdir(
parents=True, exist_ok=True
)
Path(self.preview_dir).mkdir(parents=True, exist_ok=True)
# check for existing items in cache
start_ts = (
@ -393,6 +398,7 @@ class PreviewRecorder:
self.config,
self.output_frames,
self.requestor,
self.preview_dir,
).start()
else:
logger.debug(
@ -442,6 +448,7 @@ class PreviewRecorder:
self.config,
self.output_frames,
self.requestor,
self.preview_dir,
).start()
self.reset_frame_cache(frame_time)

View File

@ -1,11 +1,18 @@
import os
import shutil
import unittest
import sys
import types
# Allow importing preview module in environments without opencv installed
sys.modules.setdefault("cv2", types.SimpleNamespace())
sys.modules.setdefault("numpy", types.SimpleNamespace(ndarray=object))
from frigate.output.preview import (
PREVIEW_CACHE_DIR,
PREVIEW_FRAME_TYPE,
get_most_recent_preview_frame,
get_preview_dir,
)
@ -78,3 +85,15 @@ class TestPreviewLoader(unittest.TestCase):
def test_get_most_recent_preview_frame_no_directory(self):
shutil.rmtree(PREVIEW_CACHE_DIR)
self.assertIsNone(get_most_recent_preview_frame("test_camera"))
def test_get_preview_dir_with_explicit_camera_path(self):
self.assertEqual(
get_preview_dir("/video2", "front"),
"/video2/preview/front",
)
def test_get_preview_dir_with_default_recordings_path(self):
self.assertEqual(
get_preview_dir("/media/frigate/recordings", "front"),
"/media/frigate/recordings/preview/front",
)