From 3bddaae76d217ffaba7130d0a68f4f4e0762b12e Mon Sep 17 00:00:00 2001 From: Nick Walker Date: Fri, 26 Jun 2026 13:30:33 -0600 Subject: [PATCH] Fix to_systemtime sub-second precision for non-standard NTSC rates System time is defined in the integer frame grid, so the sub-second calculation should always divide by _int_framerate. Using float(framerate) was imprecise for non-standard NTSC rates stored as rounded strings (e.g. "47.95" for 48000/1001), producing wrong millisecond values mid-second. Co-authored-by: cubicibo <55701024+cubicibo@users.noreply.github.com> --- src/timecode/timecode.py | 8 ++++---- tests/test_timecode.py | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/timecode/timecode.py b/src/timecode/timecode.py index 6a0e2b3..9722e0a 100644 --- a/src/timecode/timecode.py +++ b/src/timecode/timecode.py @@ -422,10 +422,10 @@ def to_systemtime(self, as_float: bool = False) -> str | float: # type:ignore return self.float - (1e-3) if as_float else str(self) hh, mm, ss, ff = self.frames_to_tc(self.frames + 1, skip_rollover=True) - framerate = ( - float(self.framerate) if self._ntsc_framerate else self._int_framerate - ) - ms = ff / framerate + # System time is in the integer frame grid, so always divide by _int_framerate. + # Using float(self.framerate) for NTSC rates gives wrong sub-second values + # because the stored string (e.g. "47.95") loses precision vs the true rate. + ms = ff / self._int_framerate if as_float: return hh * 3600 + mm * 60 + ss + ms return f"{hh:02d}:{mm:02d}:{ss:02d}.{round(ms * 1000):03d}" diff --git a/tests/test_timecode.py b/tests/test_timecode.py index 99efb52..73a9a6c 100644 --- a/tests/test_timecode.py +++ b/tests/test_timecode.py @@ -1764,3 +1764,18 @@ def test_generalized_ntsc_rational_formats(rational_str, int_framerate, is_drop) assert tc._ntsc_framerate is True assert tc._int_framerate == int_framerate assert tc.drop_frame is is_drop + + +def test_systemtime_subframe_ntsc(): + """to_systemtime uses integer framerate for sub-second calculation. + + Non-standard NTSC rates (e.g. 48000/1001) are stored as a rounded float + string ("47.95"), so dividing by float(framerate) gives wrong milliseconds + for frames mid-second. The integer framerate (48) must be used instead. + """ + from fractions import Fraction + # Frame 24 of 48000/1001 fps is exactly half a second in system time (24/48) + tc = Timecode(Fraction(48000, 1001), frames=24) + assert tc._ntsc_framerate is True + assert tc._int_framerate == 48 + assert tc.to_systemtime() == "00:00:00.500"