@@ -52,16 +52,16 @@ def _tc_to_secs(tc: str) -> float:
5252 return int (h ) * 3600 + int (m ) * 60 + int (s ) + int (ms ) / 1000
5353
5454
55- def test_vfr_position_is_timecode (test_vfr_video : str ):
55+ def test_vfr_position_is_timecode (test_vfr_video : str , auto_close ):
5656 """Position should be a Timecode-backed FrameTimecode."""
57- video = open_video (test_vfr_video , backend = "pyav" )
57+ video = auto_close ( open_video (test_vfr_video , backend = "pyav" ) )
5858 assert video .read () is not False
5959 assert isinstance (video .position ._time , Timecode )
6060
6161
62- def test_vfr_position_monotonic_pyav (test_vfr_video : str ):
62+ def test_vfr_position_monotonic_pyav (test_vfr_video : str , auto_close ):
6363 """PTS-based position should be monotonically non-decreasing (PyAV)."""
64- video = open_video (test_vfr_video , backend = "pyav" )
64+ video = auto_close ( open_video (test_vfr_video , backend = "pyav" ) )
6565 last_seconds = - 1.0
6666 frame_count = 0
6767 while True :
@@ -77,9 +77,9 @@ def test_vfr_position_monotonic_pyav(test_vfr_video: str):
7777 assert frame_count > 0
7878
7979
80- def test_vfr_position_monotonic_opencv (test_vfr_video : str ):
80+ def test_vfr_position_monotonic_opencv (test_vfr_video : str , auto_close ):
8181 """PTS-based position should be monotonically non-decreasing (OpenCV)."""
82- video = open_video (test_vfr_video , backend = "opencv" )
82+ video = auto_close ( open_video (test_vfr_video , backend = "opencv" ) )
8383 last_seconds = - 1.0
8484 frame_count = 0
8585 while True :
@@ -96,13 +96,13 @@ def test_vfr_position_monotonic_opencv(test_vfr_video: str):
9696
9797
9898@pytest .mark .parametrize ("backend" , ["pyav" , "opencv" ])
99- def test_vfr_scene_detection (test_vfr_video : str , backend : str ):
99+ def test_vfr_scene_detection (test_vfr_video : str , backend : str , auto_close ):
100100 """Scene detection on VFR video should produce timestamps matching known ground truth.
101101
102102 Both PyAV (native PTS) and OpenCV (CAP_PROP_POS_MSEC) should agree on scene cuts since
103103 both expose accurate PTS-derived timestamps.
104104 """
105- video = open_video (test_vfr_video , backend = backend )
105+ video = auto_close ( open_video (test_vfr_video , backend = backend ) )
106106 sm = SceneManager ()
107107 sm .add_detector (ContentDetector ())
108108 sm .detect_scenes (video = video , end_time = 10.0 )
@@ -123,9 +123,9 @@ def test_vfr_scene_detection(test_vfr_video: str, backend: str):
123123 )
124124
125125
126- def test_vfr_seek_pyav (test_vfr_video : str ):
126+ def test_vfr_seek_pyav (test_vfr_video : str , auto_close ):
127127 """Seeking should work with VFR video."""
128- video = open_video (test_vfr_video , backend = "pyav" )
128+ video = auto_close ( open_video (test_vfr_video , backend = "pyav" ) )
129129 target_time = 2.0 # seconds
130130 video .seek (target_time )
131131 frame = video .read ()
@@ -134,21 +134,21 @@ def test_vfr_seek_pyav(test_vfr_video: str):
134134 assert abs (video .position .seconds - target_time ) < 1.0
135135
136136
137- def test_vfr_stats_manager (test_vfr_video : str ):
137+ def test_vfr_stats_manager (test_vfr_video : str , auto_close ):
138138 """StatsManager should work correctly with VFR video."""
139- video = open_video (test_vfr_video , backend = "pyav" )
139+ video = auto_close ( open_video (test_vfr_video , backend = "pyav" ) )
140140 stats = StatsManager ()
141141 sm = SceneManager (stats_manager = stats )
142142 sm .add_detector (ContentDetector ())
143143 sm .detect_scenes (video = video )
144144 assert len (sm .get_scene_list ()) > 0
145145
146146
147- def test_vfr_csv_output (test_vfr_video : str , tmp_path ):
147+ def test_vfr_csv_output (test_vfr_video : str , tmp_path , auto_close ):
148148 """CSV export should work correctly with VFR video."""
149149 from scenedetect .output import write_scene_list
150150
151- video = open_video (test_vfr_video , backend = "pyav" )
151+ video = auto_close ( open_video (test_vfr_video , backend = "pyav" ) )
152152 sm = SceneManager ()
153153 sm .add_detector (ContentDetector ())
154154 sm .detect_scenes (video = video )
@@ -167,10 +167,10 @@ def test_vfr_csv_output(test_vfr_video: str, tmp_path):
167167
168168
169169@pytest .mark .parametrize ("backend" , ["pyav" , "opencv" ])
170- def test_vfr_drop3_scene_detection (test_vfr_drop3_video : str , backend : str ):
170+ def test_vfr_drop3_scene_detection (test_vfr_drop3_video : str , backend : str , auto_close ):
171171 """Synthetic VFR video (drop every 3rd frame, alternating 1x/2x durations) should produce
172172 timecodes matching known ground truth with both backends."""
173- video = open_video (test_vfr_drop3_video , backend = backend )
173+ video = auto_close ( open_video (test_vfr_drop3_video , backend = backend ) )
174174 sm = SceneManager ()
175175 sm .add_detector (ContentDetector ())
176176 sm .detect_scenes (video = video , show_progress = False )
@@ -191,9 +191,9 @@ def test_vfr_drop3_scene_detection(test_vfr_drop3_video: str, backend: str):
191191
192192
193193@pytest .mark .parametrize ("backend" , ["pyav" , "opencv" ])
194- def test_vfr_drop3_position_monotonic (test_vfr_drop3_video : str , backend : str ):
194+ def test_vfr_drop3_position_monotonic (test_vfr_drop3_video : str , backend : str , auto_close ):
195195 """PTS-based position should be monotonically non-decreasing on synthetic VFR video."""
196- video = open_video (test_vfr_drop3_video , backend = backend )
196+ video = auto_close ( open_video (test_vfr_drop3_video , backend = backend ) )
197197 last_seconds = - 1.0
198198 frame_count = 0
199199 while True :
@@ -208,22 +208,22 @@ def test_vfr_drop3_position_monotonic(test_vfr_drop3_video: str, backend: str):
208208 assert frame_count == 160 # 2/3 of original 240 frames in 10s at 24000/1001
209209
210210
211- def test_cfr_position_is_timecode (test_movie_clip : str ):
211+ def test_cfr_position_is_timecode (test_movie_clip : str , auto_close ):
212212 """CFR video positions should also be Timecode-backed with PTS support."""
213- video = open_video (test_movie_clip , backend = "pyav" )
213+ video = auto_close ( open_video (test_movie_clip , backend = "pyav" ) )
214214 assert video .read () is not False
215215 assert isinstance (video .position ._time , Timecode )
216216
217217
218- def test_cfr_frame_num_exact (test_movie_clip : str ):
218+ def test_cfr_frame_num_exact (test_movie_clip : str , auto_close ):
219219 """For CFR video, frame_num should be exact (not approximate)."""
220- video = open_video (test_movie_clip , backend = "pyav" )
220+ video = auto_close ( open_video (test_movie_clip , backend = "pyav" ) )
221221 for expected_frame in range (1 , 11 ):
222222 assert video .read () is not False
223223 assert video .position .frame_num == expected_frame - 1
224224
225225
226- def test_vfr_save_images_opencv_matches_pyav (test_vfr_video : str , tmp_path ):
226+ def test_vfr_save_images_opencv_matches_pyav (test_vfr_video : str , tmp_path , auto_close ):
227227 """OpenCV save-images thumbnails should match PyAV thumbnails for all scenes.
228228
229229 If the OpenCV seek off-by-one bug is present, scene thumbnails will show content from the
@@ -233,7 +233,7 @@ def test_vfr_save_images_opencv_matches_pyav(test_vfr_video: str, tmp_path):
233233 # must not run per-backend: the cut at 00:01:39.474 scores content_val=27.08 against the
234234 # default threshold of 27.0, so decoder/colorspace differences between backends (or FFmpeg
235235 # builds - e.g. av 17.1.0 on macOS arm64) can flip it, changing the scene count.
236- video = open_video (test_vfr_video , backend = "pyav" )
236+ video = auto_close ( open_video (test_vfr_video , backend = "pyav" ) )
237237 sm = SceneManager ()
238238 sm .add_detector (ContentDetector ())
239239 sm .detect_scenes (video = video )
@@ -246,7 +246,7 @@ def test_vfr_save_images_opencv_matches_pyav(test_vfr_video: str, tmp_path):
246246 for backend in ("pyav" , "opencv" ):
247247 out_dir = tmp_path / backend
248248 out_dir .mkdir ()
249- video = open_video (test_vfr_video , backend = backend )
249+ video = auto_close ( open_video (test_vfr_video , backend = backend ) )
250250 rebased = [
251251 (FrameTimecode (start , fps = video .frame_rate ), FrameTimecode (end , fps = video .frame_rate ))
252252 for start , end in scene_list
@@ -282,9 +282,9 @@ def test_vfr_save_images_opencv_matches_pyav(test_vfr_video: str, tmp_path):
282282
283283
284284@pytest .mark .parametrize ("backend" , ["pyav" , "opencv" ])
285- def test_vfr_csv_accuracy (test_vfr_video : str , backend : str , tmp_path ):
285+ def test_vfr_csv_accuracy (test_vfr_video : str , backend : str , tmp_path , auto_close ):
286286 """CSV timecodes for VFR video should match known ground truth for both backends."""
287- video = open_video (test_vfr_video , backend = backend )
287+ video = auto_close ( open_video (test_vfr_video , backend = backend ) )
288288 sm = SceneManager ()
289289 sm .add_detector (ContentDetector ())
290290 sm .detect_scenes (video = video , end_time = 10.0 )
@@ -417,15 +417,15 @@ def test_vfr_fcp_export(test_vfr_video: str, fcp_format: str, tmp_path):
417417 assert root .tag == ("fcpxml" if fcp_format == "fcpx" else "xmeml" )
418418
419419
420- def test_vfr_csv_backend_conformance (test_vfr_video : str ):
420+ def test_vfr_csv_backend_conformance (test_vfr_video : str , auto_close ):
421421 """PyAV and OpenCV should produce identical scene timecodes for VFR video.
422422
423423 Only the known interior scenes are compared; the last scene's end time may vary slightly
424424 between backends since it reflects the clip boundary rather than a detected cut.
425425 """
426426 timecodes : dict [str , list [tuple [str , str ]]] = {}
427427 for backend in ("pyav" , "opencv" ):
428- video = open_video (test_vfr_video , backend = backend )
428+ video = auto_close ( open_video (test_vfr_video , backend = backend ) )
429429 sm = SceneManager ()
430430 sm .add_detector (ContentDetector ())
431431 sm .detect_scenes (video = video , end_time = 10.0 )
0 commit comments