mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-10 16:52:47 +03:00
stage main+sub exports on disk instead of /tmp/cache
A main+sub export stages a full copy of itself before the final file is written. That copy went to /tmp/cache, so a long export outgrew the tmpfs and failed with no space left on device. Staged runs are now written to the exports directory, and startup removes any left behind by a killed export.
This commit is contained in:
@@ -15,7 +15,7 @@ from peewee import DoesNotExist
|
|||||||
from frigate.comms.inter_process import InterProcessRequestor
|
from frigate.comms.inter_process import InterProcessRequestor
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.config.camera.record import ChaptersEnum
|
from frigate.config.camera.record import ChaptersEnum
|
||||||
from frigate.const import UPDATE_JOB_STATE
|
from frigate.const import EXPORT_DIR, UPDATE_JOB_STATE
|
||||||
from frigate.jobs.job import Job
|
from frigate.jobs.job import Job
|
||||||
from frigate.models import Export
|
from frigate.models import Export
|
||||||
from frigate.record.export import (
|
from frigate.record.export import (
|
||||||
@@ -415,6 +415,10 @@ def reap_stale_exports() -> None:
|
|||||||
this in a try/except. A failure on a single row will not stop the rest
|
this in a try/except. A failure on a single row will not stop the rest
|
||||||
of the sweep, and a failure in the top-level query will log and return.
|
of the sweep, and a failure in the top-level query will log and return.
|
||||||
"""
|
"""
|
||||||
|
# staged stream runs live on disk, so a killed export leaves them behind
|
||||||
|
for staged in Path(EXPORT_DIR).glob("export_stage_*"):
|
||||||
|
staged.unlink(missing_ok=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stale_exports = list(Export.select().where(Export.in_progress == True)) # noqa: E712
|
stale_exports = list(Export.select().where(Export.in_progress == True)) # noqa: E712
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -559,7 +559,7 @@ class RecordingExporter(threading.Thread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _staged_run_path(self, index: int) -> str:
|
def _staged_run_path(self, index: int) -> str:
|
||||||
return os.path.join(CACHE_DIR, f"export_stage_{self.export_id}_{index}.mp4")
|
return os.path.join(EXPORT_DIR, f"export_stage_{self.export_id}_{index}.mp4")
|
||||||
|
|
||||||
def _probe_stream_resolution(self, run: StreamRun) -> tuple[int, int] | None:
|
def _probe_stream_resolution(self, run: StreamRun) -> tuple[int, int] | None:
|
||||||
"""Probe one recording from a run for its resolution.
|
"""Probe one recording from a run for its resolution.
|
||||||
|
|||||||
@@ -453,6 +453,21 @@ class TestHttpExport(BaseTestHttp):
|
|||||||
assert unchanged.name == "front door export"
|
assert unchanged.name == "front door export"
|
||||||
assert unchanged.video_path == video
|
assert unchanged.video_path == video
|
||||||
|
|
||||||
|
def test_reap_stale_exports_removes_staged_runs(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
staged = os.path.join(tmpdir, "export_stage_front_door_abc_0.mp4")
|
||||||
|
finished = os.path.join(tmpdir, "front_door_export.mp4")
|
||||||
|
|
||||||
|
for path in (staged, finished):
|
||||||
|
with open(path, "w") as handle:
|
||||||
|
handle.write("video")
|
||||||
|
|
||||||
|
with patch("frigate.jobs.export.EXPORT_DIR", tmpdir):
|
||||||
|
reap_stale_exports()
|
||||||
|
|
||||||
|
assert not os.path.exists(staged)
|
||||||
|
assert os.path.exists(finished)
|
||||||
|
|
||||||
def test_reap_stale_exports_deletes_rows_with_no_file(self):
|
def test_reap_stale_exports_deletes_rows_with_no_file(self):
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
stale_video = os.path.join(tmpdir, "stale.mp4")
|
stale_video = os.path.join(tmpdir, "stale.mp4")
|
||||||
|
|||||||
Reference in New Issue
Block a user