From 54ae8f9e109826dc361a8226c9b46983ed504a1a Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 8 Oct 2019 10:00:56 -0700 Subject: [PATCH] Footasge string implementation --- ptulsconv/broadcast_timecode.py | 41 +++++++++++++++++++++++--------- tests/test_broadcast_timecode.py | 32 ++++++++++++++++--------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/ptulsconv/broadcast_timecode.py b/ptulsconv/broadcast_timecode.py index e6593d9..eb2ca46 100644 --- a/ptulsconv/broadcast_timecode.py +++ b/ptulsconv/broadcast_timecode.py @@ -1,10 +1,10 @@ from fractions import Fraction import re import math -import functools -def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False): +def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False, + include_fractional=False): """ Convert a string with a SMPTE timecode representation into a frame count. @@ -14,16 +14,13 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, :param drop_frame_hint: `True` if the timecode rep is drop frame. This is ignored (and implied `True`) if the last separator in the timecode string is a semicolon. This is ignored (and implied `False`) if `frames_per_logical_second` is not 30 or 60. - :returns (frame_count, fraction): If a fractional frame is in the SMPTE string it will be returned here in the - `fraction` part. + :param include_fractional: If `True` fractional frames will be parsed and returned as a second retval in a tuple """ assert frames_per_logical_second in [24, 25, 30, 48, 50, 60] m = re.search("(\d?\d)[:;](\d\d)[:;](\d\d)([:;])(\d\d)(\.\d+)?", smpte_rep_string) hh, mm, ss, sep, ff, frac = m.groups() - hh, mm, ss, ff = int(hh), int(mm), int(ss), int(ff) - if frac is not None: - frac = float(frac) + hh, mm, ss, ff, frac = int(hh), int(mm), int(ss), int(ff), float(frac or 0.0) drop_frame = drop_frame_hint if sep == ";": @@ -35,19 +32,25 @@ def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, raw_frames = hh * 3600 * frames_per_logical_second + mm * 60 * frames_per_logical_second + \ ss * frames_per_logical_second + ff - if drop_frame is False: - return raw_frames, frac - else: + frames = raw_frames + if drop_frame is True: frames_dropped_per_inst = (frames_per_logical_second / 15) mins = hh * 60 + mm inst_count = mins - math.floor(mins / 10) dropped_frames = frames_dropped_per_inst * inst_count - return raw_frames - dropped_frames, frac + frames = raw_frames - dropped_frames + + if include_fractional: + return frames, frac + else: + return frames def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_frame: bool = False, fractional_frame: float = None): assert frames_per_logical_second in [24,25,30,48,50,60] + assert fractional_frame is None or fractional_frame < 1.0 + nominal_frames = frame_count separator = ":" if drop_frame: @@ -69,6 +72,22 @@ def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_ else: return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff) +def footage_to_frame_count(footage_string, include_fractional = False): + m = re.search("(\d+)\+(\d+)(\.\d+)?", footage_string) + feet, frm, frac = m.groups() + feet, frm, frac = int(feet), int(frm), float(frac or 0.0) + frames = feet * 16 + frm + if include_fractional: + return frames, frac + else: + return frames +def frame_count_to_footage(frame_count, fractional_frames = None): + assert fractional_frames < 1.0 or fractional_frames is None + feet, frm = divmod(frame_count, 16) + if fractional_frames: + return "%i+%02i" % (feet, frm) + else: + return "%i+%02i%s" % (feet, frm, ("%.3f" % fractional_frames)[1:]) \ No newline at end of file diff --git a/tests/test_broadcast_timecode.py b/tests/test_broadcast_timecode.py index 4762e3c..2960196 100644 --- a/tests/test_broadcast_timecode.py +++ b/tests/test_broadcast_timecode.py @@ -1,21 +1,22 @@ import unittest from ptulsconv import broadcast_timecode + class TestBroadcastTimecode(unittest.TestCase): def test_basic_to_framecount(self): r1 = "01:00:00:00" - f1, _ = broadcast_timecode.smpte_to_frame_count(r1, 24, False) + f1 = broadcast_timecode.smpte_to_frame_count(r1, 24, False) self.assertEqual(f1, 86_400) - f2, _ = broadcast_timecode.smpte_to_frame_count(r1, 30) + f2 = broadcast_timecode.smpte_to_frame_count(r1, 30) self.assertEqual(f2, 108_000) r2 = "0:00:00:01" - f3, _ = broadcast_timecode.smpte_to_frame_count(r2, 24) + f3 = broadcast_timecode.smpte_to_frame_count(r2, 24) self.assertEqual(f3, 1) def test_basic_to_string(self): c1 = 24 - s1 = broadcast_timecode.frame_count_to_smpte(c1,24) + s1 = broadcast_timecode.frame_count_to_smpte(c1, 24) self.assertEqual(s1, "00:00:01:00") s2 = broadcast_timecode.frame_count_to_smpte(c1, 30) self.assertEqual(s2, "00:00:00:24") @@ -33,7 +34,7 @@ class TestBroadcastTimecode(unittest.TestCase): def test_fractional_to_framecount(self): s1 = "00:00:01:04.105" - c1, f1 = broadcast_timecode.smpte_to_frame_count(s1, 24, drop_frame_hint=False) + c1, f1 = broadcast_timecode.smpte_to_frame_count(s1, 24, drop_frame_hint=False, include_fractional=True) self.assertEqual(c1, 28) self.assertEqual(f1, 0.105) @@ -45,24 +46,33 @@ class TestBroadcastTimecode(unittest.TestCase): def test_drop_frame_to_framecount(self): r1 = "01:00:00;00" - f1, _ = broadcast_timecode.smpte_to_frame_count(r1, 30, True) - self.assertEqual(f1, 107_892) + z1 = broadcast_timecode.smpte_to_frame_count(r1, 30, drop_frame_hint=True) + self.assertEqual(z1, 107_892) r11 = "01:00:00;01" - f11, _ = broadcast_timecode.smpte_to_frame_count(r11, 30, True) + f11 = broadcast_timecode.smpte_to_frame_count(r11, 30) self.assertEqual(f11, 107_893) r2 = "00:01:00;02" - f2, _ = broadcast_timecode.smpte_to_frame_count(r2, 30, True) + f2 = broadcast_timecode.smpte_to_frame_count(r2, 30, True) self.assertEqual(f2, 1800) r3 = "00:00:59;29" - f3, _ = broadcast_timecode.smpte_to_frame_count(r3, 30, True) + f3 = broadcast_timecode.smpte_to_frame_count(r3, 30, True) self.assertEqual(f3, 1799) + def test_footage_to_framecount(self): + s1 = "194+11" + f1 = broadcast_timecode.footage_to_frame_count(s1) + self.assertEqual(f1, 3115) + s2 = "1+1.014" + f2 = broadcast_timecode.footage_to_frame_count(s2, include_fractional=True) + self.assertEqual(f2, (17, 0.014)) - + s3 = "0+0.1" + f3 = broadcast_timecode.footage_to_frame_count(s3, include_fractional=False) + self.assertEqual(f3, 0) if __name__ == '__main__':