good progress
This commit is contained in:
@@ -4,10 +4,14 @@ RecordingTracker: monitors the growing recording file and reports duration.
|
||||
Probes with ffprobe every cycle. No bitrate estimation — initial burst frames
|
||||
make calibration unreliable. Falls back to file-size heuristic only when
|
||||
ffprobe returns nothing (e.g. file too new).
|
||||
|
||||
Supports multi-segment recording: sums completed segment durations and adds
|
||||
the current segment's growing duration.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
|
||||
import ffmpeg as ffmpeg_lib
|
||||
@@ -16,12 +20,18 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RecordingTracker:
|
||||
"""Tracks a growing recording file and reports its duration."""
|
||||
"""Tracks growing recording segments and reports total duration."""
|
||||
|
||||
def __init__(self, recording_path, on_duration_update=None):
|
||||
self._path = recording_path
|
||||
def __init__(self, get_segments, on_duration_update=None):
|
||||
"""
|
||||
Args:
|
||||
get_segments: callable returning list of Path objects (all segments, ordered)
|
||||
on_duration_update: callback(duration_float)
|
||||
"""
|
||||
self._get_segments = get_segments
|
||||
self._on_duration = on_duration_update
|
||||
self._duration = 0.0
|
||||
self._segment_cache = {} # path → finalized duration (only for completed segments)
|
||||
self._stop = False
|
||||
self._thread = None
|
||||
|
||||
@@ -33,7 +43,7 @@ class RecordingTracker:
|
||||
self._stop = False
|
||||
self._thread = Thread(target=self._poll_loop, daemon=True, name="rec_tracker")
|
||||
self._thread.start()
|
||||
log.info("RecordingTracker started: %s", self._path)
|
||||
log.info("RecordingTracker started")
|
||||
|
||||
def stop(self):
|
||||
self._stop = True
|
||||
@@ -43,38 +53,53 @@ class RecordingTracker:
|
||||
while not self._stop:
|
||||
time.sleep(2)
|
||||
|
||||
if not self._path.exists():
|
||||
segments = self._get_segments()
|
||||
if not segments:
|
||||
continue
|
||||
|
||||
size = self._path.stat().st_size
|
||||
if size < 10_000:
|
||||
continue
|
||||
total = 0.0
|
||||
for i, seg in enumerate(segments):
|
||||
is_last = (i == len(segments) - 1)
|
||||
|
||||
duration = self._probe_duration()
|
||||
if duration and duration > self._duration:
|
||||
self._duration = duration
|
||||
log.info("Duration: %.1fs", duration)
|
||||
if not is_last and seg in self._segment_cache:
|
||||
# Completed segment — use cached duration
|
||||
total += self._segment_cache[seg]
|
||||
continue
|
||||
|
||||
if not seg.exists():
|
||||
continue
|
||||
size = seg.stat().st_size
|
||||
if size < 10_000:
|
||||
continue
|
||||
|
||||
dur = self._probe_duration(seg)
|
||||
if dur:
|
||||
if not is_last:
|
||||
# Segment is done growing — cache it
|
||||
self._segment_cache[seg] = dur
|
||||
total += dur
|
||||
|
||||
if total > self._duration:
|
||||
self._duration = total
|
||||
log.info("Duration: %.1fs (%d segments)", total, len(segments))
|
||||
if self._on_duration:
|
||||
self._on_duration(self._duration)
|
||||
|
||||
def _probe_duration(self):
|
||||
def _probe_duration(self, path):
|
||||
"""Probe recording duration via ffprobe."""
|
||||
try:
|
||||
info = ffmpeg_lib.probe(str(self._path))
|
||||
# Format-level duration is 0 for fragmented MP4 (empty_moov)
|
||||
info = ffmpeg_lib.probe(str(path))
|
||||
dur = float(info.get("format", {}).get("duration", 0))
|
||||
if dur > 0:
|
||||
return dur
|
||||
# Fragmented MP4: check video stream duration
|
||||
for stream in info.get("streams", []):
|
||||
sdur = float(stream.get("duration", 0))
|
||||
if sdur > 0:
|
||||
return sdur
|
||||
except Exception as e:
|
||||
log.debug("ffprobe failed: %s", e)
|
||||
log.debug("ffprobe failed for %s: %s", path, e)
|
||||
|
||||
# Last resort: file size heuristic (~500kbps for this stream type)
|
||||
try:
|
||||
return self._path.stat().st_size / 65_000
|
||||
return path.stat().st_size / 65_000
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user