81 lines
2.4 KiB
Python
81 lines
2.4 KiB
Python
"""
|
|
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
|