From 5e902d492691a09b2b3c41a64b6c87cea4f19daf Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 29 Dec 2018 12:29:46 -0800 Subject: [PATCH 1/5] Reorganized classes into separate files --- pycmx/__init__.py | 7 +- pycmx/channel_map.py | 4 +- pycmx/edit.py | 108 +++++++++++++ pycmx/edit_list.py | 61 +++++++ pycmx/event.py | 86 ++++++++++ pycmx/parse_cmx_events.py | 332 +------------------------------------- pycmx/transition.py | 91 +++++++++++ 7 files changed, 355 insertions(+), 334 deletions(-) create mode 100644 pycmx/edit.py create mode 100644 pycmx/edit_list.py create mode 100644 pycmx/event.py create mode 100644 pycmx/transition.py diff --git a/pycmx/__init__.py b/pycmx/__init__.py index 7e72d3a..eff4800 100644 --- a/pycmx/__init__.py +++ b/pycmx/__init__.py @@ -11,6 +11,7 @@ distribution. __version__ = '0.8' __author__ = 'Jamie Hardt' -from .parse_cmx_events import parse_cmx3600, Transition, Event, Edit -from . import parse_cmx_events - +from .parse_cmx_events import parse_cmx3600 +from .transition import Transition +from .event import Event +from .edit import Edit diff --git a/pycmx/channel_map.py b/pycmx/channel_map.py index 39e6295..d3764d8 100644 --- a/pycmx/channel_map.py +++ b/pycmx/channel_map.py @@ -4,12 +4,12 @@ from re import (compile, match) class ChannelMap: - """ Represents a set of all the channels to which an event applies. """ - _chan_map = { "V" : (True, False, False), + _chan_map = { + "V" : (True, False, False), "A" : (False, True, False), "A2" : (False, False, True), "AA" : (False, True, True), diff --git a/pycmx/edit.py b/pycmx/edit.py new file mode 100644 index 0000000..40fb2e4 --- /dev/null +++ b/pycmx/edit.py @@ -0,0 +1,108 @@ +# pycmx +# (c) 2018 Jamie Hardt + +from .transition import Transition +from .channel_map import ChannelMap + +class Edit: + """ + An individual source-to-record operation, with a source roll, source and + recorder timecode in and out, a transition and channels. + """ + def __init__(self, edit_statement, audio_ext_statement, clip_name_statement, source_file_statement, other_statements = []): + self.edit_statement = edit_statement + self.audio_ext = audio_ext_statement + self.clip_name_statement = clip_name_statement + self.source_file_statement = source_file_statement + self.other_statements = other_statements + + @property + def line_number(self): + """ + Get the line number for the "standard form" statement associated with + this edit. Line numbers a zero-indexed, such that the + "TITLE:" record is line zero. + """ + return self.edit_statement.line_number + + @property + def channels(self): + """ + Get the :obj:`ChannelMap` object associated with this Edit. + """ + cm = ChannelMap() + cm._append_event(self.edit_statement.channels) + if self.audio_ext != None: + cm._append_ext(self.audio_ext) + return cm + + @property + def transition(self): + """ + Get the :obj:`Transition` object associated with this edit. + """ + return Transition(self.edit_statement.trans, self.edit_statement.trans_op) + + @property + def source_in(self): + """ + Get the source in timecode. + """ + return self.edit_statement.source_in + + @property + def source_out(self): + """ + Get the source out timecode. + """ + + return self.edit_statement.source_out + + @property + def record_in(self): + """ + Get the record in timecode. + """ + + return self.edit_statement.record_in + + @property + def record_out(self): + """ + Get the record out timecode. + """ + + return self.edit_statement.record_out + + @property + def source(self): + """ + Get the source column. This is the 8, 32 or 128-character string on the + event record line, this usually references the tape name of the source. + """ + return self.edit_statement.source + + + @property + def source_file(self): + """ + Get the source file, as attested by a "* SOURCE FILE" remark on the + EDL. This will return None if the information is not present. + """ + if self.source_file_statement is None: + return None + else: + return self.source_file_statement.filename + + + @property + def clip_name(self): + """ + Get the clip name, as attested by a "* FROM CLIP NAME" or "* TO CLIP + NAME" remark on the EDL. This will return None if the information is + not present. + """ + if self.clip_name_statement != None: + return self.clip_name_statement.name + else: + return None diff --git a/pycmx/edit_list.py b/pycmx/edit_list.py new file mode 100644 index 0000000..d73a857 --- /dev/null +++ b/pycmx/edit_list.py @@ -0,0 +1,61 @@ +# pycmx +# (c) 2018 Jamie Hardt + +from .parse_cmx_statements import (StmtUnrecognized, StmtFCM, StmtEvent) +from .event import Event + +class EditList: + """ + Represents an entire edit decision list as returned by `parse_cmx3600()`. + + """ + def __init__(self, statements): + self.title_statement = statements[0] + self.event_statements = statements[1:] + + @property + def title(self): + """ + The title of this edit list, as attensted by the 'TITLE:' statement on + the first line. + """ + 'The title of the edit list' + return self.title_statement.title + + + @property + def unrecognized_statements(self): + """ + A generator for all the unrecognized statements in the list. + """ + for s in self.event_statements: + if type(s) is StmtUnrecognized: + yield s + + + @property + def events(self): + 'A generator for all the events in the edit list' + is_drop = None + current_event_num = None + event_statements = [] + for stmt in self.event_statements: + if type(stmt) is StmtFCM: + is_drop = stmt.drop + elif type(stmt) is StmtEvent: + if current_event_num is None: + current_event_num = stmt.event + event_statements.append(stmt) + else: + if current_event_num != stmt.event: + yield Event(statements=event_statements) + event_statements = [stmt] + current_event_num = stmt.event + else: + event_statements.append(stmt) + + else: + event_statements.append(stmt) + + yield Event(statements=event_statements) + diff --git a/pycmx/event.py b/pycmx/event.py new file mode 100644 index 0000000..accc5c5 --- /dev/null +++ b/pycmx/event.py @@ -0,0 +1,86 @@ +# pycmx +# (c) 2018 Jamie Hardt + +from .parse_cmx_statements import (StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized) +from .edit import Edit + +class Event: + """ + Represents a collection of :class:`Edit`s, all with the same event number. + """ + + def __init__(self, statements): + self.statements = statements + + @property + def number(self): + """ + Return the event number. + """ + return int(self._edit_statements()[0].event) + + @property + def edits(self): + """ + Returns the edits. Most events will have a single edit, a single event + will have multiple edits when a dissolve, wipe or key transition needs + to be performed. + """ + edits_audio = list( self._statements_with_audio_ext() ) + clip_names = self._clip_name_statements() + source_files= self._source_file_statements() + + the_zip = [edits_audio] + + if len(edits_audio) == 2: + cn = [None, None] + for clip_name in clip_names: + if clip_name.affect == 'from': + cn[0] = clip_name + elif clip_name.affect == 'to': + cn[1] = clip_name + + the_zip.append(cn) + + else: + if len(edits_audio) == len(clip_names): + the_zip.append(clip_names) + else: + the_zip.append([None] * len(edits_audio) ) + + if len(edits_audio) == len(source_files): + the_zip.append(source_files) + elif len(source_files) == 1: + the_zip.append( source_files * len(edits_audio) ) + else: + the_zip.append([None] * len(edits_audio) ) + + + return [ Edit(e1[0],e1[1],n1,s1) for (e1,n1,s1) in zip(*the_zip) ] + + @property + def unrecognized_statements(self): + """ + A generator for all the unrecognized statements in the event. + """ + for s in self.statements: + if type(s) is StmtUnrecognized: + yield s + + def _edit_statements(self): + return [s for s in self.statements if type(s) is StmtEvent] + + def _clip_name_statements(self): + return [s for s in self.statements if type(s) is StmtClipName] + + def _source_file_statements(self): + return [s for s in self.statements if type(s) is StmtSourceFile] + + def _statements_with_audio_ext(self): + for (s1, s2) in zip(self.statements, self.statements[1:]): + if type(s1) is StmtEvent and type(s2) is StmtAudioExt: + yield (s1,s2) + elif type(s1) is StmtEvent: + yield (s1, None) + + diff --git a/pycmx/parse_cmx_events.py b/pycmx/parse_cmx_events.py index 9fcfd37..c193449 100644 --- a/pycmx/parse_cmx_events.py +++ b/pycmx/parse_cmx_events.py @@ -1,12 +1,9 @@ # pycmx # (c) 2018 Jamie Hardt -from .parse_cmx_statements import (parse_cmx3600_statements, - StmtEvent, StmtFCM, StmtTitle, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized) - -from .channel_map import ChannelMap - from collections import namedtuple +from .parse_cmx_statements import (parse_cmx3600_statements, StmtEvent,StmtFCM ) +from .edit_list import EditList def parse_cmx3600(f): """ @@ -16,333 +13,10 @@ def parse_cmx3600(f): f : a file-like object, anything that's readlines-able. Returns: - An :obj:`EditList`. + An :class:`EditList`. """ statements = parse_cmx3600_statements(f) return EditList(statements) -class EditList: - """ - Represents an entire edit decision list as returned by `parse_cmx3600()`. - - """ - def __init__(self, statements): - self.title_statement = statements[0] - self.event_statements = statements[1:] - - @property - def title(self): - """ - The title of this edit list, as attensted by the 'TITLE:' statement on - the first line. - """ - 'The title of the edit list' - return self.title_statement.title - - - @property - def unrecognized_statements(self): - """ - A generator for all the unrecognized statements in the list. - """ - for s in self.event_statements: - if type(s) is StmtUnrecognized: - yield s - - - @property - def events(self): - 'A generator for all the events in the edit list' - is_drop = None - current_event_num = None - event_statements = [] - for stmt in self.event_statements: - if type(stmt) is StmtFCM: - is_drop = stmt.drop - elif type(stmt) is StmtEvent: - if current_event_num is None: - current_event_num = stmt.event - event_statements.append(stmt) - else: - if current_event_num != stmt.event: - yield Event(statements=event_statements) - event_statements = [stmt] - current_event_num = stmt.event - else: - event_statements.append(stmt) - - else: - event_statements.append(stmt) - - yield Event(statements=event_statements) - - -class Edit: - def __init__(self, edit_statement, audio_ext_statement, clip_name_statement, source_file_statement, other_statements = []): - self.edit_statement = edit_statement - self.audio_ext = audio_ext_statement - self.clip_name_statement = clip_name_statement - self.source_file_statement = source_file_statement - self.other_statements = other_statements - - @property - def line_number(self): - """ - Get the line number for the "standard form" statement associated with - this edit. Line numbers a zero-indexed, such that the - "TITLE:" record is line zero. - """ - return self.edit_statement.line_number - - @property - def channels(self): - """ - Get the :obj:`ChannelMap` object associated with this Edit. - """ - cm = ChannelMap() - cm._append_event(self.edit_statement.channels) - if self.audio_ext != None: - cm._append_ext(self.audio_ext) - return cm - - @property - def transition(self): - """ - Get the :obj:`Transition` object associated with this edit. - """ - return Transition(self.edit_statement.trans, self.edit_statement.trans_op) - - @property - def source_in(self): - """ - Get the source in timecode. - """ - return self.edit_statement.source_in - - @property - def source_out(self): - """ - Get the source out timecode. - """ - - return self.edit_statement.source_out - - @property - def record_in(self): - """ - Get the record in timecode. - """ - - return self.edit_statement.record_in - - @property - def record_out(self): - """ - Get the record out timecode. - """ - - return self.edit_statement.record_out - - @property - def source(self): - """ - Get the source column. This is the 8, 32 or 128-character string on the - event record line, this usually references the tape name of the source. - """ - return self.edit_statement.source - - - @property - def source_file(self): - """ - Get the source file, as attested by a "* SOURCE FILE" remark on the - EDL. This will return None if the information is not present. - """ - if self.source_file_statement is None: - return None - else: - return self.source_file_statement.filename - - - @property - def clip_name(self): - """ - Get the clip name, as attested by a "* FROM CLIP NAME" or "* TO CLIP - NAME" remark on the EDL. This will return None if the information is - not present. - """ - if self.clip_name_statement != None: - return self.clip_name_statement.name - else: - return None - - - -class Event: - """ - Represents a collection of :obj:`Edit`s, all with the same event number. - """ - - def __init__(self, statements): - self.statements = statements - - @property - def number(self): - """Return the event number.""" - return int(self._edit_statements()[0].event) - - @property - def edits(self): - """ - Returns the edits. Most events will have a single edit, a single event - will have multiple edits when a dissolve, wipe or key transition needs - to be performed. - """ - edits_audio = list( self._statements_with_audio_ext() ) - clip_names = self._clip_name_statements() - source_files= self._source_file_statements() - - the_zip = [edits_audio] - - if len(edits_audio) == 2: - cn = [None, None] - for clip_name in clip_names: - if clip_name.affect == 'from': - cn[0] = clip_name - elif clip_name.affect == 'to': - cn[1] = clip_name - - the_zip.append(cn) - - else: - if len(edits_audio) == len(clip_names): - the_zip.append(clip_names) - else: - the_zip.append([None] * len(edits_audio) ) - - if len(edits_audio) == len(source_files): - the_zip.append(source_files) - elif len(source_files) == 1: - the_zip.append( source_files * len(edits_audio) ) - else: - the_zip.append([None] * len(edits_audio) ) - - - return [ Edit(e1[0],e1[1],n1,s1) for (e1,n1,s1) in zip(*the_zip) ] - - @property - def unrecognized_statements(self): - """ - A generator for all the unrecognized statements in the event. - """ - for s in self.statements: - if type(s) is StmtUnrecognized: - yield s - - def _edit_statements(self): - return [s for s in self.statements if type(s) is StmtEvent] - - def _clip_name_statements(self): - return [s for s in self.statements if type(s) is StmtClipName] - - def _source_file_statements(self): - return [s for s in self.statements if type(s) is StmtSourceFile] - - def _statements_with_audio_ext(self): - for (s1, s2) in zip(self.statements, self.statements[1:]): - if type(s1) is StmtEvent and type(s2) is StmtAudioExt: - yield (s1,s2) - elif type(s1) is StmtEvent: - yield (s1, None) - - - -class Transition: - """Represents a CMX transition, a wipe, dissolve or cut.""" - - Cut = "C" - - Dissolve = "D" - - 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): - """ - Return the kind of transition: Cut, Wipe, etc - """ - if self.cut: - return Transition.Cut - elif self.dissolve: - return Transition.Dissolve - elif self.wipe: - return Transition.Wipe - elif self.key_background: - return Transition.KeyBackground - elif self.key_foreground: - return Transition.Key - elif self.key_out: - return Transition.KeyOut - - @property - def cut(self): - "`True` if this transition is a cut." - return self.transition == 'C' - - @property - def dissolve(self): - "`True` if this traansition is a dissolve." - return self.transition == 'D' - - - @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 diff --git a/pycmx/transition.py b/pycmx/transition.py new file mode 100644 index 0000000..d748717 --- /dev/null +++ b/pycmx/transition.py @@ -0,0 +1,91 @@ +# pycmx +# (c) 2018 Jamie Hardt + + +class Transition: + """ + A CMX transition: a wipe, dissolve or cut. + """ + + Cut = "C" + Dissolve = "D" + 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): + """ + Return the kind of transition: Cut, Wipe, etc + """ + if self.cut: + return Transition.Cut + elif self.dissolve: + return Transition.Dissolve + elif self.wipe: + return Transition.Wipe + elif self.key_background: + return Transition.KeyBackground + elif self.key_foreground: + return Transition.Key + elif self.key_out: + return Transition.KeyOut + + @property + def cut(self): + "`True` if this transition is a cut." + return self.transition == 'C' + + @property + def dissolve(self): + "`True` if this traansition is a dissolve." + return self.transition == 'D' + + + @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 edit is a key background." + return self.transition == KeyBackground + + @property + def key_foreground(self): + "`True` if this edit is a key foreground." + return self.transition == Key + + @property + def key_out(self): + """ + `True` if this edit is a key out. This material will removed from + the key foreground and replaced with the key background. + """ + return self.transition == KeyOut From 348962c3f72d53cbc9aa3e1b40e127f73b515e52 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 29 Dec 2018 12:53:08 -0800 Subject: [PATCH 2/5] Code Style/Blank lines --- pycmx/parse_cmx_events.py | 1 + pycmx/parse_cmx_statements.py | 2 +- pycmx/transition.py | 8 +------- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/pycmx/parse_cmx_events.py b/pycmx/parse_cmx_events.py index c193449..e9c10d2 100644 --- a/pycmx/parse_cmx_events.py +++ b/pycmx/parse_cmx_events.py @@ -2,6 +2,7 @@ # (c) 2018 Jamie Hardt from collections import namedtuple + from .parse_cmx_statements import (parse_cmx3600_statements, StmtEvent,StmtFCM ) from .edit_list import EditList diff --git a/pycmx/parse_cmx_statements.py b/pycmx/parse_cmx_statements.py index b7de657..ee4386d 100644 --- a/pycmx/parse_cmx_statements.py +++ b/pycmx/parse_cmx_statements.py @@ -1,12 +1,12 @@ # pycmx # (c) 2018 Jamie Hardt -from .util import collimate import re import sys from collections import namedtuple from itertools import count +from .util import collimate StmtTitle = namedtuple("Title",["title","line_number"]) StmtFCM = namedtuple("FCM",["drop","line_number"]) diff --git a/pycmx/transition.py b/pycmx/transition.py index d748717..714cd09 100644 --- a/pycmx/transition.py +++ b/pycmx/transition.py @@ -1,12 +1,11 @@ # pycmx # (c) 2018 Jamie Hardt - class Transition: """ A CMX transition: a wipe, dissolve or cut. """ - + Cut = "C" Dissolve = "D" Wipe = "W" @@ -14,12 +13,9 @@ class Transition: Key = "K" KeyOut = "KO" - def __init__(self, transition, operand): self.transition = transition self.operand = operand - self.name = '' - @property def kind(self): @@ -49,13 +45,11 @@ class Transition: "`True` if this traansition is a dissolve." return self.transition == 'D' - @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. From 21f8880099ee181c82e95b880e9d470f50296d76 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 29 Dec 2018 12:53:18 -0800 Subject: [PATCH 3/5] Update edit.py Black and Aux Source events --- pycmx/edit.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pycmx/edit.py b/pycmx/edit.py index 40fb2e4..7b226df 100644 --- a/pycmx/edit.py +++ b/pycmx/edit.py @@ -82,6 +82,19 @@ class Edit: """ return self.edit_statement.source + @property + def black(self): + """ + Black video or silence should be used as the source for this event. + """ + return self.source == "BL" + + @property + def aux_source(self): + """ + An auxiliary source is the source of this event. + """ + return self.source == "AX" @property def source_file(self): @@ -102,7 +115,7 @@ class Edit: NAME" remark on the EDL. This will return None if the information is not present. """ - if self.clip_name_statement != None: - return self.clip_name_statement.name - else: + if self.clip_name_statement is None: return None + else: + return self.clip_name_statement.name From 914e8d5525f8187b91f41c3ee8ed114592f25da3 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 29 Dec 2018 13:14:52 -0800 Subject: [PATCH 4/5] Blank lines --- pycmx/parse_cmx_events.py | 2 -- tests/test_parse.py | 1 - 2 files changed, 3 deletions(-) diff --git a/pycmx/parse_cmx_events.py b/pycmx/parse_cmx_events.py index e9c10d2..f5c6549 100644 --- a/pycmx/parse_cmx_events.py +++ b/pycmx/parse_cmx_events.py @@ -19,5 +19,3 @@ def parse_cmx3600(f): statements = parse_cmx3600_statements(f) return EditList(statements) - - diff --git a/tests/test_parse.py b/tests/test_parse.py index 734110e..8653e04 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -98,4 +98,3 @@ class TestParse(TestCase): self.assertEqual( events[14].edits[0].line_number, 45) self.assertEqual( events[180].edits[0].line_number, 544) - From ce31cbb87971d7d213d493058eca7b59180d408f Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 29 Dec 2018 14:06:22 -0800 Subject: [PATCH 5/5] Read transition names --- pycmx/edit.py | 15 ++++++++++----- pycmx/event.py | 21 ++++++++++++++++----- pycmx/transition.py | 3 ++- tests/test_parse.py | 5 +++++ 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/pycmx/edit.py b/pycmx/edit.py index 7b226df..bbbbab3 100644 --- a/pycmx/edit.py +++ b/pycmx/edit.py @@ -3,18 +3,19 @@ from .transition import Transition from .channel_map import ChannelMap +from .parse_cmx_statements import StmtEffectsName class Edit: """ An individual source-to-record operation, with a source roll, source and recorder timecode in and out, a transition and channels. """ - def __init__(self, edit_statement, audio_ext_statement, clip_name_statement, source_file_statement, other_statements = []): + def __init__(self, edit_statement, audio_ext_statement, clip_name_statement, source_file_statement, trans_name_statement = None): self.edit_statement = edit_statement self.audio_ext = audio_ext_statement self.clip_name_statement = clip_name_statement self.source_file_statement = source_file_statement - self.other_statements = other_statements + self.trans_name_statement = trans_name_statement @property def line_number(self): @@ -41,8 +42,11 @@ class Edit: """ Get the :obj:`Transition` object associated with this edit. """ - return Transition(self.edit_statement.trans, self.edit_statement.trans_op) - + if self.trans_name_statement: + return Transition(self.edit_statement.trans, self.edit_statement.trans_op, self.trans_name_statement.name) + else: + return Transition(self.edit_statement.trans, self.edit_statement.trans_op, None) + @property def source_in(self): """ @@ -107,7 +111,6 @@ class Edit: else: return self.source_file_statement.filename - @property def clip_name(self): """ @@ -119,3 +122,5 @@ class Edit: return None else: return self.clip_name_statement.name + + diff --git a/pycmx/event.py b/pycmx/event.py index accc5c5..f43e8f8 100644 --- a/pycmx/event.py +++ b/pycmx/event.py @@ -1,7 +1,7 @@ # pycmx # (c) 2018 Jamie Hardt -from .parse_cmx_statements import (StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized) +from .parse_cmx_statements import (StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized, StmtEffectsName) from .edit import Edit class Event: @@ -29,7 +29,7 @@ class Event: edits_audio = list( self._statements_with_audio_ext() ) clip_names = self._clip_name_statements() source_files= self._source_file_statements() - + the_zip = [edits_audio] if len(edits_audio) == 2: @@ -41,7 +41,6 @@ class Event: cn[1] = clip_name the_zip.append(cn) - else: if len(edits_audio) == len(clip_names): the_zip.append(clip_names) @@ -54,9 +53,18 @@ class Event: the_zip.append( source_files * len(edits_audio) ) else: the_zip.append([None] * len(edits_audio) ) + + # attach trans name to last event + try: + trans_statement = self._trans_name_statements()[0] + trans_names = [None] * (len(edits_audio) - 1) + trans_names.append(trans_statement) + the_zip.append(trans_names) + except IndexError: + the_zip.append([None] * len(edits_audio) ) - return [ Edit(e1[0],e1[1],n1,s1) for (e1,n1,s1) in zip(*the_zip) ] + return [ Edit(e1[0],e1[1],n1,s1,u1) for (e1,n1,s1,u1) in zip(*the_zip) ] @property def unrecognized_statements(self): @@ -65,7 +73,10 @@ class Event: """ for s in self.statements: if type(s) is StmtUnrecognized: - yield s + yield s + + def _trans_name_statements(self): + return [s for s in self.statements if type(s) is StmtEffectsName] def _edit_statements(self): return [s for s in self.statements if type(s) is StmtEvent] diff --git a/pycmx/transition.py b/pycmx/transition.py index 714cd09..815f7d0 100644 --- a/pycmx/transition.py +++ b/pycmx/transition.py @@ -13,9 +13,10 @@ class Transition: Key = "K" KeyOut = "KO" - def __init__(self, transition, operand): + def __init__(self, transition, operand, name=None): self.transition = transition self.operand = operand + self.name = name @property def kind(self): diff --git a/tests/test_parse.py b/tests/test_parse.py index 8653e04..58bc0cd 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -98,3 +98,8 @@ class TestParse(TestCase): self.assertEqual( events[14].edits[0].line_number, 45) self.assertEqual( events[180].edits[0].line_number, 544) + def test_transition_name(self): + with open("tests/edls/test_25.edl","r") as f: + edl = pycmx.parse_cmx3600(f) + events = list(edl.events) + self.assertEqual( events[4].edits[1].transition.name , "CROSS DISSOLVE" )