mirror of
https://github.com/iluvcapra/pycmx.git
synced 2025-12-31 08:50:54 +00:00
Merge branch 'master' of github.com:iluvcapra/pycmx
This commit is contained in:
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"
|
||||
45
README.md
45
README.md
@@ -1,3 +1,5 @@
|
||||
[](https://travis-ci.com/iluvcapra/pycmx)
|
||||
|
||||
# pycmx
|
||||
|
||||
The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and its most most common variations.
|
||||
@@ -8,39 +10,44 @@ The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and it
|
||||
formats are automatically detected and properly read.
|
||||
* Remark or comment fields with common recognized forms are read and
|
||||
available to the client, including clip name and source file data.
|
||||
* Symbolically decodes transitions
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
|
||||
>>> import pycmx
|
||||
>>> events = pycmx.parse_cmx3600("INS4_R1_010417.edl")
|
||||
>>> print(events[5:8])
|
||||
[CmxEvent(title='INS4_R1_010417', number='000006',
|
||||
clip_name='V1A-6A', source_name='A192C008_160909_R1BY',
|
||||
>>> result = pycmx.parse_cmx3600("STP R1 v082517.edl")
|
||||
>>> print(resul[0:3])
|
||||
[CmxEvent(title='STP_Reel 1_082517',number=1,
|
||||
clip_name='FKI_LEADER_HEAD_1920X1080.MOV',
|
||||
source_name='FKI_LEADER_HEAD_1920X1080.MOV',
|
||||
channels=CmxChannelMap(v=True, audio_channels=set()),
|
||||
source_start='19:26:38:13', source_finish='19:27:12:03',
|
||||
record_start='01:00:57:15', record_finish='01:01:31:05',
|
||||
fcm_drop=False),
|
||||
CmxEvent(title='INS4_R1_010417', number='000007',
|
||||
clip_name='1-4A', source_name='A188C004_160908_R1BY',
|
||||
transition=CmxTransition(transition='C',operand=''),
|
||||
source_start='01:00:00:00',source_finish='01:00:08:00',
|
||||
record_start='01:00:00:00',record_finish='01:00:08:00',
|
||||
fcm_drop=False,remarks=[],line_number=2),
|
||||
CmxEvent(title='STP_Reel 1_082517',number=2,
|
||||
clip_name='BH_PRODUCTIONS_1.85_PRORES.MOV',
|
||||
source_name='BH_PRODUCTIONS_1.85_PRORES.MOV',
|
||||
channels=CmxChannelMap(v=True, audio_channels=set()),
|
||||
source_start='19:29:48:01', source_finish='19:30:01:00',
|
||||
record_start='01:01:31:05', record_finish='01:01:44:04',
|
||||
fcm_drop=False),
|
||||
CmxEvent(title='INS4_R1_010417', number='000008',
|
||||
clip_name='2G-3', source_name='A056C007_160819_R1BY',
|
||||
transition=CmxTransition(transition='C',operand=''),
|
||||
source_start='01:00:00:00',source_finish='01:00:14:23',
|
||||
record_start='01:00:00:00',record_finish='01:00:23:00',
|
||||
fcm_drop=False,remarks=[],line_number=5),
|
||||
CmxEvent(title='STP_Reel 1_082517',number=3,
|
||||
clip_name='V4L-1*',
|
||||
source_name='B116C001_150514_R0UR',
|
||||
channels=CmxChannelMap(v=True, audio_channels=set()),
|
||||
source_start='19:56:27:14', source_finish='19:56:41:00',
|
||||
record_start='01:01:44:04', record_finish='01:01:57:14',
|
||||
fcm_drop=False)]
|
||||
|
||||
transition=CmxTransition(transition='C',operand=''),
|
||||
source_start='16:37:29:06',source_finish='16:37:40:22',
|
||||
record_start='16:37:29:06',record_finish='01:00:50:09',
|
||||
fcm_drop=False,remarks=[],line_number=8)]
|
||||
```
|
||||
|
||||
## Known Issues/Roadmap
|
||||
|
||||
To be addressed:
|
||||
* Does not decode transitions.
|
||||
* Does not decode "M2" speed changes.
|
||||
* Does not decode repair notes, audio notes or other Avid-specific notes.
|
||||
* Does not decode Avid marker list.
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
from .parse_cmx import parse_cmx3600
|
||||
|
||||
__version__ = '0.5'
|
||||
__version__ = '0.6'
|
||||
|
||||
@@ -12,7 +12,7 @@ class CmxEvent:
|
||||
def __init__(self,title,number,clip_name,source_name,channels,
|
||||
transition,source_start,source_finish,
|
||||
record_start, record_finish, fcm_drop, remarks = [] ,
|
||||
unrecognized = []):
|
||||
unrecognized = [], line_number = None):
|
||||
self.title = title
|
||||
self.number = number
|
||||
self.clip_name = clip_name
|
||||
@@ -28,6 +28,7 @@ class CmxEvent:
|
||||
self.unrecgonized = unrecognized
|
||||
self.black = (source_name == 'BL')
|
||||
self.aux_source = (source_name == 'AX')
|
||||
self.line_number = line_number
|
||||
|
||||
|
||||
def accept_statement(self, statement):
|
||||
@@ -45,12 +46,12 @@ class CmxEvent:
|
||||
self.transition.name = statement.name
|
||||
|
||||
def __repr__(self):
|
||||
return f"""CmxEvent(title="{self.title}",number={self.number},\
|
||||
clip_name="{self.clip_name}",source_name="{self.source_name}",\
|
||||
channels={self.channels},transition={self.transition},\
|
||||
source_start="{self.source_start}",source_finish="{self.source_finish}",\
|
||||
record_start="{self.source_start}",record_finish="{self.record_finish}",\
|
||||
fcm_drop={self.fcm_drop},remarks={self.remarks})"""
|
||||
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:
|
||||
@@ -109,5 +110,5 @@ class CmxTransition:
|
||||
return self.transition == 'KO'
|
||||
|
||||
def __repr__(self):
|
||||
return f"""CmxTransition(transition="{self.transition}",operand="{self.operand}")"""
|
||||
return f"""CmxTransition(transition={self.transition.__repr__()},operand={self.operand.__repr__()})"""
|
||||
|
||||
|
||||
@@ -110,15 +110,12 @@ class CmxChannelMap:
|
||||
if matchresult:
|
||||
self.set_audio_channel(int( matchresult.group(1)), True )
|
||||
|
||||
|
||||
|
||||
|
||||
def appendExt(self, audio_ext):
|
||||
self.a3 = ext.audio3
|
||||
self.a4 = ext.audio4
|
||||
|
||||
def __repr__(self):
|
||||
return f"CmxChannelMap(v={self.v}, audio_channels={self._audio_channel_set})"
|
||||
return f"CmxChannelMap(v={self.v.__repr__()}, audio_channels={self._audio_channel_set.__repr__()})"
|
||||
|
||||
|
||||
def parse_cmx3600(file):
|
||||
@@ -155,7 +152,8 @@ def event_list(title, parser):
|
||||
source_finish= raw_event.source_out,
|
||||
record_start= raw_event.record_in,
|
||||
record_finish= raw_event.record_out,
|
||||
fcm_drop= state['fcm_drop'])
|
||||
fcm_drop= state['fcm_drop'],
|
||||
line_number = raw_event.line_number)
|
||||
elif parser.accept('AudioExt') or parser.accept('ClipName') or \
|
||||
parser.accept('SourceFile') or parser.accept('EffectsName') or \
|
||||
parser.accept('Remark'):
|
||||
|
||||
@@ -7,24 +7,26 @@ from .util import collimate
|
||||
import re
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from itertools import count
|
||||
|
||||
|
||||
StmtTitle = namedtuple("Title",["title"])
|
||||
StmtFCM = namedtuple("FCM",["drop"])
|
||||
StmtEvent = namedtuple("Event",["event","source","channels","trans","trans_op","source_in","source_out","record_in","record_out"])
|
||||
StmtAudioExt = namedtuple("AudioExt",["audio3","audio4"])
|
||||
StmtClipName = namedtuple("ClipName",["name"])
|
||||
StmtSourceFile = namedtuple("SourceFile",["filename"])
|
||||
StmtRemark = namedtuple("Remark",["text"])
|
||||
StmtEffectsName = namedtuple("EffectsName",["name"])
|
||||
StmtTrailer = namedtuple("Trailer",["text"])
|
||||
StmtUnrecognized = namedtuple("Unrecognized",["content"])
|
||||
StmtTitle = namedtuple("Title",["title","line_number"])
|
||||
StmtFCM = namedtuple("FCM",["drop","line_number"])
|
||||
StmtEvent = namedtuple("Event",["event","source","channels","trans","trans_op","source_in","source_out","record_in","record_out","line_number"])
|
||||
StmtAudioExt = namedtuple("AudioExt",["audio3","audio4","line_number"])
|
||||
StmtClipName = namedtuple("ClipName",["name","line_number"])
|
||||
StmtSourceFile = namedtuple("SourceFile",["filename","line_number"])
|
||||
StmtRemark = namedtuple("Remark",["text","line_number"])
|
||||
StmtEffectsName = namedtuple("EffectsName",["name","line_number"])
|
||||
StmtTrailer = namedtuple("Trailer",["text","line_number"])
|
||||
StmtUnrecognized = namedtuple("Unrecognized",["content","line_number"])
|
||||
|
||||
|
||||
def parse_cmx3600_statements(path):
|
||||
with open(path,'r') as file:
|
||||
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):
|
||||
return [event_field_length,2, source_field_length,1,
|
||||
@@ -36,83 +38,83 @@ def edl_column_widths(event_field_length, source_field_length):
|
||||
11,1,
|
||||
11]
|
||||
|
||||
def parse_cmx3600_line(line):
|
||||
def parse_cmx3600_line(line, line_number):
|
||||
long_event_num_p = re.compile("^[0-9]{6} ")
|
||||
short_event_num_p = re.compile("^[0-9]{3} ")
|
||||
|
||||
if isinstance(line,str):
|
||||
if line.startswith("TITLE:"):
|
||||
return parse_title(line)
|
||||
return parse_title(line,line_number)
|
||||
elif line.startswith("FCM:"):
|
||||
return parse_fcm(line)
|
||||
return parse_fcm(line, line_number)
|
||||
elif long_event_num_p.match(line) != None:
|
||||
length_file_128 = sum(edl_column_widths(6,128))
|
||||
if len(line) < length_file_128:
|
||||
return parse_long_standard_form(line, 32)
|
||||
return parse_long_standard_form(line, 32, line_number)
|
||||
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:
|
||||
return parse_standard_form(line)
|
||||
return parse_standard_form(line, line_number)
|
||||
elif line.startswith("AUD"):
|
||||
return parse_extended_audio_channels(line)
|
||||
return parse_extended_audio_channels(line,line_number)
|
||||
elif line.startswith("*"):
|
||||
return parse_remark( line[1:].strip())
|
||||
return parse_remark( line[1:].strip(), line_number)
|
||||
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)
|
||||
return parse_effects_name(line, line_number)
|
||||
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()
|
||||
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()
|
||||
if val == "DROP FRAME":
|
||||
return StmtFCM(drop= True)
|
||||
return StmtFCM(drop= True, line_number=line_num)
|
||||
else:
|
||||
return StmtFCM(drop= False)
|
||||
return StmtFCM(drop= False, line_number=line_num)
|
||||
|
||||
def parse_long_standard_form(line,source_field_length):
|
||||
return parse_columns_for_standard_form(line, 6, 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, line_number)
|
||||
|
||||
def parse_standard_form(line):
|
||||
return parse_columns_for_standard_form(line, 3, 8)
|
||||
def parse_standard_form(line, line_number):
|
||||
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()
|
||||
if content == "AUD 3":
|
||||
return StmtAudioExt(audio3=True, audio4=False)
|
||||
return StmtAudioExt(audio3=True, audio4=False, line_number=line_number)
|
||||
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":
|
||||
return StmtAudioExt(audio3=True, audio4=True)
|
||||
return StmtAudioExt(audio3=True, audio4=True, line_number=line_number)
|
||||
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:"):
|
||||
return StmtClipName(name=line[15:].strip() )
|
||||
return StmtClipName(name=line[15:].strip() , line_number=line_number)
|
||||
elif line.startswith("SOURCE FILE:"):
|
||||
return StmtSourceFile(filename=line[12:].strip() )
|
||||
return StmtSourceFile(filename=line[12:].strip() , line_number=line_number)
|
||||
else:
|
||||
return StmtRemark(text=line)
|
||||
return StmtRemark(text=line, line_number=line_number)
|
||||
|
||||
def parse_effects_name(line):
|
||||
def parse_effects_name(line, line_number):
|
||||
name = line[16:].strip()
|
||||
return StmtEffectsName(name=name)
|
||||
return StmtEffectsName(name=name, line_number=line_number)
|
||||
|
||||
def parse_unrecognized(line):
|
||||
return StmtUnrecognized(content=line)
|
||||
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):
|
||||
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)
|
||||
|
||||
if sum(col_widths) > len(line):
|
||||
return StmtUnrecognized(content=line)
|
||||
return StmtUnrecognized(content=line, line_number=line_number)
|
||||
|
||||
column_strings = collimate(line,col_widths)
|
||||
|
||||
@@ -124,10 +126,11 @@ def parse_columns_for_standard_form(line, event_field_length, source_field_lengt
|
||||
source_in=column_strings[10].strip(),
|
||||
source_out=column_strings[12].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()
|
||||
return StmtTrailer(trimmed)
|
||||
return StmtTrailer(trimmed, line_number=line_number)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user