mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 02:29:19 +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.track.object_processing import TrackedObjectProcessor
|
||||||
from frigate.util.file import get_event_thumbnail_bytes
|
from frigate.util.file import get_event_thumbnail_bytes
|
||||||
from frigate.util.image import get_image_from_recording
|
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
|
from frigate.util.time import get_dst_transitions
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(tags=[Tags.media])
|
router = APIRouter(tags=[Tags.media])
|
||||||
|
|
||||||
|
|
||||||
@ -900,6 +902,33 @@ async def vod_ts(
|
|||||||
if recording.end_time > end_ts:
|
if recording.end_time > end_ts:
|
||||||
duration -= int((recording.end_time - end_ts) * 1000)
|
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:
|
if duration < min_duration_ms:
|
||||||
# skip if the clip has no valid duration (too short to contain frames)
|
# skip if the clip has no valid duration (too short to contain frames)
|
||||||
logger.debug(
|
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