From 23174e3a979d91d0319a240abb69e55ca4aaabc0 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 27 May 2021 11:05:53 -0700 Subject: [PATCH] Work on rewrting the parser And a unit test --- ptulsconv/broadcast_timecode.py | 2 +- ptulsconv/commands.py | 2 +- ptulsconv/docparser/adr_entity.py | 5 + ptulsconv/docparser/doc_entity.py | 108 ++++++++++++++++++++++ ptulsconv/docparser/doc_parser_visitor.py | 108 ++-------------------- tests/test_doc_entities.py | 24 +++++ 6 files changed, 147 insertions(+), 102 deletions(-) create mode 100644 ptulsconv/docparser/adr_entity.py create mode 100644 ptulsconv/docparser/doc_entity.py create mode 100644 tests/test_doc_entities.py diff --git a/ptulsconv/broadcast_timecode.py b/ptulsconv/broadcast_timecode.py index 5820ee7..6712033 100644 --- a/ptulsconv/broadcast_timecode.py +++ b/ptulsconv/broadcast_timecode.py @@ -4,7 +4,7 @@ import math def smpte_to_frame_count(smpte_rep_string: str, frames_per_logical_second: int, drop_frame_hint=False, - include_fractional=False): + include_fractional=False) -> object: """ Convert a string with a SMPTE timecode representation into a frame count. diff --git a/ptulsconv/commands.py b/ptulsconv/commands.py index 8782be3..cc8523a 100644 --- a/ptulsconv/commands.py +++ b/ptulsconv/commands.py @@ -3,7 +3,6 @@ import os import sys from itertools import chain -from collections import namedtuple import csv import ptulsconv @@ -55,6 +54,7 @@ adr_field_map = ((['Title', 'PT.Session.Name'], 'Title', str), (['Movie.Start_Offset_Seconds'], 'Movie Seconds', float), ) + def dump_csv(events, output=sys.stdout): keys = set() for e in events: diff --git a/ptulsconv/docparser/adr_entity.py b/ptulsconv/docparser/adr_entity.py new file mode 100644 index 0000000..70b59ae --- /dev/null +++ b/ptulsconv/docparser/adr_entity.py @@ -0,0 +1,5 @@ +from .doc_entity import SessionDescriptor + +class ADRDocument: + def __init__(self, session: SessionDescriptor): + self.document = session diff --git a/ptulsconv/docparser/doc_entity.py b/ptulsconv/docparser/doc_entity.py new file mode 100644 index 0000000..52553d7 --- /dev/null +++ b/ptulsconv/docparser/doc_entity.py @@ -0,0 +1,108 @@ +from fractions import Fraction +from ptulsconv.broadcast_timecode import smpte_to_frame_count + + +class SessionDescriptor: + def __init__(self, **kwargs): + self.header = kwargs['header'] + self.files = kwargs['files'] + self.clips = kwargs['clips'] + self.plugins = kwargs['plugins'] + self.tracks = kwargs['tracks'] + self.markers = kwargs['markers'] + + +class HeaderDescriptor: + def __init__(self, **kwargs): + self.session_name = kwargs['session_name'] + self.sample_rate = kwargs['sample_rate'] + self.bit_depth = kwargs['bit_depth'] + self.start_timecode = kwargs['start_timecode'] + self.timecode_format = kwargs['timecode_format'] + self.timecode_drop_frame = kwargs['timecode_drop_frame'] + self.count_audio_tracks = kwargs['count_audio_tracks'] + self.count_clips = kwargs['count_clips'] + self.count_files = kwargs['count_files'] + + def convert_timecode(self, tc_string) -> Fraction: + frame_count = smpte_to_frame_count(tc_string, + self.logical_fps, + self.timecode_drop_frame, + include_fractional=False) + + return self.frame_duration * frame_count + + @property + def start_time(self) -> Fraction: + """ + The start time of this session. + :return: Start time in seconds + """ + return self.convert_timecode(self.start_timecode) + + @property + def logical_fps(self) -> int: + return self._get_tcformat_params[0] + + @property + def frame_duration(self) -> Fraction: + return self._get_tcformat_params[1] + + @property + def _get_tcformat_params(self): + frame_rates = {"23.976": (24, Fraction(1001, 24_000)), + "24": (24, Fraction(1, 24)), + "29.97": (30, Fraction(1001, 30_000)), + "30": (30, Fraction(1, 30)), + "59.94": (60, Fraction(1001, 60_000)), + "60": (60, Fraction(1, 60)) + } + + if self.timecode_format in frame_rates.keys(): + return frame_rates[self.timecode_format] + else: + raise ValueError("Unrecognized TC rate (%s)" % self.timecode_format) + + +class TrackDescriptor: + def __init__(self, **kwargs): + self.name = kwargs['name'] + self.comments = kwargs['comments'] + self.user_delay_samples = kwargs['user_delay_samples'] + self.state = kwargs['state'] + self.plugins = kwargs['plugins'] + self.clips = kwargs['clips'] + + +class FileDescriptor(dict): + pass + + +class TrackClipDescriptor: + def __init__(self, **kwargs): + self.channel = kwargs['channel'] + self.event = kwargs['event'] + self.clip_name = kwargs['clip_name'] + self.start_time = kwargs['start_time'] + self.end_time = kwargs['end_time'] + self.duration = kwargs['duration'] + self.timestamp = kwargs['timestamp'] + self.state = kwargs['state'] + + +class ClipDescriptor(dict): + pass + + +class PluginDescriptor(dict): + pass + + +class MarkerDescriptor: + def __init__(self, **kwargs): + self.number = kwargs['number'] + self.location = kwargs['location'] + self.time_reference = kwargs['time_reference'] + self.units = kwargs['units'] + self.name = kwargs['name'] + self.comments = kwargs['comments'] diff --git a/ptulsconv/docparser/doc_parser_visitor.py b/ptulsconv/docparser/doc_parser_visitor.py index 92d9d24..d823a44 100644 --- a/ptulsconv/docparser/doc_parser_visitor.py +++ b/ptulsconv/docparser/doc_parser_visitor.py @@ -1,99 +1,7 @@ -from parsimonious.nodes import NodeVisitor, Node -from collections import namedtuple - -# _SessionDescriptor = namedtuple('_SessionDescriptor', -# "header files clips plugins tracks markers") -# -# _HeaderDescriptor = namedtuple('_HeaderDescriptor', -# "session_name sample_rate bit_depth start_timecode " -# "timecode_format timecode_drop_frame " -# "count_audio_tracks count_clips count_files") -# -# _TrackDescriptor = namedtuple("_TrackDescriptor", -# "name comments user_delay_samples state plugins " -# "clips") -# -# _TrackClipDescriptor = namedtuple("_TrackClipDescriptor", -# "channel event clip_name start_time end_time " -# "duration timestamp state") -# -# _PluginDescriptor = namedtuple("_PluginDescriptor", -# "manufacturer plugin_name version format stems " -# "count_instances") -# -# _MarkerDescriptor = namedtuple("_MarkerDescriptor", -# "number location time_reference units name " -# "comments") -# -# _FileDescriptor = namedtuple("_FileDescriptor", "filename path") - - -class SessionDescriptor: - def __init__(self, **kwargs): - self.header = kwargs['header'] - self.files = kwargs['files'] - self.clips = kwargs['clips'] - self.plugins = kwargs['plugins'] - self.tracks = kwargs['tracks'] - self.markers = kwargs['markers'] - - -class HeaderDescriptor: - def __init__(self, **kwargs): - self.session_name = kwargs['session_name'] - self.sample_rate = kwargs['sample_rate'] - self.bit_depth = kwargs['bit_depth'] - self.start_timecode = kwargs['start_timecode'] - self.timecode_format = kwargs['timecode_format'] - self.timecode_drop_frame = kwargs['timecode_drop_frame'] - self.count_audio_tracks = kwargs['count_audio_tracks'] - self.count_clips = kwargs['count_clips'] - self.count_files = kwargs['count_files'] - - -class TrackDescriptor: - def __init__(self, **kwargs): - self.name = kwargs['name'] - self.comments = kwargs['comments'] - self.user_delay_samples = kwargs['user_delay_samples'] - self.state = kwargs['state'] - self.plugins = kwargs['plugins'] - self.clips = kwargs['clips'] - - -class FileDescriptor(dict): - pass - - -class TrackClipDescriptor: - def __init__(self, **kwargs): - self.channel = kwargs['channel'] - self.event = kwargs['event'] - self.clip_name = kwargs['clip_name'] - self.start_time = kwargs['start_time'] - self.end_time = kwargs['end_time'] - self.duration = kwargs['duration'] - self.timestamp = kwargs['timestamp'] - self.state = kwargs['state'] - - -class ClipDescriptor(dict): - pass - - -class PluginDescriptor(dict): - pass - - -class MarkerDescriptor: - def __init__(self, **kwargs): - self.number = kwargs['number'] - self.location = kwargs['location'] - self.time_reference = kwargs['time_reference'] - self.units = kwargs['units'] - self.name = kwargs['name'] - self.comments = kwargs['comments'] +from parsimonious.nodes import NodeVisitor +from .doc_entity import SessionDescriptor, HeaderDescriptor, TrackDescriptor, FileDescriptor, \ + TrackClipDescriptor, ClipDescriptor, PluginDescriptor, MarkerDescriptor class DocParserVisitor(NodeVisitor): @@ -215,11 +123,11 @@ class DocParserVisitor(NodeVisitor): @staticmethod def visit_marker_record(_, visited_children): return MarkerDescriptor(number=visited_children[0], - location=visited_children[3], - time_reference=visited_children[5], - units=visited_children[8], - name=visited_children[10], - comments=visited_children[12]) + location=visited_children[3], + time_reference=visited_children[5], + units=visited_children[8], + name=visited_children[10], + comments=visited_children[12]) @staticmethod def visit_formatted_clip_name(_, visited_children): diff --git a/tests/test_doc_entities.py b/tests/test_doc_entities.py new file mode 100644 index 0000000..ef5ca6e --- /dev/null +++ b/tests/test_doc_entities.py @@ -0,0 +1,24 @@ +import unittest +from ptulsconv.docparser.doc_entity import HeaderDescriptor +from fractions import Fraction + + +class DocParserTestCase(unittest.TestCase): + + def test_header(self): + header = HeaderDescriptor(session_name="Test Session", + sample_rate=48000.0, + bit_depth=24, + start_timecode="00:59:52:00", + timecode_format="30", + timecode_drop_frame=False, + count_audio_tracks=0, + count_clips=0, + count_files=0) + + self.assertEqual(header.session_name, "Test Session") + self.assertEqual(header.convert_timecode(header.start_timecode), Fraction((59 * 60 + 52) * 30, 30)) + + +if __name__ == '__main__': + unittest.main()