pycmx 1.2.2__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycmx/__init__.py +1 -3
- pycmx/channel_map.py +21 -21
- pycmx/edit.py +16 -11
- pycmx/edit_list.py +14 -15
- pycmx/event.py +34 -30
- pycmx/parse_cmx_events.py +4 -4
- pycmx/parse_cmx_statements.py +118 -94
- pycmx/transition.py +4 -5
- pycmx/util.py +9 -10
- pycmx-1.3.0.dist-info/LICENSE +19 -0
- {pycmx-1.2.2.dist-info → pycmx-1.3.0.dist-info}/METADATA +24 -21
- pycmx-1.3.0.dist-info/RECORD +13 -0
- {pycmx-1.2.2.dist-info → pycmx-1.3.0.dist-info}/WHEEL +1 -1
- pycmx-1.2.2.dist-info/RECORD +0 -12
pycmx/__init__.py
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
"""
|
|
3
3
|
pycmx is a parser for CMX 3600-style EDLs.
|
|
4
4
|
|
|
5
|
-
This module (c)
|
|
5
|
+
This module (c) 2025 Jamie Hardt. For more information on your rights to
|
|
6
6
|
copy and reuse this software, refer to the LICENSE file included with the
|
|
7
7
|
distribution.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
__version__ = '1.2.2'
|
|
11
|
-
|
|
12
10
|
from .parse_cmx_events import parse_cmx3600
|
|
13
11
|
from .transition import Transition
|
|
14
12
|
from .event import Event
|
pycmx/channel_map.py
CHANGED
|
@@ -4,23 +4,24 @@
|
|
|
4
4
|
from re import (compile, match)
|
|
5
5
|
from typing import Dict, Tuple, Generator
|
|
6
6
|
|
|
7
|
+
|
|
7
8
|
class ChannelMap:
|
|
8
9
|
"""
|
|
9
10
|
Represents a set of all the channels to which an event applies.
|
|
10
11
|
"""
|
|
11
12
|
|
|
12
|
-
_chan_map
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
_chan_map: Dict[str, Tuple] = {
|
|
14
|
+
"V": (True, False, False),
|
|
15
|
+
"A": (False, True, False),
|
|
16
|
+
"A2": (False, False, True),
|
|
17
|
+
"AA": (False, True, True),
|
|
18
|
+
"B": (True, True, False),
|
|
19
|
+
"AA/V": (True, True, True),
|
|
20
|
+
"A2/V": (True, False, True)
|
|
21
|
+
}
|
|
21
22
|
|
|
22
23
|
def __init__(self, v=False, audio_channels=set()):
|
|
23
|
-
self._audio_channel_set = audio_channels
|
|
24
|
+
self._audio_channel_set = audio_channels
|
|
24
25
|
self.v = v
|
|
25
26
|
|
|
26
27
|
@property
|
|
@@ -46,7 +47,7 @@ class ChannelMap:
|
|
|
46
47
|
|
|
47
48
|
@a1.setter
|
|
48
49
|
def a1(self, val: bool):
|
|
49
|
-
self.set_audio_channel(1,val)
|
|
50
|
+
self.set_audio_channel(1, val)
|
|
50
51
|
|
|
51
52
|
@property
|
|
52
53
|
def a2(self) -> bool:
|
|
@@ -55,7 +56,7 @@ class ChannelMap:
|
|
|
55
56
|
|
|
56
57
|
@a2.setter
|
|
57
58
|
def a2(self, val: bool):
|
|
58
|
-
self.set_audio_channel(2,val)
|
|
59
|
+
self.set_audio_channel(2, val)
|
|
59
60
|
|
|
60
61
|
@property
|
|
61
62
|
def a3(self) -> bool:
|
|
@@ -64,28 +65,28 @@ class ChannelMap:
|
|
|
64
65
|
|
|
65
66
|
@a3.setter
|
|
66
67
|
def a3(self, val: bool):
|
|
67
|
-
self.set_audio_channel(3,val)
|
|
68
|
-
|
|
68
|
+
self.set_audio_channel(3, val)
|
|
69
|
+
|
|
69
70
|
@property
|
|
70
71
|
def a4(self) -> bool:
|
|
71
72
|
"""True if A4 is included"""
|
|
72
73
|
return self.get_audio_channel(4)
|
|
73
74
|
|
|
74
75
|
@a4.setter
|
|
75
|
-
def a4(self,val: bool):
|
|
76
|
-
self.set_audio_channel(4,val)
|
|
76
|
+
def a4(self, val: bool):
|
|
77
|
+
self.set_audio_channel(4, val)
|
|
77
78
|
|
|
78
79
|
def get_audio_channel(self, chan_num) -> bool:
|
|
79
80
|
"""True if chan_num is included"""
|
|
80
81
|
return (chan_num in self._audio_channel_set)
|
|
81
82
|
|
|
82
|
-
def set_audio_channel(self,chan_num, enabled: bool):
|
|
83
|
+
def set_audio_channel(self, chan_num, enabled: bool):
|
|
83
84
|
"""If enabled is true, chan_num will be included"""
|
|
84
85
|
if enabled:
|
|
85
86
|
self._audio_channel_set.add(chan_num)
|
|
86
87
|
elif self.get_audio_channel(chan_num):
|
|
87
88
|
self._audio_channel_set.remove(chan_num)
|
|
88
|
-
|
|
89
|
+
|
|
89
90
|
def _append_event(self, event_str):
|
|
90
91
|
alt_channel_re = compile(r'^A(\d+)')
|
|
91
92
|
if event_str in self._chan_map:
|
|
@@ -96,7 +97,7 @@ class ChannelMap:
|
|
|
96
97
|
else:
|
|
97
98
|
matchresult = match(alt_channel_re, event_str)
|
|
98
99
|
if matchresult:
|
|
99
|
-
self.set_audio_channel(int(
|
|
100
|
+
self.set_audio_channel(int(matchresult.group(1)), True)
|
|
100
101
|
|
|
101
102
|
def _append_ext(self, audio_ext):
|
|
102
103
|
self.a3 = audio_ext.audio3
|
|
@@ -109,5 +110,4 @@ class ChannelMap:
|
|
|
109
110
|
out_v = self.video | other.video
|
|
110
111
|
out_a = self._audio_channel_set | other._audio_channel_set
|
|
111
112
|
|
|
112
|
-
return ChannelMap(v=out_v,audio_channels
|
|
113
|
-
|
|
113
|
+
return ChannelMap(v=out_v, audio_channels=out_a)
|
pycmx/edit.py
CHANGED
|
@@ -7,23 +7,27 @@ from .channel_map import ChannelMap
|
|
|
7
7
|
|
|
8
8
|
from typing import Optional
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
class Edit:
|
|
11
12
|
"""
|
|
12
|
-
An individual source-to-record operation, with a source roll, source and
|
|
13
|
+
An individual source-to-record operation, with a source roll, source and
|
|
13
14
|
recorder timecode in and out, a transition and channels.
|
|
14
15
|
"""
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
def __init__(self, edit_statement, audio_ext_statement,
|
|
18
|
+
clip_name_statement, source_file_statement,
|
|
19
|
+
trans_name_statement=None):
|
|
16
20
|
self.edit_statement = edit_statement
|
|
17
21
|
self.audio_ext = audio_ext_statement
|
|
18
22
|
self.clip_name_statement = clip_name_statement
|
|
19
23
|
self.source_file_statement = source_file_statement
|
|
20
|
-
self.trans_name_statement = trans_name_statement
|
|
24
|
+
self.trans_name_statement = trans_name_statement
|
|
21
25
|
|
|
22
26
|
@property
|
|
23
27
|
def line_number(self) -> int:
|
|
24
28
|
"""
|
|
25
29
|
Get the line number for the "standard form" statement associated with
|
|
26
|
-
this edit. Line numbers a zero-indexed, such that the
|
|
30
|
+
this edit. Line numbers a zero-indexed, such that the
|
|
27
31
|
"TITLE:" record is line zero.
|
|
28
32
|
"""
|
|
29
33
|
return self.edit_statement.line_number
|
|
@@ -35,7 +39,7 @@ class Edit:
|
|
|
35
39
|
"""
|
|
36
40
|
cm = ChannelMap()
|
|
37
41
|
cm._append_event(self.edit_statement.channels)
|
|
38
|
-
if self.audio_ext
|
|
42
|
+
if self.audio_ext is not None:
|
|
39
43
|
cm._append_ext(self.audio_ext)
|
|
40
44
|
return cm
|
|
41
45
|
|
|
@@ -45,10 +49,13 @@ class Edit:
|
|
|
45
49
|
Get the :obj:`Transition` object associated with this edit.
|
|
46
50
|
"""
|
|
47
51
|
if self.trans_name_statement:
|
|
48
|
-
return Transition(self.edit_statement.trans,
|
|
52
|
+
return Transition(self.edit_statement.trans,
|
|
53
|
+
self.edit_statement.trans_op,
|
|
54
|
+
self.trans_name_statement.name)
|
|
49
55
|
else:
|
|
50
|
-
return Transition(self.edit_statement.trans,
|
|
51
|
-
|
|
56
|
+
return Transition(self.edit_statement.trans,
|
|
57
|
+
self.edit_statement.trans_op, None)
|
|
58
|
+
|
|
52
59
|
@property
|
|
53
60
|
def source_in(self) -> str:
|
|
54
61
|
"""
|
|
@@ -116,7 +123,7 @@ class Edit:
|
|
|
116
123
|
@property
|
|
117
124
|
def clip_name(self) -> Optional[str]:
|
|
118
125
|
"""
|
|
119
|
-
Get the clip name, as attested by a "* FROM CLIP NAME" or "* TO CLIP
|
|
126
|
+
Get the clip name, as attested by a "* FROM CLIP NAME" or "* TO CLIP
|
|
120
127
|
NAME" remark on the EDL. This will return None if the information is
|
|
121
128
|
not present.
|
|
122
129
|
"""
|
|
@@ -124,5 +131,3 @@ class Edit:
|
|
|
124
131
|
return None
|
|
125
132
|
else:
|
|
126
133
|
return self.clip_name_statement.name
|
|
127
|
-
|
|
128
|
-
|
pycmx/edit_list.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# pycmx
|
|
2
2
|
# (c) 2018 Jamie Hardt
|
|
3
3
|
|
|
4
|
-
from .parse_cmx_statements import (
|
|
4
|
+
from .parse_cmx_statements import (
|
|
5
|
+
StmtUnrecognized, StmtEvent, StmtSourceUMID)
|
|
5
6
|
from .event import Event
|
|
6
7
|
from .channel_map import ChannelMap
|
|
7
8
|
|
|
8
9
|
from typing import Generator
|
|
9
10
|
|
|
11
|
+
|
|
10
12
|
class EditList:
|
|
11
13
|
"""
|
|
12
|
-
Represents an entire edit decision list as returned by
|
|
14
|
+
Represents an entire edit decision list as returned by
|
|
15
|
+
:func:`~pycmx.parse_cmx3600()`.
|
|
13
16
|
"""
|
|
17
|
+
|
|
14
18
|
def __init__(self, statements):
|
|
15
19
|
self.title_statement = statements[0]
|
|
16
20
|
self.event_statements = statements[1:]
|
|
@@ -20,8 +24,11 @@ class EditList:
|
|
|
20
24
|
"""
|
|
21
25
|
The detected format of the EDL. Possible values are: "3600", "File32",
|
|
22
26
|
"File128", and "unknown".
|
|
27
|
+
|
|
28
|
+
Adobe EDLs with more than 999 events will be reported as "3600".
|
|
23
29
|
"""
|
|
24
|
-
first_event = next(
|
|
30
|
+
first_event = next(
|
|
31
|
+
(s for s in self.event_statements if type(s) is StmtEvent), None)
|
|
25
32
|
|
|
26
33
|
if first_event:
|
|
27
34
|
if first_event.format == 8:
|
|
@@ -34,7 +41,6 @@ class EditList:
|
|
|
34
41
|
return 'unknown'
|
|
35
42
|
else:
|
|
36
43
|
return 'unknown'
|
|
37
|
-
|
|
38
44
|
|
|
39
45
|
@property
|
|
40
46
|
def channels(self) -> ChannelMap:
|
|
@@ -48,7 +54,6 @@ class EditList:
|
|
|
48
54
|
retval = retval | edit.channels
|
|
49
55
|
|
|
50
56
|
return retval
|
|
51
|
-
|
|
52
57
|
|
|
53
58
|
@property
|
|
54
59
|
def title(self) -> str:
|
|
@@ -57,27 +62,23 @@ class EditList:
|
|
|
57
62
|
"""
|
|
58
63
|
return self.title_statement.title
|
|
59
64
|
|
|
60
|
-
|
|
61
65
|
@property
|
|
62
|
-
def unrecognized_statements(self) -> Generator[StmtUnrecognized,
|
|
66
|
+
def unrecognized_statements(self) -> Generator[StmtUnrecognized,
|
|
67
|
+
None, None]:
|
|
63
68
|
"""
|
|
64
69
|
A generator for all the unrecognized statements in the list.
|
|
65
70
|
"""
|
|
66
71
|
for s in self.event_statements:
|
|
67
72
|
if type(s) is StmtUnrecognized:
|
|
68
73
|
yield s
|
|
69
|
-
|
|
70
74
|
|
|
71
75
|
@property
|
|
72
76
|
def events(self) -> Generator[Event, None, None]:
|
|
73
77
|
'A generator for all the events in the edit list'
|
|
74
|
-
is_drop = None
|
|
75
78
|
current_event_num = None
|
|
76
79
|
event_statements = []
|
|
77
80
|
for stmt in self.event_statements:
|
|
78
|
-
if type(stmt) is
|
|
79
|
-
is_drop = stmt.drop
|
|
80
|
-
elif type(stmt) is StmtEvent:
|
|
81
|
+
if type(stmt) is StmtEvent:
|
|
81
82
|
if current_event_num is None:
|
|
82
83
|
current_event_num = stmt.event
|
|
83
84
|
event_statements.append(stmt)
|
|
@@ -101,9 +102,7 @@ class EditList:
|
|
|
101
102
|
"""
|
|
102
103
|
A generator for all of the sources in the list
|
|
103
104
|
"""
|
|
104
|
-
|
|
105
|
+
|
|
105
106
|
for stmt in self.event_statements:
|
|
106
107
|
if type(stmt) is StmtSourceUMID:
|
|
107
108
|
yield stmt
|
|
108
|
-
|
|
109
|
-
|
pycmx/event.py
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
# pycmx
|
|
2
2
|
# (c) 2023 Jamie Hardt
|
|
3
3
|
|
|
4
|
-
from .parse_cmx_statements import (
|
|
5
|
-
|
|
4
|
+
from .parse_cmx_statements import (
|
|
5
|
+
StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized,
|
|
6
|
+
StmtEffectsName)
|
|
7
|
+
from .edit import Edit
|
|
6
8
|
|
|
7
9
|
from typing import List, Generator, Optional, Tuple, Any
|
|
8
10
|
|
|
11
|
+
|
|
9
12
|
class Event:
|
|
10
13
|
"""
|
|
11
|
-
Represents a collection of :class:`~pycmx.edit.Edit` s, all with the same
|
|
12
|
-
"""
|
|
14
|
+
Represents a collection of :class:`~pycmx.edit.Edit` s, all with the same
|
|
15
|
+
event number. """
|
|
13
16
|
|
|
14
17
|
def __init__(self, statements):
|
|
15
18
|
self.statements = statements
|
|
16
|
-
|
|
19
|
+
|
|
17
20
|
@property
|
|
18
21
|
def number(self) -> int:
|
|
19
22
|
"""
|
|
@@ -28,10 +31,10 @@ class Event:
|
|
|
28
31
|
will have multiple edits when a dissolve, wipe or key transition needs
|
|
29
32
|
to be performed.
|
|
30
33
|
"""
|
|
31
|
-
edits_audio = list(
|
|
32
|
-
clip_names
|
|
33
|
-
source_files= self._source_file_statements()
|
|
34
|
-
|
|
34
|
+
edits_audio = list(self._statements_with_audio_ext())
|
|
35
|
+
clip_names = self._clip_name_statements()
|
|
36
|
+
source_files = self._source_file_statements()
|
|
37
|
+
|
|
35
38
|
the_zip: List[List[Any]] = [edits_audio]
|
|
36
39
|
|
|
37
40
|
if len(edits_audio) == 2:
|
|
@@ -45,19 +48,19 @@ class Event:
|
|
|
45
48
|
end_name = clip_name
|
|
46
49
|
|
|
47
50
|
the_zip.append([start_name, end_name])
|
|
48
|
-
else:
|
|
51
|
+
else:
|
|
49
52
|
if len(edits_audio) == len(clip_names):
|
|
50
53
|
the_zip.append(clip_names)
|
|
51
54
|
else:
|
|
52
|
-
the_zip.append([None] * len(edits_audio)
|
|
55
|
+
the_zip.append([None] * len(edits_audio))
|
|
53
56
|
|
|
54
57
|
if len(edits_audio) == len(source_files):
|
|
55
58
|
the_zip.append(source_files)
|
|
56
59
|
elif len(source_files) == 1:
|
|
57
|
-
the_zip.append(
|
|
60
|
+
the_zip.append(source_files * len(edits_audio))
|
|
58
61
|
else:
|
|
59
|
-
the_zip.append([None] * len(edits_audio)
|
|
60
|
-
|
|
62
|
+
the_zip.append([None] * len(edits_audio))
|
|
63
|
+
|
|
61
64
|
# attach trans name to last event
|
|
62
65
|
try:
|
|
63
66
|
trans_statement = self._trans_name_statements()[0]
|
|
@@ -65,23 +68,25 @@ class Event:
|
|
|
65
68
|
trans_names.append(trans_statement)
|
|
66
69
|
the_zip.append(trans_names)
|
|
67
70
|
except IndexError:
|
|
68
|
-
the_zip.append([None] * len(edits_audio)
|
|
69
|
-
|
|
70
|
-
return [
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
the_zip.append([None] * len(edits_audio))
|
|
72
|
+
|
|
73
|
+
return [Edit(edit_statement=e1[0],
|
|
74
|
+
audio_ext_statement=e1[1],
|
|
75
|
+
clip_name_statement=n1,
|
|
76
|
+
source_file_statement=s1,
|
|
77
|
+
trans_name_statement=u1)
|
|
78
|
+
for (e1, n1, s1, u1) in zip(*the_zip)]
|
|
79
|
+
|
|
76
80
|
@property
|
|
77
|
-
def unrecognized_statements(self) -> Generator[StmtUnrecognized, None,
|
|
81
|
+
def unrecognized_statements(self) -> Generator[StmtUnrecognized, None,
|
|
82
|
+
None]:
|
|
78
83
|
"""
|
|
79
84
|
A generator for all the unrecognized statements in the event.
|
|
80
85
|
"""
|
|
81
86
|
for s in self.statements:
|
|
82
87
|
if type(s) is StmtUnrecognized:
|
|
83
88
|
yield s
|
|
84
|
-
|
|
89
|
+
|
|
85
90
|
def _trans_name_statements(self) -> List[StmtEffectsName]:
|
|
86
91
|
return [s for s in self.statements if type(s) is StmtEffectsName]
|
|
87
92
|
|
|
@@ -90,15 +95,14 @@ class Event:
|
|
|
90
95
|
|
|
91
96
|
def _clip_name_statements(self) -> List[StmtClipName]:
|
|
92
97
|
return [s for s in self.statements if type(s) is StmtClipName]
|
|
93
|
-
|
|
98
|
+
|
|
94
99
|
def _source_file_statements(self) -> List[StmtSourceFile]:
|
|
95
100
|
return [s for s in self.statements if type(s) is StmtSourceFile]
|
|
96
|
-
|
|
97
|
-
def _statements_with_audio_ext(self) -> Generator[
|
|
101
|
+
|
|
102
|
+
def _statements_with_audio_ext(self) -> Generator[
|
|
103
|
+
Tuple[StmtEvent, Optional[StmtAudioExt]], None, None]:
|
|
98
104
|
for (s1, s2) in zip(self.statements, self.statements[1:]):
|
|
99
105
|
if type(s1) is StmtEvent and type(s2) is StmtAudioExt:
|
|
100
|
-
yield (s1,s2)
|
|
106
|
+
yield (s1, s2)
|
|
101
107
|
elif type(s1) is StmtEvent:
|
|
102
108
|
yield (s1, None)
|
|
103
|
-
|
|
104
|
-
|
pycmx/parse_cmx_events.py
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
from .parse_cmx_statements import (parse_cmx3600_statements)
|
|
7
7
|
from .edit_list import EditList
|
|
8
8
|
|
|
9
|
-
from typing import TextIO
|
|
9
|
+
from typing import TextIO
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
def parse_cmx3600(f: TextIO) -> EditList:
|
|
12
13
|
"""
|
|
13
14
|
Parse a CMX 3600 EDL.
|
|
14
15
|
|
|
15
|
-
:param TextIO f: a file-like object,
|
|
16
|
+
:param TextIO f: a file-like object, an opened CMX 3600 .EDL file.
|
|
16
17
|
:returns: An :class:`pycmx.edit_list.EditList`.
|
|
17
18
|
"""
|
|
18
19
|
statements = parse_cmx3600_statements(f)
|
|
19
20
|
return EditList(statements)
|
|
20
|
-
|
pycmx/parse_cmx_statements.py
CHANGED
|
@@ -2,27 +2,28 @@
|
|
|
2
2
|
# (c) 2018 Jamie Hardt
|
|
3
3
|
|
|
4
4
|
import re
|
|
5
|
-
import sys
|
|
6
5
|
from collections import namedtuple
|
|
7
|
-
from itertools import count
|
|
8
6
|
from typing import TextIO, List
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
from .util import collimate
|
|
12
10
|
|
|
13
|
-
StmtTitle =
|
|
14
|
-
StmtFCM =
|
|
15
|
-
StmtEvent =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
StmtTitle = namedtuple("Title", ["title", "line_number"])
|
|
12
|
+
StmtFCM = namedtuple("FCM", ["drop", "line_number"])
|
|
13
|
+
StmtEvent = namedtuple("Event", ["event", "source", "channels", "trans",
|
|
14
|
+
"trans_op", "source_in", "source_out",
|
|
15
|
+
"record_in", "record_out", "format",
|
|
16
|
+
"line_number"])
|
|
17
|
+
StmtAudioExt = namedtuple("AudioExt", ["audio3", "audio4", "line_number"])
|
|
18
|
+
StmtClipName = namedtuple("ClipName", ["name", "affect", "line_number"])
|
|
19
|
+
StmtSourceFile = namedtuple("SourceFile", ["filename", "line_number"])
|
|
20
|
+
StmtRemark = namedtuple("Remark", ["text", "line_number"])
|
|
21
|
+
StmtEffectsName = namedtuple("EffectsName", ["name", "line_number"])
|
|
22
|
+
StmtSourceUMID = namedtuple("Source", ["name", "umid", "line_number"])
|
|
23
|
+
StmtSplitEdit = namedtuple("SplitEdit", ["video", "magnitude", "line_number"])
|
|
24
|
+
StmtMotionMemory = namedtuple(
|
|
25
|
+
"MotionMemory", ["source", "fps"]) # FIXME needs more fields
|
|
26
|
+
StmtUnrecognized = namedtuple("Unrecognized", ["content", "line_number"])
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def parse_cmx3600_statements(file: TextIO) -> List[object]:
|
|
@@ -30,76 +31,92 @@ def parse_cmx3600_statements(file: TextIO) -> List[object]:
|
|
|
30
31
|
Return a list of every statement in the file argument.
|
|
31
32
|
"""
|
|
32
33
|
lines = file.readlines()
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _edl_column_widths(event_field_length, source_field_length):
|
|
38
|
-
return [event_field_length,2, source_field_length,1,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _edl_m2_column_widths():
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _parse_cmx3600_line(line, line_number):
|
|
54
|
-
|
|
34
|
+
return [_parse_cmx3600_line(line.strip(), line_number)
|
|
35
|
+
for (line_number, line) in enumerate(lines)]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _edl_column_widths(event_field_length, source_field_length) -> List[int]:
|
|
39
|
+
return [event_field_length, 2, source_field_length, 1,
|
|
40
|
+
4, 2, # chans
|
|
41
|
+
4, 1, # trans
|
|
42
|
+
3, 1, # trans op
|
|
43
|
+
11, 1,
|
|
44
|
+
11, 1,
|
|
45
|
+
11, 1,
|
|
46
|
+
11]
|
|
47
|
+
|
|
48
|
+
# def _edl_m2_column_widths():
|
|
49
|
+
# return [2, # "M2"
|
|
50
|
+
# 3,3, #
|
|
51
|
+
# 8,8,1,4,2,1,4,13,3,1,1]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _parse_cmx3600_line(line: str, line_number: int) -> object:
|
|
55
|
+
"""
|
|
56
|
+
Parses a single CMX EDL line.
|
|
57
|
+
|
|
58
|
+
:param line: A single EDL line.
|
|
59
|
+
:param line_number: The index of this line in the file.
|
|
60
|
+
"""
|
|
61
|
+
long_event_num_p = re.compile("^[0-9]{6} ")
|
|
55
62
|
short_event_num_p = re.compile("^[0-9]{3} ")
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
else:
|
|
67
|
-
return _parse_long_standard_form(line, 128, line_number)
|
|
68
|
-
elif short_event_num_p.match(line) != None:
|
|
69
|
-
return _parse_standard_form(line, line_number)
|
|
70
|
-
elif line.startswith("AUD"):
|
|
71
|
-
return _parse_extended_audio_channels(line,line_number)
|
|
72
|
-
elif line.startswith("*"):
|
|
73
|
-
return _parse_remark( line[1:].strip(), line_number)
|
|
74
|
-
elif line.startswith(">>> SOURCE"):
|
|
75
|
-
return _parse_source_umid_statement(line, line_number)
|
|
76
|
-
elif line.startswith("EFFECTS NAME IS"):
|
|
77
|
-
return _parse_effects_name(line, line_number)
|
|
78
|
-
elif line.startswith("SPLIT:"):
|
|
79
|
-
return _parse_split(line, line_number)
|
|
80
|
-
elif line.startswith("M2"):
|
|
81
|
-
return _parse_motion_memory(line, line_number)
|
|
63
|
+
x_event_form_p = re.compile("^([0-9]{4,5}) ")
|
|
64
|
+
|
|
65
|
+
if line.startswith("TITLE:"):
|
|
66
|
+
return _parse_title(line, line_number)
|
|
67
|
+
elif line.startswith("FCM:"):
|
|
68
|
+
return _parse_fcm(line, line_number)
|
|
69
|
+
elif long_event_num_p.match(line) is not None:
|
|
70
|
+
length_file_128 = sum(_edl_column_widths(6, 128))
|
|
71
|
+
if len(line) < length_file_128:
|
|
72
|
+
return _parse_long_standard_form(line, 32, line_number)
|
|
82
73
|
else:
|
|
83
|
-
return
|
|
74
|
+
return _parse_long_standard_form(line, 128, line_number)
|
|
75
|
+
elif (m := x_event_form_p.match(line)) is not None:
|
|
76
|
+
assert m is not None
|
|
77
|
+
event_field_length = len(m[1])
|
|
78
|
+
return _parse_columns_for_standard_form(line, event_field_length,
|
|
79
|
+
8, line_number)
|
|
80
|
+
elif short_event_num_p.match(line) is not None:
|
|
81
|
+
return _parse_standard_form(line, line_number)
|
|
82
|
+
elif line.startswith("AUD"):
|
|
83
|
+
return _parse_extended_audio_channels(line, line_number)
|
|
84
|
+
elif line.startswith("*"):
|
|
85
|
+
return _parse_remark(line[1:].strip(), line_number)
|
|
86
|
+
elif line.startswith(">>> SOURCE"):
|
|
87
|
+
return _parse_source_umid_statement(line, line_number)
|
|
88
|
+
elif line.startswith("EFFECTS NAME IS"):
|
|
89
|
+
return _parse_effects_name(line, line_number)
|
|
90
|
+
elif line.startswith("SPLIT:"):
|
|
91
|
+
return _parse_split(line, line_number)
|
|
92
|
+
elif line.startswith("M2"):
|
|
93
|
+
return _parse_motion_memory(line, line_number)
|
|
94
|
+
else:
|
|
95
|
+
return _parse_unrecognized(line, line_number)
|
|
96
|
+
|
|
84
97
|
|
|
85
|
-
|
|
86
|
-
def _parse_title(line, line_num):
|
|
98
|
+
def _parse_title(line, line_num) -> StmtTitle:
|
|
87
99
|
title = line[6:].strip()
|
|
88
|
-
return StmtTitle(title=title,line_number=line_num)
|
|
100
|
+
return StmtTitle(title=title, line_number=line_num)
|
|
89
101
|
|
|
90
|
-
|
|
102
|
+
|
|
103
|
+
def _parse_fcm(line, line_num) -> StmtFCM:
|
|
91
104
|
val = line[4:].strip()
|
|
92
105
|
if val == "DROP FRAME":
|
|
93
|
-
return StmtFCM(drop=
|
|
106
|
+
return StmtFCM(drop=True, line_number=line_num)
|
|
94
107
|
else:
|
|
95
|
-
return StmtFCM(drop=
|
|
108
|
+
return StmtFCM(drop=False, line_number=line_num)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _parse_long_standard_form(line, source_field_length, line_number):
|
|
112
|
+
return _parse_columns_for_standard_form(line, 6, source_field_length,
|
|
113
|
+
line_number)
|
|
114
|
+
|
|
96
115
|
|
|
97
|
-
def _parse_long_standard_form(line,source_field_length, line_number):
|
|
98
|
-
return _parse_columns_for_standard_form(line, 6, source_field_length, line_number)
|
|
99
|
-
|
|
100
116
|
def _parse_standard_form(line, line_number):
|
|
101
117
|
return _parse_columns_for_standard_form(line, 3, 8, line_number)
|
|
102
|
-
|
|
118
|
+
|
|
119
|
+
|
|
103
120
|
def _parse_extended_audio_channels(line, line_number):
|
|
104
121
|
content = line.strip()
|
|
105
122
|
if content == "AUD 3":
|
|
@@ -110,60 +127,67 @@ def _parse_extended_audio_channels(line, line_number):
|
|
|
110
127
|
return StmtAudioExt(audio3=True, audio4=True, line_number=line_number)
|
|
111
128
|
else:
|
|
112
129
|
return StmtUnrecognized(content=line, line_number=line_number)
|
|
113
|
-
|
|
130
|
+
|
|
131
|
+
|
|
114
132
|
def _parse_remark(line, line_number) -> object:
|
|
115
133
|
if line.startswith("FROM CLIP NAME:"):
|
|
116
|
-
return StmtClipName(name=line[15:].strip()
|
|
134
|
+
return StmtClipName(name=line[15:].strip(), affect="from",
|
|
135
|
+
line_number=line_number)
|
|
117
136
|
elif line.startswith("TO CLIP NAME:"):
|
|
118
|
-
return StmtClipName(name=line[13:].strip(), affect="to",
|
|
137
|
+
return StmtClipName(name=line[13:].strip(), affect="to",
|
|
138
|
+
line_number=line_number)
|
|
119
139
|
elif line.startswith("SOURCE FILE:"):
|
|
120
|
-
return StmtSourceFile(filename=line[12:].strip()
|
|
140
|
+
return StmtSourceFile(filename=line[12:].strip(),
|
|
141
|
+
line_number=line_number)
|
|
121
142
|
else:
|
|
122
143
|
return StmtRemark(text=line, line_number=line_number)
|
|
123
144
|
|
|
145
|
+
|
|
124
146
|
def _parse_effects_name(line, line_number) -> StmtEffectsName:
|
|
125
147
|
name = line[16:].strip()
|
|
126
148
|
return StmtEffectsName(name=name, line_number=line_number)
|
|
127
149
|
|
|
150
|
+
|
|
128
151
|
def _parse_split(line, line_number):
|
|
129
152
|
split_type = line[10:21]
|
|
130
153
|
is_video = False
|
|
131
154
|
if split_type.startswith("VIDEO"):
|
|
132
155
|
is_video = True
|
|
133
156
|
|
|
134
|
-
split_mag
|
|
135
|
-
return StmtSplitEdit(video=is_video, magnitude=split_mag,
|
|
157
|
+
split_mag = line[24:35]
|
|
158
|
+
return StmtSplitEdit(video=is_video, magnitude=split_mag,
|
|
159
|
+
line_number=line_number)
|
|
136
160
|
|
|
137
161
|
|
|
138
162
|
def _parse_motion_memory(line, line_number):
|
|
139
|
-
return StmtMotionMemory(source
|
|
163
|
+
return StmtMotionMemory(source="", fps="")
|
|
140
164
|
|
|
141
165
|
|
|
142
166
|
def _parse_unrecognized(line, line_number):
|
|
143
167
|
return StmtUnrecognized(content=line, line_number=line_number)
|
|
144
168
|
|
|
145
|
-
|
|
169
|
+
|
|
170
|
+
def _parse_columns_for_standard_form(line, event_field_length,
|
|
171
|
+
source_field_length, line_number):
|
|
146
172
|
col_widths = _edl_column_widths(event_field_length, source_field_length)
|
|
147
|
-
|
|
173
|
+
|
|
148
174
|
if sum(col_widths) > len(line):
|
|
149
175
|
return StmtUnrecognized(content=line, line_number=line_number)
|
|
150
|
-
|
|
151
|
-
column_strings = collimate(line,col_widths)
|
|
152
|
-
|
|
153
|
-
return StmtEvent(event=column_strings[0],
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
176
|
+
|
|
177
|
+
column_strings = collimate(line, col_widths)
|
|
178
|
+
|
|
179
|
+
return StmtEvent(event=column_strings[0],
|
|
180
|
+
source=column_strings[2].strip(),
|
|
181
|
+
channels=column_strings[4].strip(),
|
|
182
|
+
trans=column_strings[6].strip(),
|
|
157
183
|
trans_op=column_strings[8].strip(),
|
|
158
184
|
source_in=column_strings[10].strip(),
|
|
159
185
|
source_out=column_strings[12].strip(),
|
|
160
186
|
record_in=column_strings[14].strip(),
|
|
161
187
|
record_out=column_strings[16].strip(),
|
|
162
|
-
|
|
163
|
-
format=source_field_length)
|
|
188
|
+
line_number=line_number, format=source_field_length)
|
|
164
189
|
|
|
165
190
|
|
|
166
191
|
def _parse_source_umid_statement(line, line_number):
|
|
167
|
-
trimmed = line[3:].strip()
|
|
192
|
+
# trimmed = line[3:].strip()
|
|
168
193
|
return StmtSourceUMID(name=None, umid=None, line_number=line_number)
|
|
169
|
-
|
pycmx/transition.py
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
class Transition:
|
|
7
8
|
"""
|
|
8
9
|
A CMX transition: a wipe, dissolve or cut.
|
|
9
10
|
"""
|
|
10
|
-
|
|
11
|
+
|
|
11
12
|
Cut = "C"
|
|
12
13
|
Dissolve = "D"
|
|
13
14
|
Wipe = "W"
|
|
@@ -41,7 +42,7 @@ class Transition:
|
|
|
41
42
|
@property
|
|
42
43
|
def cut(self) -> bool:
|
|
43
44
|
"`True` if this transition is a cut."
|
|
44
|
-
return self.transition == 'C'
|
|
45
|
+
return self.transition == 'C'
|
|
45
46
|
|
|
46
47
|
@property
|
|
47
48
|
def dissolve(self) -> bool:
|
|
@@ -56,7 +57,7 @@ class Transition:
|
|
|
56
57
|
@property
|
|
57
58
|
def effect_duration(self) -> int:
|
|
58
59
|
"""The duration of this transition, in frames of the record target.
|
|
59
|
-
|
|
60
|
+
|
|
60
61
|
In the event of a key event, this is the duration of the fade in.
|
|
61
62
|
"""
|
|
62
63
|
return int(self.operand)
|
|
@@ -86,5 +87,3 @@ class Transition:
|
|
|
86
87
|
the key foreground and replaced with the key background.
|
|
87
88
|
"""
|
|
88
89
|
return self.transition == Transition.KeyOut
|
|
89
|
-
|
|
90
|
-
|
pycmx/util.py
CHANGED
|
@@ -3,29 +3,28 @@
|
|
|
3
3
|
|
|
4
4
|
# Utility functions
|
|
5
5
|
|
|
6
|
-
def collimate(a_string, column_widths):
|
|
6
|
+
def collimate(a_string, column_widths):
|
|
7
7
|
"""
|
|
8
|
-
Split a list-type thing, like a string, into slices that are column_widths
|
|
8
|
+
Split a list-type thing, like a string, into slices that are column_widths
|
|
9
9
|
length.
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
>>> collimate("a b1 c2345",[2,3,3,2])
|
|
12
12
|
['a ','b1 ','c23','45']
|
|
13
13
|
|
|
14
14
|
Args:
|
|
15
15
|
a_string: The string to split. This parameter can actually be anything
|
|
16
16
|
sliceable.
|
|
17
|
-
column_widths: A list of integers, each one is the length of a column.
|
|
17
|
+
column_widths: A list of integers, each one is the length of a column.
|
|
18
18
|
|
|
19
19
|
Returns:
|
|
20
|
-
A list of slices. The len() of the returned list will *always* equal
|
|
21
|
-
len(:column_widths:).
|
|
20
|
+
A list of slices. The len() of the returned list will *always* equal
|
|
21
|
+
len(:column_widths:).
|
|
22
22
|
"""
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
if len(column_widths) == 0:
|
|
25
25
|
return []
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
width = column_widths[0]
|
|
28
28
|
element = a_string[:width]
|
|
29
29
|
rest = a_string[width:]
|
|
30
|
-
return [element] + collimate(rest, column_widths[1:])
|
|
31
|
-
|
|
30
|
+
return [element] + collimate(rest, column_widths[1:])
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2022 Jamie Hardt.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -1,29 +1,32 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: pycmx
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Python CMX 3600 Edit Decision List Parser
|
|
5
|
+
Home-page: https://github.com/iluvcapra/pycmx
|
|
6
|
+
License: MIT
|
|
5
7
|
Keywords: parser,film,broadcast
|
|
6
|
-
Author
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
Author: Jamie Hardt
|
|
9
|
+
Author-email: jamiehardt@me.com
|
|
10
|
+
Requires-Python: >=3.8,<4.0
|
|
9
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier:
|
|
12
|
-
Classifier: Topic :: Multimedia :: Video
|
|
13
|
-
Classifier: Topic :: Text Processing
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.8
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Project-URL: Issues, https://github.com/iluvcapra/pycmx/issues
|
|
25
|
-
Project-URL: Source, https://github.com/iluvcapra/pycmx.git
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Multimedia
|
|
21
|
+
Classifier: Topic :: Multimedia :: Video
|
|
22
|
+
Classifier: Topic :: Text Processing
|
|
26
23
|
Provides-Extra: doc
|
|
24
|
+
Requires-Dist: sphinx (>=5.3.0) ; extra == "doc"
|
|
25
|
+
Requires-Dist: sphinx_rtd_theme (>=1.1.1) ; extra == "doc"
|
|
26
|
+
Project-URL: Documentation, https://pycmx.readthedocs.io/
|
|
27
|
+
Project-URL: Repository, https://github.com/iluvcapra/pycmx.git
|
|
28
|
+
Project-URL: Tracker, https://github.com/iluvcapra/pycmx/issues
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
27
30
|
|
|
28
31
|
[](https://pycmx.readthedocs.io/en/latest/?badge=latest)   [](https://pypi.org/project/pycmx/) 
|
|
29
32
|

|
|
@@ -32,12 +35,14 @@ Provides-Extra: doc
|
|
|
32
35
|
|
|
33
36
|
# pycmx
|
|
34
37
|
|
|
35
|
-
The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and
|
|
38
|
+
The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and
|
|
39
|
+
its most most common variations.
|
|
36
40
|
|
|
37
41
|
## Features
|
|
38
42
|
|
|
39
|
-
* The major variations of the CMX 3600: the standard, "File32"
|
|
40
|
-
|
|
43
|
+
* The major variations of the CMX 3600: the standard, "File32", "File128" and
|
|
44
|
+
long Adobe Premiere event numbers are automatically detected and properly
|
|
45
|
+
read.
|
|
41
46
|
* Preserves relationship between events and individual edits/clips.
|
|
42
47
|
* Remark or comment fields with common recognized forms are read and
|
|
43
48
|
available to the client, including clip name and source file data.
|
|
@@ -111,5 +116,3 @@ Audio channel 7 is present
|
|
|
111
116
|
False
|
|
112
117
|
```
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
pycmx/__init__.py,sha256=u_VU3f3WltGoZzg_iZBLQZUtFqGFVZgBX3trqM7v3D4,365
|
|
2
|
+
pycmx/channel_map.py,sha256=vkGonW2CY5F_TCbcJm9jFp6O7JsNUW4hew5q8KiVWLo,3169
|
|
3
|
+
pycmx/edit.py,sha256=6AfQAHh8f4aRFs211d2-Jmkv-a8CmoIduOFL3N2lrQg,3893
|
|
4
|
+
pycmx/edit_list.py,sha256=hjOsCxrGwQemjpmUGdfnJa0_rSYs-LSYkfOrjlqU-gw,3170
|
|
5
|
+
pycmx/event.py,sha256=6fwbCjo1mX8Zkmbd6Maw4-msWm2zYCUGRIGSeqfFWdw,3809
|
|
6
|
+
pycmx/parse_cmx_events.py,sha256=9d0jCvOXCt1RPSEBLZhLScSsGq3oDVa2bBDar8x4j5U,477
|
|
7
|
+
pycmx/parse_cmx_statements.py,sha256=Wi0wjlJsphR3WXWXcJn7fPRDKANitZpQwvOSZ84HY2A,7242
|
|
8
|
+
pycmx/transition.py,sha256=IlVz2X3Z7CttJccqzzjhPPUNY2Pe8ZmVMIBnCrVLIG0,2364
|
|
9
|
+
pycmx/util.py,sha256=z1QYA4qUxiaCeKPhCQEmgIyhr5MHxSknW2xjl6qs1kA,782
|
|
10
|
+
pycmx-1.3.0.dist-info/LICENSE,sha256=JS087ZFloGaV7LHygeUaXmT-fGLx0VF30W62wDTLRCc,1056
|
|
11
|
+
pycmx-1.3.0.dist-info/METADATA,sha256=drKnB4Am-o2JUh_dFsDItdFV5JQYarvgOfl88icsBKY,4027
|
|
12
|
+
pycmx-1.3.0.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
|
|
13
|
+
pycmx-1.3.0.dist-info/RECORD,,
|
pycmx-1.2.2.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
pycmx/__init__.py,sha256=yS4sYKFfw3ScPaCr0_g0qSmhhZRxOLkgwR5kQ9nxYnc,388
|
|
2
|
-
pycmx/channel_map.py,sha256=M7P3b0S2cBrmQjfSP5sixFxGv_uBjvie0f9PVdy9cMI,3248
|
|
3
|
-
pycmx/edit.py,sha256=sipPmT2W0hPoYyRyZzwohohjO1fqwEU_mxsQheZxWC0,3775
|
|
4
|
-
pycmx/edit_list.py,sha256=zZaOTEPgubHxetQF01BhMf6d097jXjvohYrMXtbP1a4,3172
|
|
5
|
-
pycmx/event.py,sha256=-wlOdOwOVVk_4Wus0D8PVxRiEPBv1XZrRiQwkoRIOY4,3781
|
|
6
|
-
pycmx/parse_cmx_events.py,sha256=_KeowR6jm4LlFWtejgZ0SL5jX-sMu8GKgx7ferUuRbE,472
|
|
7
|
-
pycmx/parse_cmx_statements.py,sha256=UqS-p1kAFR8cp1pgiiBZLlyIhF-8hOWykYIS6-JuEBg,6755
|
|
8
|
-
pycmx/transition.py,sha256=fAbWMqBZmZIcpZUobTlYuTd4WxercHMik_zqp7yK4mA,2378
|
|
9
|
-
pycmx/util.py,sha256=alnJaV9vqH-KGtExrWjBiYMTiTILDXnF8Ju0sXWa69A,801
|
|
10
|
-
pycmx-1.2.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
11
|
-
pycmx-1.2.2.dist-info/METADATA,sha256=WbFuxIxB-PIjdcqaQtdsT597a9gWlZR54hSXktzPcrI,3928
|
|
12
|
-
pycmx-1.2.2.dist-info/RECORD,,
|