111 Commits
v0.2 ... v0.8

Author SHA1 Message Date
Jamie Hardt
23667fb782 Merge branch 'jamie' 2018-12-29 14:09:27 -08:00
Jamie Hardt
ce31cbb879 Read transition names 2018-12-29 14:06:22 -08:00
Jamie Hardt
914e8d5525 Blank lines 2018-12-29 13:14:52 -08:00
Jamie Hardt
21f8880099 Update edit.py
Black and Aux Source events
2018-12-29 12:53:18 -08:00
Jamie Hardt
348962c3f7 Code Style/Blank lines 2018-12-29 12:53:08 -08:00
Jamie Hardt
5e902d4926 Reorganized classes into separate files 2018-12-29 12:29:46 -08:00
Jamie Hardt
44f751fd75 Merge branch release into master 2018-12-28 23:09:02 -08:00
Jamie Hardt
16e8754a7b Merge branch master into release 2018-12-28 23:08:22 -08:00
Jamie Hardt
fbf55ec2e6 test tweaks
added a test and removed superfluous init file
2018-12-28 22:51:09 -08:00
Jamie Hardt
1fab2c3d71 channel_map.py
There was a really obvious typo
2018-12-28 22:15:27 -08:00
Jamie Hardt
28c2344a53 Version nudge
version 0.8 is next up
2018-12-26 22:42:12 -08:00
Jamie Hardt
dbf495f138 Merge branch 'master' of https://github.com/iluvcapra/pycmx 2018-12-26 22:20:35 -08:00
Jamie Hardt
2483d94d7b Docs 2018-12-26 22:19:33 -08:00
Jamie Hardt
dbe8a16eff Update index.rst 2018-12-26 21:47:59 -08:00
Jamie Hardt
dbbfc27196 Update conf.py
Experiment
2018-12-26 21:47:09 -08:00
Jamie Hardt
f0c257f15f Update index.rst 2018-12-26 21:42:58 -08:00
Jamie Hardt
cbec18607a Doc buildinfo 2018-12-26 21:38:10 -08:00
Jamie Hardt
acb12b7d9d Update index.rst 2018-12-26 21:29:31 -08:00
Jamie Hardt
618f6422cc Sphinx quickstart 2018-12-26 21:27:25 -08:00
Jamie Hardt
64001e8c78 Merge branch 'master' of https://github.com/iluvcapra/pycmx 2018-12-26 21:09:53 -08:00
Jamie Hardt
69dc7ed1ce Documentation
Docs folder
2018-12-26 21:08:25 -08:00
Jamie Hardt
98b4ff9106 Update README.md
Added docs badge
2018-12-26 20:59:26 -08:00
Jamie Hardt
18c6ff658a Update edl2scenelist.py
Format handling
2018-12-26 18:31:17 -08:00
Jamie Hardt
0a4309ab77 Update edl2scenelist.py
Ooops typos
2018-12-26 18:10:10 -08:00
Jamie Hardt
50fea58724 Update edl2scenelist.py
Output "cols" column output mode
2018-12-26 17:34:15 -08:00
Jamie Hardt
4ceb4be7ab Update test_parse.py
Some more tests
2018-12-26 17:30:19 -08:00
Jamie Hardt
fbb2b8700d Merge branch 'master' of https://github.com/iluvcapra/pycmx 2018-12-26 17:18:21 -08:00
Jamie Hardt
3f6ea4feee Create edl2scenelist.py
Wrote an example script for extracting scene lists from a video edl.
2018-12-26 17:18:17 -08:00
Jamie Hardt
fcd84b1edf Update README.md 2018-12-26 16:08:25 -08:00
Jamie Hardt
f85304d83b Update README.md 2018-12-26 16:06:57 -08:00
Jamie Hardt
205c58e52c Update README.md
Broke up usage examples
2018-12-26 15:57:57 -08:00
Jamie Hardt
d3cdce6b99 Update README.md 2018-12-26 15:53:33 -08:00
Jamie Hardt
32da584363 Update parse_cmx_events.py
Added unrecognized_statements properties to EditList and Events classes
2018-12-26 15:48:56 -08:00
Jamie Hardt
c74177953f Update test_parse.py
Added some more test case EDLs to the test
2018-12-26 15:35:27 -08:00
Jamie Hardt
5a4f57bd7e Update test_parse.py
Renamed typo method
2018-12-26 15:32:47 -08:00
Jamie Hardt
3e4c6d5955 Update test_parse.py
Tiwddle
2018-12-26 15:32:13 -08:00
Jamie Hardt
11034dd9f1 Update test_parse.py
event.number is an int now, does not need to be cast from a string
2018-12-26 15:30:25 -08:00
Jamie Hardt
bc02eb10fc Edit line numbers
Line numbers of edits are accessible
2018-12-26 15:29:49 -08:00
Jamie Hardt
9afe9d194d Update __init__.py
Added author
2018-12-26 15:29:01 -08:00
Jamie Hardt
44e911d878 Update README.md
Documentation updated
2018-12-26 14:27:05 -08:00
Jamie Hardt
82814522d1 Doc, file path
Documentation, cleaned up interface, and we now parse file handles, not file paths
2018-12-26 14:25:19 -08:00
Jamie Hardt
26b2f5274c Merge branch 'master' into jamie 2018-12-24 17:39:48 -08:00
Jamie Hardt
30ee3e0be5 Update test_parse.py 2018-12-24 17:32:23 -08:00
Jamie Hardt
82fc5f21da Update README.md 2018-12-24 17:30:56 -08:00
Jamie Hardt
d4353d1e68 Nudge version
v0.7
2018-12-24 16:31:00 -08:00
Jamie Hardt
15d14914ea Update README.md
Documentation on channels
2018-12-24 16:02:37 -08:00
Jamie Hardt
f44d5c470c ChannelMaps
Test implementation
2018-12-24 15:58:14 -08:00
Jamie Hardt
ca873af772 Adding channel map
First test works
2018-12-24 15:55:37 -08:00
Jamie Hardt
ab40ba1fa0 Update README.md 2018-12-24 14:37:37 -08:00
Jamie Hardt
782b9f7425 Update README.md 2018-12-24 14:36:54 -08:00
Jamie Hardt
483efdcc32 Update README.md 2018-12-24 14:33:03 -08:00
Jamie Hardt
6867f9ac4a Update README.md 2018-12-24 14:32:31 -08:00
Jamie Hardt
24272569e3 Update README.md 2018-12-24 14:30:35 -08:00
Jamie Hardt
16afb8fc64 Update parse_cmx_events.py
Made title a property
2018-12-24 14:30:31 -08:00
Jamie Hardt
d1e3eb85d3 Expose TC and transitions on class 2018-12-24 14:16:56 -08:00
Jamie Hardt
8d3bef2c09 Event reimplementation
Implementation of events and edits
2018-12-24 13:25:21 -08:00
Jamie Hardt
e0b7025fff Update parse_cmx_events.py
More implementation
2018-12-24 11:17:47 -08:00
Jamie Hardt
fbe9e9eeb9 Update parse_cmx_events.py
Tweaked loop initialization to make more clear
2018-12-24 02:03:43 -08:00
Jamie Hardt
168fd16473 Implementing events
Statements separated into events
2018-12-24 02:02:30 -08:00
Jamie Hardt
e4b6036ab7 Deleted parse_cmx etc
Going to reimplement these
2018-12-24 00:49:00 -08:00
Jamie Hardt
ce3d8088a1 Update parse_cmx_statements.py
Tweaked code style of tuple declarations
2018-12-24 00:30:06 -08:00
Jamie Hardt
9f41758b37 More Audio Test EDLs 2018-12-22 22:38:08 -08:00
Jamie Hardt
07407baf96 Added split events, some implementation of M2s 2018-12-12 16:10:16 -06:00
Jamie Hardt
aa309a4458 Ignore swapfiles 2018-12-12 16:09:48 -06:00
Jamie Hardt
2b8dd4c1c9 Typos 2018-12-10 23:46:44 -06:00
Jamie Hardt
387158b07c typo 2018-12-10 23:29:49 -06:00
Jamie Hardt
741c9d95e8 Moved TupleParser to util 2018-12-10 23:27:44 -06:00
Jamie Hardt
53764900ba Update parse_cmx.py
Cleared out blank lines
2018-12-10 23:19:04 -06:00
Jamie Hardt
66791081be Added some docstrings 2018-12-08 05:13:42 -08:00
Jamie Hardt
5e49c19ac2 Added kind method to CmxTransition 2018-12-07 10:34:02 -08:00
Jamie Hardt
4593729e3a Merge branch 'master' of github.com:iluvcapra/pycmx 2018-12-07 10:00:30 -08:00
Jamie Hardt
703ba1140a Added some docs 2018-12-07 10:00:03 -08:00
Jamie Hardt
0f06c4de5c Updated version to 0.6 2018-12-05 17:07:55 -08:00
Jamie Hardt
920af8a86d Updated README 2018-12-05 17:07:00 -08:00
Jamie Hardt
57ea48e5e8 Improved __repr__() methods 2018-12-05 16:49:54 -08:00
Jamie Hardt
7e13978d9a Added line numbers to statement parser 2018-12-05 16:36:46 -08:00
Jamie Hardt
f358704139 Travis 2018-12-05 16:19:14 -08:00
Jamie Hardt
6201633956 Travis 2018-12-05 12:17:30 -08:00
Jamie Hardt
2924ea548b Travis tweak 2018-12-05 12:14:55 -08:00
Jamie Hardt
88bf68c78e .travis.yml 2018-12-05 12:03:34 -08:00
Jamie Hardt
6d1ca12e42 Some work in the travis file and adding line numbers to parsing statements 2018-12-05 11:59:00 -08:00
Jamie Hardt
829d98f4b4 Tweaking travis.yml 2018-12-05 11:46:48 -08:00
Jamie Hardt
8969e31969 Tweaking travis.yml 2018-12-05 11:43:46 -08:00
Jamie Hardt
484d2ae98f Tweaking travis.yml 2018-12-05 11:41:15 -08:00
Jamie Hardt
07652eaaa8 Tweaking travis.yml 2018-12-05 11:38:37 -08:00
Jamie Hardt
a9124e1f97 Adding .travis.yml 2018-12-05 11:35:33 -08:00
Jamie Hardt
7e709241f8 Revert "Adding a travis config script"
This reverts commit 597dceb9c5.
2018-12-05 11:35:33 -08:00
Jamie Hardt
731b8fcc00 Added Travis Status image 2018-12-05 11:31:38 -08:00
Jamie Hardt
597dceb9c5 Adding a travis config script 2018-12-05 11:23:10 -08:00
Jamie Hardt
c2c83d826a Added version symbol to __init__ 2018-12-02 21:04:39 -08:00
Jamie Hardt
08dd1f956d Implemented better logic for reading event-modifying lines 2018-12-01 15:33:50 -08:00
Jamie Hardt
989d52aaee CmxEvent and CmxTransition implementation 2018-12-01 15:15:09 -08:00
Jamie Hardt
b60610aa8b Some infrastructure for CmxTransitions 2018-12-01 14:35:26 -08:00
Jamie Hardt
6611e38b9f modified copyright notices 2018-12-01 14:16:54 -08:00
Jamie Hardt
abcce06865 Added some more test files from other projects 2018-12-01 13:52:26 -08:00
Jamie Hardt
9d586342be Roadmap note 2018-12-01 13:10:10 -08:00
Jamie Hardt
e5f632d8a4 Updated setup, mnudge to version 0.5 2018-12-01 13:08:16 -08:00
Jamie Hardt
30cd99431b updated code example 2018-12-01 13:05:58 -08:00
Jamie Hardt
cc76223cbc Tweaked tests to catch my previous error
Also nudged version number
2018-12-01 12:48:41 -08:00
Jamie Hardt
966f8c1ca4 Fixed another audio channel parsing bug 2018-12-01 12:46:10 -08:00
Jamie Hardt
1e7aea30d4 Added a channel map test, fixed some bugs reading audio channels 2018-12-01 12:34:38 -08:00
Jamie Hardt
f8ab99226b added "TEST.edl" for example of many audio channels 2018-12-01 12:21:01 -08:00
Jamie Hardt
7119be58ac Modified channel parsing, another test case here 2018-12-01 12:20:19 -08:00
Jamie Hardt
d976f22b92 Adding some test cases 2018-11-30 15:44:24 -08:00
Jamie Hardt
754abb1995 Merge branch 'master' of github.com:iluvcapra/pycmx 2018-11-30 15:03:46 -08:00
Jamie Hardt
68c65f01e7 fixed channel assignments 2018-11-30 15:03:00 -08:00
Jamie Hardt
772fbeb909 Update README.md 2018-11-30 14:13:23 -08:00
Jamie Hardt
8a2106f849 Update README.md 2018-11-30 13:41:49 -08:00
Jamie Hardt
a90f6305c3 Update README.md 2018-11-30 13:20:41 -08:00
Jamie Hardt
d9978a454e Roadmap items 2018-11-30 13:07:50 -08:00
Jamie Hardt
fd8a790983 Bump version v0.3 2018-11-30 12:30:31 -08:00
40 changed files with 13798 additions and 251 deletions

3
.gitignore vendored
View File

@@ -7,3 +7,6 @@
# Python egg metadata, regenerated from source files by setuptools. # Python egg metadata, regenerated from source files by setuptools.
/*.egg-info /*.egg-info
/build/ /build/
# Vim Swapfiles
*.swp

7
.travis.yml Normal file
View File

@@ -0,0 +1,7 @@
language: python
python:
- "3.6"
script:
- "python3 setup.py test"
install:
- "pip3 install setuptools"

127
README.md
View File

@@ -1,60 +1,113 @@
# pycmx [![Build Status](https://travis-ci.com/iluvcapra/pycmx.svg?branch=master)](https://travis-ci.com/iluvcapra/pycmx) [![Documentation Status](https://readthedocs.org/projects/pycmx/badge/?version=latest)](https://pycmx.readthedocs.io/en/latest/?badge=latest)
Python CMX3600 Edit Decision List Parser
The `pycmx` package provides a basic interface for parsing a CMX3600 EDL.
# pycmx
The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and its most most common variations.
## Features ## Features
* The major variations of the CMX3600, the standard, "File32" and "File128" * The major variations of the CMX 3600: the standard, "File32" and "File128"
formats are automatically detected and properly read. formats are automatically detected and properly read.
* Preserves relationship between events and individual edits/clips.
* Remark or comment fields with common recognized forms are read and * Remark or comment fields with common recognized forms are read and
available to the client, including clip name and source file data. available to the client, including clip name and source file data.
* Symbolically decodes transitions and audio channels.
* Does not parse or validate timecodes, does not enforce framerates, does not
parameterize timecode or framerates in any way. This makes the parser more
tolerant of EDLs with mixed rates.
* Unrecognized lines are accessible on the `EditList` and `Event` classes
along with the line numbers, to help the client diagnose problems with a
list and give the client the ability to extend the package with their own
parsing code.
## Usage ## Usage
### Opening and Parsing EDL Files
``` ```
import pycmx >>> import pycmx
pycmx.parse_cmx3600("INS4_R1_010417.edl") >>> with open("tests/edls/TEST.edl") as f
print(events[5:8]) ... edl = pycmx.parse_cmx3600(f)
>>> [CmxEvent(title='INS4_R1_010417', number='000006', ...
clip_name='V1A-6A', source_name='A192C008_160909_R1BY', >>> edl.title
channels=CmxChannelMap(v=True,a1=False,a2=False,a3=False,a4=False), 'DC7 R1_v8.2'
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',
channels=CmxChannelMap(v=True,a1=False,a2=False,a3=False,a4=False),
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',
channels=CmxChannelMap(v=True,a1=False,a2=False,a3=False,a4=False),
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)]
``` ```
## Known Issues/Roadmap ### Reading Events and Edits
To be addressed: `EditList.events` is a generator...
* Does not decode transitions.
* Does not decode "M2" speed changes.
* Does not decode repair notes, audio notes or other Avid-specific notes.
May not be addressed: ```
>>> events = list( edl.events )
>>> len(events)
120
>>> events[43].number
'044'
```
* Does not parse source list at end of EDL. ...and events contain 1...n edits.
Probably beyond the scope of this module: ```
* Does not parse timecode entries >>> events[43].edits[0].source_in
'00:00:00:00'
>>> events[43].edits[0].transition.cut
True
>>> events[43].edits[0].record_out
'01:10:21:10'
```
### Acessing Transitions and Enabled Channels
```
>>> events[41].edits[0].transition.dissolve
False
>>> events[41].edits[1].transition.dissolve
True
>>> events[41].edits[0].clip_name
'TC R1 V1.2 TEMP1 DX M.WAV'
>>> events[41].edits[1].clip_name
'TC R1 V6 TEMP2 M DX.WAV'
# parsed channel maps are also
# available to the client
>>> events[2].edits[0].channels.get_audio_channel(7)
True
>>> events[2].edits[0].channels.get_audio_channel(6)
False
>>> for c in events[2].edits[0].channels.channels:
... print(f"Audio channel {c} is present")
...
Audio channel 7 is present
>>> events[2].edits[0].channels.video
False
```
## How is this different from `python-edl`?
There are two important differences between `import edl` and `import pycmx`
and motivated my development of this module.
1. The `pycmx` parser doesn't take timecode or framerates into account,
and strictly treats timecodes like opaque values. As far as `pycmx` is
concerend, they're just strings. This was done because in my experience,
the frame rate of an EDL is often difficult to precisely determine and
often the frame rate of various sources is different from the frame rate
of the target track.
In any event, timecodes in an EDL are a kind of *address* and are not
exactly scalar, they're meant to point to a particular block of video or
audio data on a medium and presuming that they refer to a real time, or
duration, or are convertible, etc. isn't always safe.
2. The `pycmx` parser reads event numbers and keeps track of which EDL rows
are meant to happen "at the same time," with two decks. This makes it
easier to reconstruct transition A/B clips, and read clip names from
such events appropriately.
## Should I Use This? ## Should I Use This?
At this time, this is (at best) alpha software and the interface will be At this time, this is (at best) beta software. I feel like the interface is
changing often. It may be fun to experiment with but it is not suitable about where where I'd like it to be but more testing is required.
at this time for production code.
Contributions are welcome and will make this module production-ready all the Contributions are welcome and will make this module production-ready all the
faster! Please reach out or file a ticket! faster! Please reach out or file a ticket!

102
bin/edl2scenelist.py Normal file
View File

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

4
docs/.buildinfo Normal file
View File

@@ -0,0 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 9f647f525db0c82aadec132928a40ec5
tags: 645f666f9bcd5a90fca523b33c5a78b7

Binary file not shown.

Binary file not shown.

1
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build/*

19
docs/Makefile Normal file
View File

@@ -0,0 +1,19 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

35
docs/make.bat Normal file
View File

@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

186
docs/source/conf.py Normal file
View File

@@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath('../../pycmx'))
# -- Project information -----------------------------------------------------
project = u'pycmx'
copyright = u'2018, Jamie Hardt'
author = u'Jamie Hardt'
# The short X.Y version
version = u''
# The full version, including alpha/beta/rc tags
release = u''
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {'collapse_navigation': False}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'pycmxdoc'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'pycmx.tex', u'pycmx Documentation',
u'Jamie Hardt', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'pycmx', u'pycmx Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'pycmx', u'pycmx Documentation',
author, 'pycmx', 'One line description of project.',
'Miscellaneous'),
]
# -- Options for Epub output -------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = project
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
# epub_identifier = ''
# A unique identification for the text.
#
# epub_uid = ''
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# -- Extension configuration -------------------------------------------------
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True

20
docs/source/index.rst Normal file
View File

@@ -0,0 +1,20 @@
.. pycmx documentation master file, created by
sphinx-quickstart on Wed Dec 26 21:51:43 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to pycmx's documentation!
=================================
.. toctree::
:maxdepth: 5
:caption: API Reference:
pycmx
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

7
docs/source/modules.rst Normal file
View File

@@ -0,0 +1,7 @@
pycmx
=====
.. toctree::
:maxdepth: 4
pycmx

46
docs/source/pycmx.rst Normal file
View File

@@ -0,0 +1,46 @@
pycmx package
=============
Submodules
----------
pycmx.channel\_map module
-------------------------
.. automodule:: pycmx.channel_map
:members:
:undoc-members:
:show-inheritance:
pycmx.parse\_cmx\_events module
-------------------------------
.. automodule:: pycmx.parse_cmx_events
:members:
:undoc-members:
:show-inheritance:
pycmx.parse\_cmx\_statements module
-----------------------------------
.. automodule:: pycmx.parse_cmx_statements
:members:
:undoc-members:
:show-inheritance:
pycmx.util module
-----------------
.. automodule:: pycmx.util
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: pycmx
:members:
:undoc-members:
:show-inheritance:

View File

@@ -1,3 +1,17 @@
# pycmx init # -*- coding: utf-8 -*-
"""
pycmx is a module for parsing CMX 3600-style EDLs. For more information and
examples see README.md
from .parse_cmx import parse_cmx3600 This module (c) 2018 Jamie Hardt. For more information on your rights to
copy and reuse this software, refer to the LICENSE file included with the
distribution.
"""
__version__ = '0.8'
__author__ = 'Jamie Hardt'
from .parse_cmx_events import parse_cmx3600
from .transition import Transition
from .event import Event
from .edit import Edit

98
pycmx/channel_map.py Normal file
View File

@@ -0,0 +1,98 @@
# pycmx
# (c) 2018 Jamie Hardt
from re import (compile, match)
class ChannelMap:
"""
Represents a set of all the channels to which an event applies.
"""
_chan_map = {
"V" : (True, False, False),
"A" : (False, True, False),
"A2" : (False, False, True),
"AA" : (False, True, True),
"B" : (True, True, False),
"AA/V" : (True, True, True),
"A2/V" : (True, False, True)
}
def __init__(self, v=False, audio_channels=set()):
self._audio_channel_set = audio_channels
self.v = v
@property
def video(self):
'True if video is included'
return self.v
@property
def channels(self):
'A generator for each audio channel'
for c in self._audio_channel_set:
yield c
@property
def a1(self):
"""True if A1 is included."""
return self.get_audio_channel(1)
@a1.setter
def a1(self,val):
self.set_audio_channel(1,val)
@property
def a2(self):
"""True if A2 is included."""
return self.get_audio_channel(2)
@a2.setter
def a2(self,val):
self.set_audio_channel(2,val)
@property
def a3(self):
"""True if A3 is included."""
return self.get_audio_channel(3)
@a3.setter
def a3(self,val):
self.set_audio_channel(3,val)
@property
def a4(self):
"""True if A4 is included."""
return self.get_audio_channel(4)
@a4.setter
def a4(self,val):
self.set_audio_channel(4,val)
def get_audio_channel(self,chan_num):
"""True if chan_num is included."""
return (chan_num in self._audio_channel_set)
def set_audio_channel(self,chan_num,enabled):
"""If enabled is true, chan_num will be included."""
if enabled:
self._audio_channel_set.add(chan_num)
elif self.get_audio_channel(chan_num):
self._audio_channel_set.remove(chan_num)
def _append_event(self, event_str):
alt_channel_re = compile('^A(\d+)')
if event_str in self._chan_map:
channels = self._chan_map[event_str]
self.v = channels[0]
self.a1 = channels[1]
self.a2 = channels[2]
else:
matchresult = match(alt_channel_re, event_str)
if matchresult:
self.set_audio_channel(int( matchresult.group(1)), True )
def _append_ext(self, audio_ext):
self.a3 = ext.audio3
self.a4 = ext.audio4

View File

@@ -1,17 +0,0 @@
class CmxEvent:
def __init__(self,title,number,clip_name,source_name,channels,source_start,source_finish,
record_start, record_finish, fcm_drop, remarks = [] , unrecognized = []):
self.title = title
self.number = number
self.clip_name = clip_name
self.source_name = source_name
self.channels = channels
self.source_start = source_start
self.source_finish = source_finish
self.record_start = record_start
self.record_finish = record_finish
self.fcm_drop = fcm_drop
self.remarks = remarks
self.unrecgonized = unrecognized

126
pycmx/edit.py Normal file
View File

@@ -0,0 +1,126 @@
# pycmx
# (c) 2018 Jamie Hardt
from .transition import Transition
from .channel_map import ChannelMap
from .parse_cmx_statements import StmtEffectsName
class Edit:
"""
An individual source-to-record operation, with a source roll, source and
recorder timecode in and out, a transition and channels.
"""
def __init__(self, edit_statement, audio_ext_statement, clip_name_statement, source_file_statement, trans_name_statement = None):
self.edit_statement = edit_statement
self.audio_ext = audio_ext_statement
self.clip_name_statement = clip_name_statement
self.source_file_statement = source_file_statement
self.trans_name_statement = trans_name_statement
@property
def line_number(self):
"""
Get the line number for the "standard form" statement associated with
this edit. Line numbers a zero-indexed, such that the
"TITLE:" record is line zero.
"""
return self.edit_statement.line_number
@property
def channels(self):
"""
Get the :obj:`ChannelMap` object associated with this Edit.
"""
cm = ChannelMap()
cm._append_event(self.edit_statement.channels)
if self.audio_ext != None:
cm._append_ext(self.audio_ext)
return cm
@property
def transition(self):
"""
Get the :obj:`Transition` object associated with this edit.
"""
if self.trans_name_statement:
return Transition(self.edit_statement.trans, self.edit_statement.trans_op, self.trans_name_statement.name)
else:
return Transition(self.edit_statement.trans, self.edit_statement.trans_op, None)
@property
def source_in(self):
"""
Get the source in timecode.
"""
return self.edit_statement.source_in
@property
def source_out(self):
"""
Get the source out timecode.
"""
return self.edit_statement.source_out
@property
def record_in(self):
"""
Get the record in timecode.
"""
return self.edit_statement.record_in
@property
def record_out(self):
"""
Get the record out timecode.
"""
return self.edit_statement.record_out
@property
def source(self):
"""
Get the source column. This is the 8, 32 or 128-character string on the
event record line, this usually references the tape name of the source.
"""
return self.edit_statement.source
@property
def black(self):
"""
Black video or silence should be used as the source for this event.
"""
return self.source == "BL"
@property
def aux_source(self):
"""
An auxiliary source is the source of this event.
"""
return self.source == "AX"
@property
def source_file(self):
"""
Get the source file, as attested by a "* SOURCE FILE" remark on the
EDL. This will return None if the information is not present.
"""
if self.source_file_statement is None:
return None
else:
return self.source_file_statement.filename
@property
def clip_name(self):
"""
Get the clip name, as attested by a "* FROM CLIP NAME" or "* TO CLIP
NAME" remark on the EDL. This will return None if the information is
not present.
"""
if self.clip_name_statement is None:
return None
else:
return self.clip_name_statement.name

61
pycmx/edit_list.py Normal file
View File

@@ -0,0 +1,61 @@
# pycmx
# (c) 2018 Jamie Hardt
from .parse_cmx_statements import (StmtUnrecognized, StmtFCM, StmtEvent)
from .event import Event
class EditList:
"""
Represents an entire edit decision list as returned by `parse_cmx3600()`.
"""
def __init__(self, statements):
self.title_statement = statements[0]
self.event_statements = statements[1:]
@property
def title(self):
"""
The title of this edit list, as attensted by the 'TITLE:' statement on
the first line.
"""
'The title of the edit list'
return self.title_statement.title
@property
def unrecognized_statements(self):
"""
A generator for all the unrecognized statements in the list.
"""
for s in self.event_statements:
if type(s) is StmtUnrecognized:
yield s
@property
def events(self):
'A generator for all the events in the edit list'
is_drop = None
current_event_num = None
event_statements = []
for stmt in self.event_statements:
if type(stmt) is StmtFCM:
is_drop = stmt.drop
elif type(stmt) is StmtEvent:
if current_event_num is None:
current_event_num = stmt.event
event_statements.append(stmt)
else:
if current_event_num != stmt.event:
yield Event(statements=event_statements)
event_statements = [stmt]
current_event_num = stmt.event
else:
event_statements.append(stmt)
else:
event_statements.append(stmt)
yield Event(statements=event_statements)

97
pycmx/event.py Normal file
View File

@@ -0,0 +1,97 @@
# pycmx
# (c) 2018 Jamie Hardt
from .parse_cmx_statements import (StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized, StmtEffectsName)
from .edit import Edit
class Event:
"""
Represents a collection of :class:`Edit`s, all with the same event number.
"""
def __init__(self, statements):
self.statements = statements
@property
def number(self):
"""
Return the event number.
"""
return int(self._edit_statements()[0].event)
@property
def edits(self):
"""
Returns the edits. Most events will have a single edit, a single event
will have multiple edits when a dissolve, wipe or key transition needs
to be performed.
"""
edits_audio = list( self._statements_with_audio_ext() )
clip_names = self._clip_name_statements()
source_files= self._source_file_statements()
the_zip = [edits_audio]
if len(edits_audio) == 2:
cn = [None, None]
for clip_name in clip_names:
if clip_name.affect == 'from':
cn[0] = clip_name
elif clip_name.affect == 'to':
cn[1] = clip_name
the_zip.append(cn)
else:
if len(edits_audio) == len(clip_names):
the_zip.append(clip_names)
else:
the_zip.append([None] * len(edits_audio) )
if len(edits_audio) == len(source_files):
the_zip.append(source_files)
elif len(source_files) == 1:
the_zip.append( source_files * len(edits_audio) )
else:
the_zip.append([None] * len(edits_audio) )
# attach trans name to last event
try:
trans_statement = self._trans_name_statements()[0]
trans_names = [None] * (len(edits_audio) - 1)
trans_names.append(trans_statement)
the_zip.append(trans_names)
except IndexError:
the_zip.append([None] * len(edits_audio) )
return [ Edit(e1[0],e1[1],n1,s1,u1) for (e1,n1,s1,u1) in zip(*the_zip) ]
@property
def unrecognized_statements(self):
"""
A generator for all the unrecognized statements in the event.
"""
for s in self.statements:
if type(s) is StmtUnrecognized:
yield s
def _trans_name_statements(self):
return [s for s in self.statements if type(s) is StmtEffectsName]
def _edit_statements(self):
return [s for s in self.statements if type(s) is StmtEvent]
def _clip_name_statements(self):
return [s for s in self.statements if type(s) is StmtClipName]
def _source_file_statements(self):
return [s for s in self.statements if type(s) is StmtSourceFile]
def _statements_with_audio_ext(self):
for (s1, s2) in zip(self.statements, self.statements[1:]):
if type(s1) is StmtEvent and type(s2) is StmtAudioExt:
yield (s1,s2)
elif type(s1) is StmtEvent:
yield (s1, None)

View File

@@ -1,132 +0,0 @@
# pycmx
# (c) 2018 Jamie Hardt
from .parse_cmx_statements import parse_cmx3600_statements
from collections import namedtuple
class NamedTupleParser:
def __init__(self, tuple_list):
self.tokens = tuple_list
self.current_token = None
def peek(self):
return self.tokens[0]
def at_end(self):
return len(self.tokens) == 0
def next_token(self):
self.current_token = self.peek()
self.tokens = self.tokens[1:]
def accept(self, type_name):
if self.at_end():
return False
elif (type(self.peek()).__name__ == type_name ):
self.next_token()
return True
else:
return False
def expect(self, type_name):
assert( self.accept(type_name) )
class CmxChannelMap:
chan_map = { "V" : (True, False, False),
"A" : (False, True, False),
"A2" : (False, False, True),
"AA" : (False, True, True),
"B" : (True, True, False),
"V/AA" : (True, True, True),
"V/A2" : (True, False, True)
}
def __init__(self, v=False, a1=False, a2=False, a3=False, a4=False):
self.v = v
self.a1 = a1
self.a2 = a2
self.a3 = a3
self.a4 = a4
def appendEvent(self, event_str):
if event_str in self.chan_map:
channels = self.chan_map[event_str]
self.v = channels[0]
self.a1 = channels[1]
self.a2 = channels[2]
def appendExt(self, audio_ext):
self.a3 = ext.audio3
self.a4 = ext.audio4
def __repr__(self):
return "CmxChannelMap(v="+ self.v.__repr__( ) + \
",a1=" + self.a1.__repr__() + \
",a2=" + self.a2.__repr__() + \
",a3=" + self.a3.__repr__() + \
",a4=" + self.a4.__repr__() +")"
def parse_cmx3600(file):
"""Accepts the path to a CMX EDL and returns a list of all events contained therein."""
statements = parse_cmx3600_statements(file)
parser = NamedTupleParser(statements)
parser.expect('Title')
title = parser.current_token.title
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):
state = {"fcm_drop" : False}
events_result = []
this_event = None
while not parser.at_end():
if parser.accept('FCM'):
state['fcm_drop'] = parser.current_token.drop
elif parser.accept('Event'):
if this_event != None:
event_t = CmxEvent(**this_event)
events_result.append(event_t)
raw_event = parser.current_token
channels = CmxChannelMap()
channels.appendEvent(raw_event.channels)
this_event = {'title': title, 'number': raw_event.event, 'clip_name': None ,
'source_name': raw_event.source,
'channels': channels,
'source_start': raw_event.source_in,
'source_finish': raw_event.source_out,
'record_start': raw_event.record_in,
'record_finish': raw_event.record_out,
'fcm_drop': state['fcm_drop']}
elif parser.accept('AudioExt'):
this_event['channels'].appendExt(parser.current_token)
elif parser.accept('ClipName'):
this_event['clip_name'] = parser.current_token.name
elif parser.accept('SourceFile'):
this_event['source_name'] = parser.current_token.filename
elif parser.accept('Trailer'):
break
else:
parser.next_token()
if this_event != None:
event_t = CmxEvent(**this_event)
events_result.append(event_t)
return events_result

21
pycmx/parse_cmx_events.py Normal file
View File

@@ -0,0 +1,21 @@
# pycmx
# (c) 2018 Jamie Hardt
from collections import namedtuple
from .parse_cmx_statements import (parse_cmx3600_statements, StmtEvent,StmtFCM )
from .edit_list import EditList
def parse_cmx3600(f):
"""
Parse a CMX 3600 EDL.
Args:
f : a file-like object, anything that's readlines-able.
Returns:
An :class:`EditList`.
"""
statements = parse_cmx3600_statements(f)
return EditList(statements)

View File

@@ -1,31 +1,38 @@
# pycmx
# (c) 2018 Jamie Hardt
# Parsed Statement Data Structures
#
# These represent individual lines that have been typed and have undergone some light symbolic parsing.
from .util import collimate
import re import re
import sys import sys
from collections import namedtuple from collections import namedtuple
from itertools import count
from .util import collimate
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","affect","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"])
StmtSplitEdit = namedtuple("SplitEdit",["video","magnitue", "line_number"])
StmtMotionMemory = namedtuple("MotionMemory",["source","fps"]) # FIXME needs more fields
StmtUnrecognized = namedtuple("Unrecognized",["content","line_number"])
StmtTitle = namedtuple("Title",["title"]) def parse_cmx3600_statements(file):
StmtFCM = namedtuple("FCM",["drop"]) """
StmtEvent = namedtuple("Event",["event","source","channels","trans","trans_op","source_in","source_out","record_in","record_out"]) Return a list of every statement in the file argument.
StmtAudioExt = namedtuple("AudioExt",["audio3","audio4"]) """
StmtClipName = namedtuple("ClipName",["name"]) lines = file.readlines()
StmtSourceFile = namedtuple("SourceFile",["filename"]) line_numbers = count()
StmtRemark = namedtuple("Remark",["text"]) return [_parse_cmx3600_line(line.strip(), line_number) \
StmtTrailer = namedtuple("Trailer",["text"]) for (line, line_number) in zip(lines,line_numbers)]
StmtUnrecognized = namedtuple("Unrecognized",["content"])
def parse_cmx3600_statements(path):
with open(path,'rU') as file:
lines = file.readlines()
return [parse_cmx3600_line(line.strip()) for line in lines]
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,
4,2, # chans 4,2, # chans
4,1, # trans 4,1, # trans
@@ -34,78 +41,110 @@ def edl_column_widths(event_field_length, source_field_length):
11,1, 11,1,
11,1, 11,1,
11] 11]
def parse_cmx3600_line(line): def _edl_m2_column_widths():
return [2, # "M2"
3,3, #
8,8,1,4,2,1,4,13,3,1,1]
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)
elif line.startswith("SPLIT:"):
return _parse_split(line, line_number)
elif line.startswith("M2"):
return _parse_motion_memory(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() , affect="from", line_number=line_number)
elif line.startswith("TO CLIP NAME:"):
return StmtClipName(name=line[13:].strip(), affect="to", 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_split(line, line_number):
col_widths = edl_column_widths(event_field_length, source_field_length) split_type = line[10:21]
is_video = False
if split_type.startswith("VIDEO"):
is_video = True
split_mag = line[24:35]
return StmtSplitEdit(video=is_video, magnitude=split_mag, line_number=line_number)
def _parse_motion_memory(line, line_number):
return StmtMotionMemory(source = "", fps="")
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)
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 +156,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)

86
pycmx/transition.py Normal file
View File

@@ -0,0 +1,86 @@
# pycmx
# (c) 2018 Jamie Hardt
class Transition:
"""
A CMX transition: a wipe, dissolve or cut.
"""
Cut = "C"
Dissolve = "D"
Wipe = "W"
KeyBackground = "KB"
Key = "K"
KeyOut = "KO"
def __init__(self, transition, operand, name=None):
self.transition = transition
self.operand = operand
self.name = name
@property
def kind(self):
"""
Return the kind of transition: Cut, Wipe, etc
"""
if self.cut:
return Transition.Cut
elif self.dissolve:
return Transition.Dissolve
elif self.wipe:
return Transition.Wipe
elif self.key_background:
return Transition.KeyBackground
elif self.key_foreground:
return Transition.Key
elif self.key_out:
return Transition.KeyOut
@property
def cut(self):
"`True` if this transition is a cut."
return self.transition == 'C'
@property
def dissolve(self):
"`True` if this traansition is a dissolve."
return self.transition == 'D'
@property
def wipe(self):
"`True` if this transition is a wipe."
return self.transition.startswith('W')
@property
def effect_duration(self):
"""The duration of this transition, in frames of the record target.
In the event of a key event, this is the duration of the fade in.
"""
return int(self.operand)
@property
def wipe_number(self):
"Wipes are identified by a particular number."
if self.wipe:
return int(self.transition[1:])
else:
return None
@property
def key_background(self):
"`True` if this edit is a key background."
return self.transition == KeyBackground
@property
def key_foreground(self):
"`True` if this edit is a key foreground."
return self.transition == Key
@property
def key_out(self):
"""
`True` if this edit is a key out. This material will removed from
the key foreground and replaced with the key background.
"""
return self.transition == KeyOut

View File

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

View File

@@ -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.2', version='0.8',
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',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

585
tests/edls/TEST.edl Normal file
View File

@@ -0,0 +1,585 @@
TITLE: DC7 R1_v8.2
FCM: NON-DROP FRAME
001 OY_HEAD_ A2 C 00:00:00:00 00:00:00:00 01:00:00:00 01:00:08:00
* FROM CLIP NAME: HEAD LEADER MONO
* PATCH OY_HEAD_: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: OY_HEAD_LEADER.MOV
002 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:00:00:00 01:04:24:06
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
003 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:00:00:00 01:04:24:07
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
004 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:00:00:00 01:04:24:07
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
005 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:00:00:00 01:04:24:07
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
006 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:04:24:06 01:04:24:06
006 TC_R1_V6 A D 002 00:00:00:00 00:00:00:00 01:04:24:06 01:04:24:08
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
007 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:04:24:07 01:04:42:08
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
008 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:04:24:07 01:04:45:16
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
009 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:04:24:07 01:04:45:19
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
010 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:04:24:08 01:04:35:19
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
011 TC_R1_V1 A C 00:00:00:00 00:00:00:00 01:04:35:19 01:04:46:16
* FROM CLIP NAME: TC R1 V1.2 TEMP1 DX M.WAV
* SOURCE FILE: TC R1 V1.2 TEMP1 DX M.WAV
012 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:04:42:08 01:04:42:08
012 TC_R1_V6 A12 D 002 00:00:00:00 00:00:00:00 01:04:42:08 01:04:42:10
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
013 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:04:42:10 01:04:46:23
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
014 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:04:45:16 01:04:47:00
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
015 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:04:45:19 01:04:45:19
015 TC_R1_V6 A11 D 002 00:00:00:00 00:00:00:00 01:04:45:19 01:04:45:21
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
016 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:04:45:21 01:04:46:23
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
017 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:04:46:16 01:05:47:12
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
018 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:04:46:23 01:04:46:23
018 TC_R1_V6 A11 D 002 00:00:00:00 00:00:00:00 01:04:46:23 01:04:47:01
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
019 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:04:46:23 01:04:46:23
019 TC_R1_V6 A12 D 002 00:00:00:00 00:00:00:00 01:04:46:23 01:04:47:01
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
020 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:04:47:00 01:05:47:12
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
021 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:04:47:01 01:05:47:12
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
022 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:04:47:01 01:05:47:12
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
023 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:05:47:12 01:06:08:06
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
024 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:05:47:12 01:07:27:09
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
025 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:05:47:12 01:07:27:09
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
026 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:05:47:12 01:10:21:10
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
027 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:06:08:06 01:07:27:01
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* A12 VOL = +3.0 DB PAN L100
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
028 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:07:27:09 01:07:27:09
028 TC_R1_V1 A D 002 00:00:00:00 00:00:00:00 01:07:27:09 01:07:27:11
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* TO CLIP NAME: TC R1 V1.2 TEMP1 DX M.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
029 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:07:27:09 01:07:27:09
029 TC_R1_V1 A7 D 002 00:00:00:00 00:00:00:00 01:07:27:09 01:07:27:11
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* TO CLIP NAME: TC R1 V1.2 TEMP1 FX ST.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* PATCH TC_R1_V3: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
030 TC_R1_V1 A C 00:00:00:00 00:00:00:00 01:07:27:11 01:07:36:22
* FROM CLIP NAME: TC R1 V1.2 TEMP1 DX M.WAV
* SOURCE FILE: TC R1 V1.2 TEMP1 DX M.WAV
031 TC_R1_V1 A7 C 00:00:00:00 00:00:00:00 01:07:27:11 01:07:36:22
* FROM CLIP NAME: TC R1 V1.2 TEMP1 FX ST.WAV
* PATCH TC_R1_V3: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V1.2 TEMP1 FX ST.WAV
032 TC_R1_V1 A C 00:00:00:00 00:00:00:00 01:07:36:22 01:07:36:22
032 TC_R1_V1 A D 002 00:00:00:00 00:00:00:00 01:07:36:22 01:07:37:00
* FROM CLIP NAME: TC R1 V1.2 TEMP1 DX M.WAV
* TO CLIP NAME: TC R1 V1.2 TEMP1 DX M.WAV
* SOURCE FILE: TC R1 V1.2 TEMP1 DX M.WAV
033 TC_R1_V1 A7 C 00:00:00:00 00:00:00:00 01:07:36:22 01:07:36:22
033 TC_R1_V1 A7 D 002 00:00:00:00 00:00:00:00 01:07:36:22 01:07:37:00
* FROM CLIP NAME: TC R1 V1.2 TEMP1 FX ST.WAV
* TO CLIP NAME: TC R1 V1.2 TEMP1 FX ST.WAV
* PATCH TC_R1_V3: FROM SOURCE 2 TO RECORD 7
* PATCH TC_R1_V3: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V1.2 TEMP1 FX ST.WAV
034 TC_R1_V1 A C 00:00:00:00 00:00:00:00 01:07:37:00 01:07:49:01
* FROM CLIP NAME: TC R1 V1.2 TEMP1 DX M.WAV
* SOURCE FILE: TC R1 V1.2 TEMP1 DX M.WAV
035 TC_R1_V1 A7 C 00:00:00:00 00:00:00:00 01:07:37:00 01:07:49:01
* FROM CLIP NAME: TC R1 V1.2 TEMP1 FX ST.WAV
* PATCH TC_R1_V3: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V1.2 TEMP1 FX ST.WAV
036 TC_R1_V1 A C 00:00:00:00 00:00:00:00 01:07:49:01 01:07:49:01
036 TC_R1_V1 A D 002 00:00:00:00 00:00:00:00 01:07:49:01 01:07:49:03
* FROM CLIP NAME: TC R1 V1.2 TEMP1 DX M.WAV
* TO CLIP NAME: TC R1 V1.2 TEMP1 DX M.WAV
* SOURCE FILE: TC R1 V1.2 TEMP1 DX M.WAV
037 TC_R1_V1 A7 C 00:00:00:00 00:00:00:00 01:07:49:01 01:07:49:01
037 TC_R1_V1 A7 D 002 00:00:00:00 00:00:00:00 01:07:49:01 01:07:49:03
* FROM CLIP NAME: TC R1 V1.2 TEMP1 FX ST.WAV
* TO CLIP NAME: TC R1 V1.2 TEMP1 FX ST.WAV
* PATCH TC_R1_V3: FROM SOURCE 2 TO RECORD 7
* PATCH TC_R1_V3: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V1.2 TEMP1 FX ST.WAV
038 TC_R1_V1 A C 00:00:00:00 00:00:00:00 01:07:49:03 01:08:56:09
* FROM CLIP NAME: TC R1 V1.2 TEMP1 DX M.WAV
* SOURCE FILE: TC R1 V1.2 TEMP1 DX M.WAV
039 TC_R1_V1 A7 C 00:00:00:00 00:00:00:00 01:07:49:03 01:08:56:09
* FROM CLIP NAME: TC R1 V1.2 TEMP1 FX ST.WAV
* PATCH TC_R1_V3: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V1.2 TEMP1 FX ST.WAV
040 BL A12 C 00:00:00:00 00:00:00:00 01:08:56:00 01:08:56:00
040 TC_R1_V6 A12 D 002 00:00:00:00 00:00:00:00 01:08:56:00 01:08:56:02
* TO CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: (NULL)
041 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:08:56:02 01:10:21:10
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
042 TC_R1_V1 A C 00:00:00:00 00:00:00:00 01:08:56:09 01:08:56:09
042 TC_R1_V6 A D 002 00:00:00:00 00:00:00:00 01:08:56:09 01:08:56:11
* FROM CLIP NAME: TC R1 V1.2 TEMP1 DX M.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V1.2 TEMP1 DX M.WAV
043 TC_R1_V1 A7 C 00:00:00:00 00:00:00:00 01:08:56:09 01:08:56:09
043 TC_R1_V6 A7 D 002 00:00:00:00 00:00:00:00 01:08:56:09 01:08:56:11
* FROM CLIP NAME: TC R1 V1.2 TEMP1 FX ST.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V3: FROM SOURCE 2 TO RECORD 7
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V1.2 TEMP1 FX ST.WAV
044 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:08:56:11 01:10:21:10
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
045 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:08:56:11 01:10:21:10
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
046 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:10:21:10 01:11:34:01
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
047 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:10:21:10 01:11:54:07
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
048 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:10:21:10 01:11:54:07
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
049 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:10:21:10 01:11:54:07
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
050 LINE_02_ A5 C 00:00:00:00 00:00:00:00 01:11:36:10 01:11:37:10
* FROM CLIP NAME: LINE 02_08.L.WAV
* PATCH LINE_02_: FROM SOURCE 2 TO RECORD 5
* SOURCE FILE: LINE 02_08.L.WAV
051 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:11:36:23 01:11:46:01
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
052 TC_R1_V6 A2 C 00:00:00:00 00:00:00:00 01:11:46:01 01:11:47:08
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* A2 VOL = -3.0 DB PAN L100
* PATCH TC_R1_V6: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
053 TC_R1_V6 A2 C 00:00:00:00 00:00:00:00 01:11:47:08 01:11:47:20
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* A2 VOL = -3.0 DB PAN L100
* PATCH TC_R1_V6: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
054 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:11:47:19 01:11:48:07
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* A1 VOL = -3.0 DB PAN R100
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
055 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:11:48:07 01:11:54:07
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
056 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:11:54:07 01:12:09:17
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
057 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:11:54:07 01:12:10:04
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
058 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:11:54:07 01:12:12:10
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
059 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:11:54:07 01:12:17:01
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
060 BL A10 C 00:00:00:00 00:00:00:00 01:12:09:16 01:12:09:16
060 TC_R1_V1 A10 D 002 00:00:00:00 00:00:00:00 01:12:09:16 01:12:09:18
* TO CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* A10 VOL = +6.0 DB PAN L100
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* SOURCE FILE: (NULL)
061 TC_R1_V1 A10 C 00:00:00:00 00:00:00:00 01:12:09:18 01:12:26:22
* FROM CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* A10 VOL = +6.0 DB PAN L100
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* SOURCE FILE: TC R1 V1.2 TEMP1 BG ST.WAV
062 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:12:10:04 01:12:10:04
062 TC_R1_V6 A D 002 00:00:00:00 00:00:00:00 01:12:10:04 01:12:10:06
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
063 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:12:10:06 01:12:10:22
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
064 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:12:10:22 01:12:10:22
064 TC_R1_V6 A D 002 00:00:00:00 00:00:00:00 01:12:10:22 01:12:11:00
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
065 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:12:11:00 01:12:17:01
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
066 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:12:12:10 01:12:17:01
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
067 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:12:17:01 01:12:27:00
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
068 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:12:17:01 01:12:27:00
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
069 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:12:17:01 01:12:27:00
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
070 TC_R1_V1 A10 C 00:00:00:00 00:00:00:00 01:12:26:22 01:12:26:22
070 TC_R1_V1 A10 D 004 00:00:00:00 00:00:00:00 01:12:26:22 01:12:27:02
* FROM CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* TO CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* A10 VOL = +6.0 DB PAN L100
* A10 VOL = +6.0 DB PAN L100
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* SOURCE FILE: TC R1 V1.2 TEMP1 BG ST.WAV
071 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:12:27:00 01:12:50:19
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
072 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:12:27:00 01:12:50:19
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
073 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:12:27:00 01:12:50:19
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
074 TC_R1_V1 A10 C 00:00:00:00 00:00:00:00 01:12:27:02 01:13:14:13
* FROM CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* A10 VOL = +6.0 DB PAN L100
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* SOURCE FILE: TC R1 V1.2 TEMP1 BG ST.WAV
075 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:12:50:19 01:13:00:09
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
076 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:12:50:19 01:13:00:09
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
077 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:12:50:19 01:13:00:18
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A7 VOL = +0.0 DB PAN R100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
078 SROY-12B A C 17:03:33:22 17:03:40:16 01:13:00:09 01:13:07:03
* FROM CLIP NAME: 20A-1A
* SOURCE FILE: A079C004_170801_R0M8
079 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:13:00:09 01:13:12:00
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
080 TC_R1_V6 A8 C 00:00:00:00 00:00:00:00 01:13:04:23 01:13:07:22
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A8 VOL = -6.0 DB PAN L100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 8
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
081 SROY-12B A2 C 17:13:10:23 17:13:12:22 01:13:07:00 01:13:08:23
* FROM CLIP NAME: 20A-2A
* PATCH SROY-12B: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: A079C004_170801_R0M8
082 TC_R1_V6 A8 C 00:00:00:00 00:00:00:00 01:13:07:22 01:13:07:22
082 TC_R1_V6 A8 D 380 00:00:00:00 00:00:00:00 01:13:07:22 01:13:23:18
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A8 VOL = -6.0 DB PAN L100
* A8 VOL = +0.0 DB PAN L100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 8
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 8
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
* REPAIR: TRANSITION DURATION TRIMMED FROM 380 FRAMES DUE TO FORMAT LIMITS
083 SROY-12B A C 17:03:42:07 17:03:43:11 01:13:08:18 01:13:09:22
* FROM CLIP NAME: 20A-1A
* SOURCE FILE: A079C004_170801_R0M8
084 SROY-12B A2 C 17:13:02:03 17:13:03:13 01:13:09:17 01:13:11:03
* FROM CLIP NAME: 20A-2A
* PATCH SROY-12B: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: A079C004_170801_R0M8
085 SROY-12B A2 C 17:13:03:13 17:13:03:13 01:13:11:03 01:13:11:03
085 SROY-12B A2 D 002 17:13:10:22 17:13:11:00 01:13:11:03 01:13:11:05
* FROM CLIP NAME: 20A-2A
* TO CLIP NAME: 20A-2A
* PATCH SROY-12B: FROM SOURCE 1 TO RECORD 2
* PATCH SROY-12B: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: A079C004_170801_R0M8
086 SROY-12B A2 C 17:13:11:00 17:13:12:05 01:13:11:05 01:13:12:10
* FROM CLIP NAME: 20A-2A
* PATCH SROY-12B: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: A079C004_170801_R0M8
087 SROY-12B A C 17:03:45:12 17:03:46:07 01:13:11:23 01:13:12:18
* FROM CLIP NAME: 20A-1A
* SOURCE FILE: A079C004_170801_R0M8
088 SROY-12B A2 C 17:13:09:23 17:13:11:01 01:13:12:15 01:13:13:17
* FROM CLIP NAME: 20A-2A
* PATCH SROY-12B: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: A079C004_170801_R0M8
089 SROY-12B A C 17:03:47:02 17:03:48:08 01:13:13:13 01:13:14:19
* FROM CLIP NAME: 20A-1A
* SOURCE FILE: A079C004_170801_R0M8
090 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:13:13:19 01:13:19:19
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
091 TC_R1_V1 A10 C 00:00:00:00 00:00:00:00 01:13:14:13 01:13:14:13
091 TC_R1_V1 A10 D 010 00:00:00:00 00:00:00:00 01:13:14:13 01:13:14:23
* FROM CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* TO CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* A10 VOL = +6.0 DB PAN L100
* A10 VOL = +6.0 DB PAN L100
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* SOURCE FILE: TC R1 V1.2 TEMP1 BG ST.WAV
092 SROY-12B A2 C 17:13:09:23 17:13:12:07 01:13:14:19 01:13:17:03
* FROM CLIP NAME: 20A-2A
* PATCH SROY-12B: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: A079C004_170801_R0M8
093 TC_R1_V1 A10 C 00:00:00:00 00:00:00:00 01:13:14:23 01:13:55:05
* FROM CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* A10 VOL = +6.0 DB PAN L100
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* SOURCE FILE: TC R1 V1.2 TEMP1 BG ST.WAV
094 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:13:17:03 01:13:24:00
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
095 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:13:19:19 01:13:19:19
095 TC_R1_V6 A12 D 020 00:00:00:00 00:00:00:00 01:13:19:19 01:13:20:15
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
096 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:13:20:15 01:14:04:18
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
097 TC_R1_V6 A8 C 00:00:00:00 00:00:00:00 01:13:23:18 01:13:23:20
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A8 VOL = +0.0 DB PAN L100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 8
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
098 TC_R1_V6 A8 C 00:00:00:00 00:00:00:00 01:13:23:20 01:13:38:00
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A8 VOL = +0.0 DB PAN L100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 8
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
099 SROY-12B A2 C 17:13:21:08 17:13:27:18 01:13:24:00 01:13:30:10
* FROM CLIP NAME: 20A-2A
* PATCH SROY-12B: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: A079C004_170801_R0M8
100 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:13:29:10 01:14:04:18
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
101 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:13:42:04 01:13:53:15
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A7 VOL = +0.0 DB PAN R100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
102 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:13:53:15 01:13:53:15
102 TC_R1_V6 A7 D 002 00:00:00:00 00:00:00:00 01:13:53:15 01:13:53:17
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A7 VOL = +0.0 DB PAN R100
* A7 VOL = +2.0 DB PAN R100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
103 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:13:53:17 01:14:04:18
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A7 VOL = +2.0 DB PAN R100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
104 TC_R1_V1 A10 C 00:00:00:00 00:00:00:00 01:13:55:05 01:13:55:05
104 TC_R1_V1 A10 D 008 00:00:00:00 00:00:00:00 01:13:55:05 01:13:55:13
* FROM CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* TO CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* A10 VOL = +6.0 DB PAN L100
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* SOURCE FILE: TC R1 V1.2 TEMP1 BG ST.WAV
105 TC_R1_V1 A10 C 00:00:00:00 00:00:00:00 01:13:55:13 01:14:04:17
* FROM CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* SOURCE FILE: TC R1 V1.2 TEMP1 BG ST.WAV
106 BL A11 C 00:00:00:00 00:00:00:00 01:14:00:21 01:14:00:21
106 TC_R1_V6 A11 D 008 00:00:00:00 00:00:00:00 01:14:00:21 01:14:01:05
* TO CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: (NULL)
107 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:14:01:05 01:14:04:18
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
108 TC_R1_V1 A10 C 00:00:00:00 00:00:00:00 01:14:04:17 01:14:04:17
108 BL A10 D 001 00:00:00:00 00:00:00:01 01:14:04:17 01:14:04:18
* FROM CLIP NAME: TC R1 V1.2 TEMP1 BG ST.WAV
* PATCH TC_R1_V1: FROM SOURCE 2 TO RECORD 10
* SOURCE FILE: TC R1 V1.2 TEMP1 BG ST.WAV
109 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:14:04:18 01:14:08:01
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A7 VOL = +2.0 DB PAN R100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
110 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:14:04:18 01:14:12:01
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
111 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:14:04:18 01:14:13:07
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
112 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:14:04:18 01:14:13:07
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
113 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:14:08:01 01:14:08:01
113 TC_R1_V6 A7 D 002 00:00:00:00 00:00:00:00 01:14:08:01 01:14:08:03
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A7 VOL = +2.0 DB PAN R100
* A7 VOL = +0.0 DB PAN R100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
114 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:14:08:03 01:14:13:07
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* A7 VOL = +0.0 DB PAN R100
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
115 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:14:12:01 01:14:12:01
115 TC_R1_V6 A12 D 090 00:00:00:00 00:00:00:00 01:14:12:01 01:14:15:19
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* TO CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
116 TC_R1_V6 A C 00:00:00:00 00:00:00:00 01:14:13:07 01:16:55:17
* FROM CLIP NAME: TC R1 V6 TEMP2 M DX.WAV
* SOURCE FILE: TC R1 V6 TEMP2 M DX.WAV
117 TC_R1_V6 A7 C 00:00:00:00 00:00:00:00 01:14:13:07 01:16:55:17
* FROM CLIP NAME: TC R1 V6 TEMP2 ST FX.WAV
* PATCH TC_R1_V5: FROM SOURCE 2 TO RECORD 7
* SOURCE FILE: TC R1 V6 TEMP2 ST FX.WAV
118 TC_R1_V6 A11 C 00:00:00:00 00:00:00:00 01:14:13:07 01:16:55:17
* FROM CLIP NAME: TC R1 V6 TEMP2 ST BG.WAV
* PATCH TC_R1_V4: FROM SOURCE 2 TO RECORD 11
* SOURCE FILE: TC R1 V6 TEMP2 ST BG.WAV
119 TC_R1_V6 A12 C 00:00:00:00 00:00:00:00 01:14:15:19 01:16:55:17
* FROM CLIP NAME: TC R1 V6 TEMP2 ST MX.WAV
* PATCH TC_R1_V7: FROM SOURCE 2 TO RECORD 12
* SOURCE FILE: TC R1 V6 TEMP2 ST MX.WAV
120 OY_TAIL_ A2 C 00:00:00:00 00:00:00:00 01:16:51:16 01:16:55:17
* FROM CLIP NAME: TAIL LEADER STEREO
* PATCH OY_TAIL_: FROM SOURCE 1 TO RECORD 2
* SOURCE FILE: OY_TAIL LEADER.MOV
*
* ============================================================
* Marker Metadata
* ------------------------------------------------------------
*
* Sequence name: DC7 R1_v8.2
* Number Color Marker Name Start TC End TC Duration Track Part Comment
* 1 White WBROGO 01:02:12:00 V1 002_020 - Phone Screen - CU
* 2 White WBROGO 01:02:33:08 V1 004_010 - Remove Coke Logo
* 3 White WBROGO 01:03:00:16 V1 004_020 - Remove Logo - Equal
* 4 White WBROGO 01:03:48:03 V1 004_015 - Remove Jack Jerk / line before he says "what"
* 5 White WBROGO 01:03:55:08 V1 004_030 - Split screen - Comp other hand into shot
* 6 White WBROGO 01:04:21:13 V1 004_040 - Split Screen - Simple
* 7 White WBROGO 01:04:32:19 V1 008_010 - Phone Screen - CU
* 8 White WBROGO 01:05:34:10 V1 008_015 - Phone Screen - Distant
* 9 White WBROGO 01:05:44:00 V1 008_020 - Phone Screen - CU
* 10 White WBROGO 01:06:00:01 V1 011_010 - Phone Screen - Distant
* 11 White WBROGO 01:08:16:06 V1 016_005 - Add family photos on wall behind jack to match later when sees Ash dead
* 12 White WBROGO 01:08:30:23 V1 016_007 - Add family photos on wall behind jack to match later when sees Ash dead
* 13 White WBROGO 01:08:36:16 V1 016_010 - Make dog look less fake / reverse to extend timing
* 14 Red SCOTT_H 01:08:57:12 V1 Color Note: Vignette or window to focus our eye into the mirror to see the dead body
* 15 White WBROGO 01:09:43:14 V1 016_020 - Add family photos behind Jack
* 16 White WBROGO 01:11:16:10 V1 018_020 - Add headstones / Stabalize
* 17 White WBROGO 01:12:39:09 V1 020_015 - Phone Screen - CU - Comp video with push in
* 18 Red SCOTT_H 01:12:58:19 V1 NEW VFX SHOT
* 19 White SCOTT_H 01:12:59:01 V1 VFX shot morph for performance speed.
* 20 White WBROGO 01:13:19:22 V1 020_020 - Phone Screen - CU
* 21 White WBROGO 01:13:37:21 V1 020_030 - Phone Screen - CU
* 22 White WBROGO 01:13:52:12 V1 020_040 - Phone Screen - CU - Match thumb to click
* 23 White WBROGO 01:14:22:05 V1 021_010 - remove boom reflection / clean up light when actor leans back
* 24 White WBROGO 01:15:01:14 V1 024_010 - Remove camera operator from mirror / Phone Screen / Add picture frames on wall to match Sc 16 when Jack sees Ash dead
* 25 White WBROGO 01:15:24:17 V1 024_020 - Phone Screen CU
* 26 Yellow SCOTT_H 01:08:26:16 V2 Speed change.
*
* 27 White WBROGO 01:09:59:14 V2 016_030 - Add family photos on wall
* 28 White WBROGO 01:11:40:22 V2 018_030 - Add headstones
* 29 Red SCOTT_H 01:11:41:11 V2 Moved retimed
* 30 Red SCOTT_H 01:09:10:13 A6 KEEP IN TIME LINE. ADR OF DAVID for his moans etc...to keep in timeline in case needed by JE in the mix
>>> SOURCE SROY-12B SROY-12B 060a2b340101010101010f00-13-00-00-00-{00000511-6f6e-53f5-060e2b347f7f2a80}
* SOURCE FILE: A079C004_170801_R0M8

File diff suppressed because it is too large Load Diff

60
tests/edls/test_24psf.edl Executable file
View 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
View 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

View 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.

106
tests/test_parse.py Normal file
View File

@@ -0,0 +1,106 @@
from unittest import TestCase
import pycmx
class TestParse(TestCase):
files = ["INS4_R1_010417.edl" ,
"INS4_R1_DX_092117.edl",
"STP R1 v082517.edl",
"ToD_R4_LOCK3.1_030618_Video.edl",
"TEST.edl",
"test_edl_cdl.edl",
"INS4_R1_DX_092117.edl"
]
def test_event_counts(self):
counts = [ 287, 466, 250 , 376, 120 , 3 , 466 ]
for fn, count in zip(type(self).files, counts):
with open(f"tests/edls/{fn}" ,'r') as f:
edl = pycmx.parse_cmx3600(f)
actual = len( list( edl.events ))
self.assertTrue( actual == count , f"expected {count} in file {fn} but found {actual}")
def test_list_sanity(self):
for fn in type(self).files:
with open(f"tests/edls/{fn}" ,'r') as f:
edl = pycmx.parse_cmx3600(f)
self.assertTrue( type(edl.title) is str )
self.assertTrue( len(edl.title) > 0 )
def test_event_sanity(self):
for fn in type(self).files:
with open(f"tests/edls/{fn}" ,'r') as f:
edl = pycmx.parse_cmx3600(f)
for index, event in enumerate(edl.events):
self.assertTrue( len(event.edits) > 0 )
self.assertTrue( event.number == index + 1 )
def test_events(self):
with open("tests/edls/TEST.edl",'r') as f:
edl = pycmx.parse_cmx3600(f)
events = list( edl.events )
self.assertEqual( events[0].number , 1)
self.assertEqual( events[0].edits[0].source , "OY_HEAD_")
self.assertEqual( events[0].edits[0].clip_name , "HEAD LEADER MONO")
self.assertEqual( events[0].edits[0].source_file , "OY_HEAD_LEADER.MOV")
self.assertEqual( events[0].edits[0].source_in , "00:00:00:00")
self.assertEqual( events[0].edits[0].source_out , "00:00:00:00")
self.assertEqual( events[0].edits[0].record_in , "01:00:00:00")
self.assertEqual( events[0].edits[0].record_out , "01:00:08:00")
self.assertTrue( events[0].edits[0].transition.kind == pycmx.Transition.Cut)
def test_channel_map(self):
with open("tests/edls/TEST.edl",'r') as f:
edl = pycmx.parse_cmx3600(f)
events = list( edl.events )
self.assertFalse( events[0].edits[0].channels.video)
self.assertFalse( events[0].edits[0].channels.a1)
self.assertTrue( events[0].edits[0].channels.a2)
self.assertTrue( events[2].edits[0].channels.get_audio_channel(7) )
def test_multi_edit_events(self):
with open("tests/edls/TEST.edl",'r') as f:
edl = pycmx.parse_cmx3600(f)
events = list( edl.events )
self.assertEqual( events[42].number , 43)
self.assertEqual( len(events[42].edits), 2)
self.assertEqual( events[42].edits[0].source , "TC_R1_V1")
self.assertEqual( events[42].edits[0].clip_name , "TC R1 V1.2 TEMP1 FX ST.WAV")
self.assertEqual( events[42].edits[0].source_in , "00:00:00:00")
self.assertEqual( events[42].edits[0].source_out , "00:00:00:00")
self.assertEqual( events[42].edits[0].record_in , "01:08:56:09")
self.assertEqual( events[42].edits[0].record_out , "01:08:56:09")
self.assertTrue( events[42].edits[0].transition.kind == pycmx.Transition.Cut)
self.assertEqual( events[42].edits[1].source , "TC_R1_V6")
self.assertEqual( events[42].edits[1].clip_name , "TC R1 V6 TEMP2 ST FX.WAV")
self.assertEqual( events[42].edits[1].source_in , "00:00:00:00")
self.assertEqual( events[42].edits[1].source_out , "00:00:00:00")
self.assertEqual( events[42].edits[1].record_in , "01:08:56:09")
self.assertEqual( events[42].edits[1].record_out , "01:08:56:11")
self.assertTrue( events[42].edits[1].transition.kind == pycmx.Transition.Dissolve)
def test_line_numbers(self):
with open("tests/edls/ToD_R4_LOCK3.1_030618_Video.edl") as f:
edl = pycmx.parse_cmx3600(f)
events = list( edl.events )
self.assertEqual( events[0].edits[0].line_number, 2)
self.assertEqual( events[14].edits[0].line_number, 45)
self.assertEqual( events[180].edits[0].line_number, 544)
def test_transition_name(self):
with open("tests/edls/test_25.edl","r") as f:
edl = pycmx.parse_cmx3600(f)
events = list(edl.events)
self.assertEqual( events[4].edits[1].transition.name , "CROSS DISSOLVE" )