OTIO export for VFR video should have no spurious float precision and correct timecodes. Regression test for the float precision bug where seconds * frame_rate could produce values like 90.00000000000001 instead of 90.0 for CFR video.
(test_vfr_video: str, backend: str, tmp_path)
| 298 | |
| 299 | @pytest.mark.parametrize("backend", ["pyav", "opencv"]) |
| 300 | def test_vfr_otio_export(test_vfr_video: str, backend: str, tmp_path): |
| 301 | """OTIO export for VFR video should have no spurious float precision and correct timecodes. |
| 302 | |
| 303 | Regression test for the float precision bug where seconds * frame_rate could produce |
| 304 | values like 90.00000000000001 instead of 90.0 for CFR video. |
| 305 | """ |
| 306 | exit_code, _ = invoke_cli( |
| 307 | [ |
| 308 | "-i", |
| 309 | test_vfr_video, |
| 310 | "-b", |
| 311 | backend, |
| 312 | "-o", |
| 313 | str(tmp_path), |
| 314 | "detect-content", |
| 315 | "time", |
| 316 | "--end", |
| 317 | "10s", |
| 318 | "save-otio", |
| 319 | ] |
| 320 | ) |
| 321 | assert exit_code == 0 |
| 322 | |
| 323 | otio_path = next(tmp_path.glob("*.otio")) |
| 324 | data = json.loads(otio_path.read_text()) |
| 325 | frame_rate = data["global_start_time"]["rate"] |
| 326 | one_frame_secs = 1.0 / frame_rate |
| 327 | |
| 328 | clips = data["tracks"]["children"][0]["children"] |
| 329 | assert len(clips) >= len(EXPECTED_SCENES_VFR) |
| 330 | |
| 331 | for i, (clip, (exp_start_tc, exp_end_tc)) in enumerate( |
| 332 | zip(clips, EXPECTED_SCENES_VFR, strict=False) |
| 333 | ): |
| 334 | sr = clip["source_range"] |
| 335 | start_val = sr["start_time"]["value"] |
| 336 | dur_val = sr["duration"]["value"] |
| 337 | |
| 338 | # No spurious float precision: values should have at most 6 decimal places. |
| 339 | assert round(start_val, 6) == start_val, ( |
| 340 | f"[{backend}] Clip {i + 1} start_time.value has excess precision: {start_val!r}" |
| 341 | ) |
| 342 | assert round(dur_val, 6) == dur_val, ( |
| 343 | f"[{backend}] Clip {i + 1} duration.value has excess precision: {dur_val!r}" |
| 344 | ) |
| 345 | |
| 346 | # Values should round-trip to the expected timecodes within 1 frame. |
| 347 | start_secs = start_val / frame_rate |
| 348 | end_secs = (start_val + dur_val) / frame_rate |
| 349 | assert abs(start_secs - _tc_to_secs(exp_start_tc)) < one_frame_secs, ( |
| 350 | f"[{backend}] Clip {i + 1} start: {start_secs:.4f}s vs expected {exp_start_tc}" |
| 351 | ) |
| 352 | assert abs(end_secs - _tc_to_secs(exp_end_tc)) < one_frame_secs, ( |
| 353 | f"[{backend}] Clip {i + 1} end: {end_secs:.4f}s vs expected {exp_end_tc}" |
| 354 | ) |
| 355 | |
| 356 | |
| 357 | def test_vfr_edl_export(test_vfr_video: str, tmp_path): |
nothing calls this directly
no test coverage detected