mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-18 22:28:23 +03:00
Merge pull request #9 from ibs0d/codex/fix-recording-roots-normalization-logic
Normalize recordings storage roots to avoid date/hour pseudo-roots
This commit is contained in:
commit
ced95052ee
@ -1,6 +1,7 @@
|
|||||||
"""Handle storage retention and usage."""
|
"""Handle storage retention and usage."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import threading
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -178,6 +179,22 @@ class StorageMaintainer(threading.Thread):
|
|||||||
if camera_segment in recording_path:
|
if camera_segment in recording_path:
|
||||||
return recording_path.split(camera_segment, 1)[0].rstrip("/") or "/"
|
return recording_path.split(camera_segment, 1)[0].rstrip("/") or "/"
|
||||||
|
|
||||||
|
# Prefer configured recording roots when available.
|
||||||
|
for configured_root in sorted(
|
||||||
|
self.config.get_recordings_paths(), key=len, reverse=True
|
||||||
|
):
|
||||||
|
if recording_path == configured_root or recording_path.startswith(
|
||||||
|
f"{configured_root}/"
|
||||||
|
):
|
||||||
|
return configured_root.rstrip("/") or "/"
|
||||||
|
|
||||||
|
# Support layouts like /root/YYYY-MM-DD/HH/... and normalize to /root.
|
||||||
|
date_hour_match = re.match(
|
||||||
|
r"^(?P<root>.+?)/\d{4}-\d{2}-\d{2}/\d{2}(?:/|$)", recording_path
|
||||||
|
)
|
||||||
|
if date_hour_match:
|
||||||
|
return date_hour_match.group("root").rstrip("/") or "/"
|
||||||
|
|
||||||
# Fallback for unexpected path layouts; expected format is root/camera/date/file
|
# Fallback for unexpected path layouts; expected format is root/camera/date/file
|
||||||
path = Path(recording_path)
|
path = Path(recording_path)
|
||||||
if len(path.parents) >= 3:
|
if len(path.parents) >= 3:
|
||||||
|
|||||||
@ -227,3 +227,83 @@ class TestHttpRecordingsStorage(BaseTestHttp):
|
|||||||
# Config metadata still reflects current root assignment.
|
# Config metadata still reflects current root assignment.
|
||||||
assert roots["/media/frigate/recordings"]["configured_cameras"] == []
|
assert roots["/media/frigate/recordings"]["configured_cameras"] == []
|
||||||
assert roots["/mnt/new-root"]["configured_cameras"] == ["front_door"]
|
assert roots["/mnt/new-root"]["configured_cameras"] == ["front_door"]
|
||||||
|
|
||||||
|
def test_recordings_storage_normalizes_date_hour_paths_to_configured_roots(self):
|
||||||
|
self.minimal_config["cameras"]["front_door"]["path"] = "/media/frigate/recordings"
|
||||||
|
self.minimal_config["cameras"]["back_yard"] = {
|
||||||
|
"ffmpeg": {
|
||||||
|
"inputs": [{"path": "rtsp://10.0.0.2:554/video", "roles": ["detect"]}]
|
||||||
|
},
|
||||||
|
"detect": {"height": 1080, "width": 1920, "fps": 5},
|
||||||
|
"path": "/video1",
|
||||||
|
}
|
||||||
|
|
||||||
|
self.test_stats["service"]["storage"]["/video1"] = {
|
||||||
|
"free": 600,
|
||||||
|
"mount_type": "ext4",
|
||||||
|
"total": 1000,
|
||||||
|
"used": 400,
|
||||||
|
}
|
||||||
|
|
||||||
|
app = self._build_app()
|
||||||
|
|
||||||
|
Recordings.insert_many(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "front_08",
|
||||||
|
"path": "/media/frigate/recordings/2026-03-07/08/seg1.mp4",
|
||||||
|
"camera": "front_door",
|
||||||
|
"start_time": 100,
|
||||||
|
"end_time": 110,
|
||||||
|
"duration": 10,
|
||||||
|
"motion": 1,
|
||||||
|
"segment_size": 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "front_09",
|
||||||
|
"path": "/media/frigate/recordings/2026-03-07/09/seg1.mp4",
|
||||||
|
"camera": "front_door",
|
||||||
|
"start_time": 120,
|
||||||
|
"end_time": 130,
|
||||||
|
"duration": 10,
|
||||||
|
"motion": 1,
|
||||||
|
"segment_size": 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "video1_08",
|
||||||
|
"path": "/video1/2026-03-07/08/seg1.mp4",
|
||||||
|
"camera": "back_yard",
|
||||||
|
"start_time": 140,
|
||||||
|
"end_time": 150,
|
||||||
|
"duration": 10,
|
||||||
|
"motion": 1,
|
||||||
|
"segment_size": 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "video1_09",
|
||||||
|
"path": "/video1/2026-03-07/09/seg1.mp4",
|
||||||
|
"camera": "back_yard",
|
||||||
|
"start_time": 160,
|
||||||
|
"end_time": 170,
|
||||||
|
"duration": 10,
|
||||||
|
"motion": 1,
|
||||||
|
"segment_size": 50,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
).execute()
|
||||||
|
|
||||||
|
with AuthTestClient(app) as client:
|
||||||
|
payload = client.get("/recordings/storage").json()
|
||||||
|
|
||||||
|
roots = {root["path"]: root for root in payload["recording_roots"]}
|
||||||
|
|
||||||
|
assert set(roots.keys()) == {"/media/frigate/recordings", "/video1"}
|
||||||
|
assert roots["/media/frigate/recordings"]["recordings_size"] == 250
|
||||||
|
assert roots["/video1"]["recordings_size"] == 250
|
||||||
|
|
||||||
|
# Ensure date/hour pseudo-roots are not emitted.
|
||||||
|
assert "/media/frigate/recordings/2026-03-07/08" not in roots
|
||||||
|
assert "/media/frigate/recordings/2026-03-07/09" not in roots
|
||||||
|
assert "/video1/2026-03-07/08" not in roots
|
||||||
|
assert "/video1/2026-03-07/09" not in roots
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user