Footasge string implementation

This commit is contained in:
Jamie Hardt
2019-10-08 10:00:56 -07:00
parent 145f7b2982
commit 54ae8f9e10
2 changed files with 51 additions and 22 deletions

View File

@@ -1,10 +1,10 @@
from fractions import Fraction from fractions import Fraction
import re import re
import math 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. 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 :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 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. `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 :param include_fractional: If `True` fractional frames will be parsed and returned as a second retval in a tuple
`fraction` part.
""" """
assert frames_per_logical_second in [24, 25, 30, 48, 50, 60] 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) 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, sep, ff, frac = m.groups()
hh, mm, ss, ff = int(hh), int(mm), int(ss), int(ff) hh, mm, ss, ff, frac = int(hh), int(mm), int(ss), int(ff), float(frac or 0.0)
if frac is not None:
frac = float(frac)
drop_frame = drop_frame_hint drop_frame = drop_frame_hint
if sep == ";": 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 + \ raw_frames = hh * 3600 * frames_per_logical_second + mm * 60 * frames_per_logical_second + \
ss * frames_per_logical_second + ff ss * frames_per_logical_second + ff
if drop_frame is False: frames = raw_frames
return raw_frames, frac if drop_frame is True:
else:
frames_dropped_per_inst = (frames_per_logical_second / 15) frames_dropped_per_inst = (frames_per_logical_second / 15)
mins = hh * 60 + mm mins = hh * 60 + mm
inst_count = mins - math.floor(mins / 10) inst_count = mins - math.floor(mins / 10)
dropped_frames = frames_dropped_per_inst * inst_count 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, def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_frame: bool = False,
fractional_frame: float = None): fractional_frame: float = None):
assert frames_per_logical_second in [24,25,30,48,50,60] 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 nominal_frames = frame_count
separator = ":" separator = ":"
if drop_frame: if drop_frame:
@@ -69,6 +72,22 @@ def frame_count_to_smpte(frame_count: int, frames_per_logical_second: int, drop_
else: else:
return "%02i:%02i:%02i%s%02i" % (hh, mm, ss, separator, ff) 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:])

View File

@@ -1,16 +1,17 @@
import unittest import unittest
from ptulsconv import broadcast_timecode from ptulsconv import broadcast_timecode
class TestBroadcastTimecode(unittest.TestCase): class TestBroadcastTimecode(unittest.TestCase):
def test_basic_to_framecount(self): def test_basic_to_framecount(self):
r1 = "01:00:00:00" 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) 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) self.assertEqual(f2, 108_000)
r2 = "0:00:00:01" 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) self.assertEqual(f3, 1)
def test_basic_to_string(self): def test_basic_to_string(self):
@@ -33,7 +34,7 @@ class TestBroadcastTimecode(unittest.TestCase):
def test_fractional_to_framecount(self): def test_fractional_to_framecount(self):
s1 = "00:00:01:04.105" 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(c1, 28)
self.assertEqual(f1, 0.105) self.assertEqual(f1, 0.105)
@@ -45,24 +46,33 @@ class TestBroadcastTimecode(unittest.TestCase):
def test_drop_frame_to_framecount(self): def test_drop_frame_to_framecount(self):
r1 = "01:00:00;00" r1 = "01:00:00;00"
f1, _ = broadcast_timecode.smpte_to_frame_count(r1, 30, True) z1 = broadcast_timecode.smpte_to_frame_count(r1, 30, drop_frame_hint=True)
self.assertEqual(f1, 107_892) self.assertEqual(z1, 107_892)
r11 = "01:00:00;01" 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) self.assertEqual(f11, 107_893)
r2 = "00:01:00;02" 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) self.assertEqual(f2, 1800)
r3 = "00:00:59;29" 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) 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__': if __name__ == '__main__':