71 lines
1.6 KiB
Python
71 lines
1.6 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RecordingItem:
|
|
camera: str
|
|
filename: str
|
|
timestamp: str
|
|
url: str
|
|
|
|
|
|
def _parse_filename(filename: str) -> Optional[datetime]:
|
|
if filename.endswith(".fmp4"):
|
|
stem = filename[:-5]
|
|
elif filename.endswith(".mp4"):
|
|
stem = filename[:-4]
|
|
else:
|
|
return None
|
|
for fmt in ("%Y-%m-%d_%H-%M-%S-%f", "%Y-%m-%d_%H-%M-%S"):
|
|
try:
|
|
return datetime.strptime(stem, fmt)
|
|
except ValueError:
|
|
continue
|
|
return None
|
|
|
|
|
|
def list_recordings(
|
|
recordings_dir: str,
|
|
camera: str,
|
|
date: Optional[str],
|
|
limit: int,
|
|
offset: int,
|
|
) -> list[RecordingItem]:
|
|
base = Path(recordings_dir)
|
|
cam_dir = base / camera
|
|
if not cam_dir.exists() or not cam_dir.is_dir():
|
|
return []
|
|
|
|
items: list[tuple[datetime, str, str]] = []
|
|
for p in cam_dir.rglob("*"):
|
|
if not p.is_file():
|
|
continue
|
|
if not (p.name.endswith(".fmp4") or p.name.endswith(".mp4")):
|
|
continue
|
|
dt = _parse_filename(p.name) or datetime.fromtimestamp(p.stat().st_mtime)
|
|
if date is not None and dt.strftime("%Y-%m-%d") != date:
|
|
continue
|
|
rel = p.relative_to(base).as_posix()
|
|
items.append((dt, p.name, rel))
|
|
|
|
items.sort(key=lambda x: x[0], reverse=True)
|
|
sliced = items[offset : offset + limit]
|
|
|
|
out: list[RecordingItem] = []
|
|
for dt, name, rel in sliced:
|
|
out.append(
|
|
RecordingItem(
|
|
camera=camera,
|
|
filename=name,
|
|
timestamp=dt.isoformat(),
|
|
url=f"/videos/{rel}",
|
|
)
|
|
)
|
|
return out
|
|
|