mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 10:33:11 +03:00
Improve playback of videos in Tracking Details (#22301)
Some checks are pending
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
Some checks are pending
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
* prevent short hls segments by extending clip backwards * clean up * snap to keyframe instead of arbitrarily subtracting time * formatting
This commit is contained in:
parent
d1f3a807d3
commit
f316244495
@ -50,10 +50,12 @@ from frigate.models import Event, Previews, Recordings, Regions, ReviewSegment
|
||||
from frigate.track.object_processing import TrackedObjectProcessor
|
||||
from frigate.util.file import get_event_thumbnail_bytes
|
||||
from frigate.util.image import get_image_from_recording
|
||||
from frigate.util.media import get_keyframe_before
|
||||
from frigate.util.time import get_dst_transitions
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
router = APIRouter(tags=[Tags.media])
|
||||
|
||||
|
||||
@ -900,6 +902,33 @@ async def vod_ts(
|
||||
if recording.end_time > end_ts:
|
||||
duration -= int((recording.end_time - end_ts) * 1000)
|
||||
|
||||
# nginx-vod-module pushes clipFrom forward to the next keyframe,
|
||||
# which can leave too few frames and produce an empty/unplayable
|
||||
# segment. Snap clipFrom back to the preceding keyframe so the
|
||||
# segment always starts with a decodable frame.
|
||||
if "clipFrom" in clip:
|
||||
keyframe_ms = get_keyframe_before(recording.path, clip["clipFrom"])
|
||||
if keyframe_ms is not None:
|
||||
gained = clip["clipFrom"] - keyframe_ms
|
||||
clip["clipFrom"] = keyframe_ms
|
||||
duration += gained
|
||||
logger.debug(
|
||||
"VOD: snapped clipFrom to keyframe at %sms for %s, duration now %sms",
|
||||
keyframe_ms,
|
||||
recording.path,
|
||||
duration,
|
||||
)
|
||||
else:
|
||||
# could not read keyframes, remove clipFrom to use full recording
|
||||
logger.debug(
|
||||
"VOD: no keyframe info for %s, removing clipFrom to use full recording",
|
||||
recording.path,
|
||||
)
|
||||
del clip["clipFrom"]
|
||||
duration = int(recording.duration * 1000)
|
||||
if recording.end_time > end_ts:
|
||||
duration -= int((recording.end_time - end_ts) * 1000)
|
||||
|
||||
if duration < min_duration_ms:
|
||||
# skip if the clip has no valid duration (too short to contain frames)
|
||||
logger.debug(
|
||||
|
||||
61
frigate/util/media.py
Normal file
61
frigate/util/media.py
Normal file
@ -0,0 +1,61 @@
|
||||
"""Utilities for media file inspection."""
|
||||
|
||||
import subprocess as sp
|
||||
|
||||
from frigate.const import DEFAULT_FFMPEG_VERSION
|
||||
|
||||
FFPROBE_PATH = (
|
||||
f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffprobe"
|
||||
if DEFAULT_FFMPEG_VERSION
|
||||
else "ffprobe"
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
Loading…
Reference in New Issue
Block a user