From 8e8c8b977429ca83ee7f806365151ae5a1910a62 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Tue, 14 Apr 2026 15:48:00 +0200 Subject: [PATCH] feat: add resolve_keyframe helper to extract sorted-keyframe lookup Adds a pure function that returns the latest keyframe at or before a given time (with tolerance), replacing the inline lookup pattern that appears multiple times in main.py. Includes 6 tests covering empty list, before-first, exact match, between, after-last, and tolerance. Co-Authored-By: Claude Opus 4.6 --- main.py | 15 +++++++++++++++ tests/test_utils.py | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index b109cf9..820e12d 100755 --- a/main.py +++ b/main.py @@ -53,6 +53,21 @@ def format_time(seconds: float) -> str: return f"{m}:{s:04.1f}" +def resolve_keyframe( + keyframes: list[tuple[float, float, str | None, bool, bool]], + t: float, + tolerance: float = 0.05, +) -> tuple[float, float, str | None, bool, bool] | None: + """Return the latest keyframe at or before *t*, or None.""" + result = None + for kf in keyframes: + if kf[0] <= t + tolerance: + result = kf + else: + break + return result + + def build_ffmpeg_command( input_path: str, start: float, output_path: str, short_side: int | None = None, diff --git a/tests/test_utils.py b/tests/test_utils.py index 9bce74e..b7bee55 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,5 @@ import tempfile, os, json -from main import build_export_path, format_time, build_ffmpeg_command, build_sequence_dir, build_audio_extract_command, build_annotation_json_path, upsert_clip_annotation +from main import build_export_path, format_time, resolve_keyframe, build_ffmpeg_command, build_sequence_dir, build_audio_extract_command, build_annotation_json_path, upsert_clip_annotation from main import ProcessedDB @@ -369,3 +369,35 @@ def test_db_default_profile_backward_compat(): assert db.get_profiles() == ["default"] finally: os.unlink(path) + + +# --- resolve_keyframe --- + +def test_resolve_keyframe_empty(): + assert resolve_keyframe([], 5.0) is None + +def test_resolve_keyframe_before_first(): + kfs = [(3.0, 0.5, None, False, False)] + assert resolve_keyframe(kfs, 1.0) is None + +def test_resolve_keyframe_exact(): + kfs = [(2.0, 0.3, "9:16", True, False)] + assert resolve_keyframe(kfs, 2.0) == (2.0, 0.3, "9:16", True, False) + +def test_resolve_keyframe_between(): + kfs = [ + (1.0, 0.2, None, False, False), + (5.0, 0.8, "1:1", False, True), + ] + assert resolve_keyframe(kfs, 3.0) == (1.0, 0.2, None, False, False) + +def test_resolve_keyframe_after_last(): + kfs = [ + (1.0, 0.2, None, False, False), + (5.0, 0.8, "1:1", False, True), + ] + assert resolve_keyframe(kfs, 10.0) == (5.0, 0.8, "1:1", False, True) + +def test_resolve_keyframe_tolerance(): + kfs = [(4.0, 0.5, None, True, True)] + assert resolve_keyframe(kfs, 3.96) == (4.0, 0.5, None, True, True)