""" 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). """ import logging import time from threading import Thread import ffmpeg as ffmpeg_lib log = logging.getLogger(__name__) class RecordingTracker: """Tracks a growing recording file and reports its duration.""" def __init__(self, recording_path, on_duration_update=None): self._path = recording_path self._on_duration = on_duration_update self._duration = 0.0 self._stop = False self._thread = None @property def duration(self): return self._duration def start(self): 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) def stop(self): self._stop = True log.info("RecordingTracker stopped") def _poll_loop(self): while not self._stop: time.sleep(2) if not self._path.exists(): continue size = self._path.stat().st_size if size < 10_000: continue duration = self._probe_duration() if duration and duration > self._duration: self._duration = duration log.info("Duration: %.1fs", duration) if self._on_duration: self._on_duration(self._duration) def _probe_duration(self): """Probe recording duration via ffprobe.""" try: info = ffmpeg_lib.probe(str(self._path)) # Format-level duration is 0 for fragmented MP4 (empty_moov) 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) # Last resort: file size heuristic (~500kbps for this stream type) try: return self._path.stat().st_size / 65_000 except Exception: return None