Merge remote-tracking branch 'origin/master' into dev
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions

This commit is contained in:
Blake Blackshear
2026-03-22 17:34:11 -05:00
76 changed files with 1315 additions and 381 deletions
+64 -1
View File
@@ -4,13 +4,20 @@ import datetime
import errno
import logging
import os
import subprocess as sp
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterable
from peewee import DatabaseError, chunked
from frigate.const import CLIPS_DIR, EXPORT_DIR, RECORD_DIR, THUMB_DIR
from frigate.const import (
CLIPS_DIR,
DEFAULT_FFMPEG_VERSION,
EXPORT_DIR,
RECORD_DIR,
THUMB_DIR,
)
from frigate.models import (
Event,
Export,
@@ -26,6 +33,12 @@ logger = logging.getLogger(__name__)
# Safety threshold - abort if more than 50% of files would be deleted
SAFETY_THRESHOLD = 0.5
FFPROBE_PATH = (
f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffprobe"
if DEFAULT_FFMPEG_VERSION
else "ffprobe"
)
@dataclass
class SyncResult:
@@ -808,3 +821,53 @@ def sync_all_media(
)
return results
def get_keyframe_before(path: str, offset_ms: int) -> int | None:
"""Get the timestamp (ms) of the last keyframe at or before offset_ms.
Uses ffprobe packet index to read keyframe positions from the mp4 file.
Returns None if ffprobe fails or no keyframe is found before the offset.
"""
try:
result = sp.run(
[
FFPROBE_PATH,
"-select_streams",
"v:0",
"-show_entries",
"packet=pts_time,flags",
"-of",
"csv=p=0",
"-loglevel",
"error",
path,
],
capture_output=True,
timeout=5,
)
except (sp.TimeoutExpired, FileNotFoundError):
return None
if result.returncode != 0:
return None
offset_s = offset_ms / 1000.0
best_ms = None
for line in result.stdout.decode().strip().splitlines():
parts = line.strip().split(",")
if len(parts) != 2:
continue
ts_str, flags = parts
if "K" not in flags:
continue
try:
ts = float(ts_str)
except ValueError:
continue
if ts <= offset_s:
best_ms = int(ts * 1000)
else:
break
return best_ms