53 Commits

Author SHA1 Message Date
27fcc2eb61 Updated readme 2025-12-16 12:49:11 -08:00
87dbbb1c68 autopep 2025-12-16 12:39:04 -08:00
229f6d646b FRMT implementation 2025-12-16 12:37:16 -08:00
23499b140c flake8 2025-12-16 12:21:51 -08:00
78f5c8ea08 Made imports explicit 2025-12-16 12:20:25 -08:00
878bdcc8c8 Made statement imports explicit 2025-12-16 12:18:50 -08:00
8a825c8164 ASC SOP and SAT support in Edit type 2025-12-16 12:14:26 -08:00
039bf8de6d autopep 2025-12-16 11:26:38 -08:00
73853d215e FRMC statement form implementation 2025-12-16 11:24:42 -08:00
2483e7fe43 Implemented Cdl ASC_SAT 2025-12-16 11:10:08 -08:00
6c7f7d2de1 Implemented SOP form parsing 2025-12-16 10:35:40 -08:00
2ee94ca358 Moved statement structs into a new file 2025-12-16 09:44:36 -08:00
f03b2e74bf Merge branch 'master' of https://github.com/iluvcapra/pycmx into 16-feat-asc_cdl-and-frmc 2025-12-16 09:42:29 -08:00
Jamie Hardt
52ecc02eae Merge pull request #15 from iluvcapra/14-issue-parsing-edl
Parsing odd EDLs re Issue #14
2025-12-16 09:37:09 -08:00
b85e02585c Nudged version 2025-12-16 09:36:25 -08:00
7a9e9627c3 Added EDL test cases 2025-12-16 09:32:32 -08:00
88f3a7a659 Added unit test for new parsing functionality 2025-12-16 09:23:46 -08:00
959b824dcd flake8 2025-12-15 15:39:25 -08:00
0c6a3896fb Autopep 2025-12-15 15:36:25 -08:00
a128b6ca7d Tests now work for these new files 2025-12-15 15:33:16 -08:00
79778bb847 Adding EDL test cases from issue #14 2025-12-15 14:10:11 -08:00
Jamie Hardt
81997c763e Update pythonpublish.yml
Updated python-publish to latest version
2025-09-08 12:49:46 -07:00
Jamie Hardt
6da04b2c07 Nudged version 2025-01-07 11:52:26 -08:00
Jamie Hardt
89b6cde808 Merge pull request #12 from iluvcapra/feat-adobe
Added support for Adobe Premiere EDLs > 999 events
2025-01-07 11:49:54 -08:00
Jamie Hardt
5e4dde5aa2 Merge branch 'master' of https://github.com/iluvcapra/pycmx into feat-adobe 2025-01-05 22:34:30 -08:00
Jamie Hardt
b6379ec1fe tweaking doc version method 2025-01-05 12:44:03 -08:00
Jamie Hardt
04731d634f Updated version read logic 2025-01-05 12:37:56 -08:00
Jamie Hardt
2dcfdabf01 Merge branch 'master' of https://github.com/iluvcapra/pycmx into feat-adobe 2025-01-05 12:26:03 -08:00
Jamie Hardt
7ed423deaf Dropping max-complexity argument 2025-01-05 12:19:22 -08:00
Jamie Hardt
2be779fe53 Flake8 for docs, test, bin 2025-01-05 12:16:16 -08:00
Jamie Hardt
2adff6dd01 Flake8 lints, flake8 linting fully activated. 2025-01-05 12:03:38 -08:00
Jamie Hardt
7871acdfd0 Merge branch 'master' of https://github.com/iluvcapra/pycmx into feat-adobe 2025-01-05 11:33:57 -08:00
Jamie Hardt
8bb6dad1da Some documentation and autopep 2025-01-05 11:32:23 -08:00
Jamie Hardt
f88b82e8fb README updated 2025-01-05 10:47:57 -08:00
Jamie Hardt
a86ef7ed2e Added support for Adobe Premiere EDLs > 999 events 2025-01-04 13:20:21 -08:00
Jamie Hardt
1e1331eadb Adding a test case for #11 2025-01-02 22:07:20 -08:00
Jamie Hardt
7a0481dbf9 Update bsky_test.yml 2024-11-26 11:50:10 -08:00
Jamie Hardt
20ad04e862 Update bsky_test.yml 2024-11-26 11:43:39 -08:00
Jamie Hardt
9e54aa5fcf Update bsky_test.yml 2024-11-26 11:34:16 -08:00
Jamie Hardt
71aff9baf7 Update bsky_test.yml 2024-11-26 11:33:37 -08:00
Jamie Hardt
ab55cab160 Create bsky_test.yml 2024-11-26 11:29:43 -08:00
Jamie Hardt
a717589683 Bumped version and tweaked Bluesky message 2024-11-26 11:19:01 -08:00
Jamie Hardt
f48c164e1b Merge pull request #10 from iluvcapra/maint-poetry
Sprucing-up Build System and Workflows
2024-11-26 11:17:10 -08:00
Jamie Hardt
be1dc99e94 Update pyproject.toml
Fixed typo
2024-11-26 11:16:33 -08:00
Jamie Hardt
fbdfcddfff Re-activated Bluesky posting
Updated classifiers in pyproject.toml
2024-11-26 11:15:08 -08:00
Jamie Hardt
156828b648 Update pythonpublish.yml 2024-11-26 11:11:25 -08:00
Jamie Hardt
1894a143b1 Removed second dependencies block 2024-11-26 11:02:53 -08:00
Jamie Hardt
c0d278e079 Removing 3.7 support 2024-11-26 11:01:49 -08:00
Jamie Hardt
7d3a58bff8 Small change 2024-11-26 10:57:01 -08:00
Jamie Hardt
f1381f5f46 Merge branch 'maint-poetry' of https://github.com/iluvcapra/pycmx into maint-poetry 2024-11-26 10:54:37 -08:00
Jamie Hardt
dc00b52b61 Merge branch 'master' of https://github.com/iluvcapra/pycmx into maint-poetry 2024-11-26 10:53:39 -08:00
Jamie Hardt
571ffdefd7 Merge branch 'master' into maint-poetry 2024-11-26 10:52:55 -08:00
Jamie Hardt
a79cb02139 Updating workflows
Added 3.13 support, publish to Bluesky, Poetry build system
2024-11-26 10:50:38 -08:00
27 changed files with 15054 additions and 361 deletions

5
.flake8 Normal file
View File

@@ -0,0 +1,5 @@
[flake8]
per-file-ignores =
pycmx/__init__.py: F401
tests/__init__.py: F401

25
.github/workflows/bsky_test.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Test Post to Bluesky
on:
workflow_dispatch:
# permissions:
# contents: read
# id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: release
steps:
- name: Send Bluesky Post
uses: myConsciousness/bluesky-post@v5
with:
text: |
This is a test post!
link-preview-url: ${{ github.server_url }}/${{ github.repository }}
identifier: ${{ secrets.BLUESKY_APP_USER }}
password: ${{ secrets.BLUESKY_APP_PASSWORD }}
service: bsky.social
retry-count: 1

View File

@@ -16,8 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
@@ -28,14 +27,12 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest
python3 -m pip install -e .
# if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
python -m pip install -e .
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
flake8 . --count --max-line-length=79 --statistics
- name: Test with pytest
run: |
pytest

View File

@@ -3,7 +3,6 @@ name: Upload Python Package
on:
release:
types: [published]
workflow_dispatch:
permissions:
contents: read
@@ -15,9 +14,9 @@ jobs:
environment:
name: release
steps:
- uses: actions/checkout@v3.5.2
- uses: actions/checkout@v4.2.2
- name: Set up Python
uses: actions/setup-python@v4.6.0
uses: actions/setup-python@v5.3.0
with:
python-version: '3.x'
- name: Install dependencies
@@ -25,16 +24,19 @@ jobs:
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: pypi-publish
uses: pypa/gh-action-pypi-publish@v1.8.6
- name: Report to Mastodon
uses: cbrgm/mastodon-github-action@v1.0.1
run: python -m build .
- name: Publish to Pypi
uses: pypa/gh-action-pypi-publish@v1.13.0
with:
message: |
I just released a new version of pycmx, my library for reading CMX EDLs!
#sounddesign #filmmaking #python
${{ github.server_url }}/${{ github.repository }}
env:
MASTODON_URL: ${{ secrets.MASTODON_URL }}
MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }}
password: ${{ secrets.PYPI_APIKEY }}
- name: Send Bluesky Post
uses: myConsciousness/bluesky-post@v5
with:
text: |
I've released a new version of pycmx, my python module for
reading CMX EDLs.
link-preview-url: ${{ github.server_url }}/${{ github.repository }}
identifier: ${{ secrets.BLUESKY_APP_USER }}
password: ${{ secrets.BLUESKY_APP_PASSWORD }}
service: bsky.social
retry-count: 1

View File

@@ -5,15 +5,20 @@
# pycmx
The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and its most most common variations.
The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and
its most most common variations.
## Features
* The major variations of the CMX 3600: the standard, "File32" and "File128"
formats are automatically detected and properly read.
* The major variations of the CMX 3600: the standard, "File32", "File128" and
long Adobe Premiere event numbers are automatically detected and properly
read. Event number field and source name field sizes are determined
dynamically for each statement for a high level of compliance at the expense
of strictness.
* Preserves relationship between events and individual edits/clips.
* Remark or comment fields with common recognized forms are read and
available to the client, including clip name and source file data.
* ASC SOP, Saturation and FRMC statements are parsed and decoded.
* 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
@@ -83,5 +88,3 @@ Audio channel 7 is present
>>> events[2].edits[0].channels.video
False
```

View File

@@ -12,6 +12,7 @@ logging.basicConfig(format=FORMAT)
log = logging.getLogger(__name__)
def all_video_edits(edl):
for event in edl.events:
for edit in event.edits:
@@ -30,19 +31,22 @@ def get_scene_name(edit, pattern):
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'])
for i, o in enumerate(out_list):
line = '%03i AX V C ' % (i)
line += '00:00:00:00 00:00:00:00 %s %s\r\n' % (o['start'], o['end'])
outfile.write(line)
outfile.write("* FROM CLIP NAME: %s\r\n" % (o['scene']) )
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'] ))
outfile.write("%-12s\t%-12s\t%s\n" %
(o['start'], o['end'], o['scene']))
def scene_list(infile, outfile, out_format, pattern):
@@ -51,24 +55,24 @@ def scene_list(infile, outfile, out_format, pattern):
current_scene_name = None
grouped_edits = [ ]
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([ ])
grouped_edits.append([])
current_scene_name = this_scene_name
grouped_edits[-1].append(edit)
out_list = [ ]
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 ) }
)
'scene': get_scene_name(group[0], pattern)}
)
if out_format == 'cmx':
output_cmx(outfile, out_list)
@@ -80,23 +84,29 @@ def scene_list(infile, outfile, out_format, pattern):
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.')
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)
scene_list(infile=infile, outfile=args.outfile,
out_format=args.format, pattern=args.pattern)
if __name__ == '__main__':
scene_list_cli()
scene_list_cli()

View File

@@ -12,22 +12,22 @@
# 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 importlib
import os
import sys
sys.path.insert(0, os.path.abspath('../..'))
import pycmx
# -- Project information -----------------------------------------------------
project = u'pycmx'
copyright = u'(c) 2023, Jamie Hardt'
copyright = u'(c) 2025, Jamie Hardt'
author = u'Jamie Hardt'
release = importlib.metadata.version("pycmx")
version = release
# The short X.Y version
version = pycmx.__version__
# The full version, including alpha/beta/rc tags
release = pycmx.__version__
# -- General configuration ---------------------------------------------------

View File

@@ -2,13 +2,11 @@
"""
pycmx is a parser for CMX 3600-style EDLs.
This module (c) 2023 Jamie Hardt. For more information on your rights to
This module (c) 2025 Jamie Hardt. For more information on your rights to
copy and reuse this software, refer to the LICENSE file included with the
distribution.
"""
__version__ = '1.2.2'
from .parse_cmx_events import parse_cmx3600
from .transition import Transition
from .event import Event

View File

@@ -4,20 +4,21 @@
from re import (compile, match)
from typing import Dict, Tuple, Generator
class ChannelMap:
"""
Represents a set of all the channels to which an event applies.
"""
_chan_map : Dict[str, Tuple] = {
"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)
}
_chan_map: Dict[str, Tuple] = {
"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
@@ -46,7 +47,7 @@ class ChannelMap:
@a1.setter
def a1(self, val: bool):
self.set_audio_channel(1,val)
self.set_audio_channel(1, val)
@property
def a2(self) -> bool:
@@ -55,7 +56,7 @@ class ChannelMap:
@a2.setter
def a2(self, val: bool):
self.set_audio_channel(2,val)
self.set_audio_channel(2, val)
@property
def a3(self) -> bool:
@@ -64,7 +65,7 @@ class ChannelMap:
@a3.setter
def a3(self, val: bool):
self.set_audio_channel(3,val)
self.set_audio_channel(3, val)
@property
def a4(self) -> bool:
@@ -72,14 +73,14 @@ class ChannelMap:
return self.get_audio_channel(4)
@a4.setter
def a4(self,val: bool):
self.set_audio_channel(4,val)
def a4(self, val: bool):
self.set_audio_channel(4, val)
def get_audio_channel(self, chan_num) -> bool:
"""True if chan_num is included"""
return (chan_num in self._audio_channel_set)
def set_audio_channel(self,chan_num, enabled: bool):
def set_audio_channel(self, chan_num, enabled: bool):
"""If enabled is true, chan_num will be included"""
if enabled:
self._audio_channel_set.add(chan_num)
@@ -96,7 +97,7 @@ class ChannelMap:
else:
matchresult = match(alt_channel_re, event_str)
if matchresult:
self.set_audio_channel(int( matchresult.group(1)), True )
self.set_audio_channel(int(matchresult.group(1)), True)
def _append_ext(self, audio_ext):
self.a3 = audio_ext.audio3
@@ -109,5 +110,4 @@ class ChannelMap:
out_v = self.video | other.video
out_a = self._audio_channel_set | other._audio_channel_set
return ChannelMap(v=out_v,audio_channels = out_a)
return ChannelMap(v=out_v, audio_channels=out_a)

View File

@@ -1,23 +1,32 @@
# pycmx
# (c) 2018 Jamie Hardt
from pycmx.statements import StmtCdlSat, StmtCdlSop, StmtFrmc
from .transition import Transition
from .channel_map import ChannelMap
# from .parse_cmx_statements import StmtEffectsName
from typing import Optional
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):
def __init__(self, edit_statement, audio_ext_statement,
clip_name_statement, source_file_statement,
trans_name_statement=None, asc_sop_statement=None,
asc_sat_statement=None, frmc_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
self.asc_sop_statement: Optional[StmtCdlSop] = asc_sop_statement
self.asc_sat_statement: Optional[StmtCdlSat] = asc_sat_statement
self.frmc_statement: Optional[StmtFrmc] = frmc_statement
@property
def line_number(self) -> int:
@@ -35,7 +44,7 @@ class Edit:
"""
cm = ChannelMap()
cm._append_event(self.edit_statement.channels)
if self.audio_ext != None:
if self.audio_ext is not None:
cm._append_ext(self.audio_ext)
return cm
@@ -45,9 +54,12 @@ class Edit:
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)
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)
return Transition(self.edit_statement.trans,
self.edit_statement.trans_op, None)
@property
def source_in(self) -> str:
@@ -125,4 +137,23 @@ class Edit:
else:
return self.clip_name_statement.name
@property
def asc_sop(self) -> Optional[StmtCdlSop]:
"""
Get ASC CDL Slope-Offset-Power transfer function for clip, if present
"""
return self.asc_sop_statement
@property
def asc_sat(self) -> Optional[StmtCdlSat]:
"""
Get ASC CDL saturation value for clip, if present
"""
return self.asc_sat_statement
@property
def frmc(self) -> Optional[StmtFrmc]:
"""
Get FRMC data
"""
return self.frmc_statement

View File

@@ -1,18 +1,23 @@
# pycmx
# (c) 2018 Jamie Hardt
from .parse_cmx_statements import (StmtUnrecognized, StmtFCM, StmtEvent, StmtSourceUMID)
from pycmx.statements import StmtTitle
from .parse_cmx_statements import (
StmtUnrecognized, StmtEvent, StmtSourceUMID)
from .event import Event
from .channel_map import ChannelMap
from typing import Generator
class EditList:
"""
Represents an entire edit decision list as returned by :func:`~pycmx.parse_cmx3600()`.
Represents an entire edit decision list as returned by
:func:`~pycmx.parse_cmx3600()`.
"""
def __init__(self, statements):
self.title_statement = statements[0]
self.title_statement: StmtTitle = statements[0]
self.event_statements = statements[1:]
@property
@@ -20,8 +25,11 @@ class EditList:
"""
The detected format of the EDL. Possible values are: "3600", "File32",
"File128", and "unknown".
Adobe EDLs with more than 999 events will be reported as "3600".
"""
first_event = next( (s for s in self.event_statements if type(s) is StmtEvent), None)
first_event = next(
(s for s in self.event_statements if type(s) is StmtEvent), None)
if first_event:
if first_event.format == 8:
@@ -35,7 +43,6 @@ class EditList:
else:
return 'unknown'
@property
def channels(self) -> ChannelMap:
"""
@@ -49,7 +56,6 @@ class EditList:
return retval
@property
def title(self) -> str:
"""
@@ -57,9 +63,9 @@ class EditList:
"""
return self.title_statement.title
@property
def unrecognized_statements(self) -> Generator[StmtUnrecognized, None, None]:
def unrecognized_statements(self) -> Generator[StmtUnrecognized,
None, None]:
"""
A generator for all the unrecognized statements in the list.
"""
@@ -67,17 +73,13 @@ class EditList:
if type(s) is StmtUnrecognized:
yield s
@property
def events(self) -> Generator[Event, None, None]:
'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 type(stmt) is StmtEvent:
if current_event_num is None:
current_event_num = stmt.event
event_statements.append(stmt)
@@ -89,8 +91,6 @@ class EditList:
else:
event_statements.append(stmt)
elif type(stmt) is StmtSourceUMID:
break
else:
event_statements.append(stmt)
@@ -105,5 +105,3 @@ class EditList:
for stmt in self.event_statements:
if type(stmt) is StmtSourceUMID:
yield stmt

View File

@@ -1,15 +1,19 @@
# pycmx
# (c) 2023 Jamie Hardt
from .parse_cmx_statements import (StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized, StmtEffectsName)
from pycmx.statements import StmtFrmc
from .parse_cmx_statements import (
StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized,
StmtEffectsName, StmtCdlSop, StmtCdlSat)
from .edit import Edit
from typing import List, Generator, Optional, Tuple, Any
class Event:
"""
Represents a collection of :class:`~pycmx.edit.Edit` s, all with the same event number.
"""
Represents a collection of :class:`~pycmx.edit.Edit` s, all with the same
event number. """
def __init__(self, statements):
self.statements = statements
@@ -28,9 +32,9 @@ class 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()
edits_audio = list(self._statements_with_audio_ext())
clip_names = self._clip_name_statements()
source_files = self._source_file_statements()
the_zip: List[List[Any]] = [edits_audio]
@@ -49,14 +53,14 @@ class Event:
if len(edits_audio) == len(clip_names):
the_zip.append(clip_names)
else:
the_zip.append([None] * len(edits_audio) )
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) )
the_zip.append(source_files * len(edits_audio))
else:
the_zip.append([None] * len(edits_audio) )
the_zip.append([None] * len(edits_audio))
# attach trans name to last event
try:
@@ -65,16 +69,21 @@ class Event:
trans_names.append(trans_statement)
the_zip.append(trans_names)
except IndexError:
the_zip.append([None] * len(edits_audio) )
the_zip.append([None] * len(edits_audio))
return [ Edit(edit_statement=e1[0],
audio_ext_statement=e1[1],
clip_name_statement=n1,
source_file_statement=s1,
trans_name_statement=u1) for (e1,n1,s1,u1) in zip(*the_zip) ]
return [Edit(edit_statement=e1[0],
audio_ext_statement=e1[1],
clip_name_statement=n1,
source_file_statement=s1,
trans_name_statement=u1,
asc_sop_statement=self._asc_sop_statement(),
asc_sat_statement=self._asc_sat_statement(),
frmc_statement=self._frmc_statement())
for (e1, n1, s1, u1) in zip(*the_zip)]
@property
def unrecognized_statements(self) -> Generator[StmtUnrecognized, None, None]:
def unrecognized_statements(self) -> Generator[StmtUnrecognized, None,
None]:
"""
A generator for all the unrecognized statements in the event.
"""
@@ -94,11 +103,21 @@ class Event:
def _source_file_statements(self) -> List[StmtSourceFile]:
return [s for s in self.statements if type(s) is StmtSourceFile]
def _statements_with_audio_ext(self) -> Generator[Tuple[StmtEvent, Optional[StmtAudioExt]], None, None]:
def _statements_with_audio_ext(self) -> Generator[
Tuple[StmtEvent, Optional[StmtAudioExt]], None, None]:
for (s1, s2) in zip(self.statements, self.statements[1:]):
if type(s1) is StmtEvent and type(s2) is StmtAudioExt:
yield (s1,s2)
yield (s1, s2)
elif type(s1) is StmtEvent:
yield (s1, None)
def _asc_sop_statement(self) -> Optional[StmtCdlSop]:
return next((s for s in self.statements if type(s) is StmtCdlSop),
None)
def _asc_sat_statement(self) -> Optional[StmtCdlSat]:
return next((s for s in self.statements if type(s) is StmtCdlSat),
None)
def _frmc_statement(self) -> Optional[StmtFrmc]:
return next((s for s in self.statements if type(s) is StmtFrmc), None)

View File

@@ -8,13 +8,13 @@ from .edit_list import EditList
from typing import TextIO
def parse_cmx3600(f: TextIO):
def parse_cmx3600(f: TextIO) -> EditList:
"""
Parse a CMX 3600 EDL.
:param TextIO f: a file-like object, anything that's readlines-able.
:param TextIO f: a file-like object, an opened CMX 3600 .EDL file.
:returns: An :class:`pycmx.edit_list.EditList`.
"""
statements = parse_cmx3600_statements(f)
return EditList(statements)

View File

@@ -2,168 +2,198 @@
# (c) 2018 Jamie Hardt
import re
import sys
from collections import namedtuple
from itertools import count
from typing import TextIO, List
from .statements import (StmtCdlSat, StmtCdlSop, StmtFrmc, StmtRemark,
StmtTitle, StmtUnrecognized, StmtFCM, StmtAudioExt,
StmtClipName, StmtEffectsName, StmtEvent,
StmtSourceFile, StmtSplitEdit, StmtMotionMemory,
StmtSourceUMID)
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","format","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"])
StmtSourceUMID = namedtuple("Source",["name","umid","line_number"])
StmtSplitEdit = namedtuple("SplitEdit",["video","magnitude", "line_number"])
StmtMotionMemory = namedtuple("MotionMemory",["source","fps"]) # FIXME needs more fields
StmtUnrecognized = namedtuple("Unrecognized",["content","line_number"])
def parse_cmx3600_statements(file: TextIO) -> List[object]:
"""
Return a list of every statement in the file argument.
"""
lines = file.readlines()
line_numbers = count()
return [_parse_cmx3600_line(line.strip(), line_number) \
for (line, line_number) in zip(lines,line_numbers)]
def _edl_column_widths(event_field_length, source_field_length):
return [event_field_length,2, source_field_length,1,
4,2, # chans
4,1, # trans
3,1, # trans op
11,1,
11,1,
11,1,
11]
def _edl_m2_column_widths():
return [2, # "M2"
3,3, #
8,8,1,4,2,1,4,13,3,1,1]
return [_parse_cmx3600_line(line.strip(), line_number)
for (line_number, line) in enumerate(lines)]
def _parse_cmx3600_line(line, line_number):
long_event_num_p = re.compile("^[0-9]{6} ")
short_event_num_p = re.compile("^[0-9]{3} ")
def _edl_column_widths(event_field_length, source_field_length) -> List[int]:
return [event_field_length, 2, source_field_length, 1,
4, 2, # chans
4, 1, # trans
3, 1, # trans op
11, 1,
11, 1,
11, 1,
11]
if isinstance(line,str):
if line.startswith("TITLE:"):
return _parse_title(line,line_number)
elif line.startswith("FCM:"):
return _parse_fcm(line, line_number)
elif long_event_num_p.match(line) != None:
length_file_128 = sum(_edl_column_widths(6,128))
if len(line) < length_file_128:
return _parse_long_standard_form(line, 32, line_number)
else:
return _parse_long_standard_form(line, 128, line_number)
elif short_event_num_p.match(line) != None:
return _parse_standard_form(line, line_number)
elif line.startswith("AUD"):
return _parse_extended_audio_channels(line,line_number)
elif line.startswith("*"):
return _parse_remark( line[1:].strip(), line_number)
elif line.startswith(">>> SOURCE"):
return _parse_source_umid_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:
return _parse_unrecognized(line, line_number)
# def _edl_m2_column_widths():
# return [2, # "M2"
# 3,3, #
# 8,8,1,4,2,1,4,13,3,1,1]
def _parse_title(line, line_num):
def _parse_cmx3600_line(line: str, line_number: int) -> object:
"""
Parses a single CMX EDL line.
:param line: A single EDL line.
:param line_number: The index of this line in the file.
"""
event_num_p = re.compile(r"^(\d+) ")
line_matcher = event_num_p.match(line)
if line.startswith("TITLE:"):
return _parse_title(line, line_number)
elif line.startswith("FCM:"):
return _parse_fcm(line, line_number)
elif line_matcher is not None:
event_field_len = len(line_matcher.group(1))
source_field_len = len(line) - (event_field_len + 65)
return _parse_columns_for_standard_form(line, event_field_len,
source_field_len, line_number)
elif line.startswith("AUD"):
return _parse_extended_audio_channels(line, line_number)
elif line.startswith("*"):
return _parse_remark(line[1:].strip(), line_number)
elif line.startswith(">>> SOURCE"):
return _parse_source_umid_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:
return _parse_unrecognized(line, line_number)
def _parse_title(line, line_num) -> StmtTitle:
title = line[6:].strip()
return StmtTitle(title=title,line_number=line_num)
return StmtTitle(title=title, line_number=line_num)
def _parse_fcm(line, line_num):
def _parse_fcm(line, line_num) -> StmtFCM:
val = line[4:].strip()
if val == "DROP FRAME":
return StmtFCM(drop= True, line_number=line_num)
return StmtFCM(drop=True, line_number=line_num)
else:
return StmtFCM(drop= False, line_number=line_num)
return StmtFCM(drop=False, line_number=line_num)
def _parse_long_standard_form(line,source_field_length, line_number):
return _parse_columns_for_standard_form(line, 6, source_field_length, line_number)
def _parse_standard_form(line, line_number):
return _parse_columns_for_standard_form(line, 3, 8, line_number)
def _parse_extended_audio_channels(line, line_number):
content = line.strip()
if content == "AUD 3":
return StmtAudioExt(audio3=True, audio4=False, line_number=line_number)
elif content == "AUD 4":
return StmtAudioExt(audio3=False, audio4=True, line_number=line_number)
elif content == "AUD 3 4":
return StmtAudioExt(audio3=True, audio4=True, line_number=line_number)
audio3 = True if "3" in content else False
audio4 = True if "4" in content else False
if audio3 or audio4:
return StmtAudioExt(audio3, audio4, line_number)
else:
return StmtUnrecognized(content=line, line_number=line_number)
return StmtUnrecognized(line, line_number)
def _parse_remark(line, line_number) -> object:
if line.startswith("FROM CLIP NAME:"):
return StmtClipName(name=line[15:].strip() , affect="from", line_number=line_number)
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)
return StmtClipName(name=line[13:].strip(), affect="to",
line_number=line_number)
elif line.startswith("SOURCE FILE:"):
return StmtSourceFile(filename=line[12:].strip() , line_number=line_number)
return StmtSourceFile(filename=line[12:].strip(),
line_number=line_number)
elif line.startswith("ASC_SOP"):
group_patterns: list[str] = re.findall(r'\((.*?)\)', line)
v1: list[list[tuple[str, str]]] = \
[re.findall(r'(-?\d+(\.\d+)?)', a) for a in group_patterns]
v: list[list[str]] = [[a[0] for a in b] for b in v1]
if len(v) != 3 or any([len(a) != 3 for a in v]):
return StmtRemark(line, line_number)
else:
return StmtCdlSop(slope_r=v[0][0], slope_g=v[0][1],
slope_b=v[0][2], offset_r=v[1][0],
offset_g=v[1][1], offset_b=v[1][2],
power_r=v[2][0], power_g=v[2][1],
power_b=v[2][2], line_number=line_number)
elif line.startswith("ASC_SAT"):
value = re.findall(r'(-?\d+(\.\d+)?)', line)
if len(value) != 1:
return StmtRemark(line, line_number)
else:
return StmtCdlSat(value=value[0][0], line_number=line_number)
elif line.startswith("FRMC"):
match = re.match(
r'^FRMC START:\s*(\d+)\s+FRMC END:\s*(\d+)'
r'\s+FRMC DURATION:\s*(\d+)', line, re.IGNORECASE)
if match is None:
return StmtRemark(line, line_number)
else:
return StmtFrmc(start=match.group(1), end=match.group(2),
duration=match.group(3), line_number=line_number)
else:
return StmtRemark(text=line, line_number=line_number)
def _parse_effects_name(line, line_number) -> StmtEffectsName:
name = line[16:].strip()
return StmtEffectsName(name=name, line_number=line_number)
def _parse_split(line, line_number):
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)
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="")
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):
def _parse_columns_for_standard_form(line, event_field_length,
source_field_length, line_number):
col_widths = _edl_column_widths(event_field_length, source_field_length)
if sum(col_widths) > len(line):
return StmtUnrecognized(content=line, line_number=line_number)
column_strings = collimate(line,col_widths)
column_strings = collimate(line, col_widths)
return StmtEvent(event=column_strings[0],
source=column_strings[2].strip(),
channels=column_strings[4].strip(),
trans=column_strings[6].strip(),
source=column_strings[2].strip(),
channels=column_strings[4].strip(),
trans=column_strings[6].strip(),
trans_op=column_strings[8].strip(),
source_in=column_strings[10].strip(),
source_out=column_strings[12].strip(),
record_in=column_strings[14].strip(),
record_out=column_strings[16].strip(),
line_number=line_number,
format=source_field_length)
line_number=line_number, format=source_field_length)
def _parse_source_umid_statement(line, line_number):
trimmed = line[3:].strip()
# trimmed = line[3:].strip()
return StmtSourceUMID(name=None, umid=None, line_number=line_number)

24
pycmx/statements.py Normal file
View File

@@ -0,0 +1,24 @@
from collections import namedtuple
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", "format",
"line_number"])
StmtAudioExt = namedtuple("AudioExt", ["audio3", "audio4", "line_number"])
StmtClipName = namedtuple("ClipName", ["name", "affect", "line_number"])
StmtSourceFile = namedtuple("SourceFile", ["filename", "line_number"])
StmtCdlSop = namedtuple("CdlSop", ['slope_r', 'slope_g', 'slope_b',
'offset_r', 'offset_g', 'offset_b',
'power_r', 'power_g', 'power_b',
'line_number'])
StmtCdlSat = namedtuple("SdlSat", ['value', 'line_number'])
StmtFrmc = namedtuple("Frmc", ['start', 'end', 'duration', 'line_number'])
StmtRemark = namedtuple("Remark", ["text", "line_number"])
StmtEffectsName = namedtuple("EffectsName", ["name", "line_number"])
StmtSourceUMID = namedtuple("Source", ["name", "umid", "line_number"])
StmtSplitEdit = namedtuple("SplitEdit", ["video", "magnitude", "line_number"])
StmtMotionMemory = namedtuple(
"MotionMemory", ["source", "fps"]) # FIXME needs more fields
StmtUnrecognized = namedtuple("Unrecognized", ["content", "line_number"])

View File

@@ -3,6 +3,7 @@
from typing import Optional
class Transition:
"""
A CMX transition: a wipe, dissolve or cut.
@@ -86,5 +87,3 @@ class Transition:
the key foreground and replaced with the key background.
"""
return self.transition == Transition.KeyOut

View File

@@ -28,4 +28,3 @@ def collimate(a_string, column_widths):
element = a_string[:width]
rest = a_string[width:]
return [element] + collimate(rest, column_widths[1:])

View File

@@ -1,49 +1,40 @@
[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"
[project]
[tool.poetry]
name = "pycmx"
authors = [{name = "Jamie Hardt", email = "jamiehardt@me.com"}]
version = "1.4.0"
description = "Python CMX 3600 Edit Decision List Parser"
authors = ["Jamie Hardt <jamiehardt@me.com>"]
license = "MIT"
readme = "README.md"
dynamic = ["version", "description"]
requires-python = "~=3.7"
keywords = [
'parser',
'film',
'broadcast'
]
classifiers = [
'Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: MIT License',
'Topic :: Multimedia',
'Topic :: Multimedia :: Video',
'Topic :: Text Processing',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12'
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13'
]
dependencies = [
homepage = "https://github.com/iluvcapra/pycmx"
documentation = "https://pycmx.readthedocs.io/"
repository = "https://github.com/iluvcapra/pycmx.git"
urls.Tracker = "https://github.com/iluvcapra/pycmx/issues"
]
keywords = [
'parser',
'film',
'broadcast'
]
[tool.poetry.extras]
doc = ['sphinx', 'sphinx_rtd_theme']
[tool.flit.module]
name = "pycmx"
[project.optional-dependencies]
doc = [
'sphinx >= 5.3.0',
'sphinx_rtd_theme >= 1.1.1',
]
[project.urls]
Home = "https://github.com/iluvcapra/pycmx"
Documentation = "https://pycmx.readthedocs.io/"
Source = "https://github.com/iluvcapra/pycmx.git"
Issues = "https://github.com/iluvcapra/pycmx/issues"
[tool.poetry.dependencies]
python = "^3.8"
sphinx = { version='>= 5.3.0', optional=true}
sphinx_rtd_theme = {version ='>= 1.1.1', optional=true}
[tool.pyright]
typeCheckingMode = "basic"
@@ -59,3 +50,7 @@ disable = [
"R0913", # (too-many-arguments)
"W0105", # (pointless-string-statement)
]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -0,0 +1,13 @@
TITLE: conform_edl_issue_01
FCM: NON-DROP FRAME
001 C_0022C003_241016_092821_h1F4X V C 09:33:31:12 09:33:33:14 01:00:06:20 01:00:08:22
* FROM CLIP NAME: 13A-1-C
002 B_0020C009_241003_214837_h1C2T V C 21:48:54:22 21:48:55:15 01:01:34:06 01:01:34:23
* FROM CLIP NAME: 111B-1-B
003 B_0088C002_241125_144410_h1C2T V C 13:48:57:10 13:48:58:11 01:01:41:13 01:01:42:14
M2 B_0088C002_241125_144410_h1C2T 031.7 13:48:57:10
* FROM CLIP NAME: 102C-2-B

View File

@@ -0,0 +1,33 @@
TITLE: conform_edl_issue_02
FCM: NON-DROP FRAME
001 C_0019C005_241014_204338_h1F4X V C 11:19:41:07 11:19:49:03 02:04:48:10 02:04:56:06
* FROM CLIP NAME: 40E-4 MOS*
002 B_0075C004_241114_164247_h1C2T V C 15:43:03:12 15:43:05:17 02:06:03:03 02:06:05:08
* FROM CLIP NAME: 39B-4-B
003 A_0079C015_241112_160227_h1EHP V C 16:06:21:10 16:06:23:03 02:08:14:07 02:08:16:00
* FROM CLIP NAME: 46L-2
004 C_0047C005_241121_123629_h1F4X V C 11:41:15:17 11:41:21:01 02:09:08:01 02:09:13:09
* FROM CLIP NAME: 49-5-C
005 A_0003C002_240923_130856_h1EHP V C 14:11:06:01 14:11:12:03 02:11:26:11 02:11:32:13
* FROM CLIP NAME: 54-2-A
006 A_0090C003_241119_095341_h1EHP V C 09:57:25:14 09:57:32:20 02:13:40:13 02:13:47:19
* FROM CLIP NAME: 57B-3-A*
007 A_0090C008_241119_102624_h1EHP V C 10:30:43:07 10:30:46:23 02:14:16:01 02:14:19:17
* FROM CLIP NAME: 57D-2*
008 B_0079C003_241119_105658_h1C2T V C 09:58:57:15 09:59:00:14 02:15:01:08 02:15:04:07
* FROM CLIP NAME: 57B-3-B*
009 A_0005C011_240924_113730_h1EHP V C 12:40:01:07 12:40:04:06 02:16:13:11 02:16:16:10
* FROM CLIP NAME: 58C-3-A
010 A_0060C001_241030_133415_h1EHP V C 13:35:38:11 13:35:41:17 02:18:29:17 02:18:32:23
* FROM CLIP NAME: 61A-1

View File

@@ -0,0 +1,129 @@
TITLE: conform_edl_issue_03
FCM: NON-DROP FRAME
002 A_0113C007_250602_103141_h1D4P V C 10:32:32:22 10:32:48:16 01:00:20:23 01:00:36:17
* FROM CLIP NAME: AP002A-3*
003 A_0113C004_250602_101043_h1D4P V C 10:11:34:01 10:11:48:01 01:00:36:17 01:00:50:17
* FROM CLIP NAME: AP002-4*
004 A_0113C018_250602_122238_h1D4P V C 12:24:03:12 12:24:04:17 01:01:15:17 01:01:16:22
* FROM CLIP NAME: AP002E-3-A*
005 A_0113C018_250602_122238_h1D4P V C 12:24:05:22 12:24:18:02 01:01:20:21 01:01:33:01
* FROM CLIP NAME: AP002E-3-A*
006 A_0113C022_250602_125451_h1D4P V C 12:55:43:09 12:55:55:03 01:01:33:01 01:01:44:19
* FROM CLIP NAME: AP002F-4-A*
007 A_0113C024_250602_132048_h1D4P V C 13:21:54:17 13:22:00:07 01:01:44:19 01:01:50:09
* FROM CLIP NAME: AP002G-2-A
008 B_0098C009_250602_115121_h1EZ3 V C 12:56:02:03 12:56:05:11 01:01:50:09 01:01:53:17
* FROM CLIP NAME: AP002F-4-B*
009 A_0113C024_250602_132048_h1D4P V C 13:21:41:20 13:21:46:01 01:01:53:17 01:01:57:22
* FROM CLIP NAME: AP002G-2-A
010 A_0113C022_250602_125451_h1D4P V C 12:56:08:09 12:56:12:23 01:01:57:22 01:02:02:12
* FROM CLIP NAME: AP002F-4-A*
011 B_0099C004_250602_133201_h1EZ3 V C 14:36:34:09 14:36:38:03 01:02:06:07 01:02:10:01
* FROM CLIP NAME: AP002H-4-B*
012 A_0114C009_250602_161406_h1D4P V C 16:15:15:09 16:15:17:16 01:02:10:01 01:02:12:08
* FROM CLIP NAME: AP002M-1-A
013 A_0115C001_250602_172408_h1D4P V C 17:25:55:01 17:25:59:17 01:02:20:15 01:02:25:07
* FROM CLIP NAME: AP002Q-1-A
014 B_0099C019_250602_153856_h1EZ3 V C 16:43:35:06 16:43:38:13 01:02:25:07 01:02:28:14
* FROM CLIP NAME: AP002N-2
015 A_0115C002_250602_172803_h1D4P V C 17:29:28:01 17:29:32:02 01:02:28:14 01:02:32:15
* FROM CLIP NAME: AP002Q-2-A*
016 B_0099C019_250602_153856_h1EZ3 V C 16:43:41:10 16:43:45:12 01:02:32:15 01:02:36:17
* FROM CLIP NAME: AP002N-2
017 A_0115C001_250602_172408_h1D4P V C 17:26:08:05 17:26:10:20 01:02:36:17 01:02:39:08
* FROM CLIP NAME: AP002Q-1-A
018 B_0099C018_250602_153023_h1EZ3 V C 16:35:28:00 16:35:30:10 01:02:39:08 01:02:41:18
* FROM CLIP NAME: AP002N-1*
019 B_0100C001_250602_162041_h1EZ3 V C 17:26:17:05 17:26:24:23 01:02:41:18 01:02:49:12
* FROM CLIP NAME: AP002Q-1-B
020 A_0114C010_250602_162156_h1D4P V C 16:24:28:03 16:24:33:20 01:02:49:12 01:02:55:05
* FROM CLIP NAME: AP002M-2-A*
021 B_0099C007_250602_140229_h1EZ3 V C 15:07:55:18 15:08:00:01 01:02:55:05 01:02:59:12
* FROM CLIP NAME: AP002K-2-B
022 B_0099C018_250602_153023_h1EZ3 V C 16:35:47:23 16:35:49:05 01:02:59:12 01:03:00:18
* FROM CLIP NAME: AP002N-1*
023 B_0099C007_250602_140229_h1EZ3 V C 15:08:01:17 15:08:12:19 01:03:00:18 01:03:11:20
* FROM CLIP NAME: AP002K-2-B
024 B_0099C018_250602_153023_h1EZ3 V C 16:36:10:00 16:36:11:10 01:03:11:20 01:03:13:06
* FROM CLIP NAME: AP002N-1*
025 B_0099C007_250602_140229_h1EZ3 V C 15:08:15:08 15:08:22:13 01:03:13:06 01:03:20:11
* FROM CLIP NAME: AP002K-2-B
026 B_0099C018_250602_153023_h1EZ3 V C 16:36:18:21 16:36:23:01 01:03:20:11 01:03:24:15
* FROM CLIP NAME: AP002N-1*
027 B_0099C007_250602_140229_h1EZ3 V C 15:08:33:21 15:08:37:23 01:03:30:14 01:03:34:16
* FROM CLIP NAME: AP002K-2-B
028 B_0099C017_250602_151824_h1EZ3 V C 16:25:21:01 16:25:29:04 01:03:34:16 01:03:42:19
* FROM CLIP NAME: AP002M-2-B*
029 B_0099C007_250602_140229_h1EZ3 V C 15:08:47:10 15:08:55:02 01:03:42:19 01:03:50:11
* FROM CLIP NAME: AP002K-2-B
030 B_0099C017_250602_151824_h1EZ3 V C 16:25:39:00 16:25:42:03 01:03:50:11 01:03:53:14
* FROM CLIP NAME: AP002M-2-B*
031 B_0099C007_250602_140229_h1EZ3 V C 15:08:58:10 15:09:03:04 01:03:53:14 01:03:58:08
* FROM CLIP NAME: AP002K-2-B
032 B_0099C016_250602_151038_h1EZ3 V C 16:17:20:21 16:17:23:10 01:03:58:08 01:04:00:21
* FROM CLIP NAME: AP002M-1-B
033 B_0099C007_250602_140229_h1EZ3 V C 15:09:05:09 15:09:16:04 01:04:00:21 01:04:11:16
* FROM CLIP NAME: AP002K-2-B
034 B_0099C019_250602_153856_h1EZ3 V C 16:45:46:08 16:45:52:18 01:04:11:16 01:04:18:02
* FROM CLIP NAME: AP002N-2
035 B_0099C007_250602_140229_h1EZ3 V C 15:09:24:21 15:09:27:22 01:04:18:02 01:04:21:03
* FROM CLIP NAME: AP002K-2-B
036 B_0099C019_250602_153856_h1EZ3 V C 16:45:55:19 16:45:58:02 01:04:21:03 01:04:23:10
* FROM CLIP NAME: AP002N-2
037 B_0099C004_250602_133201_h1EZ3 V C 14:38:53:22 14:38:58:19 01:04:23:10 01:04:28:07
* FROM CLIP NAME: AP002H-4-B*
038 B_0099C015_250602_150046_h1EZ3 V C 16:07:52:23 16:07:55:04 01:04:28:07 01:04:30:12
* FROM CLIP NAME: AP002L-1-B
039 B_0099C007_250602_140229_h1EZ3 V C 15:09:35:22 15:09:40:19 01:04:30:12 01:04:35:09
* FROM CLIP NAME: AP002K-2-B
040 B_0099C015_250602_150046_h1EZ3 V C 16:08:01:21 16:08:05:01 01:04:35:09 01:04:38:13
* FROM CLIP NAME: AP002L-1-B
041 B_0099C007_250602_140229_h1EZ3 V C 15:09:45:16 15:09:55:00 01:04:38:13 01:04:47:21
* FROM CLIP NAME: AP002K-2-B
042 M018C0005_240925_1F4L13 V C 18:44:20:12 18:44:23:18 01:04:50:16 01:04:53:22
* FROM CLIP NAME: 13-1-SER-1-M MOS
043 A_0022C009_241003_141208_h1EHP V C 15:13:54:11 15:13:56:04 01:12:22:02 01:12:23:19
* FROM CLIP NAME: 26H-3-A*

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,226 @@
TITLE: cdl_example01
FCM: NON-DROP FRAME
000001 B_0031C010_241010_133208_h1C2T V C 13:30:27:13 13:30:29:01 02:13:08:12 02:13:10:00
*FROM CLIP NAME: 117C-3-B
*LOC: 02:13:08:12 Green Sc 117 INTERCUT
*ASC_SOP (0.9405 0.9439 0.9424)(-0.0500 -0.0276 -0.0144)(0.8888 0.9138 0.9896)
*ASC_SAT 0.9640
000002 B_0031C009_241010_132827_h1C2T V C 13:26:36:14 13:26:38:03 02:13:10:00 02:13:11:13
*VFX 117_GUD_0030 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117C-2-B
*LOC: 02:13:10:04 White VFX 117_GUD_0030 add Norway landscape
*ASC_SOP (0.9282 0.9312 0.9296)(-0.0362 -0.0138 -0.0006)(0.8888 0.9138 0.9896)
*ASC_SAT 0.9640
000003 A_0038C002_241010_114016_h1EHP V C 12:41:35:05 12:41:41:11 02:13:12:13 02:13:18:19
*VFX 117_GUD_0060 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117A-2-A
*LOC: 02:13:17:01 White VFX 117_GUD_0060 add Norway landscape
*ASC_SOP (0.8315 0.8307 0.8290)(0.0301 0.0416 0.0435)(0.9351 0.9413 0.9816)
*ASC_SAT 0.9640
000004 A_0038C016_241010_130056_h1EHP V C 14:02:14:03 14:02:15:03 02:13:18:19 02:13:19:19
*VFX 117_GUD_0070 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117F-2
*LOC: 02:13:19:02 White VFX 117_GUD_0070 add Norway landscape
*ASC_SOP (0.9129 0.9045 0.8950)(0.0197 0.0394 0.0495)(0.9274 0.9472 0.9931)
*ASC_SAT 0.9640
000005 A_0038C006_241010_120437_h1EHP V C 13:06:10:07 13:06:11:08 02:13:19:19 02:13:20:20
*VFX 117_GUD_0080 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117B-4-A
*LOC: 02:13:20:07 White VFX 117_GUD_0080 add Norway landscape
*ASC_SOP (0.8650 0.8604 0.8535)(0.0160 0.0319 0.0397)(0.9235 0.9485 0.9937)
*ASC_SAT 0.9640
000006 B_0031C005_241010_130536_h1C2T V C 13:03:45:20 13:03:47:14 02:13:20:20 02:13:22:14
*VFX 117_GUD_0090 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117B-3-B
*LOC: 02:13:21:07 White VFX 117_GUD_0090 add Norway landscape
*ASC_SOP (0.7644 0.7755 0.7640)(0.0194 0.0392 0.0557)(0.8384 0.8687 0.9321)
*ASC_SAT 0.9640
000007 A_0038C016_241010_130056_h1EHP V C 14:02:21:15 14:02:24:12 02:13:22:14 02:13:25:11
*VFX 117_GUD_0100 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117F-2
*LOC: 02:13:24:07 White VFX 117_GUD_0100 add Norway landscape
*ASC_SOP (0.9129 0.9045 0.8950)(0.0197 0.0394 0.0495)(0.9274 0.9472 0.9931)
*ASC_SAT 0.9640
000008 A_0038C006_241010_120437_h1EHP V C 13:06:16:18 13:06:23:09 02:13:25:11 02:13:32:02
*VFX 117_GUD_0110 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117B-4-A
*LOC: 02:13:28:06 White VFX 117_GUD_0110 add Norway landscape
*ASC_SOP (0.8650 0.8604 0.8535)(0.0160 0.0319 0.0397)(0.9235 0.9485 0.9937)
*ASC_SAT 0.9640
000009 A_0038C011_241010_123111_h1EHP V C 13:32:49:04 13:32:51:01 02:13:32:02 02:13:33:23
*FROM CLIP NAME: 117C-4
*ASC_SOP (0.8656 0.8610 0.8542)(0.0153 0.0312 0.0389)(0.9318 0.9425 0.9884)
*ASC_SAT 0.9640
000010 A_0038C006_241010_120437_h1EHP V C 13:06:25:06 13:06:27:14 02:13:33:23 02:13:36:07
*VFX 117_GUD_0120 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117B-4-A
*LOC: 02:13:35:01 White VFX 117_GUD_0120 add Norway landscape
*ASC_SOP (0.8650 0.8604 0.8535)(0.0160 0.0319 0.0397)(0.9235 0.9485 0.9937)
*ASC_SAT 0.9640
000011 A_0038C011_241010_123111_h1EHP V C 13:32:53:16 13:32:54:23 02:13:36:07 02:13:37:14
*FROM CLIP NAME: 117C-4
*ASC_SOP (0.8656 0.8610 0.8542)(0.0153 0.0312 0.0389)(0.9318 0.9425 0.9884)
*ASC_SAT 0.9640
000012 A_0038C006_241010_120437_h1EHP V C 13:06:30:14 13:06:34:01 02:13:37:14 02:13:41:01
*VFX 117_GUD_0130 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117B-4-A
*LOC: 02:13:39:05 White VFX 117_GUD_0130 add Norway landscape
*ASC_SOP (0.8650 0.8604 0.8535)(0.0160 0.0319 0.0397)(0.9235 0.9485 0.9937)
*ASC_SAT 0.9640
000013 A_0038C012_241010_124231_h1EHP V C 13:44:10:05 13:44:12:06 02:13:41:01 02:13:43:02
*VFX 117_GUD_0140 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117D-1
*LOC: 02:13:42:03 White VFX 117_GUD_0140 add Norway landscape
*ASC_SOP (0.8791 0.8754 0.8660)(-0.0000 0.0150 0.0256)(0.9310 0.9432 0.9893)
*ASC_SAT 0.9640
000014 A_0038C014_241010_125128_h1EHP V C 13:52:56:16 13:53:01:13 02:13:43:02 02:13:47:23
*VFX 117_GUD_0150 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 117E-2 PU
*LOC: 02:13:45:10 White VFX 117_GUD_0150 add Norway landscape
*ASC_SOP (0.9320 0.9203 0.9093)(-0.0262 -0.0022 0.0097)(0.9196 0.9569 1.0007)
*ASC_SAT 0.9640
000015 D006C0011_241001_Q3J911 V C 14:26:00:22 14:26:03:23 02:13:47:23 02:13:51:00
*FROM CLIP NAME: 121B-4*
*LOC: 02:13:47:23 Green Sc 121
*ASC_SOP (0.9688 0.9286 0.9044)(-0.0050 0.0335 0.0584)(1.0019 1.0499 1.0817)
*ASC_SAT 0.8500
000016 D006C0013_241001_Q3J911 V C 14:38:25:03 14:38:27:00 02:13:51:00 02:13:52:21
*FROM CLIP NAME: 121C-2
*ASC_SOP (0.9700 0.9296 0.9054)(-0.0053 0.0332 0.0581)(0.9871 1.0506 1.0894)
*ASC_SAT 0.8500
000017 D006C0011_241001_Q3J911 V C 14:26:10:22 14:26:13:15 02:13:52:21 02:13:55:14
*FROM CLIP NAME: 121B-4*
*ASC_SOP (0.9688 0.9286 0.9044)(-0.0050 0.0335 0.0584)(1.0019 1.0499 1.0817)
*ASC_SAT 0.8500
000018 A_0038C027_241010_145134_h1EHP V C 15:54:02:03 15:54:13:04 02:13:55:14 02:14:06:15
*VFX 117_GUD_0160 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 120C-3
*LOC: 02:13:55:14 Green Sc 120 INTERCUT
*LOC: 02:13:56:13 White VFX 117_GUD_0160 add Norway landscape
*ASC_SOP (0.9812 0.9783 1.0016)(-0.0737 -0.0335 -0.0141)(0.8337 0.8918 1.0014)
*ASC_SAT 0.9640
000019 A_0044C012_241015_112339_h1EHP V C 12:24:45:15 12:24:55:14 02:14:07:15 02:14:17:14
*VFX 124_SYF_0010 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124-3-A
*LOC: 02:14:07:15 Green Sc 124
*LOC: 02:14:12:04 White VFX 124_SYF_0010 add Norway landscape
*ASC_SOP (0.8971 0.8843 0.8818)(0.0070 0.0211 0.0238)(0.9792 0.9850 1.0157)
*ASC_SAT 1.0000
000020 A_0044C020_241015_115202_h1EHP V C 12:53:11:04 12:53:12:19 02:14:17:14 02:14:19:05
*FROM CLIP NAME: 124B-5
*ASC_SOP (0.9201 0.9118 0.9150)(-0.0137 0.0007 0.0036)(0.9530 0.9664 1.0033)
*ASC_SAT 1.0000
000021 A_0044C013_241015_113513_h1EHP V C 12:36:33:02 12:36:36:13 02:14:19:05 02:14:22:16
*VFX 124_SYF_0020 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124A-1-A
*LOC: 02:14:20:23 White VFX 124_SYF_0020 add Norway landscape
*ASC_SOP (0.8897 0.8752 0.8668)(0.0033 0.0195 0.0290)(0.9671 0.9811 1.0153)
*ASC_SAT 1.0000
000022 A_0044C020_241015_115202_h1EHP V C 12:53:18:02 12:53:20:19 02:14:22:16 02:14:25:09
*FROM CLIP NAME: 124B-5
*ASC_SOP (0.9201 0.9118 0.9150)(-0.0137 0.0007 0.0036)(0.9530 0.9664 1.0033)
*ASC_SAT 1.0000
000023 A_0044C013_241015_113513_h1EHP V C 12:36:36:22 12:36:38:07 02:14:25:09 02:14:26:18
*VFX 124_SYF_0025 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124A-1-A
*LOC: 02:14:25:23 White VFX 124_SYF_0025 add Norway landscape
*ASC_SOP (0.8897 0.8752 0.8668)(0.0033 0.0195 0.0290)(0.9671 0.9811 1.0153)
*ASC_SAT 1.0000
000024 A_0044C012_241015_112339_h1EHP V C 12:25:01:21 12:25:03:09 02:14:26:18 02:14:28:06
*VFX 124_SYF_0030 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124-3-A
*LOC: 02:14:27:09 White VFX 124_SYF_0030 add Norway landscape
*ASC_SOP (0.8971 0.8843 0.8818)(0.0070 0.0211 0.0238)(0.9792 0.9850 1.0157)
*ASC_SAT 1.0000
000025 A_0044C013_241015_113513_h1EHP V C 12:36:39:11 12:36:41:13 02:14:28:06 02:14:30:08
*VFX 124_SYF_0040 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124A-1-A
*LOC: 02:14:28:22 White VFX 124_SYF_0040 add Norway landscape
*ASC_SOP (0.8897 0.8752 0.8668)(0.0033 0.0195 0.0290)(0.9671 0.9811 1.0153)
*ASC_SAT 1.0000
000026 A_0044C020_241015_115202_h1EHP V C 12:53:23:11 12:53:25:13 02:14:30:08 02:14:32:10
*VFX 124_SYF_0043 ADD NORWAY BG
*FROM CLIP NAME: 124B-5
*LOC: 02:14:31:13 White VFX 124_SYF_0043 add Norway bg
*ASC_SOP (0.9201 0.9118 0.9150)(-0.0137 0.0007 0.0036)(0.9530 0.9664 1.0033)
*ASC_SAT 1.0000
000027 A_0044C013_241015_113513_h1EHP V C 12:36:41:18 12:36:43:05 02:14:32:10 02:14:33:21
*VFX 124_SYF_0048 ADD NORWAY BG
*FROM CLIP NAME: 124A-1-A
*LOC: 02:14:33:11 White VFX 124_SYF_0048 add Norway bg
*ASC_SOP (0.8897 0.8752 0.8668)(0.0033 0.0195 0.0290)(0.9671 0.9811 1.0153)
*ASC_SAT 1.0000
000028 A_0044C019_241015_115048_h1EHP V C 12:52:16:18 12:52:18:13 02:14:33:21 02:14:35:16
*VFX 124_SYF_0050 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124B-4
*LOC: 02:14:34:16 White VFX 124_SYF_0050 add Norway landscape
*ASC_SOP (0.9162 0.9080 0.9112)(-0.0095 0.0049 0.0078)(0.9530 0.9664 1.0033)
*ASC_SAT 1.0000
000029 A_0044C014_241015_113829_h1EHP V C 12:39:54:17 12:39:57:03 02:14:35:16 02:14:38:02
*VFX 124_SYF_0060 ADD NORWAY BG
*FROM CLIP NAME: 124A-2-A
*LOC: 02:14:36:16 White VFX 124_SYF_0060 Add Norway bg
*ASC_SOP (0.8898 0.8754 0.8669)(0.0032 0.0194 0.0288)(0.9671 0.9811 1.0153)
*ASC_SAT 1.0000
000030 B_0037C009_241015_122652_h1C2T V C 12:25:10:01 12:25:11:05 02:14:38:02 02:14:39:06
*VFX 124_SYF_0073 ADD NORWAY BG
*FROM CLIP NAME: 124-3-B
*LOC: 02:14:38:02 Green Sc 125
*LOC: 02:14:38:22 White VFX 124_SYF_0073 Add Norway bg
*ASC_SOP (0.9201 0.9118 0.9150)(-0.0137 0.0007 0.0036)(0.9530 0.9664 1.0033)
*ASC_SAT 1.0000
000031 A_0045C002_241015_131216_h1EHP V C 14:13:16:23 14:13:18:23 02:14:39:06 02:14:41:06
*VFX 124_SYF_0078 ADD NORWAY BG
*FROM CLIP NAME: 124E-2
*LOC: 02:14:40:01 White VFX 124_SYF_0078 Add Norway bg
*ASC_SOP (0.9201 0.9118 0.9150)(-0.0137 0.0007 0.0036)(0.9530 0.9664 1.0033)
*ASC_SAT 1.0000
000032 B_0037C009_241015_122652_h1C2T V C 12:25:11:13 12:25:12:06 02:14:41:06 02:14:41:23
*VFX 124_SYF_0080 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124-3-B
*LOC: 02:14:41:09 White VFX 124_SYF_0080 add Norway landscape
*ASC_SOP (0.9201 0.9118 0.9150)(-0.0137 0.0007 0.0036)(0.9530 0.9664 1.0033)
*ASC_SAT 1.0000
000033 D007C0005_241008_Q3J912 V C 13:19:00:22 13:19:02:15 02:14:41:23 02:14:43:16
*VFX 124_SYF_0100 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124C-4
*LOC: 02:14:42:08 White VFX 124_SYF_0100 add Norway landscape
*ASC_SOP (0.8789 0.8685 0.8723)(0.0094 0.0316 0.0328)(0.9300 0.9406 1.0012)
*ASC_SAT 1.0000
000034 A_0045C002_241015_131216_h1EHP V C 14:13:20:03 14:13:20:16 02:14:43:16 02:14:44:05
*VFX 124_SYF_0090 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124E-2
*LOC: 02:14:43:21 White VFX 124_SYF_0090 add Norway landscape
*ASC_SOP (0.9201 0.9118 0.9150)(-0.0137 0.0007 0.0036)(0.9530 0.9664 1.0033)
*ASC_SAT 1.0000
000035 D007C0007_241008_Q3J912 V C 13:26:59:07 13:27:00:18 02:14:44:05 02:14:45:16
*VFX 124_SYF_0110 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124D-2
*LOC: 02:14:44:19 White VFX 124_SYF_0110 add Norway landscape
*ASC_SOP (0.9058 0.8914 0.8960)(-0.0006 0.0207 0.0219)(0.9087 0.9191 0.9783)
*ASC_SAT 1.0000
000036 D007C0005_241008_Q3J912 V C 13:19:04:09 13:19:07:01 02:14:45:16 02:14:48:08
*VFX 124_SYF_0120 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124C-4
*LOC: 02:14:47:22 White VFX 124_SYF_0120 add Norway landscape
*ASC_SOP (0.8789 0.8685 0.8723)(0.0094 0.0316 0.0328)(0.9300 0.9406 1.0012)
*ASC_SAT 1.0000
000037 D007C0007_241008_Q3J912 V C 13:27:03:08 13:27:04:10 02:14:48:08 02:14:49:10
*VFX 124_SYF_0130 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124D-2
*LOC: 02:14:48:19 White VFX 124_SYF_0130 add Norway landscape
*ASC_SOP (0.9058 0.8914 0.8960)(-0.0006 0.0207 0.0219)(0.9087 0.9191 0.9783)
*ASC_SAT 1.0000
000038 D007C0003_241008_Q3J912 V C 13:15:29:20 13:15:30:23 02:14:49:10 02:14:50:13
*VFX 124_SYF_0140 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 124C-2
*LOC: 02:14:49:15 White VFX 124_SYF_0140 add Norway landscape
*ASC_SOP (0.8711 0.8610 0.8647)(0.0125 0.0345 0.0357)(0.9300 0.9406 1.0012)
*ASC_SAT 1.0000
000039 A_0045C009_241015_135209_h1EHP V C 14:53:13:14 14:53:15:09 02:14:50:13 02:14:52:08
*VFX 124_SYF_0150 ADD NORWAY LANDSCAPE
*FROM CLIP NAME: 125A-3*
*LOC: 02:14:50:13 Green Sc 126
*LOC: 02:14:50:14 White VFX 124_SYF_0150 add Norway landscape
*ASC_SOP (0.9541 0.9282 0.9124)(-0.0197 0.0032 0.0111)(0.9317 0.9350 0.9640)
*ASC_SAT 1.0000

View File

@@ -0,0 +1,17 @@
TITLE: cdl_example02
FCM: NON-DROP FRAME
000001 A205C016_220204_R24B V C 22:47:18:20 22:47:21:16 03:09:09:03 03:09:11:23
*FROM CLIP NAME: 49D-3
*ASC_SOP (0.98875 0.9878 0.98659)(-0.0008 0.00263 -0.00269)(0.9769 0.9767 0.97709)
*ASC_SAT 1.0
*SOURCE FILE: A205C016_220204_R24B
000002 A238C007_220221_R24B V C 11:53:01:13 11:53:05:20 03:09:55:18 03:10:00:01
*FROM CLIP NAME: 52B-7
*ASC_SOP (1.05572 1.06914 1.05607)(-0.03004 -0.03044 -0.03044)(1.02112 1.01956 1.01707)
*ASC_SAT 1.0
*SOURCE FILE: A238C007_220221_R24B
000004 A239C004_220221_R24B V C 15:19:53:22 15:19:55:02 03:10:00:01 03:10:01:05
*FROM CLIP NAME: 52G-4*
*ASC_SOP (1.00515 0.99542 0.9934)(-0.02412 -0.01467 -0.01351)(0.97348 0.97074 0.96887)
*ASC_SAT 1.0
*SOURCE FILE: A239C004_220221_R24B

View File

@@ -0,0 +1,7 @@
TITLE: cdl_frmc_example01
FCM: NON-DROP FRAME
000001 C004C008_240813ZW V C 14:41:59:15 14:42:03:21 01:00:00:00 01:00:04:06
*FROM CLIP NAME: QTLF_101_010_113_BG_01_V001
*FRMC START: 1001 FRMC END: 1102 FRMC DURATION:102
*ASC_SOP (1.04751 1.0378 1.02485)(-0.00909 0.00062 0.01468)(1.0 1.0 1.0)
*ASC_SAT 1.0

View File

@@ -0,0 +1,123 @@
TITLE: cdl_frmc_example02
FCM: NON-DROP FRAME
000001 A_0095C008_250116_182427_h1DQ9 V C 17:31:54:08 17:31:58:00 01:00:00:00 01:00:03:16
*FROM CLIP NAME: 000_trl_0010_bg01_v01
*FRMC start: 1001 FRMC end: 1088 FRMC duration:88
*ASC_SOP (1.0035 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000002 V204_01051811_C010 V C 13:07:43:14 13:07:49:06 01:00:03:16 01:00:09:08
*FROM CLIP NAME: 000_trl_0010_el01_v01
*FRMC start: 1001 FRMC end: 1136 FRMC duration:136
*ASC_SOP (1.0 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000003 A_0092C017_250115_182121_h1DQ9 V C 17:27:07:01 17:27:12:09 01:00:09:08 01:00:14:16
*FROM CLIP NAME: 027_mtr_1770_bg01_v01
*FRMC start: 1001 FRMC end: 1128 FRMC duration:128
*ASC_SOP (1.0 1.0 1.0)(0.0 -0.004 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000004 A_0092C018_250115_182308_h1DQ9 V C 17:28:39:03 17:28:51:00 01:00:14:16 01:00:26:13
*FROM CLIP NAME: 027_mtr_1770_rf01_v01
*FRMC start: 1001 FRMC end: 1285 FRMC duration:285
*ASC_SOP (1.0 1.0 1.0)(0.0 -0.004 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000005 C_0122C010_250219_123205_h1E5N V C 11:34:58:01 11:35:01:07 01:00:26:13 01:00:29:19
*FROM CLIP NAME: 084_ebk_2120_bg01_v01
*FRMC start: 1001 FRMC end: 1078 FRMC duration:78
*ASC_SOP (1.0 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000006 C_0122C011_250219_123354_h1E5N V C 11:36:19:22 11:36:40:04 01:00:29:19 01:00:50:01
*FROM CLIP NAME: 084_ebk_2120_rf01_v01
*FRMC start: 1001 FRMC end: 1486 FRMC duration:486
*ASC_SOP (1.0 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000007 B_0124C002_250219_045355_h1CUT V C 12:00:32:21 12:00:36:01 01:00:50:01 01:00:53:05
*FROM CLIP NAME: 084_ebk_2140_bg01_v01
*FRMC start: 1001 FRMC end: 1076 FRMC duration:76
*ASC_SOP (1.0 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000008 C_0126C013_250221_121324_h1E5N V C 11:16:03:18 11:16:09:11 01:00:53:05 01:00:58:22
*FROM CLIP NAME: 084_ebk_2280_bg01_v01
*FRMC start: 1001 FRMC end: 1137 FRMC duration:137
*ASC_SOP (1.0 1.0 0.999)(-0.0078 -0.0078 -0.0098)(1.0 1.0 1.0)
*ASC_SAT 1.0
000009 D_0079C004_250223_110040_h1DZT V C 11:04:05:08 11:04:08:08 01:00:58:22 01:01:01:22
*FROM CLIP NAME: 086_ebk_4160_bg01_v01
*FRMC start: 1001 FRMC end: 1072 FRMC duration:72
*ASC_SOP (1.0031 1.003 1.0025)(-0.0116 -0.0191 -0.0266)(1.0 1.0 1.0)
*ASC_SAT 1.0
000010 D_0079C005_250223_110303_h1DZT V C 11:06:01:07 11:06:14:12 01:01:01:22 01:01:15:03
*FROM CLIP NAME: 086_ebk_4160_rf01_v01
*FRMC start: 1001 FRMC end: 1317 FRMC duration:317
*ASC_SOP (1.0 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000011 W_0005C003_250223_105431_h1EPT V C 09:59:09:05 09:59:11:19 01:01:15:03 01:01:17:17
*FROM CLIP NAME: 086_ebk_4180_bg01_v01
*FRMC start: 1001 FRMC end: 1062 FRMC duration:62
*ASC_SOP (1.0198 1.0198 1.0193)(-0.0019 -0.0082 -0.0174)(1.0 1.0 1.0)
*ASC_SAT 1.0
000012 W_0005C004_250223_110023_h1EPT V C 10:03:33:16 10:03:41:05 01:01:17:17 01:01:25:06
*FROM CLIP NAME: 086_ebk_4180_rf01_v01
*FRMC start: 1001 FRMC end: 1181 FRMC duration:181
*ASC_SOP (1.0 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000013 D_0079C001_250223_095442_h1DZT V C 09:59:09:13 09:59:12:23 01:01:25:06 01:01:28:16
*FROM CLIP NAME: 086_ebk_4200_bg01_v01
*FRMC start: 1001 FRMC end: 1082 FRMC duration:82
*ASC_SOP (1.0031 1.003 1.0025)(0.0067 -0.0008 -0.0083)(1.0 1.0 1.0)
*ASC_SAT 1.0
000014 D_0079C002_250223_100041_h1DZT V C 10:03:42:19 10:03:51:04 01:01:28:16 01:01:37:01
*FROM CLIP NAME: 086_ebk_4200_rf01_v01
*FRMC start: 1001 FRMC end: 1201 FRMC duration:201
*ASC_SOP (1.0 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000015 A_0095C006_250116_181426_h1DQ9 V C 17:20:11:09 17:21:44:21 01:01:37:01 01:03:10:13
*FROM CLIP NAME: 088_mcm_0200_bg01_v01
*FRMC start: 1001 FRMC end: 3244 FRMC duration:2244
*ASC_SOP (1.0035 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000016 V204_01051756_C008 V C 12:54:36:09 12:54:43:14 01:03:10:13 01:03:17:18
*FROM CLIP NAME: 088_mcm_0200_el02_v01
*FRMC start: 1829 FRMC end: 2001 FRMC duration:173
*ASC_SOP (1.0 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000017 V204_01051808_C009 V C 13:03:57:17 13:04:08:00 01:03:17:18 01:03:28:01
*FROM CLIP NAME: 088_mcm_0200_el03_v01
*FRMC start: 2592 FRMC end: 2838 FRMC duration:247
*ASC_SOP (1.0 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000018 V204_01051811_C010 V C 13:07:57:07 13:08:04:12 01:03:28:01 01:03:35:06
*FROM CLIP NAME: 088_mcm_0200_el04_v01
*FRMC start: 3072 FRMC end: 3244 FRMC duration:173
*ASC_SOP (1.0 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000019 A_0095C012_250116_184034_h1DQ9 V C 17:46:03:10 17:46:11:09 01:03:35:06 01:03:43:05
*FROM CLIP NAME: 088_mcm_0200_rf01_v01
*FRMC start: 1001 FRMC end: 1191 FRMC duration:191
*ASC_SOP (1.0035 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000020 A_0095C013_250116_184129_h1DQ9 V C 17:47:00:10 17:47:21:07 01:03:43:05 01:04:04:02
*FROM CLIP NAME: 088_mcm_0200_rf02_v01
*FRMC start: 1001 FRMC end: 1501 FRMC duration:501
*ASC_SOP (1.0035 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000021 A_0095C006_250116_181426_h1DQ9 V C 17:21:43:22 17:21:58:07 01:04:04:02 01:04:18:11
*FROM CLIP NAME: 088_mcm_0240_bg01_V01
*FRMC start: 1001 FRMC end: 1345 FRMC duration:345
*ASC_SOP (1.0035 1.0 1.0)(0.0 0.0 0.0)(1.0 1.0 1.0)
*ASC_SAT 1.0
000022 A_0002C019_241030_174120_h1DQ9 V C 16:43:24:02 16:43:27:09 01:04:18:11 01:04:21:18
*FROM CLIP NAME: fv025_prw_0160_bg01_v01
*FRMC start: 1001 FRMC end: 1079 FRMC duration:79
000023 018_hdh_0040_lineup_v0003 V C 00:00:42:09 00:00:48:16 01:04:21:18 01:04:28:01
*FROM CLIP NAME: fv025_prw_0160_tv01_v01
*FRMC start: 1017 FRMC end: 1167 FRMC duration:151
000024 Z_0003C015_241120_031008_h1CIM V C 14:09:11:18 14:09:14:12 01:04:28:01 01:04:30:19
*FROM CLIP NAME: fv075_lgk_0500_bg01_v01
*FRMC start: 1001 FRMC end: 1066 FRMC duration:66
*ASC_SOP (1.005 0.9993 0.9917)(-0.005 0.0007 0.0083)(1.0 1.0 1.0)
*ASC_SAT 1.0
000025 Z_0003C011_241120_022000_h1CIM V C 13:19:03:19 13:19:16:04 01:04:30:19 01:04:43:04
*FROM CLIP NAME: fv075_lgk_0500_rf01_v01
*FRMC start: 1001 FRMC end: 1297 FRMC duration:297
*ASC_SOP (1.005 0.9993 0.9917)(-0.005 0.0007 0.0083)(1.0 1.0 1.0)
*ASC_SAT 1.0

View File

@@ -2,112 +2,177 @@ 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"
]
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 ]
counts = [287, 466, 250, 376, 120, 3, 466]
for fn, count in zip(type(self).files, counts):
with open("tests/edls/" + fn ,'r') as f:
with open("tests/edls/" + fn, 'r') as f:
edl = pycmx.parse_cmx3600(f)
actual = len( list( edl.events ))
self.assertTrue( actual == count ,
"expected %i in file %s but found %i" % (count, fn, actual))
actual = len(list(edl.events))
self.assertTrue(actual == count,
"expected %i in file %s but found %i"
% (count, fn, actual))
def test_list_sanity(self):
for fn in type(self).files:
with open("tests/edls/" + fn ,'r') as f:
with open("tests/edls/" + fn, 'r') as f:
edl = pycmx.parse_cmx3600(f)
self.assertTrue( type(edl.title) is str )
self.assertTrue( len(edl.title) > 0 )
self.assertTrue(type(edl.title) is str)
self.assertTrue(len(edl.title) > 0)
def test_event_sanity(self):
for fn in type(self).files:
path = "tests/edls/" + fn
with open(path ,'r') as f:
with open(path, '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 )
self.assertTrue(len(event.edits) > 0,
f"Failed for {path}")
self.assertEqual(event.number, index + 1,
f"Failed for {path}")
def test_events(self):
with open("tests/edls/TEST.edl",'r') as f:
with open("tests/edls/TEST.edl", 'r') as f:
edl = pycmx.parse_cmx3600(f)
events = list( edl.events )
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)
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:
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) )
self.assertTrue( events[2].edits[0].channels.audio)
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))
self.assertTrue(events[2].edits[0].channels.audio)
def test_multi_edit_events(self):
with open("tests/edls/TEST.edl",'r') as f:
with open("tests/edls/TEST.edl", 'r') as f:
edl = pycmx.parse_cmx3600(f)
events = list( edl.events )
events = list(edl.events)
self.assertEqual( events[42].number , 43)
self.assertEqual( len(events[42].edits), 2)
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[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)
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)
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:
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" )
self.assertEqual(
events[4].edits[1].transition.name, "CROSS DISSOLVE")
def test_adobe_wide(self):
with open("tests/edls/adobe_dai109_test.txt", 'r',
encoding='ISO-8859-1') as f:
edl = pycmx.parse_cmx3600(f)
events = list(edl.events)
# add test for edit_list.channels
self.assertEqual(len(events), 2839)
def test_issue14(self):
with open("tests/edls/ISSUE_14_conform_edl_issue_03.edl", "r") as f:
edl = pycmx.parse_cmx3600(f)
for event in edl.events:
if event.number == 42:
self.assertEqual(len(event.edits), 1)
self.assertEqual(event.edits[0].source,
"M018C0005_240925_1F4L13")
self.assertEqual(event.edits[0].transition.kind,
pycmx.Transition.Cut)
self.assertEqual(event.edits[0].source_in,
"18:44:20:12")
def test_cdl(self):
with open("tests/edls/cdl_example01.edl", "r") as f:
edl = pycmx.parse_cmx3600(f)
for event in edl.events:
if event.number == 1:
sop = event.edits[0].asc_sop_statement
self.assertIsNotNone(sop)
assert sop
self.assertEqual(sop.slope_r, "0.9405")
self.assertEqual(sop.offset_g, "-0.0276")
sat = event.edits[0].asc_sat_statement
self.assertIsNotNone(sat)
assert sat
self.assertEqual(sat.value, '0.9640')
def test_frmc(self):
with open("tests/edls/cdl_frmc_example01.edl", "r") as f:
edl = pycmx.parse_cmx3600(f)
for event in edl.events:
if event.number == 1:
frmc = event.edits[0].frmc_statement
self.assertIsNotNone(frmc)
assert frmc
self.assertEqual(frmc.start, "1001")
self.assertEqual(frmc.end, "1102")
self.assertEqual(frmc.duration, "102")
with open("tests/edls/cdl_frmc_example02.edl", "r") as f:
edl = pycmx.parse_cmx3600(f)
for event in edl.events:
if event.number == 6:
frmc = event.edits[0].frmc_statement
self.assertIsNotNone(frmc)
assert frmc
self.assertEqual(frmc.start, "1001")
self.assertEqual(frmc.end, "1486")
self.assertEqual(frmc.duration, "486")