diff --git a/pycmx/cmx_event.py b/pycmx/cmx_event.py deleted file mode 100644 index 921e9da..0000000 --- a/pycmx/cmx_event.py +++ /dev/null @@ -1,137 +0,0 @@ - -class CmxEvent: - """Represents a source-record event. - -Aside from exposing properites related to the raw CMX event itself, -(the `source_start`, `source_finish`, `transition` etc.) the event -also contains contextual information from the parsed CMX list, such as -`clip_name` and the frame counting mode in effect on the event. - """ - - def __init__(self,title,number,clip_name,source_name,channels, - transition,source_start,source_finish, - record_start, record_finish, fcm_drop, remarks = [] , - unrecognized = [], line_number = None): - self.title = title - self.number = number - self.clip_name = clip_name - self.source_name = source_name - self.channels = channels - self.transition = transition - self.source_start = source_start - self.source_finish = source_finish - self.record_start = record_start - self.record_finish = record_finish - self.fcm_drop = fcm_drop - self.remarks = remarks - self.unrecgonized = unrecognized - self.black = (source_name == 'BL') - self.aux_source = (source_name == 'AX') - self.line_number = line_number - - - def accept_statement(self, statement): - """Used by the parser to attach clip names and notes to this event.""" - statement_type = type(statement).__name__ - if statement_type == 'AudioExt': - self.channels.appendExt(statement) - elif statement_type == 'Remark': - self.remarks.append(statement.text) - elif statement_type == 'SourceFile': - self.source_name = statement.filename - elif statement_type == 'ClipName': - self.clip_name = statement.name - elif statement_type == 'EffectsName': - self.transition.name = statement.name - - def __repr__(self): - return f"""CmxEvent(title={self.title.__repr__()},number={self.number.__repr__()},\ -clip_name={self.clip_name.__repr__()},source_name={self.source_name.__repr__()},\ -channels={self.channels.__repr__()},transition={self.transition.__repr__()},\ -source_start={self.source_start.__repr__()},source_finish={self.source_finish.__repr__()},\ -record_start={self.source_start.__repr__()},record_finish={self.record_finish.__repr__()},\ -fcm_drop={self.fcm_drop.__repr__()},remarks={self.remarks.__repr__()},line_number={self.line_number.__repr__()})""" - - -class CmxTransition: - """Represents a CMX transition, a wipe, dissolve or cut.""" - - Cut = "C" - Dissolve = "V" - Wipe = "W" - KeyBackground = "KB" - Key = "K" - KeyOut = "KO" - - def __init__(self, transition, operand): - self.transition = transition - self.operand = operand - self.name = '' - - - @property - def kind(self): - if self.cut: - return Cut - elif self.dissove: - return Dissolve - elif self.wipe: - return Wipe - elif self.key_background: - return KeyBackground - elif self.key_foreground: - return Key - elif self.key_out: - return KeyOut - - @property - def cut(self): - "`True` if this transition is a cut." - return self.transition == Cut - - @property - def dissolve(self): - "`True` if this traansition is a dissolve." - return self.transition == Dissolve - - - @property - def wipe(self): - "`True` if this transition is a wipe." - return self.transition.startswith('W') - - - @property - def effect_duration(self): - """"`The duration of this transition, in frames of the record target. - - In the event of a key event, this is the duration of the fade in. - """ - return int(self.operand) - - @property - def wipe_number(self): - "Wipes are identified by a particular number." - if self.wipe: - return int(self.transition[1:]) - else: - return None - - @property - def key_background(self): - "`True` if this is a key background event." - return self.transition == KeyBackground - - @property - def key_foreground(self): - "`True` if this is a key foreground event." - return self.transition == Key - - @property - def key_out(self): - "`True` if this is a key out event." - return self.transition == KeyOut - - def __repr__(self): - return f"""CmxTransition(transition={self.transition.__repr__()},operand={self.operand.__repr__()})""" - diff --git a/pycmx/parse_cmx.py b/pycmx/parse_cmx.py deleted file mode 100644 index 9a33854..0000000 --- a/pycmx/parse_cmx.py +++ /dev/null @@ -1,139 +0,0 @@ -# pycmx -# (c) 2018 Jamie Hardt - -from .util import NamedTupleParser - -from .parse_cmx_statements import parse_cmx3600_statements -from .cmx_event import CmxEvent, CmxTransition -from collections import namedtuple - -from re import compile, match - -class CmxChannelMap: - """ - Represents a set of all the channels to which an event applies. - """ - - chan_map = { "V" : (True, False, False), - "A" : (False, True, False), - "A2" : (False, False, True), - "AA" : (False, True, True), - "B" : (True, True, False), - "AA/V" : (True, True, True), - "A2/V" : (True, False, True) - } - - def __init__(self, v=False, audio_channels=set()): - self._audio_channel_set = audio_channels - self.v = v - - @property - def a1(self): - return self.get_audio_channel(1) - - @a1.setter - def a1(self,val): - self.set_audio_channel(1,val) - - @property - def a2(self): - return self.get_audio_channel(2) - - @a2.setter - def a2(self,val): - self.set_audio_channel(2,val) - - @property - def a3(self): - return self.get_audio_channel(3) - - @a3.setter - def a3(self,val): - self.set_audio_channel(3,val) - - @property - def a4(self): - return self.get_audio_channel(4) - - @a4.setter - def a4(self,val): - self.set_audio_channel(4,val) - - def get_audio_channel(self,chan_num): - return (chan_num in self._audio_channel_set) - - def set_audio_channel(self,chan_num,enabled): - if enabled: - self._audio_channel_set.add(chan_num) - elif self.get_audio_channel(chan_num): - self._audio_channel_set.remove(chan_num) - - def appendEvent(self, event_str): - alt_channel_re = compile('^A(\d+)') - if event_str in self.chan_map: - channels = self.chan_map[event_str] - self.v = channels[0] - self.a1 = channels[1] - self.a2 = channels[2] - else: - matchresult = match(alt_channel_re, event_str) - if matchresult: - self.set_audio_channel(int( matchresult.group(1)), True ) - - def appendExt(self, audio_ext): - self.a3 = ext.audio3 - self.a4 = ext.audio4 - - def __repr__(self): - return f"CmxChannelMap(v={self.v.__repr__()}, audio_channels={self._audio_channel_set.__repr__()})" - - -def parse_cmx3600(file): - """Accepts the path to a CMX EDL and returns a list of all events contained therein.""" - statements = parse_cmx3600_statements(file) - parser = NamedTupleParser(statements) - parser.expect('Title') - title = parser.current_token.title - return event_list(title, parser) - -def event_list(title, parser): - state = {"fcm_drop" : False} - - events_result = [] - this_event = None - - while not parser.at_end(): - if parser.accept('FCM'): - state['fcm_drop'] = parser.current_token.drop - elif parser.accept('Event'): - if this_event != None: - events_result.append(this_event) - - raw_event = parser.current_token - channels = CmxChannelMap(v=False, audio_channels=set([])) - channels.appendEvent(raw_event.channels) - - this_event = CmxEvent(title=title,number=int(raw_event.event), clip_name=None , - source_name=raw_event.source, - channels=channels, - transition=CmxTransition(raw_event.trans, raw_event.trans_op), - source_start= raw_event.source_in, - source_finish= raw_event.source_out, - record_start= raw_event.record_in, - record_finish= raw_event.record_out, - fcm_drop= state['fcm_drop'], - line_number = raw_event.line_number) - elif parser.accept('AudioExt') or parser.accept('ClipName') or \ - parser.accept('SourceFile') or parser.accept('EffectsName') or \ - parser.accept('Remark'): - this_event.accept_statement(parser.current_token) - elif parser.accept('Trailer'): - break - else: - parser.next_token() - - if this_event != None: - events_result.append(this_event) - - return events_result - diff --git a/pycmx/parse_cmx_events.py b/pycmx/parse_cmx_events.py new file mode 100644 index 0000000..d9f0619 --- /dev/null +++ b/pycmx/parse_cmx_events.py @@ -0,0 +1,13 @@ +# pycmx +# (c) 2018 Jamie Hardt + +def events(statements=[]): + if statements[0]. + +class Event: + def __init__(self, statements): + self.statements = statements + + def number(): + return statements[0].event + diff --git a/pycmx/parse_cmx_statements.py b/pycmx/parse_cmx_statements.py index 282cf81..67b2dba 100644 --- a/pycmx/parse_cmx_statements.py +++ b/pycmx/parse_cmx_statements.py @@ -1,7 +1,5 @@ - -# Parsed Statement Data Structures -# -# These represent individual lines that have been typed and have undergone some light symbolic parsing. +# pycmx +# (c) 2018 Jamie Hardt from .util import collimate import re @@ -29,7 +27,8 @@ def parse_cmx3600_statements(path): with open(path,'r') as file: lines = file.readlines() line_numbers = count() - return [parse_cmx3600_line(line.strip(), line_number) for (line, line_number) in zip(lines,line_numbers)] + return [parse_cmx3600_line(line.strip(), line_number) \ + for (line, line_number) in zip(lines,line_numbers)] def edl_column_widths(event_field_length, source_field_length): return [event_field_length,2, source_field_length,1, diff --git a/pycmx/util.py b/pycmx/util.py index 600b510..f262ae6 100644 --- a/pycmx/util.py +++ b/pycmx/util.py @@ -17,8 +17,8 @@ def collimate(a_string, column_widths): class NamedTupleParser: """ -Accepts a list of namedtuple and the client can step through the list with -parser operations such as `accept()` and `expect()` + Accepts a list of namedtuple and the client can step through the list with + parser operations such as `accept()` and `expect()` """ def __init__(self, tuple_list): @@ -26,8 +26,10 @@ parser operations such as `accept()` and `expect()` self.current_token = None def peek(self): - """Returns the token to come after the `current_token` without -popping the current token.""" + """ + Returns the token to come after the `current_token` without + popping the current token. + """ return self.tokens[0] def at_end(self): @@ -40,8 +42,10 @@ popping the current token.""" self.tokens = self.tokens[1:] def accept(self, type_name): - """If the next token.__name__ is `type_name`, returns true and advances -to the next token with `next_token()`.""" + """ + If the next token.__name__ is `type_name`, returns true and advances + to the next token with `next_token()`. + """ if self.at_end(): return False elif (type(self.peek()).__name__ == type_name ): @@ -52,8 +56,9 @@ to the next token with `next_token()`.""" def expect(self, type_name): """ -If the next token.__name__ is `type_name`, the parser is advanced. -If it is not, an assertion failure occurs.""" + If the next token.__name__ is `type_name`, the parser is advanced. + If it is not, an assertion failure occurs. + """ assert( self.accept(type_name) )