pycmx 1.2.0__tar.gz → 1.2.2__tar.gz
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-1.2.0 → pycmx-1.2.2}/PKG-INFO +3 -2
- {pycmx-1.2.0 → pycmx-1.2.2}/pycmx/__init__.py +3 -4
- {pycmx-1.2.0 → pycmx-1.2.2}/pycmx/channel_map.py +14 -14
- {pycmx-1.2.0 → pycmx-1.2.2}/pycmx/edit.py +15 -13
- {pycmx-1.2.0 → pycmx-1.2.2}/pycmx/edit_list.py +11 -11
- {pycmx-1.2.0 → pycmx-1.2.2}/pycmx/event.py +26 -19
- pycmx-1.2.2/pycmx/parse_cmx_events.py +20 -0
- {pycmx-1.2.0 → pycmx-1.2.2}/pycmx/parse_cmx_statements.py +6 -4
- {pycmx-1.2.0 → pycmx-1.2.2}/pycmx/transition.py +14 -10
- {pycmx-1.2.0 → pycmx-1.2.2}/pyproject.toml +2 -1
- pycmx-1.2.0/pycmx/parse_cmx_events.py +0 -21
- {pycmx-1.2.0 → pycmx-1.2.2}/README.md +0 -0
- {pycmx-1.2.0 → pycmx-1.2.2}/pycmx/util.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pycmx
|
|
3
|
-
Version: 1.2.
|
|
4
|
-
Summary: pycmx is a
|
|
3
|
+
Version: 1.2.2
|
|
4
|
+
Summary: pycmx is a parser for CMX 3600-style EDLs.
|
|
5
5
|
Keywords: parser,film,broadcast
|
|
6
6
|
Author-email: Jamie Hardt <jamiehardt@me.com>
|
|
7
7
|
Requires-Python: ~=3.7
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
20
|
Requires-Dist: sphinx >= 5.3.0 ; extra == "doc"
|
|
20
21
|
Requires-Dist: sphinx_rtd_theme >= 1.1.1 ; extra == "doc"
|
|
21
22
|
Project-URL: Documentation, https://pycmx.readthedocs.io/
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
pycmx is a
|
|
4
|
-
examples see README.md
|
|
3
|
+
pycmx is a parser for CMX 3600-style EDLs.
|
|
5
4
|
|
|
6
|
-
This module (c)
|
|
5
|
+
This module (c) 2023 Jamie Hardt. For more information on your rights to
|
|
7
6
|
copy and reuse this software, refer to the LICENSE file included with the
|
|
8
7
|
distribution.
|
|
9
8
|
"""
|
|
10
9
|
|
|
11
|
-
__version__ = '1.2.
|
|
10
|
+
__version__ = '1.2.2'
|
|
12
11
|
|
|
13
12
|
from .parse_cmx_events import parse_cmx3600
|
|
14
13
|
from .transition import Transition
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# (c) 2018 Jamie Hardt
|
|
3
3
|
|
|
4
4
|
from re import (compile, match)
|
|
5
|
-
from typing import Dict, Tuple
|
|
5
|
+
from typing import Dict, Tuple, Generator
|
|
6
6
|
|
|
7
7
|
class ChannelMap:
|
|
8
8
|
"""
|
|
@@ -24,62 +24,62 @@ class ChannelMap:
|
|
|
24
24
|
self.v = v
|
|
25
25
|
|
|
26
26
|
@property
|
|
27
|
-
def video(self):
|
|
27
|
+
def video(self) -> bool:
|
|
28
28
|
'True if video is included'
|
|
29
29
|
return self.v
|
|
30
30
|
|
|
31
31
|
@property
|
|
32
|
-
def audio(self):
|
|
32
|
+
def audio(self) -> bool:
|
|
33
33
|
'True if an audio channel is included'
|
|
34
34
|
return len(self._audio_channel_set) > 0
|
|
35
35
|
|
|
36
36
|
@property
|
|
37
|
-
def channels(self):
|
|
37
|
+
def channels(self) -> Generator[int, None, None]:
|
|
38
38
|
'A generator for each audio channel'
|
|
39
39
|
for c in self._audio_channel_set:
|
|
40
40
|
yield c
|
|
41
41
|
|
|
42
42
|
@property
|
|
43
|
-
def a1(self):
|
|
43
|
+
def a1(self) -> bool:
|
|
44
44
|
"""True if A1 is included"""
|
|
45
45
|
return self.get_audio_channel(1)
|
|
46
46
|
|
|
47
47
|
@a1.setter
|
|
48
|
-
def a1(self,val):
|
|
48
|
+
def a1(self, val: bool):
|
|
49
49
|
self.set_audio_channel(1,val)
|
|
50
50
|
|
|
51
51
|
@property
|
|
52
|
-
def a2(self):
|
|
52
|
+
def a2(self) -> bool:
|
|
53
53
|
"""True if A2 is included"""
|
|
54
54
|
return self.get_audio_channel(2)
|
|
55
55
|
|
|
56
56
|
@a2.setter
|
|
57
|
-
def a2(self,val):
|
|
57
|
+
def a2(self, val: bool):
|
|
58
58
|
self.set_audio_channel(2,val)
|
|
59
59
|
|
|
60
60
|
@property
|
|
61
|
-
def a3(self):
|
|
61
|
+
def a3(self) -> bool:
|
|
62
62
|
"""True if A3 is included"""
|
|
63
63
|
return self.get_audio_channel(3)
|
|
64
64
|
|
|
65
65
|
@a3.setter
|
|
66
|
-
def a3(self,val):
|
|
66
|
+
def a3(self, val: bool):
|
|
67
67
|
self.set_audio_channel(3,val)
|
|
68
68
|
|
|
69
69
|
@property
|
|
70
|
-
def a4(self):
|
|
70
|
+
def a4(self) -> bool:
|
|
71
71
|
"""True if A4 is included"""
|
|
72
72
|
return self.get_audio_channel(4)
|
|
73
73
|
|
|
74
74
|
@a4.setter
|
|
75
|
-
def a4(self,val):
|
|
75
|
+
def a4(self,val: bool):
|
|
76
76
|
self.set_audio_channel(4,val)
|
|
77
77
|
|
|
78
|
-
def get_audio_channel(self,chan_num):
|
|
78
|
+
def get_audio_channel(self, chan_num) -> bool:
|
|
79
79
|
"""True if chan_num is included"""
|
|
80
80
|
return (chan_num in self._audio_channel_set)
|
|
81
81
|
|
|
82
|
-
def set_audio_channel(self,chan_num,enabled):
|
|
82
|
+
def set_audio_channel(self,chan_num, enabled: bool):
|
|
83
83
|
"""If enabled is true, chan_num will be included"""
|
|
84
84
|
if enabled:
|
|
85
85
|
self._audio_channel_set.add(chan_num)
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
from .transition import Transition
|
|
5
5
|
from .channel_map import ChannelMap
|
|
6
|
-
from .parse_cmx_statements import StmtEffectsName
|
|
6
|
+
# from .parse_cmx_statements import StmtEffectsName
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
7
9
|
|
|
8
10
|
class Edit:
|
|
9
11
|
"""
|
|
@@ -18,7 +20,7 @@ class Edit:
|
|
|
18
20
|
self.trans_name_statement = trans_name_statement
|
|
19
21
|
|
|
20
22
|
@property
|
|
21
|
-
def line_number(self):
|
|
23
|
+
def line_number(self) -> int:
|
|
22
24
|
"""
|
|
23
25
|
Get the line number for the "standard form" statement associated with
|
|
24
26
|
this edit. Line numbers a zero-indexed, such that the
|
|
@@ -27,7 +29,7 @@ class Edit:
|
|
|
27
29
|
return self.edit_statement.line_number
|
|
28
30
|
|
|
29
31
|
@property
|
|
30
|
-
def channels(self):
|
|
32
|
+
def channels(self) -> ChannelMap:
|
|
31
33
|
"""
|
|
32
34
|
Get the :obj:`ChannelMap` object associated with this Edit.
|
|
33
35
|
"""
|
|
@@ -38,7 +40,7 @@ class Edit:
|
|
|
38
40
|
return cm
|
|
39
41
|
|
|
40
42
|
@property
|
|
41
|
-
def transition(self):
|
|
43
|
+
def transition(self) -> Transition:
|
|
42
44
|
"""
|
|
43
45
|
Get the :obj:`Transition` object associated with this edit.
|
|
44
46
|
"""
|
|
@@ -48,14 +50,14 @@ class Edit:
|
|
|
48
50
|
return Transition(self.edit_statement.trans, self.edit_statement.trans_op, None)
|
|
49
51
|
|
|
50
52
|
@property
|
|
51
|
-
def source_in(self):
|
|
53
|
+
def source_in(self) -> str:
|
|
52
54
|
"""
|
|
53
55
|
Get the source in timecode.
|
|
54
56
|
"""
|
|
55
57
|
return self.edit_statement.source_in
|
|
56
58
|
|
|
57
59
|
@property
|
|
58
|
-
def source_out(self):
|
|
60
|
+
def source_out(self) -> str:
|
|
59
61
|
"""
|
|
60
62
|
Get the source out timecode.
|
|
61
63
|
"""
|
|
@@ -63,7 +65,7 @@ class Edit:
|
|
|
63
65
|
return self.edit_statement.source_out
|
|
64
66
|
|
|
65
67
|
@property
|
|
66
|
-
def record_in(self):
|
|
68
|
+
def record_in(self) -> str:
|
|
67
69
|
"""
|
|
68
70
|
Get the record in timecode.
|
|
69
71
|
"""
|
|
@@ -71,7 +73,7 @@ class Edit:
|
|
|
71
73
|
return self.edit_statement.record_in
|
|
72
74
|
|
|
73
75
|
@property
|
|
74
|
-
def record_out(self):
|
|
76
|
+
def record_out(self) -> str:
|
|
75
77
|
"""
|
|
76
78
|
Get the record out timecode.
|
|
77
79
|
"""
|
|
@@ -79,7 +81,7 @@ class Edit:
|
|
|
79
81
|
return self.edit_statement.record_out
|
|
80
82
|
|
|
81
83
|
@property
|
|
82
|
-
def source(self):
|
|
84
|
+
def source(self) -> str:
|
|
83
85
|
"""
|
|
84
86
|
Get the source column. This is the 8, 32 or 128-character string on the
|
|
85
87
|
event record line, this usually references the tape name of the source.
|
|
@@ -87,21 +89,21 @@ class Edit:
|
|
|
87
89
|
return self.edit_statement.source
|
|
88
90
|
|
|
89
91
|
@property
|
|
90
|
-
def black(self):
|
|
92
|
+
def black(self) -> bool:
|
|
91
93
|
"""
|
|
92
94
|
Black video or silence should be used as the source for this event.
|
|
93
95
|
"""
|
|
94
96
|
return self.source == "BL"
|
|
95
97
|
|
|
96
98
|
@property
|
|
97
|
-
def aux_source(self):
|
|
99
|
+
def aux_source(self) -> bool:
|
|
98
100
|
"""
|
|
99
101
|
An auxiliary source is the source of this event.
|
|
100
102
|
"""
|
|
101
103
|
return self.source == "AX"
|
|
102
104
|
|
|
103
105
|
@property
|
|
104
|
-
def source_file(self):
|
|
106
|
+
def source_file(self) -> Optional[str]:
|
|
105
107
|
"""
|
|
106
108
|
Get the source file, as attested by a "* SOURCE FILE" remark on the
|
|
107
109
|
EDL. This will return None if the information is not present.
|
|
@@ -112,7 +114,7 @@ class Edit:
|
|
|
112
114
|
return self.source_file_statement.filename
|
|
113
115
|
|
|
114
116
|
@property
|
|
115
|
-
def clip_name(self):
|
|
117
|
+
def clip_name(self) -> Optional[str]:
|
|
116
118
|
"""
|
|
117
119
|
Get the clip name, as attested by a "* FROM CLIP NAME" or "* TO CLIP
|
|
118
120
|
NAME" remark on the EDL. This will return None if the information is
|
|
@@ -5,21 +5,21 @@ from .parse_cmx_statements import (StmtUnrecognized, StmtFCM, StmtEvent, StmtSou
|
|
|
5
5
|
from .event import Event
|
|
6
6
|
from .channel_map import ChannelMap
|
|
7
7
|
|
|
8
|
+
from typing import Generator
|
|
9
|
+
|
|
8
10
|
class EditList:
|
|
9
11
|
"""
|
|
10
|
-
Represents an entire edit decision list as returned by
|
|
11
|
-
|
|
12
|
+
Represents an entire edit decision list as returned by :func:`~pycmx.parse_cmx3600()`.
|
|
12
13
|
"""
|
|
13
14
|
def __init__(self, statements):
|
|
14
15
|
self.title_statement = statements[0]
|
|
15
16
|
self.event_statements = statements[1:]
|
|
16
17
|
|
|
17
|
-
|
|
18
18
|
@property
|
|
19
|
-
def format(self):
|
|
19
|
+
def format(self) -> str:
|
|
20
20
|
"""
|
|
21
|
-
The detected format of the EDL. Possible values are:
|
|
22
|
-
|
|
21
|
+
The detected format of the EDL. Possible values are: "3600", "File32",
|
|
22
|
+
"File128", and "unknown".
|
|
23
23
|
"""
|
|
24
24
|
first_event = next( (s for s in self.event_statements if type(s) is StmtEvent), None)
|
|
25
25
|
|
|
@@ -37,7 +37,7 @@ class EditList:
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
@property
|
|
40
|
-
def channels(self):
|
|
40
|
+
def channels(self) -> ChannelMap:
|
|
41
41
|
"""
|
|
42
42
|
Return the union of every channel channel.
|
|
43
43
|
"""
|
|
@@ -51,7 +51,7 @@ class EditList:
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
@property
|
|
54
|
-
def title(self):
|
|
54
|
+
def title(self) -> str:
|
|
55
55
|
"""
|
|
56
56
|
The title of this edit list.
|
|
57
57
|
"""
|
|
@@ -59,7 +59,7 @@ class EditList:
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
@property
|
|
62
|
-
def unrecognized_statements(self):
|
|
62
|
+
def unrecognized_statements(self) -> Generator[StmtUnrecognized, None, None]:
|
|
63
63
|
"""
|
|
64
64
|
A generator for all the unrecognized statements in the list.
|
|
65
65
|
"""
|
|
@@ -69,7 +69,7 @@ class EditList:
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
@property
|
|
72
|
-
def events(self):
|
|
72
|
+
def events(self) -> Generator[Event, None, None]:
|
|
73
73
|
'A generator for all the events in the edit list'
|
|
74
74
|
is_drop = None
|
|
75
75
|
current_event_num = None
|
|
@@ -97,7 +97,7 @@ class EditList:
|
|
|
97
97
|
yield Event(statements=event_statements)
|
|
98
98
|
|
|
99
99
|
@property
|
|
100
|
-
def sources(self):
|
|
100
|
+
def sources(self) -> Generator[StmtSourceUMID, None, None]:
|
|
101
101
|
"""
|
|
102
102
|
A generator for all of the sources in the list
|
|
103
103
|
"""
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
# pycmx
|
|
2
|
-
# (c)
|
|
2
|
+
# (c) 2023 Jamie Hardt
|
|
3
3
|
|
|
4
4
|
from .parse_cmx_statements import (StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized, StmtEffectsName)
|
|
5
|
-
from .edit import Edit
|
|
5
|
+
from .edit import Edit
|
|
6
|
+
|
|
7
|
+
from typing import List, Generator, Optional, Tuple, Any
|
|
6
8
|
|
|
7
9
|
class Event:
|
|
8
10
|
"""
|
|
9
|
-
Represents a collection of :class
|
|
11
|
+
Represents a collection of :class:`~pycmx.edit.Edit` s, all with the same event number.
|
|
10
12
|
"""
|
|
11
13
|
|
|
12
14
|
def __init__(self, statements):
|
|
13
15
|
self.statements = statements
|
|
14
16
|
|
|
15
17
|
@property
|
|
16
|
-
def number(self):
|
|
18
|
+
def number(self) -> int:
|
|
17
19
|
"""
|
|
18
20
|
Return the event number.
|
|
19
21
|
"""
|
|
20
22
|
return int(self._edit_statements()[0].event)
|
|
21
23
|
|
|
22
24
|
@property
|
|
23
|
-
def edits(self):
|
|
25
|
+
def edits(self) -> List[Edit]:
|
|
24
26
|
"""
|
|
25
27
|
Returns the edits. Most events will have a single edit, a single event
|
|
26
28
|
will have multiple edits when a dissolve, wipe or key transition needs
|
|
@@ -30,17 +32,19 @@ class Event:
|
|
|
30
32
|
clip_names = self._clip_name_statements()
|
|
31
33
|
source_files= self._source_file_statements()
|
|
32
34
|
|
|
33
|
-
the_zip = [edits_audio]
|
|
35
|
+
the_zip: List[List[Any]] = [edits_audio]
|
|
34
36
|
|
|
35
37
|
if len(edits_audio) == 2:
|
|
36
|
-
|
|
38
|
+
start_name: Optional[StmtClipName] = None
|
|
39
|
+
end_name: Optional[StmtClipName] = None
|
|
40
|
+
|
|
37
41
|
for clip_name in clip_names:
|
|
38
42
|
if clip_name.affect == 'from':
|
|
39
|
-
|
|
43
|
+
start_name = clip_name
|
|
40
44
|
elif clip_name.affect == 'to':
|
|
41
|
-
|
|
45
|
+
end_name = clip_name
|
|
42
46
|
|
|
43
|
-
the_zip.append(
|
|
47
|
+
the_zip.append([start_name, end_name])
|
|
44
48
|
else:
|
|
45
49
|
if len(edits_audio) == len(clip_names):
|
|
46
50
|
the_zip.append(clip_names)
|
|
@@ -57,17 +61,20 @@ class Event:
|
|
|
57
61
|
# attach trans name to last event
|
|
58
62
|
try:
|
|
59
63
|
trans_statement = self._trans_name_statements()[0]
|
|
60
|
-
trans_names = [None] * (len(edits_audio) - 1)
|
|
64
|
+
trans_names: List[Optional[Any]] = [None] * (len(edits_audio) - 1)
|
|
61
65
|
trans_names.append(trans_statement)
|
|
62
66
|
the_zip.append(trans_names)
|
|
63
67
|
except IndexError:
|
|
64
68
|
the_zip.append([None] * len(edits_audio) )
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
return [ Edit(edit_statement=e1[0],
|
|
71
|
+
audio_ext_statement=e1[1],
|
|
72
|
+
clip_name_statement=n1,
|
|
73
|
+
source_file_statement=s1,
|
|
74
|
+
trans_name_statement=u1) for (e1,n1,s1,u1) in zip(*the_zip) ]
|
|
68
75
|
|
|
69
76
|
@property
|
|
70
|
-
def unrecognized_statements(self):
|
|
77
|
+
def unrecognized_statements(self) -> Generator[StmtUnrecognized, None, None]:
|
|
71
78
|
"""
|
|
72
79
|
A generator for all the unrecognized statements in the event.
|
|
73
80
|
"""
|
|
@@ -75,19 +82,19 @@ class Event:
|
|
|
75
82
|
if type(s) is StmtUnrecognized:
|
|
76
83
|
yield s
|
|
77
84
|
|
|
78
|
-
def _trans_name_statements(self):
|
|
85
|
+
def _trans_name_statements(self) -> List[StmtEffectsName]:
|
|
79
86
|
return [s for s in self.statements if type(s) is StmtEffectsName]
|
|
80
87
|
|
|
81
|
-
def _edit_statements(self):
|
|
88
|
+
def _edit_statements(self) -> List[StmtEvent]:
|
|
82
89
|
return [s for s in self.statements if type(s) is StmtEvent]
|
|
83
90
|
|
|
84
|
-
def _clip_name_statements(self):
|
|
91
|
+
def _clip_name_statements(self) -> List[StmtClipName]:
|
|
85
92
|
return [s for s in self.statements if type(s) is StmtClipName]
|
|
86
93
|
|
|
87
|
-
def _source_file_statements(self):
|
|
94
|
+
def _source_file_statements(self) -> List[StmtSourceFile]:
|
|
88
95
|
return [s for s in self.statements if type(s) is StmtSourceFile]
|
|
89
96
|
|
|
90
|
-
def _statements_with_audio_ext(self):
|
|
97
|
+
def _statements_with_audio_ext(self) -> Generator[Tuple[StmtEvent, Optional[StmtAudioExt]], None, None]:
|
|
91
98
|
for (s1, s2) in zip(self.statements, self.statements[1:]):
|
|
92
99
|
if type(s1) is StmtEvent and type(s2) is StmtAudioExt:
|
|
93
100
|
yield (s1,s2)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# pycmx
|
|
2
|
+
# (c) 2018 Jamie Hardt
|
|
3
|
+
|
|
4
|
+
# from collections import namedtuple
|
|
5
|
+
|
|
6
|
+
from .parse_cmx_statements import (parse_cmx3600_statements)
|
|
7
|
+
from .edit_list import EditList
|
|
8
|
+
|
|
9
|
+
from typing import TextIO
|
|
10
|
+
|
|
11
|
+
def parse_cmx3600(f: TextIO):
|
|
12
|
+
"""
|
|
13
|
+
Parse a CMX 3600 EDL.
|
|
14
|
+
|
|
15
|
+
:param TextIO f: a file-like object, anything that's readlines-able.
|
|
16
|
+
:returns: An :class:`pycmx.edit_list.EditList`.
|
|
17
|
+
"""
|
|
18
|
+
statements = parse_cmx3600_statements(f)
|
|
19
|
+
return EditList(statements)
|
|
20
|
+
|
|
@@ -5,6 +5,8 @@ import re
|
|
|
5
5
|
import sys
|
|
6
6
|
from collections import namedtuple
|
|
7
7
|
from itertools import count
|
|
8
|
+
from typing import TextIO, List
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
from .util import collimate
|
|
10
12
|
|
|
@@ -18,12 +20,12 @@ StmtSourceFile = namedtuple("SourceFile",["filename","line_number"])
|
|
|
18
20
|
StmtRemark = namedtuple("Remark",["text","line_number"])
|
|
19
21
|
StmtEffectsName = namedtuple("EffectsName",["name","line_number"])
|
|
20
22
|
StmtSourceUMID = namedtuple("Source",["name","umid","line_number"])
|
|
21
|
-
StmtSplitEdit = namedtuple("SplitEdit",["video","
|
|
23
|
+
StmtSplitEdit = namedtuple("SplitEdit",["video","magnitude", "line_number"])
|
|
22
24
|
StmtMotionMemory = namedtuple("MotionMemory",["source","fps"]) # FIXME needs more fields
|
|
23
25
|
StmtUnrecognized = namedtuple("Unrecognized",["content","line_number"])
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
def parse_cmx3600_statements(file):
|
|
28
|
+
def parse_cmx3600_statements(file: TextIO) -> List[object]:
|
|
27
29
|
"""
|
|
28
30
|
Return a list of every statement in the file argument.
|
|
29
31
|
"""
|
|
@@ -109,7 +111,7 @@ def _parse_extended_audio_channels(line, line_number):
|
|
|
109
111
|
else:
|
|
110
112
|
return StmtUnrecognized(content=line, line_number=line_number)
|
|
111
113
|
|
|
112
|
-
def _parse_remark(line, line_number):
|
|
114
|
+
def _parse_remark(line, line_number) -> object:
|
|
113
115
|
if line.startswith("FROM CLIP NAME:"):
|
|
114
116
|
return StmtClipName(name=line[15:].strip() , affect="from", line_number=line_number)
|
|
115
117
|
elif line.startswith("TO CLIP NAME:"):
|
|
@@ -119,7 +121,7 @@ def _parse_remark(line, line_number):
|
|
|
119
121
|
else:
|
|
120
122
|
return StmtRemark(text=line, line_number=line_number)
|
|
121
123
|
|
|
122
|
-
def _parse_effects_name(line, line_number):
|
|
124
|
+
def _parse_effects_name(line, line_number) -> StmtEffectsName:
|
|
123
125
|
name = line[16:].strip()
|
|
124
126
|
return StmtEffectsName(name=name, line_number=line_number)
|
|
125
127
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# pycmx
|
|
2
|
-
# (c)
|
|
2
|
+
# (c) 2023 Jamie Hardt
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
3
5
|
|
|
4
6
|
class Transition:
|
|
5
7
|
"""
|
|
@@ -19,7 +21,7 @@ class Transition:
|
|
|
19
21
|
self.name = name
|
|
20
22
|
|
|
21
23
|
@property
|
|
22
|
-
def kind(self):
|
|
24
|
+
def kind(self) -> Optional[str]:
|
|
23
25
|
"""
|
|
24
26
|
Return the kind of transition: Cut, Wipe, etc
|
|
25
27
|
"""
|
|
@@ -37,22 +39,22 @@ class Transition:
|
|
|
37
39
|
return Transition.KeyOut
|
|
38
40
|
|
|
39
41
|
@property
|
|
40
|
-
def cut(self):
|
|
42
|
+
def cut(self) -> bool:
|
|
41
43
|
"`True` if this transition is a cut."
|
|
42
44
|
return self.transition == 'C'
|
|
43
45
|
|
|
44
46
|
@property
|
|
45
|
-
def dissolve(self):
|
|
47
|
+
def dissolve(self) -> bool:
|
|
46
48
|
"`True` if this traansition is a dissolve."
|
|
47
49
|
return self.transition == 'D'
|
|
48
50
|
|
|
49
51
|
@property
|
|
50
|
-
def wipe(self):
|
|
52
|
+
def wipe(self) -> bool:
|
|
51
53
|
"`True` if this transition is a wipe."
|
|
52
54
|
return self.transition.startswith('W')
|
|
53
55
|
|
|
54
56
|
@property
|
|
55
|
-
def effect_duration(self):
|
|
57
|
+
def effect_duration(self) -> int:
|
|
56
58
|
"""The duration of this transition, in frames of the record target.
|
|
57
59
|
|
|
58
60
|
In the event of a key event, this is the duration of the fade in.
|
|
@@ -60,7 +62,7 @@ class Transition:
|
|
|
60
62
|
return int(self.operand)
|
|
61
63
|
|
|
62
64
|
@property
|
|
63
|
-
def wipe_number(self):
|
|
65
|
+
def wipe_number(self) -> Optional[int]:
|
|
64
66
|
"Wipes are identified by a particular number."
|
|
65
67
|
if self.wipe:
|
|
66
68
|
return int(self.transition[1:])
|
|
@@ -68,19 +70,21 @@ class Transition:
|
|
|
68
70
|
return None
|
|
69
71
|
|
|
70
72
|
@property
|
|
71
|
-
def key_background(self):
|
|
73
|
+
def key_background(self) -> bool:
|
|
72
74
|
"`True` if this edit is a key background."
|
|
73
75
|
return self.transition == Transition.KeyBackground
|
|
74
76
|
|
|
75
77
|
@property
|
|
76
|
-
def key_foreground(self):
|
|
78
|
+
def key_foreground(self) -> bool:
|
|
77
79
|
"`True` if this edit is a key foreground."
|
|
78
80
|
return self.transition == Transition.Key
|
|
79
81
|
|
|
80
82
|
@property
|
|
81
|
-
def key_out(self):
|
|
83
|
+
def key_out(self) -> bool:
|
|
82
84
|
"""
|
|
83
85
|
`True` if this edit is a key out. This material will removed from
|
|
84
86
|
the key foreground and replaced with the key background.
|
|
85
87
|
"""
|
|
86
88
|
return self.transition == Transition.KeyOut
|
|
89
|
+
|
|
90
|
+
|
|
@@ -18,7 +18,8 @@ classifiers = [
|
|
|
18
18
|
'Programming Language :: Python :: 3.8',
|
|
19
19
|
'Programming Language :: Python :: 3.9',
|
|
20
20
|
'Programming Language :: Python :: 3.10',
|
|
21
|
-
'Programming Language :: Python :: 3.11'
|
|
21
|
+
'Programming Language :: Python :: 3.11',
|
|
22
|
+
'Programming Language :: Python :: 3.12'
|
|
22
23
|
]
|
|
23
24
|
dependencies = [
|
|
24
25
|
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# pycmx
|
|
2
|
-
# (c) 2018 Jamie Hardt
|
|
3
|
-
|
|
4
|
-
from collections import namedtuple
|
|
5
|
-
|
|
6
|
-
from .parse_cmx_statements import (parse_cmx3600_statements, StmtEvent,StmtFCM )
|
|
7
|
-
from .edit_list import EditList
|
|
8
|
-
|
|
9
|
-
def parse_cmx3600(f):
|
|
10
|
-
"""
|
|
11
|
-
Parse a CMX 3600 EDL.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
f : a file-like object, anything that's readlines-able.
|
|
15
|
-
|
|
16
|
-
Returns:
|
|
17
|
-
An :class:`pycmx.edit_list.EditList`.
|
|
18
|
-
"""
|
|
19
|
-
statements = parse_cmx3600_statements(f)
|
|
20
|
-
return EditList(statements)
|
|
21
|
-
|
|
File without changes
|
|
File without changes
|