Flake8 lints, flake8 linting fully activated.

This commit is contained in:
Jamie Hardt
2025-01-05 12:03:38 -08:00
parent 8bb6dad1da
commit 2adff6dd01
12 changed files with 141 additions and 131 deletions

5
.flake8 Normal file
View File

@@ -0,0 +1,5 @@
[flake8]
per-file-ignores =
pycmx/__init__.py: F401

View File

@@ -32,8 +32,7 @@ jobs:
run: | run: |
# stop the build if there are Python syntax errors or undefined names # stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --max-complexity=10 --max-line-length=79 --statistics
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest - name: Test with pytest
run: | run: |
pytest pytest

View File

@@ -12,6 +12,7 @@ logging.basicConfig(format=FORMAT)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def all_video_edits(edl): def all_video_edits(edl):
for event in edl.events: for event in edl.events:
for edit in event.edits: for edit in event.edits:
@@ -30,46 +31,49 @@ def get_scene_name(edit, pattern):
else: else:
return edit.clip_name return edit.clip_name
def output_cmx(outfile, out_list): def output_cmx(outfile, out_list):
outfile.write("TITLE: SCENE LIST\r\n") outfile.write("TITLE: SCENE LIST\r\n")
outfile.write("FCM: NON-DROP FRAME\r\n") outfile.write("FCM: NON-DROP FRAME\r\n")
for o in out_list: for o in out_list:
line = "%03i AX V C 00:00:00:00 00:00:00:00 %s %s\r\n" % (0, o['start'],o['end']) line = "%03i AX V C 00:00:00:00 00:00:00:00 %s %s\r\n" % (
0, o['start'], o['end'])
outfile.write(line) outfile.write(line)
outfile.write("* FROM CLIP NAME: %s\r\n" % (o['scene']) ) outfile.write("* FROM CLIP NAME: %s\r\n" % (o['scene']))
def output_cols(outfile, out_list): def output_cols(outfile, out_list):
for o in out_list: for o in out_list:
outfile.write("%-12s\t%-12s\t%s\n" % (o['start'], o['end'], o['scene'] )) outfile.write("%-12s\t%-12s\t%s\n" %
(o['start'], o['end'], o['scene']))
def scene_list(infile, outfile, out_format, pattern): def scene_list(infile, outfile, out_format, pattern):
edl = pycmx.parse_cmx3600(infile) edl = pycmx.parse_cmx3600(infile)
current_scene_name = None current_scene_name = None
grouped_edits = [ ] grouped_edits = []
for edit in all_video_edits(edl): for edit in all_video_edits(edl):
this_scene_name = get_scene_name(edit, pattern) this_scene_name = get_scene_name(edit, pattern)
if this_scene_name is not None: if this_scene_name is not None:
if current_scene_name != this_scene_name: if current_scene_name != this_scene_name:
grouped_edits.append([ ]) grouped_edits.append([])
current_scene_name = this_scene_name current_scene_name = this_scene_name
grouped_edits[-1].append(edit) grouped_edits[-1].append(edit)
out_list = [ ] out_list = []
for group in grouped_edits: for group in grouped_edits:
out_list.append({ out_list.append({
'start': group[0].record_in, 'start': group[0].record_in,
'end': group[-1].record_out, 'end': group[-1].record_out,
'scene': get_scene_name(group[0], pattern ) } 'scene': get_scene_name(group[0], pattern)}
) )
if out_format == 'cmx': if out_format == 'cmx':
output_cmx(outfile, out_list) output_cmx(outfile, out_list)
if out_format == 'cols': if out_format == 'cols':
@@ -80,23 +84,24 @@ def scene_list(infile, outfile, out_format, pattern):
def scene_list_cli(): def scene_list_cli():
parser = argparse.ArgumentParser(description= parser = argparse.ArgumentParser(
'Read video events from an input CMX EDL and output events merged into scenes.') description='Read video events from an input CMX EDL and output events merged into scenes.')
parser.add_argument('-o','--outfile', default=sys.stdout, type=argparse.FileType('w'), parser.add_argument('-o', '--outfile', default=sys.stdout, type=argparse.FileType('w'),
help='Output file. Default is stdout.') help='Output file. Default is stdout.')
parser.add_argument('-f','--format', default='cmx', type=str, parser.add_argument('-f', '--format', default='cmx', type=str,
help='Output format. Options are cols and cmx, cmx is the default.') help='Output format. Options are cols and cmx, cmx is the default.')
parser.add_argument('-p','--pattern', default='V?([A-Z]*[0-9]+)', parser.add_argument('-p', '--pattern', default='V?([A-Z]*[0-9]+)',
help='RE pattern for extracting scene name from clip name. The default is "V?([A-Z]*[0-9]+)". ' + \ help='RE pattern for extracting scene name from clip name. The default is "V?([A-Z]*[0-9]+)". ' +
'This pattern will be matched case-insensitively.') 'This pattern will be matched case-insensitively.')
parser.add_argument('input_edl', default=sys.stdin, type=argparse.FileType('r'), nargs='?', parser.add_argument('input_edl', default=sys.stdin, type=argparse.FileType('r'), nargs='?',
help='Input file. Default is stdin.') help='Input file. Default is stdin.')
args = parser.parse_args() args = parser.parse_args()
infile = args.input_edl infile = args.input_edl
scene_list(infile=infile, outfile=args.outfile , out_format=args.format, pattern=args.pattern) scene_list(infile=infile, outfile=args.outfile,
out_format=args.format, pattern=args.pattern)
if __name__ == '__main__': if __name__ == '__main__':
scene_list_cli() scene_list_cli()

View File

@@ -2,13 +2,11 @@
""" """
pycmx is a parser for CMX 3600-style EDLs. pycmx is a parser for CMX 3600-style EDLs.
This module (c) 2023 Jamie Hardt. For more information on your rights to This module (c) 2025 Jamie Hardt. For more information on your rights to
copy and reuse this software, refer to the LICENSE file included with the copy and reuse this software, refer to the LICENSE file included with the
distribution. distribution.
""" """
__version__ = '1.2.2'
from .parse_cmx_events import parse_cmx3600 from .parse_cmx_events import parse_cmx3600
from .transition import Transition from .transition import Transition
from .event import Event from .event import Event

View File

@@ -4,23 +4,24 @@
from re import (compile, match) from re import (compile, match)
from typing import Dict, Tuple, Generator from typing import Dict, Tuple, Generator
class ChannelMap: class ChannelMap:
""" """
Represents a set of all the channels to which an event applies. Represents a set of all the channels to which an event applies.
""" """
_chan_map : Dict[str, Tuple] = { _chan_map: Dict[str, Tuple] = {
"V" : (True, False, False), "V": (True, False, False),
"A" : (False, True, False), "A": (False, True, False),
"A2" : (False, False, True), "A2": (False, False, True),
"AA" : (False, True, True), "AA": (False, True, True),
"B" : (True, True, False), "B": (True, True, False),
"AA/V" : (True, True, True), "AA/V": (True, True, True),
"A2/V" : (True, False, True) "A2/V": (True, False, True)
} }
def __init__(self, v=False, audio_channels=set()): def __init__(self, v=False, audio_channels=set()):
self._audio_channel_set = audio_channels self._audio_channel_set = audio_channels
self.v = v self.v = v
@property @property
@@ -46,7 +47,7 @@ class ChannelMap:
@a1.setter @a1.setter
def a1(self, val: bool): def a1(self, val: bool):
self.set_audio_channel(1,val) self.set_audio_channel(1, val)
@property @property
def a2(self) -> bool: def a2(self) -> bool:
@@ -55,7 +56,7 @@ class ChannelMap:
@a2.setter @a2.setter
def a2(self, val: bool): def a2(self, val: bool):
self.set_audio_channel(2,val) self.set_audio_channel(2, val)
@property @property
def a3(self) -> bool: def a3(self) -> bool:
@@ -64,28 +65,28 @@ class ChannelMap:
@a3.setter @a3.setter
def a3(self, val: bool): def a3(self, val: bool):
self.set_audio_channel(3,val) self.set_audio_channel(3, val)
@property @property
def a4(self) -> bool: def a4(self) -> bool:
"""True if A4 is included""" """True if A4 is included"""
return self.get_audio_channel(4) return self.get_audio_channel(4)
@a4.setter @a4.setter
def a4(self,val: bool): def a4(self, val: bool):
self.set_audio_channel(4,val) self.set_audio_channel(4, val)
def get_audio_channel(self, chan_num) -> bool: def get_audio_channel(self, chan_num) -> bool:
"""True if chan_num is included""" """True if chan_num is included"""
return (chan_num in self._audio_channel_set) return (chan_num in self._audio_channel_set)
def set_audio_channel(self,chan_num, enabled: bool): def set_audio_channel(self, chan_num, enabled: bool):
"""If enabled is true, chan_num will be included""" """If enabled is true, chan_num will be included"""
if enabled: if enabled:
self._audio_channel_set.add(chan_num) self._audio_channel_set.add(chan_num)
elif self.get_audio_channel(chan_num): elif self.get_audio_channel(chan_num):
self._audio_channel_set.remove(chan_num) self._audio_channel_set.remove(chan_num)
def _append_event(self, event_str): def _append_event(self, event_str):
alt_channel_re = compile(r'^A(\d+)') alt_channel_re = compile(r'^A(\d+)')
if event_str in self._chan_map: if event_str in self._chan_map:
@@ -96,7 +97,7 @@ class ChannelMap:
else: else:
matchresult = match(alt_channel_re, event_str) matchresult = match(alt_channel_re, event_str)
if matchresult: if matchresult:
self.set_audio_channel(int( matchresult.group(1)), True ) self.set_audio_channel(int(matchresult.group(1)), True)
def _append_ext(self, audio_ext): def _append_ext(self, audio_ext):
self.a3 = audio_ext.audio3 self.a3 = audio_ext.audio3
@@ -109,5 +110,4 @@ class ChannelMap:
out_v = self.video | other.video out_v = self.video | other.video
out_a = self._audio_channel_set | other._audio_channel_set out_a = self._audio_channel_set | other._audio_channel_set
return ChannelMap(v=out_v,audio_channels = out_a) return ChannelMap(v=out_v, audio_channels=out_a)

View File

@@ -7,23 +7,27 @@ from .channel_map import ChannelMap
from typing import Optional from typing import Optional
class Edit: class Edit:
""" """
An individual source-to-record operation, with a source roll, source and An individual source-to-record operation, with a source roll, source and
recorder timecode in and out, a transition and channels. recorder timecode in and out, a transition and channels.
""" """
def __init__(self, edit_statement, audio_ext_statement, clip_name_statement, source_file_statement, trans_name_statement = None):
def __init__(self, edit_statement, audio_ext_statement,
clip_name_statement, source_file_statement,
trans_name_statement=None):
self.edit_statement = edit_statement self.edit_statement = edit_statement
self.audio_ext = audio_ext_statement self.audio_ext = audio_ext_statement
self.clip_name_statement = clip_name_statement self.clip_name_statement = clip_name_statement
self.source_file_statement = source_file_statement self.source_file_statement = source_file_statement
self.trans_name_statement = trans_name_statement self.trans_name_statement = trans_name_statement
@property @property
def line_number(self) -> int: def line_number(self) -> int:
""" """
Get the line number for the "standard form" statement associated with Get the line number for the "standard form" statement associated with
this edit. Line numbers a zero-indexed, such that the this edit. Line numbers a zero-indexed, such that the
"TITLE:" record is line zero. "TITLE:" record is line zero.
""" """
return self.edit_statement.line_number return self.edit_statement.line_number
@@ -35,7 +39,7 @@ class Edit:
""" """
cm = ChannelMap() cm = ChannelMap()
cm._append_event(self.edit_statement.channels) cm._append_event(self.edit_statement.channels)
if self.audio_ext != None: if self.audio_ext is not None:
cm._append_ext(self.audio_ext) cm._append_ext(self.audio_ext)
return cm return cm
@@ -45,10 +49,13 @@ class Edit:
Get the :obj:`Transition` object associated with this edit. Get the :obj:`Transition` object associated with this edit.
""" """
if self.trans_name_statement: if self.trans_name_statement:
return Transition(self.edit_statement.trans, self.edit_statement.trans_op, self.trans_name_statement.name) return Transition(self.edit_statement.trans,
self.edit_statement.trans_op,
self.trans_name_statement.name)
else: else:
return Transition(self.edit_statement.trans, self.edit_statement.trans_op, None) return Transition(self.edit_statement.trans,
self.edit_statement.trans_op, None)
@property @property
def source_in(self) -> str: def source_in(self) -> str:
""" """
@@ -116,7 +123,7 @@ class Edit:
@property @property
def clip_name(self) -> Optional[str]: def clip_name(self) -> Optional[str]:
""" """
Get the clip name, as attested by a "* FROM CLIP NAME" or "* TO CLIP 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 NAME" remark on the EDL. This will return None if the information is
not present. not present.
""" """
@@ -124,5 +131,3 @@ class Edit:
return None return None
else: else:
return self.clip_name_statement.name return self.clip_name_statement.name

View File

@@ -1,16 +1,20 @@
# pycmx # pycmx
# (c) 2018 Jamie Hardt # (c) 2018 Jamie Hardt
from .parse_cmx_statements import (StmtUnrecognized, StmtFCM, StmtEvent, StmtSourceUMID) from .parse_cmx_statements import (
StmtUnrecognized, StmtEvent, StmtSourceUMID)
from .event import Event from .event import Event
from .channel_map import ChannelMap from .channel_map import ChannelMap
from typing import Generator from typing import Generator
class EditList: class EditList:
""" """
Represents an entire edit decision list as returned by :func:`~pycmx.parse_cmx3600()`. Represents an entire edit decision list as returned by
:func:`~pycmx.parse_cmx3600()`.
""" """
def __init__(self, statements): def __init__(self, statements):
self.title_statement = statements[0] self.title_statement = statements[0]
self.event_statements = statements[1:] self.event_statements = statements[1:]
@@ -23,7 +27,8 @@ class EditList:
Adobe EDLs with more than 999 events will be reported as "3600". Adobe EDLs with more than 999 events will be reported as "3600".
""" """
first_event = next( (s for s in self.event_statements if type(s) is StmtEvent), None) first_event = next(
(s for s in self.event_statements if type(s) is StmtEvent), None)
if first_event: if first_event:
if first_event.format == 8: if first_event.format == 8:
@@ -36,7 +41,6 @@ class EditList:
return 'unknown' return 'unknown'
else: else:
return 'unknown' return 'unknown'
@property @property
def channels(self) -> ChannelMap: def channels(self) -> ChannelMap:
@@ -50,7 +54,6 @@ class EditList:
retval = retval | edit.channels retval = retval | edit.channels
return retval return retval
@property @property
def title(self) -> str: def title(self) -> str:
@@ -59,27 +62,23 @@ class EditList:
""" """
return self.title_statement.title return self.title_statement.title
@property @property
def unrecognized_statements(self) -> Generator[StmtUnrecognized, None, None]: def unrecognized_statements(self) -> Generator[StmtUnrecognized,
None, None]:
""" """
A generator for all the unrecognized statements in the list. A generator for all the unrecognized statements in the list.
""" """
for s in self.event_statements: for s in self.event_statements:
if type(s) is StmtUnrecognized: if type(s) is StmtUnrecognized:
yield s yield s
@property @property
def events(self) -> Generator[Event, None, None]: def events(self) -> Generator[Event, None, None]:
'A generator for all the events in the edit list' 'A generator for all the events in the edit list'
is_drop = None
current_event_num = None current_event_num = None
event_statements = [] event_statements = []
for stmt in self.event_statements: for stmt in self.event_statements:
if type(stmt) is StmtFCM: if type(stmt) is StmtEvent:
is_drop = stmt.drop
elif type(stmt) is StmtEvent:
if current_event_num is None: if current_event_num is None:
current_event_num = stmt.event current_event_num = stmt.event
event_statements.append(stmt) event_statements.append(stmt)
@@ -103,9 +102,7 @@ class EditList:
""" """
A generator for all of the sources in the list A generator for all of the sources in the list
""" """
for stmt in self.event_statements: for stmt in self.event_statements:
if type(stmt) is StmtSourceUMID: if type(stmt) is StmtSourceUMID:
yield stmt yield stmt

View File

@@ -1,19 +1,22 @@
# pycmx # pycmx
# (c) 2023 Jamie Hardt # (c) 2023 Jamie Hardt
from .parse_cmx_statements import (StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized, StmtEffectsName) from .parse_cmx_statements import (
from .edit import Edit StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized,
StmtEffectsName)
from .edit import Edit
from typing import List, Generator, Optional, Tuple, Any from typing import List, Generator, Optional, Tuple, Any
class Event: class Event:
""" """
Represents a collection of :class:`~pycmx.edit.Edit` s, all with the same event number. Represents a collection of :class:`~pycmx.edit.Edit` s, all with the same
""" event number. """
def __init__(self, statements): def __init__(self, statements):
self.statements = statements self.statements = statements
@property @property
def number(self) -> int: def number(self) -> int:
""" """
@@ -28,10 +31,10 @@ class Event:
will have multiple edits when a dissolve, wipe or key transition needs will have multiple edits when a dissolve, wipe or key transition needs
to be performed. to be performed.
""" """
edits_audio = list( self._statements_with_audio_ext() ) edits_audio = list(self._statements_with_audio_ext())
clip_names = self._clip_name_statements() clip_names = self._clip_name_statements()
source_files= self._source_file_statements() source_files = self._source_file_statements()
the_zip: List[List[Any]] = [edits_audio] the_zip: List[List[Any]] = [edits_audio]
if len(edits_audio) == 2: if len(edits_audio) == 2:
@@ -45,19 +48,19 @@ class Event:
end_name = clip_name end_name = clip_name
the_zip.append([start_name, end_name]) the_zip.append([start_name, end_name])
else: else:
if len(edits_audio) == len(clip_names): if len(edits_audio) == len(clip_names):
the_zip.append(clip_names) the_zip.append(clip_names)
else: else:
the_zip.append([None] * len(edits_audio) ) the_zip.append([None] * len(edits_audio))
if len(edits_audio) == len(source_files): if len(edits_audio) == len(source_files):
the_zip.append(source_files) the_zip.append(source_files)
elif len(source_files) == 1: elif len(source_files) == 1:
the_zip.append( source_files * len(edits_audio) ) the_zip.append(source_files * len(edits_audio))
else: else:
the_zip.append([None] * len(edits_audio) ) the_zip.append([None] * len(edits_audio))
# attach trans name to last event # attach trans name to last event
try: try:
trans_statement = self._trans_name_statements()[0] trans_statement = self._trans_name_statements()[0]
@@ -65,23 +68,25 @@ class Event:
trans_names.append(trans_statement) trans_names.append(trans_statement)
the_zip.append(trans_names) the_zip.append(trans_names)
except IndexError: except IndexError:
the_zip.append([None] * len(edits_audio) ) the_zip.append([None] * len(edits_audio))
return [Edit(edit_statement=e1[0],
audio_ext_statement=e1[1],
clip_name_statement=n1,
source_file_statement=s1,
trans_name_statement=u1)
for (e1, n1, s1, u1) in zip(*the_zip)]
return [ Edit(edit_statement=e1[0],
audio_ext_statement=e1[1],
clip_name_statement=n1,
source_file_statement=s1,
trans_name_statement=u1) for (e1,n1,s1,u1) in zip(*the_zip) ]
@property @property
def unrecognized_statements(self) -> Generator[StmtUnrecognized, None, None]: def unrecognized_statements(self) -> Generator[StmtUnrecognized, None,
None]:
""" """
A generator for all the unrecognized statements in the event. A generator for all the unrecognized statements in the event.
""" """
for s in self.statements: for s in self.statements:
if type(s) is StmtUnrecognized: if type(s) is StmtUnrecognized:
yield s yield s
def _trans_name_statements(self) -> List[StmtEffectsName]: def _trans_name_statements(self) -> List[StmtEffectsName]:
return [s for s in self.statements if type(s) is StmtEffectsName] return [s for s in self.statements if type(s) is StmtEffectsName]
@@ -90,15 +95,14 @@ class Event:
def _clip_name_statements(self) -> List[StmtClipName]: def _clip_name_statements(self) -> List[StmtClipName]:
return [s for s in self.statements if type(s) is StmtClipName] return [s for s in self.statements if type(s) is StmtClipName]
def _source_file_statements(self) -> List[StmtSourceFile]: def _source_file_statements(self) -> List[StmtSourceFile]:
return [s for s in self.statements if type(s) is StmtSourceFile] return [s for s in self.statements if type(s) is StmtSourceFile]
def _statements_with_audio_ext(self) -> Generator[Tuple[StmtEvent, Optional[StmtAudioExt]], None, None]: def _statements_with_audio_ext(self) -> Generator[
Tuple[StmtEvent, Optional[StmtAudioExt]], None, None]:
for (s1, s2) in zip(self.statements, self.statements[1:]): for (s1, s2) in zip(self.statements, self.statements[1:]):
if type(s1) is StmtEvent and type(s2) is StmtAudioExt: if type(s1) is StmtEvent and type(s2) is StmtAudioExt:
yield (s1,s2) yield (s1, s2)
elif type(s1) is StmtEvent: elif type(s1) is StmtEvent:
yield (s1, None) yield (s1, None)

View File

@@ -6,7 +6,8 @@
from .parse_cmx_statements import (parse_cmx3600_statements) from .parse_cmx_statements import (parse_cmx3600_statements)
from .edit_list import EditList from .edit_list import EditList
from typing import TextIO from typing import TextIO
def parse_cmx3600(f: TextIO) -> EditList: def parse_cmx3600(f: TextIO) -> EditList:
""" """
@@ -17,4 +18,3 @@ def parse_cmx3600(f: TextIO) -> EditList:
""" """
statements = parse_cmx3600_statements(f) statements = parse_cmx3600_statements(f)
return EditList(statements) return EditList(statements)

View File

@@ -56,24 +56,23 @@ def _parse_cmx3600_line(line: str, line_number: int) -> object:
Parses a single CMX EDL line. Parses a single CMX EDL line.
:param line: A single EDL line. :param line: A single EDL line.
:param line_number: The index of this line in the file. :param line_number: The index of this line in the file.
""" """
long_event_num_p = re.compile("^[0-9]{6} ") long_event_num_p = re.compile("^[0-9]{6} ")
short_event_num_p = re.compile("^[0-9]{3} ") short_event_num_p = re.compile("^[0-9]{3} ")
if isinstance(line, str): if isinstance(line, str):
if line.startswith("TITLE:"): if line.startswith("TITLE:"):
return _parse_title(line, line_number) return _parse_title(line, line_number)
elif line.startswith("FCM:"): elif line.startswith("FCM:"):
return _parse_fcm(line, line_number) return _parse_fcm(line, line_number)
elif long_event_num_p.match(line) != None: elif long_event_num_p.match(line) is not None:
length_file_128 = sum(_edl_column_widths(6, 128)) length_file_128 = sum(_edl_column_widths(6, 128))
if len(line) < length_file_128: if len(line) < length_file_128:
return _parse_long_standard_form(line, 32, line_number) return _parse_long_standard_form(line, 32, line_number)
else: else:
return _parse_long_standard_form(line, 128, line_number) return _parse_long_standard_form(line, 128, line_number)
elif short_event_num_p.match(line) != None: elif short_event_num_p.match(line) is not None:
return _parse_standard_form(line, line_number) return _parse_standard_form(line, line_number)
elif line.startswith("AUD"): elif line.startswith("AUD"):
return _parse_extended_audio_channels(line, line_number) return _parse_extended_audio_channels(line, line_number)
@@ -185,5 +184,5 @@ def _parse_columns_for_standard_form(line, event_field_length,
def _parse_source_umid_statement(line, line_number): def _parse_source_umid_statement(line, line_number):
trimmed = line[3:].strip() # trimmed = line[3:].strip()
return StmtSourceUMID(name=None, umid=None, line_number=line_number) return StmtSourceUMID(name=None, umid=None, line_number=line_number)

View File

@@ -3,11 +3,12 @@
from typing import Optional from typing import Optional
class Transition: class Transition:
""" """
A CMX transition: a wipe, dissolve or cut. A CMX transition: a wipe, dissolve or cut.
""" """
Cut = "C" Cut = "C"
Dissolve = "D" Dissolve = "D"
Wipe = "W" Wipe = "W"
@@ -41,7 +42,7 @@ class Transition:
@property @property
def cut(self) -> bool: def cut(self) -> bool:
"`True` if this transition is a cut." "`True` if this transition is a cut."
return self.transition == 'C' return self.transition == 'C'
@property @property
def dissolve(self) -> bool: def dissolve(self) -> bool:
@@ -56,7 +57,7 @@ class Transition:
@property @property
def effect_duration(self) -> int: def effect_duration(self) -> int:
"""The duration of this transition, in frames of the record target. """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. In the event of a key event, this is the duration of the fade in.
""" """
return int(self.operand) return int(self.operand)
@@ -86,5 +87,3 @@ class Transition:
the key foreground and replaced with the key background. the key foreground and replaced with the key background.
""" """
return self.transition == Transition.KeyOut return self.transition == Transition.KeyOut

View File

@@ -3,29 +3,28 @@
# Utility functions # Utility functions
def collimate(a_string, column_widths): def collimate(a_string, column_widths):
""" """
Split a list-type thing, like a string, into slices that are column_widths Split a list-type thing, like a string, into slices that are column_widths
length. length.
>>> collimate("a b1 c2345",[2,3,3,2]) >>> collimate("a b1 c2345",[2,3,3,2])
['a ','b1 ','c23','45'] ['a ','b1 ','c23','45']
Args: Args:
a_string: The string to split. This parameter can actually be anything a_string: The string to split. This parameter can actually be anything
sliceable. sliceable.
column_widths: A list of integers, each one is the length of a column. column_widths: A list of integers, each one is the length of a column.
Returns: Returns:
A list of slices. The len() of the returned list will *always* equal A list of slices. The len() of the returned list will *always* equal
len(:column_widths:). len(:column_widths:).
""" """
if len(column_widths) == 0: if len(column_widths) == 0:
return [] return []
width = column_widths[0] width = column_widths[0]
element = a_string[:width] element = a_string[:width]
rest = a_string[width:] rest = a_string[width:]
return [element] + collimate(rest, column_widths[1:]) return [element] + collimate(rest, column_widths[1:])