From e237073512de80ec5b6f2974606f0ba7ad21671e Mon Sep 17 00:00:00 2001 From: Dominik Winecki Date: Thu, 8 Jan 2026 20:00:48 +0000 Subject: [PATCH] Add support for VideoCapture.get(CAP_PROP_POS_MSEC) --- snake-pit/test_cv2.py | 45 ++++++++++++++++++++++++++ vidformer-py/vidformer/cv2/__init__.py | 11 +++++-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/snake-pit/test_cv2.py b/snake-pit/test_cv2.py index 99ccdec6..f1c352f6 100644 --- a/snake-pit/test_cv2.py +++ b/snake-pit/test_cv2.py @@ -752,6 +752,51 @@ def test_seek_vf(): seek(vf_cv2) +def get_pos_msec(cv2): + # test getting position in milliseconds + cap = cv2.VideoCapture(TEST_VID_PATH) + assert cap.isOpened() + + fps = cap.get(cv2.CAP_PROP_FPS) + + # At start, should be at position 0 + pos_msec = cap.get(cv2.CAP_PROP_POS_MSEC) + assert pos_msec == 0.0 + + # Seek to frame 24 (1 second at 24fps) + cap.set(cv2.CAP_PROP_POS_FRAMES, 24) + pos_msec = cap.get(cv2.CAP_PROP_POS_MSEC) + expected_msec = 24 * (1000 / fps) + assert abs(pos_msec - expected_msec) < 1.0 # within 1ms + + # Seek to 5000ms using set, then verify with get + cap.set(cv2.CAP_PROP_POS_MSEC, 5000) + pos_msec = cap.get(cv2.CAP_PROP_POS_MSEC) + assert abs(pos_msec - 5000) < 2 * (1000 / fps) # within two frame durations + + # Seek to frame 1000 + cap.set(cv2.CAP_PROP_POS_FRAMES, 1000) + pos_msec = cap.get(cv2.CAP_PROP_POS_MSEC) + expected_msec = 1000 * (1000 / fps) + assert abs(pos_msec - expected_msec) < 2 * ( + 1000 / fps + ) # within two frame durations + + # Read a frame and verify position advances + pos_before = cap.get(cv2.CAP_PROP_POS_MSEC) + ret, frame = cap.read() + assert ret + pos_after = cap.get(cv2.CAP_PROP_POS_MSEC) + assert pos_after > pos_before + + cap.release() + + +# We can not run this with ocv_cv2 since OpenCV's get CAP_PROP_POS_MSEC is quite inaccurate +def test_get_pos_msec_vf(): + get_pos_msec(vf_cv2) + + def test_getFontScaleFromHeight(): import cv2 as ocv_cv2 diff --git a/vidformer-py/vidformer/cv2/__init__.py b/vidformer-py/vidformer/cv2/__init__.py index 79c1fb8e..72f1b28d 100644 --- a/vidformer-py/vidformer/cv2/__init__.py +++ b/vidformer-py/vidformer/cv2/__init__.py @@ -74,7 +74,6 @@ def _ts_to_fps(timestamps): return float(fps) - def _fps_to_ts(fps, n_frames): assert type(fps) is int return [Fraction(i, fps) for i in range(n_frames)] @@ -363,6 +362,14 @@ def get(self, prop): return len(self._source) elif prop == CAP_PROP_POS_FRAMES: return self._next_frame_idx + elif prop == CAP_PROP_POS_MSEC: + ts = self._source.ts() + if self._next_frame_idx >= len(ts): + # Past the end, return the last timestamp + if len(ts) > 0: + return float(ts[-1] * 1000) + return 0.0 + return float(ts[self._next_frame_idx] * 1000) raise Exception(f"Unknown property {prop}") @@ -439,7 +446,7 @@ def __init__( # Round to nearest integer fps self._f_time = Fraction(1, int(round(fps))) else: - raise Exception("fps must be an integer or a Fraction") + raise Exception("fps must be an integer, float, or Fraction") assert isinstance(size, tuple) or isinstance(size, list) assert len(size) == 2