mirror of
https://github.com/iluvcapra/pycmx.git
synced 2025-12-31 17:00:53 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57ea48e5e8 | ||
|
|
7e13978d9a | ||
|
|
f358704139 | ||
|
|
6201633956 | ||
|
|
2924ea548b | ||
|
|
88bf68c78e | ||
|
|
6d1ca12e42 | ||
|
|
829d98f4b4 | ||
|
|
8969e31969 | ||
|
|
484d2ae98f | ||
|
|
07652eaaa8 | ||
|
|
a9124e1f97 | ||
|
|
7e709241f8 | ||
|
|
731b8fcc00 | ||
|
|
597dceb9c5 | ||
|
|
c2c83d826a | ||
|
|
08dd1f956d | ||
|
|
989d52aaee | ||
|
|
b60610aa8b | ||
|
|
6611e38b9f | ||
|
|
abcce06865 | ||
|
|
9d586342be | ||
|
|
e5f632d8a4 | ||
|
|
30cd99431b | ||
|
|
cc76223cbc | ||
|
|
966f8c1ca4 |
7
.travis.yml
Normal file
7
.travis.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
language: python
|
||||||
|
python:
|
||||||
|
- "3.6"
|
||||||
|
script:
|
||||||
|
- "python3 setup.py test"
|
||||||
|
install:
|
||||||
|
- "pip3 install setuptools"
|
||||||
47
README.md
47
README.md
@@ -1,3 +1,5 @@
|
|||||||
|
[](https://travis-ci.com/iluvcapra/pycmx)
|
||||||
|
|
||||||
# pycmx
|
# pycmx
|
||||||
|
|
||||||
The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and its most most common variations.
|
The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and its most most common variations.
|
||||||
@@ -12,27 +14,29 @@ The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and it
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
import pycmx
|
|
||||||
pycmx.parse_cmx3600("INS4_R1_010417.edl")
|
>>> import pycmx
|
||||||
print(events[5:8])
|
>>> events = pycmx.parse_cmx3600("INS4_R1_010417.edl")
|
||||||
>>> [CmxEvent(title='INS4_R1_010417', number='000006',
|
>>> print(events[5:8])
|
||||||
clip_name='V1A-6A', source_name='A192C008_160909_R1BY',
|
[CmxEvent(title='INS4_R1_010417', number='000006',
|
||||||
channels=CmxChannelMap(v=True,a1=False,a2=False,a3=False,a4=False),
|
clip_name='V1A-6A', source_name='A192C008_160909_R1BY',
|
||||||
source_start='19:26:38:13', source_finish='19:27:12:03',
|
channels=CmxChannelMap(v=True, audio_channels=set()),
|
||||||
record_start='01:00:57:15', record_finish='01:01:31:05',
|
source_start='19:26:38:13', source_finish='19:27:12:03',
|
||||||
fcm_drop=False),
|
record_start='01:00:57:15', record_finish='01:01:31:05',
|
||||||
CmxEvent(title='INS4_R1_010417', number='000007',
|
fcm_drop=False),
|
||||||
clip_name='1-4A', source_name='A188C004_160908_R1BY',
|
CmxEvent(title='INS4_R1_010417', number='000007',
|
||||||
channels=CmxChannelMap(v=True,a1=False,a2=False,a3=False,a4=False),
|
clip_name='1-4A', source_name='A188C004_160908_R1BY',
|
||||||
source_start='19:29:48:01', source_finish='19:30:01:00',
|
channels=CmxChannelMap(v=True, audio_channels=set()),
|
||||||
record_start='01:01:31:05', record_finish='01:01:44:04',
|
source_start='19:29:48:01', source_finish='19:30:01:00',
|
||||||
fcm_drop=False),
|
record_start='01:01:31:05', record_finish='01:01:44:04',
|
||||||
CmxEvent(title='INS4_R1_010417', number='000008',
|
fcm_drop=False),
|
||||||
clip_name='2G-3', source_name='A056C007_160819_R1BY',
|
CmxEvent(title='INS4_R1_010417', number='000008',
|
||||||
channels=CmxChannelMap(v=True,a1=False,a2=False,a3=False,a4=False),
|
clip_name='2G-3', source_name='A056C007_160819_R1BY',
|
||||||
source_start='19:56:27:14', source_finish='19:56:41:00',
|
channels=CmxChannelMap(v=True, audio_channels=set()),
|
||||||
record_start='01:01:44:04', record_finish='01:01:57:14',
|
source_start='19:56:27:14', source_finish='19:56:41:00',
|
||||||
fcm_drop=False)]
|
record_start='01:01:44:04', record_finish='01:01:57:14',
|
||||||
|
fcm_drop=False)]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Known Issues/Roadmap
|
## Known Issues/Roadmap
|
||||||
@@ -41,6 +45,7 @@ To be addressed:
|
|||||||
* Does not decode transitions.
|
* Does not decode transitions.
|
||||||
* Does not decode "M2" speed changes.
|
* Does not decode "M2" speed changes.
|
||||||
* Does not decode repair notes, audio notes or other Avid-specific notes.
|
* Does not decode repair notes, audio notes or other Avid-specific notes.
|
||||||
|
* Does not decode Avid marker list.
|
||||||
|
|
||||||
May not be addressed:
|
May not be addressed:
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
# pycmx init
|
# pycmx init
|
||||||
|
|
||||||
from .parse_cmx import parse_cmx3600
|
from .parse_cmx import parse_cmx3600
|
||||||
|
|
||||||
|
__version__ = '0.5'
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
class CmxEvent:
|
class CmxEvent:
|
||||||
def __init__(self,title,number,clip_name,source_name,channels,source_start,source_finish,
|
def __init__(self,title,number,clip_name,source_name,channels,
|
||||||
record_start, record_finish, fcm_drop, remarks = [] , unrecognized = []):
|
transition,source_start,source_finish,
|
||||||
|
record_start, record_finish, fcm_drop, remarks = [] ,
|
||||||
|
unrecognized = [], line_number = None):
|
||||||
self.title = title
|
self.title = title
|
||||||
self.number = number
|
self.number = number
|
||||||
self.clip_name = clip_name
|
self.clip_name = clip_name
|
||||||
self.source_name = source_name
|
self.source_name = source_name
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
|
self.transition = transition
|
||||||
self.source_start = source_start
|
self.source_start = source_start
|
||||||
self.source_finish = source_finish
|
self.source_finish = source_finish
|
||||||
self.record_start = record_start
|
self.record_start = record_start
|
||||||
@@ -13,5 +16,79 @@ class CmxEvent:
|
|||||||
self.fcm_drop = fcm_drop
|
self.fcm_drop = fcm_drop
|
||||||
self.remarks = remarks
|
self.remarks = remarks
|
||||||
self.unrecgonized = unrecognized
|
self.unrecgonized = unrecognized
|
||||||
|
self.black = (source_name == 'BL')
|
||||||
|
self.aux_source = (source_name == 'AX')
|
||||||
|
self.line_number = line_number
|
||||||
|
|
||||||
|
|
||||||
|
def can_accept(self):
|
||||||
|
return {'AudioExt','Remark','SourceFile','ClipName','EffectsName'}
|
||||||
|
|
||||||
|
def accept_statement(self, statement):
|
||||||
|
statement_type = type(statement).__name__
|
||||||
|
if statement_type == 'AudioExt':
|
||||||
|
self.channels.appendExt(statement)
|
||||||
|
elif statement_type == 'Remark':
|
||||||
|
self.remarks.append(statement.text)
|
||||||
|
elif statement_type == 'SourceFile':
|
||||||
|
self.source_name = statement.filename
|
||||||
|
elif statement_type == 'ClipName':
|
||||||
|
self.clip_name = statement.name
|
||||||
|
elif statement_type == 'EffectsName':
|
||||||
|
self.transition.name = statement.name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"""CmxEvent(title={self.title.__repr__()},number={self.number.__repr__()},\
|
||||||
|
clip_name={self.clip_name.__repr__()},source_name={self.source_name.__repr__()},\
|
||||||
|
channels={self.channels.__repr__()},transition={self.transition.__repr__()},\
|
||||||
|
source_start={self.source_start.__repr__()},source_finish={self.source_finish.__repr__()},\
|
||||||
|
record_start={self.source_start.__repr__()},record_finish={self.record_finish.__repr__()},\
|
||||||
|
fcm_drop={self.fcm_drop.__repr__()},remarks={self.remarks.__repr__()},line_number={self.line_number.__repr__()})"""
|
||||||
|
|
||||||
|
|
||||||
|
class CmxTransition:
|
||||||
|
def __init__(self, transition, operand):
|
||||||
|
self.transition = transition
|
||||||
|
self.operand = operand
|
||||||
|
self.name = ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cut(self):
|
||||||
|
return self.transition == 'C'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dissolve(self):
|
||||||
|
return self.transition == 'D'
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wipe(self):
|
||||||
|
return self.transition.startswith('W')
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def effect_duration(self):
|
||||||
|
return int(self.operand)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wipe_number(self):
|
||||||
|
if self.wipe:
|
||||||
|
return int(self.transition[1:])
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def key_background(self):
|
||||||
|
return self.transition == 'KB'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def key_foreground(self):
|
||||||
|
return self.transition == 'K'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def key_out(self):
|
||||||
|
return self.transition == 'KO'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"""CmxTransition(transition={self.transition.__repr__()},operand={self.operand.__repr__()})"""
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# (c) 2018 Jamie Hardt
|
# (c) 2018 Jamie Hardt
|
||||||
|
|
||||||
from .parse_cmx_statements import parse_cmx3600_statements
|
from .parse_cmx_statements import parse_cmx3600_statements
|
||||||
|
from .cmx_event import CmxEvent, CmxTransition
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from re import compile, match
|
from re import compile, match
|
||||||
@@ -109,19 +110,12 @@ class CmxChannelMap:
|
|||||||
if matchresult:
|
if matchresult:
|
||||||
self.set_audio_channel(int( matchresult.group(1)), True )
|
self.set_audio_channel(int( matchresult.group(1)), True )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def appendExt(self, audio_ext):
|
def appendExt(self, audio_ext):
|
||||||
self.a3 = ext.audio3
|
self.a3 = ext.audio3
|
||||||
self.a4 = ext.audio4
|
self.a4 = ext.audio4
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "CmxChannelMap(v="+ self.v.__repr__( ) + \
|
return f"CmxChannelMap(v={self.v.__repr__()}, audio_channels={self._audio_channel_set.__repr__()})"
|
||||||
",a1=" + self.a1.__repr__() + \
|
|
||||||
",a2=" + self.a2.__repr__() + \
|
|
||||||
",a3=" + self.a3.__repr__() + \
|
|
||||||
",a4=" + self.a4.__repr__() +")"
|
|
||||||
|
|
||||||
|
|
||||||
def parse_cmx3600(file):
|
def parse_cmx3600(file):
|
||||||
@@ -133,11 +127,6 @@ def parse_cmx3600(file):
|
|||||||
return event_list(title, parser)
|
return event_list(title, parser)
|
||||||
|
|
||||||
|
|
||||||
CmxEvent = namedtuple('CmxEvent',['title','number','clip_name',
|
|
||||||
'source_name','channels','source_start','source_finish','record_start',
|
|
||||||
'record_finish','fcm_drop'])
|
|
||||||
|
|
||||||
|
|
||||||
def event_list(title, parser):
|
def event_list(title, parser):
|
||||||
state = {"fcm_drop" : False}
|
state = {"fcm_drop" : False}
|
||||||
|
|
||||||
@@ -149,35 +138,33 @@ def event_list(title, parser):
|
|||||||
state['fcm_drop'] = parser.current_token.drop
|
state['fcm_drop'] = parser.current_token.drop
|
||||||
elif parser.accept('Event'):
|
elif parser.accept('Event'):
|
||||||
if this_event != None:
|
if this_event != None:
|
||||||
event_t = CmxEvent(**this_event)
|
events_result.append(this_event)
|
||||||
events_result.append(event_t)
|
|
||||||
|
|
||||||
raw_event = parser.current_token
|
raw_event = parser.current_token
|
||||||
channels = CmxChannelMap({})
|
channels = CmxChannelMap(v=False, audio_channels=set([]))
|
||||||
channels.appendEvent(raw_event.channels)
|
channels.appendEvent(raw_event.channels)
|
||||||
|
|
||||||
this_event = {'title': title, 'number': raw_event.event, 'clip_name': None ,
|
this_event = CmxEvent(title=title,number=int(raw_event.event), clip_name=None ,
|
||||||
'source_name': raw_event.source,
|
source_name=raw_event.source,
|
||||||
'channels': channels,
|
channels=channels,
|
||||||
'source_start': raw_event.source_in,
|
transition=CmxTransition(raw_event.trans, raw_event.trans_op),
|
||||||
'source_finish': raw_event.source_out,
|
source_start= raw_event.source_in,
|
||||||
'record_start': raw_event.record_in,
|
source_finish= raw_event.source_out,
|
||||||
'record_finish': raw_event.record_out,
|
record_start= raw_event.record_in,
|
||||||
'fcm_drop': state['fcm_drop']}
|
record_finish= raw_event.record_out,
|
||||||
elif parser.accept('AudioExt'):
|
fcm_drop= state['fcm_drop'],
|
||||||
this_event['channels'].appendExt(parser.current_token)
|
line_number = raw_event.line_number)
|
||||||
elif parser.accept('ClipName'):
|
elif parser.accept('AudioExt') or parser.accept('ClipName') or \
|
||||||
this_event['clip_name'] = parser.current_token.name
|
parser.accept('SourceFile') or parser.accept('EffectsName') or \
|
||||||
elif parser.accept('SourceFile'):
|
parser.accept('Remark'):
|
||||||
this_event['source_name'] = parser.current_token.filename
|
this_event.accept_statement(parser.current_token)
|
||||||
elif parser.accept('Trailer'):
|
elif parser.accept('Trailer'):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
parser.next_token()
|
parser.next_token()
|
||||||
|
|
||||||
if this_event != None:
|
if this_event != None:
|
||||||
event_t = CmxEvent(**this_event)
|
events_result.append(this_event)
|
||||||
events_result.append(event_t)
|
|
||||||
|
|
||||||
return events_result
|
return events_result
|
||||||
|
|
||||||
|
|||||||
@@ -7,23 +7,26 @@ from .util import collimate
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from itertools import count
|
||||||
|
|
||||||
|
|
||||||
StmtTitle = namedtuple("Title",["title"])
|
StmtTitle = namedtuple("Title",["title","line_number"])
|
||||||
StmtFCM = namedtuple("FCM",["drop"])
|
StmtFCM = namedtuple("FCM",["drop","line_number"])
|
||||||
StmtEvent = namedtuple("Event",["event","source","channels","trans","trans_op","source_in","source_out","record_in","record_out"])
|
StmtEvent = namedtuple("Event",["event","source","channels","trans","trans_op","source_in","source_out","record_in","record_out","line_number"])
|
||||||
StmtAudioExt = namedtuple("AudioExt",["audio3","audio4"])
|
StmtAudioExt = namedtuple("AudioExt",["audio3","audio4","line_number"])
|
||||||
StmtClipName = namedtuple("ClipName",["name"])
|
StmtClipName = namedtuple("ClipName",["name","line_number"])
|
||||||
StmtSourceFile = namedtuple("SourceFile",["filename"])
|
StmtSourceFile = namedtuple("SourceFile",["filename","line_number"])
|
||||||
StmtRemark = namedtuple("Remark",["text"])
|
StmtRemark = namedtuple("Remark",["text","line_number"])
|
||||||
StmtTrailer = namedtuple("Trailer",["text"])
|
StmtEffectsName = namedtuple("EffectsName",["name","line_number"])
|
||||||
StmtUnrecognized = namedtuple("Unrecognized",["content"])
|
StmtTrailer = namedtuple("Trailer",["text","line_number"])
|
||||||
|
StmtUnrecognized = namedtuple("Unrecognized",["content","line_number"])
|
||||||
|
|
||||||
|
|
||||||
def parse_cmx3600_statements(path):
|
def parse_cmx3600_statements(path):
|
||||||
with open(path,'r') as file:
|
with open(path,'r') as file:
|
||||||
lines = file.readlines()
|
lines = file.readlines()
|
||||||
return [parse_cmx3600_line(line.strip()) for line in lines]
|
line_numbers = count()
|
||||||
|
return [parse_cmx3600_line(line.strip(), line_number) for (line, line_number) in zip(lines,line_numbers)]
|
||||||
|
|
||||||
def edl_column_widths(event_field_length, source_field_length):
|
def edl_column_widths(event_field_length, source_field_length):
|
||||||
return [event_field_length,2, source_field_length,1,
|
return [event_field_length,2, source_field_length,1,
|
||||||
@@ -35,77 +38,83 @@ def edl_column_widths(event_field_length, source_field_length):
|
|||||||
11,1,
|
11,1,
|
||||||
11]
|
11]
|
||||||
|
|
||||||
def parse_cmx3600_line(line):
|
def parse_cmx3600_line(line, line_number):
|
||||||
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)
|
return parse_title(line,line_number)
|
||||||
elif line.startswith("FCM:"):
|
elif line.startswith("FCM:"):
|
||||||
return parse_fcm(line)
|
return parse_fcm(line, line_number)
|
||||||
elif long_event_num_p.match(line) != None:
|
elif long_event_num_p.match(line) != 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)
|
return parse_long_standard_form(line, 32, line_number)
|
||||||
else:
|
else:
|
||||||
return parse_long_standard_form(line, 128)
|
return parse_long_standard_form(line, 128, line_number)
|
||||||
elif short_event_num_p.match(line) != None:
|
elif short_event_num_p.match(line) != None:
|
||||||
return parse_standard_form(line)
|
return parse_standard_form(line, line_number)
|
||||||
elif line.startswith("AUD"):
|
elif line.startswith("AUD"):
|
||||||
return parse_extended_audio_channels(line)
|
return parse_extended_audio_channels(line,line_number)
|
||||||
elif line.startswith("*"):
|
elif line.startswith("*"):
|
||||||
return parse_remark( line[1:].strip())
|
return parse_remark( line[1:].strip(), line_number)
|
||||||
elif line.startswith(">>>"):
|
elif line.startswith(">>>"):
|
||||||
return parse_trailer_statement(line)
|
return parse_trailer_statement(line, line_number)
|
||||||
|
elif line.startswith("EFFECTS NAME IS"):
|
||||||
|
return parse_effects_name(line, line_number)
|
||||||
else:
|
else:
|
||||||
return parse_unrecognized(line)
|
return parse_unrecognized(line, line_number)
|
||||||
|
|
||||||
|
|
||||||
def parse_title(line):
|
def parse_title(line, line_num):
|
||||||
title = line[6:].strip()
|
title = line[6:].strip()
|
||||||
return StmtTitle(title=title)
|
return StmtTitle(title=title,line_number=line_num)
|
||||||
|
|
||||||
def parse_fcm(line):
|
def parse_fcm(line, line_num):
|
||||||
val = line[4:].strip()
|
val = line[4:].strip()
|
||||||
if val == "DROP FRAME":
|
if val == "DROP FRAME":
|
||||||
return StmtFCM(drop= True)
|
return StmtFCM(drop= True, line_number=line_num)
|
||||||
else:
|
else:
|
||||||
return StmtFCM(drop= False)
|
return StmtFCM(drop= False, line_number=line_num)
|
||||||
|
|
||||||
def parse_long_standard_form(line,source_field_length):
|
def parse_long_standard_form(line,source_field_length, line_number):
|
||||||
return parse_columns_for_standard_form(line, 6, source_field_length)
|
return parse_columns_for_standard_form(line, 6, source_field_length, line_number)
|
||||||
|
|
||||||
def parse_standard_form(line):
|
def parse_standard_form(line, line_number):
|
||||||
return parse_columns_for_standard_form(line, 3, 8)
|
return parse_columns_for_standard_form(line, 3, 8, line_number)
|
||||||
|
|
||||||
def parse_extended_audio_channels(line):
|
def parse_extended_audio_channels(line, line_number):
|
||||||
content = line.strip()
|
content = line.strip()
|
||||||
if content == "AUD 3":
|
if content == "AUD 3":
|
||||||
return StmtAudioExt(audio3=True, audio4=False)
|
return StmtAudioExt(audio3=True, audio4=False, line_number=line_number)
|
||||||
elif content == "AUD 4":
|
elif content == "AUD 4":
|
||||||
return StmtAudioExt(audio3=False, audio4=True)
|
return StmtAudioExt(audio3=False, audio4=True, line_number=line_number)
|
||||||
elif content == "AUD 3 4":
|
elif content == "AUD 3 4":
|
||||||
return StmtAudioExt(audio3=True, audio4=True)
|
return StmtAudioExt(audio3=True, audio4=True, line_number=line_number)
|
||||||
else:
|
else:
|
||||||
return StmtUnrecognized(content=line)
|
return StmtUnrecognized(content=line, line_number=line_number)
|
||||||
|
|
||||||
def parse_remark(line):
|
def parse_remark(line, line_number):
|
||||||
if line.startswith("FROM CLIP NAME:"):
|
if line.startswith("FROM CLIP NAME:"):
|
||||||
return StmtClipName(name=line[15:].strip() )
|
return StmtClipName(name=line[15:].strip() , line_number=line_number)
|
||||||
elif line.startswith("SOURCE FILE:"):
|
elif line.startswith("SOURCE FILE:"):
|
||||||
return StmtSourceFile(filename=line[12:].strip() )
|
return StmtSourceFile(filename=line[12:].strip() , line_number=line_number)
|
||||||
else:
|
else:
|
||||||
return StmtRemark(text=line)
|
return StmtRemark(text=line, line_number=line_number)
|
||||||
|
|
||||||
def parse_unrecognized(line):
|
def parse_effects_name(line, line_number):
|
||||||
return StmtUnrecognized(content=line)
|
name = line[16:].strip()
|
||||||
|
return StmtEffectsName(name=name, line_number=line_number)
|
||||||
|
|
||||||
def parse_columns_for_standard_form(line, event_field_length, source_field_length):
|
def parse_unrecognized(line, line_number):
|
||||||
|
return StmtUnrecognized(content=line, line_number=line_number)
|
||||||
|
|
||||||
|
def parse_columns_for_standard_form(line, event_field_length, source_field_length, line_number):
|
||||||
col_widths = edl_column_widths(event_field_length, source_field_length)
|
col_widths = edl_column_widths(event_field_length, source_field_length)
|
||||||
|
|
||||||
if sum(col_widths) > len(line):
|
if sum(col_widths) > len(line):
|
||||||
return StmtUnrecognized(content=line)
|
return StmtUnrecognized(content=line, line_number=line_number)
|
||||||
|
|
||||||
column_strings = collimate(line,col_widths)
|
column_strings = collimate(line,col_widths)
|
||||||
|
|
||||||
@@ -117,10 +126,11 @@ def parse_columns_for_standard_form(line, event_field_length, source_field_lengt
|
|||||||
source_in=column_strings[10].strip(),
|
source_in=column_strings[10].strip(),
|
||||||
source_out=column_strings[12].strip(),
|
source_out=column_strings[12].strip(),
|
||||||
record_in=column_strings[14].strip(),
|
record_in=column_strings[14].strip(),
|
||||||
record_out=column_strings[16].strip())
|
record_out=column_strings[16].strip(),
|
||||||
|
line_number=line_number)
|
||||||
|
|
||||||
|
|
||||||
def parse_trailer_statement(line):
|
def parse_trailer_statement(line, line_number):
|
||||||
trimmed = line[3:].strip()
|
trimmed = line[3:].strip()
|
||||||
return StmtTrailer(trimmed)
|
return StmtTrailer(trimmed, line_number=line_number)
|
||||||
|
|
||||||
|
|||||||
4
setup.py
4
setup.py
@@ -4,14 +4,14 @@ with open("README.md", "r") as fh:
|
|||||||
long_description = fh.read()
|
long_description = fh.read()
|
||||||
|
|
||||||
setup(name='pycmx',
|
setup(name='pycmx',
|
||||||
version='0.3',
|
version='0.5',
|
||||||
author='Jamie Hardt',
|
author='Jamie Hardt',
|
||||||
author_email='jamiehardt@me.com',
|
author_email='jamiehardt@me.com',
|
||||||
description='CMX 3600 Edit Decision List Parser',
|
description='CMX 3600 Edit Decision List Parser',
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
url='https://github.com/iluvcapra/pycmx',
|
url='https://github.com/iluvcapra/pycmx',
|
||||||
classifiers=['Development Status :: 3 - Alpha',
|
classifiers=['Development Status :: 4 - Beta',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Topic :: Multimedia',
|
'Topic :: Multimedia',
|
||||||
'Topic :: Multimedia :: Video',
|
'Topic :: Multimedia :: Video',
|
||||||
|
|||||||
Binary file not shown.
60
tests/edls/test_24psf.edl
Executable file
60
tests/edls/test_24psf.edl
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
TITLE: Test EDL 24
|
||||||
|
|
||||||
|
001 AX V C 01:00:00:00 01:00:59:24 00:00:00:00 00:00:59:24
|
||||||
|
* FROM CLIP NAME: clip 1
|
||||||
|
|
||||||
|
002 AX AA C 00:00:00:00 00:01:30:00 00:00:00:00 00:01:30:00
|
||||||
|
* FROM CLIP NAME: clip #2
|
||||||
|
|
||||||
|
003 AX V C 00:00:00:00 00:00:30:01 00:00:59:24 00:01:30:00
|
||||||
|
* FROM CLIP NAME: clip -3
|
||||||
|
AUD 3 4
|
||||||
|
|
||||||
|
004 AX V C 00:00:00:00 00:00:24:17 00:01:30:00 00:01:54:17
|
||||||
|
* FROM CLIP NAME: clip $4
|
||||||
|
|
||||||
|
005 AX V C 00:00:00:00 00:00:24:17 00:01:30:00 00:01:54:17
|
||||||
|
* FROM CLIP NAME: clip &5
|
||||||
|
|
||||||
|
006 AX AA C 00:00:29:01 00:00:29:01 00:01:59:01 00:01:59:01
|
||||||
|
006 BL AA W001 025 00:00:00:00 00:00:10:20 00:01:59:01 00:02:10:21
|
||||||
|
EFFECTS NAME IS Constant Power
|
||||||
|
* FROM CLIP NAME: Test rename
|
||||||
|
* TO CLIP NAME: BL
|
||||||
|
|
||||||
|
007 AX V C 01:00:00:00 01:00:05:00 00:02:01:10 00:02:06:10
|
||||||
|
* FROM CLIP NAME: Black Video
|
||||||
|
|
||||||
|
008 AX V C 01:00:10:14 01:00:15:00 00:02:06:10 00:02:10:21
|
||||||
|
* FROM CLIP NAME: Jellyfish.jpg
|
||||||
|
|
||||||
|
009 AX V C 00:00:00:00 00:00:30:01 00:02:10:21 00:02:40:22
|
||||||
|
* FROM CLIP NAME: Test rename
|
||||||
|
M2 AX -25.0 00:00:00:00
|
||||||
|
|
||||||
|
010 AX AA C 00:00:00:00 00:00:30:01 00:02:10:21 00:02:40:22
|
||||||
|
* FROM CLIP NAME: Test rename
|
||||||
|
M2 AX -25.0 00:00:00:00
|
||||||
|
|
||||||
|
REM The MIT License (MIT)
|
||||||
|
REM
|
||||||
|
REM Copyright (c) 2013 <simon@simon-hargreaves.com>
|
||||||
|
REM
|
||||||
|
REM Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
REM of this software and associated documentation files (the "Software"), to deal
|
||||||
|
REM in the Software without restriction, including without limitation the rights
|
||||||
|
REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
REM copies of the Software, and to permit persons to whom the Software is
|
||||||
|
REM furnished to do so, subject to the following conditions:
|
||||||
|
REM
|
||||||
|
REM The above copyright notice and this permission notice shall be included in
|
||||||
|
REM all copies or substantial portions of the Software.
|
||||||
|
REM
|
||||||
|
REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
REM THE SOFTWARE.
|
||||||
|
REM
|
||||||
66
tests/edls/test_25.edl
Executable file
66
tests/edls/test_25.edl
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
TITLE: Sequence 01
|
||||||
|
|
||||||
|
001 AX V C 01:00:00:00 01:00:59:24 00:00:00:00 00:00:59:24
|
||||||
|
* FROM CLIP NAME: Jellyfish.jpg
|
||||||
|
|
||||||
|
002 AX AA C 00:00:00:00 00:01:30:00 00:00:00:00 00:01:30:00
|
||||||
|
* FROM CLIP NAME: N5_final screensaver_mp4.mov
|
||||||
|
|
||||||
|
003 AX V C 00:00:00:00 00:00:30:01 00:00:59:24 00:01:30:00
|
||||||
|
* FROM CLIP NAME: Test rename
|
||||||
|
AUD 3 4
|
||||||
|
|
||||||
|
004 AX V C 00:00:00:00 00:00:24:17 00:01:30:00 00:01:54:17
|
||||||
|
* FROM CLIP NAME: Test rename
|
||||||
|
|
||||||
|
005 AX V C 00:00:24:17 00:00:24:17 00:01:54:17 00:01:54:17
|
||||||
|
005 AX V D 070 00:59:58:21 01:00:05:14 00:01:54:17 00:02:01:10
|
||||||
|
EFFECTS NAME IS CROSS DISSOLVE
|
||||||
|
* FROM CLIP NAME: Test rename
|
||||||
|
* TO CLIP NAME: Jellyfish.jpg
|
||||||
|
|
||||||
|
006 AX AA C 00:00:00:00 00:00:29:01 00:01:30:00 00:01:59:01
|
||||||
|
* FROM CLIP NAME: Test rename
|
||||||
|
|
||||||
|
007 AX AA C 00:00:29:01 00:00:29:01 00:01:59:01 00:01:59:01
|
||||||
|
007 BL AA W001 025 00:00:00:00 00:00:10:20 00:01:59:01 00:02:10:21
|
||||||
|
EFFECTS NAME IS Constant Power
|
||||||
|
* FROM CLIP NAME: Test rename
|
||||||
|
* TO CLIP NAME: BL
|
||||||
|
|
||||||
|
008 AX V C 01:00:00:00 01:00:05:00 00:02:01:10 00:02:06:10
|
||||||
|
* FROM CLIP NAME: Black Video
|
||||||
|
|
||||||
|
009 AX V C 01:00:10:14 01:00:15:00 00:02:06:10 00:02:10:21
|
||||||
|
* FROM CLIP NAME: Jellyfish.jpg
|
||||||
|
|
||||||
|
010 AX V C 00:00:00:00 00:00:30:01 00:02:10:21 00:02:40:22
|
||||||
|
* FROM CLIP NAME: Test rename
|
||||||
|
M2 AX -25.0 00:00:00:00
|
||||||
|
|
||||||
|
011 AX AA C 00:00:00:00 00:00:30:01 00:02:10:21 00:02:40:22
|
||||||
|
* FROM CLIP NAME: Test rename
|
||||||
|
M2 AX -25.0 00:00:00:00
|
||||||
|
|
||||||
|
REM The MIT License (MIT)
|
||||||
|
REM
|
||||||
|
REM Copyright (c) 2013 <simon@simon-hargreaves.com>
|
||||||
|
REM
|
||||||
|
REM Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
REM of this software and associated documentation files (the "Software"), to deal
|
||||||
|
REM in the Software without restriction, including without limitation the rights
|
||||||
|
REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
REM copies of the Software, and to permit persons to whom the Software is
|
||||||
|
REM furnished to do so, subject to the following conditions:
|
||||||
|
REM
|
||||||
|
REM The above copyright notice and this permission notice shall be included in
|
||||||
|
REM all copies or substantial portions of the Software.
|
||||||
|
REM
|
||||||
|
REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
REM THE SOFTWARE.
|
||||||
|
REM
|
||||||
42
tests/edls/test_edl_cdl.edl
Normal file
42
tests/edls/test_edl_cdl.edl
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
TITLE: test_edl_cdl
|
||||||
|
FCM: NON-DROP FRAME
|
||||||
|
000001 BAAAAAAA_00001_ABCD V C 01:00:00:01 01:00:02:01 01:00:00:00 01:00:02:00
|
||||||
|
*ASC_SOP (0.8111 0.8112 0.8113)(0.2111 0.2112 0.2113)(1.8111 1.8112 1.8113)
|
||||||
|
*ASC_SAT 0.91
|
||||||
|
*Descript:
|
||||||
|
*FROM CLIP NAME: dra_001_0001_v0001
|
||||||
|
*LOC: 1:00:01:00 YELLOW DRA_001_0001
|
||||||
|
000002 BAAAAAAA_00001_ABCD V C 01:00:00:02 01:00:02:02 01:00:02:00 01:00:04:00
|
||||||
|
*ASC_SOP (0.8121 0.8122 0.8123)(0.2121 0.2122 0.2123)(1.8121 1.8122 1.8123)
|
||||||
|
*ASC_SAT 0.82
|
||||||
|
*Descript:
|
||||||
|
*FROM CLIP NAME: dra_001_0002_v0001
|
||||||
|
*LOC: 1:00:03:00 YELLOW DRA_001_0002
|
||||||
|
000003 BAAAAAAA_00001_ABCD V C 01:00:00:03 01:00:02:03 01:00:04:00 01:00:06:00
|
||||||
|
*ASC_SOP (0.8131 0.8132 0.8133)(0.2131 0.2132 0.2133)(1.8131 1.8132 1.8133)
|
||||||
|
*ASC_SAT 0.73
|
||||||
|
*Descript:
|
||||||
|
*FROM CLIP NAME: dra_001_0003_v0001
|
||||||
|
*LOC: 1:00:05:00 YELLOW DRA_001_0003
|
||||||
|
|
||||||
|
REM The MIT License (MIT)
|
||||||
|
REM
|
||||||
|
REM Copyright (c) 2014 Simon Hargreaves
|
||||||
|
REM
|
||||||
|
REM Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
REM of this software and associated documentation files (the "Software"), to deal
|
||||||
|
REM in the Software without restriction, including without limitation the rights
|
||||||
|
REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
REM copies of the Software, and to permit persons to whom the Software is
|
||||||
|
REM furnished to do so, subject to the following conditions:
|
||||||
|
REM
|
||||||
|
REM The above copyright notice and this permission notice shall be included in all
|
||||||
|
REM copies or substantial portions of the Software.
|
||||||
|
REM
|
||||||
|
REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
REM SOFTWARE.
|
||||||
@@ -23,5 +23,12 @@ class TestParse(TestCase):
|
|||||||
self.assertTrue(events[0].channels.a2)
|
self.assertTrue(events[0].channels.a2)
|
||||||
self.assertFalse(events[0].channels.a1)
|
self.assertFalse(events[0].channels.a1)
|
||||||
self.assertTrue(events[2].channels.get_audio_channel(7))
|
self.assertTrue(events[2].channels.get_audio_channel(7))
|
||||||
|
self.assertFalse(events[2].channels.get_audio_channel(1))
|
||||||
|
self.assertFalse(events[2].channels.get_audio_channel(2))
|
||||||
|
self.assertFalse(events[2].channels.get_audio_channel(3))
|
||||||
|
self.assertFalse(events[2].channels.get_audio_channel(4))
|
||||||
|
self.assertFalse(events[2].channels.get_audio_channel(10))
|
||||||
|
self.assertFalse(events[2].channels.get_audio_channel(11))
|
||||||
|
self.assertFalse(events[2].channels.get_audio_channel(12))
|
||||||
|
self.assertFalse(events[2].channels.get_audio_channel(13))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user