123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- import datetime
- import json
- import logging
- import os
- import queue
- import subprocess as sp
- import threading
- import time
- from collections import defaultdict
- from pathlib import Path
- import psutil
- from frigate.config import FrigateConfig
- from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
- logger = logging.getLogger(__name__)
- SECONDS_IN_DAY = 60 * 60 * 24
- def remove_empty_directories(directory):
- # list all directories recursively and sort them by path,
- # longest first
- paths = sorted(
- [x[0] for x in os.walk(RECORD_DIR)],
- key=lambda p: len(str(p)),
- reverse=True,
- )
- for path in paths:
- # don't delete the parent
- if path == RECORD_DIR:
- continue
- if len(os.listdir(path)) == 0:
- os.rmdir(path)
- class RecordingMaintainer(threading.Thread):
- def __init__(self, config: FrigateConfig, stop_event):
- threading.Thread.__init__(self)
- self.name = "recording_maint"
- self.config = config
- self.stop_event = stop_event
- def move_files(self):
- recordings = [
- d
- for d in os.listdir(RECORD_DIR)
- if os.path.isfile(os.path.join(RECORD_DIR, d)) and d.endswith(".mp4")
- ]
- files_in_use = []
- for process in psutil.process_iter():
- try:
- if process.name() != "ffmpeg":
- continue
- flist = process.open_files()
- if flist:
- for nt in flist:
- if nt.path.startswith(RECORD_DIR):
- files_in_use.append(nt.path.split("/")[-1])
- except:
- continue
- for f in recordings:
- if f in files_in_use:
- continue
- basename = os.path.splitext(f)[0]
- camera, date = basename.rsplit("-", maxsplit=1)
- start_time = datetime.datetime.strptime(date, "%Y%m%d%H%M%S")
- ffprobe_cmd = [
- "ffprobe",
- "-v",
- "error",
- "-show_entries",
- "format=duration",
- "-of",
- "default=noprint_wrappers=1:nokey=1",
- f"{os.path.join(RECORD_DIR, f)}",
- ]
- p = sp.run(ffprobe_cmd, capture_output=True)
- if p.returncode == 0:
- duration = float(p.stdout.decode().strip())
- else:
- logger.info(f"bad file: {f}")
- os.remove(os.path.join(RECORD_DIR, f))
- continue
- directory = os.path.join(
- RECORD_DIR, start_time.strftime("%Y-%m/%d/%H"), camera
- )
- if not os.path.exists(directory):
- os.makedirs(directory)
- file_name = f"{start_time.strftime('%M.%S.mp4')}"
- os.rename(os.path.join(RECORD_DIR, f), os.path.join(directory, file_name))
- def expire_files(self):
- delete_before = {}
- for name, camera in self.config.cameras.items():
- delete_before[name] = (
- datetime.datetime.now().timestamp()
- - SECONDS_IN_DAY * camera.record.retain_days
- )
- for p in Path("/media/frigate/recordings").rglob("*.mp4"):
- if not p.parent.name in delete_before:
- continue
- if p.stat().st_mtime < delete_before[p.parent.name]:
- p.unlink(missing_ok=True)
- def run(self):
- counter = 0
- self.expire_files()
- while True:
- if self.stop_event.is_set():
- logger.info(f"Exiting recording maintenance...")
- break
- # only expire events every 10 minutes, but check for new files every 10 seconds
- time.sleep(10)
- counter = counter + 1
- if counter > 60:
- self.expire_files()
- remove_empty_directories(RECORD_DIR)
- counter = 0
- self.move_files()
|