mirror of
https://github.com/iluvcapra/pycmx.git
synced 2025-12-31 17:00:53 +00:00
Compare commits
366 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b0ccd4ef7 | |||
| 498d5c8fea | |||
| af5d937aeb | |||
| 637f4ab9a4 | |||
| 4cd635ff17 | |||
| 26e1e38320 | |||
| 0e97742336 | |||
| 67ea12042f | |||
| 6b910a0920 | |||
| a8f35a9ffc | |||
|
|
242f2e08d5 | ||
| 36be259177 | |||
| 4b73dc7730 | |||
| 3cf31fa462 | |||
| 33bd5a0001 | |||
| ebdc73198c | |||
| 610d406e97 | |||
| 28307608fc | |||
| 9ab3804c89 | |||
| cf1b3fb42c | |||
| 6041d4158e | |||
| 886561f2c5 | |||
| 5e4bce7be8 | |||
| 7a2917c357 | |||
| 0dd5ab33a5 | |||
| 4471953696 | |||
| 45228dcdf4 | |||
| 09ccdb8c8d | |||
| 43dd48dea7 | |||
| abfdfaffd8 | |||
| 48f41ef4dc | |||
| 139259777e | |||
| f4f0ba9d74 | |||
| a0c2bc77bf | |||
| 7d6a6e9a33 | |||
| 73d860aa49 | |||
|
|
51e1946c5d | ||
|
|
16a61bfcb0 | ||
|
|
5a093e10d2 | ||
| 58f28e3e2e | |||
| 5dc6da8f99 | |||
| c9987dac91 | |||
| 1d6c99aba3 | |||
| 717b045967 | |||
| f0a445b2b2 | |||
| f968b777a8 | |||
| 89fdefa565 | |||
|
|
ead9f8aa13 | ||
| 3248601445 | |||
| 82daa88b8d | |||
| ed4b81adf3 | |||
| d17354682c | |||
| f56a2aef4f | |||
| 7b875900f9 | |||
| fa19b9841f | |||
| 437ebef9c6 | |||
| fe0818bfcc | |||
| 69dee73299 | |||
| 1d78f11b11 | |||
| d071d6c27e | |||
| 785b7a9a08 | |||
| 3cdae1761f | |||
| 5c02d09a7a | |||
| d0d30702ac | |||
| cc07efef24 | |||
| 155011abfa | |||
| e0d59e3ec7 | |||
| 8cdcccce45 | |||
| 656f546d12 | |||
| 14320c709c | |||
| 5ad938c54b | |||
| ec8c3e30f5 | |||
| 30bbb05c2d | |||
| dd78fcc1ac | |||
| ecb09da7ba | |||
| aed9560b1e | |||
| 27fcc2eb61 | |||
|
|
5e0b5f4708 | ||
| 87dbbb1c68 | |||
| 229f6d646b | |||
| 23499b140c | |||
| 78f5c8ea08 | |||
| 878bdcc8c8 | |||
| 8a825c8164 | |||
| 039bf8de6d | |||
| 73853d215e | |||
| 2483e7fe43 | |||
| 6c7f7d2de1 | |||
| 2ee94ca358 | |||
| f03b2e74bf | |||
|
|
52ecc02eae | ||
| b85e02585c | |||
| 7a9e9627c3 | |||
| 88f3a7a659 | |||
| 959b824dcd | |||
| 0c6a3896fb | |||
| a128b6ca7d | |||
| 79778bb847 | |||
|
|
81997c763e | ||
|
|
6da04b2c07 | ||
|
|
89b6cde808 | ||
|
|
5e4dde5aa2 | ||
|
|
b6379ec1fe | ||
|
|
04731d634f | ||
|
|
2dcfdabf01 | ||
|
|
7ed423deaf | ||
|
|
2be779fe53 | ||
|
|
2adff6dd01 | ||
|
|
7871acdfd0 | ||
|
|
8bb6dad1da | ||
|
|
f88b82e8fb | ||
|
|
a86ef7ed2e | ||
|
|
1e1331eadb | ||
|
|
7a0481dbf9 | ||
|
|
20ad04e862 | ||
|
|
9e54aa5fcf | ||
|
|
71aff9baf7 | ||
|
|
ab55cab160 | ||
|
|
a717589683 | ||
|
|
f48c164e1b | ||
|
|
be1dc99e94 | ||
|
|
fbdfcddfff | ||
|
|
156828b648 | ||
|
|
1894a143b1 | ||
|
|
c0d278e079 | ||
|
|
7d3a58bff8 | ||
|
|
f1381f5f46 | ||
|
|
dc00b52b61 | ||
|
|
571ffdefd7 | ||
|
|
a79cb02139 | ||
|
|
ce1a0d32bb | ||
|
|
daa5d58a66 | ||
|
|
38ce1445a1 | ||
|
|
552d007360 | ||
|
|
d79fdcc6a8 | ||
|
|
3187a50a6b | ||
|
|
8e8d4f5753 | ||
|
|
cc371cd486 | ||
|
|
795a666a74 | ||
|
|
cd35f4f80d | ||
|
|
6525840151 | ||
|
|
b78ae05d8c | ||
|
|
51ed92f5df | ||
|
|
a6f042c76f | ||
|
|
179808fbf2 | ||
|
|
2b38d8aaf9 | ||
|
|
e19bb3c5c5 | ||
|
|
d0e2e5bbe5 | ||
|
|
e90897e503 | ||
|
|
4daa008e91 | ||
|
|
cf1d71800e | ||
|
|
fcb25ae183 | ||
|
|
97ada84bfe | ||
|
|
d157512c32 | ||
|
|
1e2d31716e | ||
|
|
6be7c54de5 | ||
|
|
a3109cdb7a | ||
|
|
3a9d81417e | ||
|
|
9838e6b357 | ||
|
|
41fdeeaf56 | ||
|
|
b3b6e57f6c | ||
|
|
62e7f10cf4 | ||
|
|
f855d3581d | ||
|
|
0b5555d333 | ||
|
|
7123a6f1bb | ||
|
|
173493a610 | ||
|
|
d20d9d1bdd | ||
|
|
d328e12283 | ||
|
|
4d83f81fc8 | ||
|
|
69b6c7236d | ||
|
|
71ffe8cd0d | ||
|
|
3fff6c8d2a | ||
|
|
e350565430 | ||
|
|
9ec30ede02 | ||
|
|
e5130b8011 | ||
|
|
00eaccabac | ||
|
|
521d86e444 | ||
|
|
0fdba2408b | ||
|
|
199bba2466 | ||
|
|
d8f0b5694e | ||
|
|
742b0f96c3 | ||
|
|
d44948bdc1 | ||
|
|
93cff15446 | ||
|
|
fc914409ce | ||
|
|
770e7f45a4 | ||
|
|
d3cf5fa5f2 | ||
|
|
67d2bd7093 | ||
|
|
26a7eae437 | ||
|
|
407aa1c1fd | ||
|
|
13d0a80a10 | ||
|
|
dcd2a22a43 | ||
|
|
c586740269 | ||
|
|
e28dbbbe5e | ||
|
|
41df450452 | ||
|
|
c37464036d | ||
|
|
9d89834eb3 | ||
|
|
8b53d2249c | ||
|
|
0cdbc4e9be | ||
|
|
e229e807b1 | ||
|
|
a7ee1f6737 | ||
|
|
9097de8efa | ||
|
|
bc6d7f34c0 | ||
|
|
b642f859f3 | ||
|
|
29a9a5fba7 | ||
|
|
20ff7d7ee8 | ||
|
|
183f121cfc | ||
|
|
e2dffcb745 | ||
|
|
8c2ba3cc09 | ||
|
|
c7569045c1 | ||
|
|
50bcac23bb | ||
|
|
67b1631ba9 | ||
|
|
85cbafba8f | ||
|
|
595cf35e57 | ||
|
|
7fa22d4b85 | ||
|
|
42f2de54b5 | ||
|
|
f7d1432014 | ||
|
|
db4eadb73e | ||
|
|
3305bc7920 | ||
|
|
6ba77b3568 | ||
|
|
c68f8bca80 | ||
|
|
284267c9c0 | ||
|
|
bd196f2dbf | ||
|
|
b14a9a6319 | ||
|
|
0cbd01f418 | ||
|
|
50d48708e9 | ||
|
|
f67c4ac2c5 | ||
|
|
1b8a3c3288 | ||
|
|
b37b57d7c9 | ||
|
|
a9937683e5 | ||
|
|
4fae65fa8d | ||
|
|
566e6257f4 | ||
|
|
c56d2066ad | ||
|
|
8b49a788ae | ||
|
|
b31450f03d | ||
|
|
5d14c3177a | ||
|
|
08dea6031d | ||
|
|
d23fa33558 | ||
|
|
fcc4732d1a | ||
|
|
47c1ad96f0 | ||
|
|
804f649570 | ||
|
|
58483198c3 | ||
|
|
aa01e9ad2d | ||
|
|
464052f510 | ||
|
|
3ba28a61dd | ||
|
|
b80339267a | ||
|
|
27d1073f8c | ||
|
|
0840ade312 | ||
|
|
a52e4329ce | ||
|
|
47772b21d0 | ||
|
|
7b4a76448e | ||
|
|
c6c5d15e09 | ||
|
|
c439ea1fbe | ||
|
|
68f118ab6b | ||
|
|
a20491297e | ||
|
|
9d57c2d374 | ||
|
|
1882cc5308 | ||
|
|
c57fe94335 | ||
|
|
007661ef38 | ||
|
|
f34c6dd4db | ||
|
|
eb89708bab | ||
|
|
9fde608fa0 | ||
|
|
4c4ca428f2 | ||
|
|
fe4e3b9d85 | ||
|
|
119467a884 | ||
|
|
b5a3285e64 | ||
|
|
af1c532a67 | ||
|
|
23667fb782 | ||
|
|
ce31cbb879 | ||
|
|
914e8d5525 | ||
|
|
21f8880099 | ||
|
|
348962c3f7 | ||
|
|
5e902d4926 | ||
|
|
44f751fd75 | ||
|
|
16e8754a7b | ||
|
|
fbf55ec2e6 | ||
|
|
1fab2c3d71 | ||
|
|
28c2344a53 | ||
|
|
dbf495f138 | ||
|
|
2483d94d7b | ||
|
|
dbe8a16eff | ||
|
|
dbbfc27196 | ||
|
|
f0c257f15f | ||
|
|
cbec18607a | ||
|
|
acb12b7d9d | ||
|
|
618f6422cc | ||
|
|
64001e8c78 | ||
|
|
69dc7ed1ce | ||
|
|
98b4ff9106 | ||
|
|
18c6ff658a | ||
|
|
0a4309ab77 | ||
|
|
50fea58724 | ||
|
|
4ceb4be7ab | ||
|
|
fbb2b8700d | ||
|
|
3f6ea4feee | ||
|
|
fcd84b1edf | ||
|
|
f85304d83b | ||
|
|
205c58e52c | ||
|
|
d3cdce6b99 | ||
|
|
32da584363 | ||
|
|
c74177953f | ||
|
|
5a4f57bd7e | ||
|
|
3e4c6d5955 | ||
|
|
11034dd9f1 | ||
|
|
bc02eb10fc | ||
|
|
9afe9d194d | ||
|
|
44e911d878 | ||
|
|
82814522d1 | ||
|
|
26b2f5274c | ||
|
|
30ee3e0be5 | ||
|
|
82fc5f21da | ||
|
|
d4353d1e68 | ||
|
|
15d14914ea | ||
|
|
f44d5c470c | ||
|
|
ca873af772 | ||
|
|
ab40ba1fa0 | ||
|
|
782b9f7425 | ||
|
|
483efdcc32 | ||
|
|
6867f9ac4a | ||
|
|
24272569e3 | ||
|
|
16afb8fc64 | ||
|
|
d1e3eb85d3 | ||
|
|
8d3bef2c09 | ||
|
|
e0b7025fff | ||
|
|
fbe9e9eeb9 | ||
|
|
168fd16473 | ||
|
|
e4b6036ab7 | ||
|
|
ce3d8088a1 | ||
|
|
9f41758b37 | ||
|
|
07407baf96 | ||
|
|
aa309a4458 | ||
|
|
2b8dd4c1c9 | ||
|
|
387158b07c | ||
|
|
741c9d95e8 | ||
|
|
53764900ba | ||
|
|
66791081be | ||
|
|
5e49c19ac2 | ||
|
|
4593729e3a | ||
|
|
703ba1140a | ||
|
|
0f06c4de5c | ||
|
|
920af8a86d | ||
|
|
57ea48e5e8 | ||
|
|
7e13978d9a | ||
|
|
f358704139 | ||
|
|
6201633956 | ||
|
|
2924ea548b | ||
|
|
88bf68c78e | ||
|
|
6d1ca12e42 | ||
|
|
829d98f4b4 | ||
|
|
8969e31969 | ||
|
|
484d2ae98f | ||
|
|
07652eaaa8 | ||
|
|
a9124e1f97 | ||
|
|
7e709241f8 | ||
|
|
731b8fcc00 | ||
|
|
597dceb9c5 | ||
|
|
c2c83d826a | ||
|
|
08dd1f956d | ||
|
|
989d52aaee | ||
|
|
b60610aa8b | ||
|
|
6611e38b9f | ||
|
|
abcce06865 | ||
|
|
9d586342be | ||
|
|
e5f632d8a4 | ||
|
|
30cd99431b | ||
|
|
cc76223cbc | ||
|
|
966f8c1ca4 |
5
.flake8
Normal file
5
.flake8
Normal file
@@ -0,0 +1,5 @@
|
||||
[flake8]
|
||||
per-file-ignores =
|
||||
src/pycmx/__init__.py: F401
|
||||
tests/__init__.py: F401
|
||||
|
||||
25
.github/workflows/bsky_test.yml
vendored
Normal file
25
.github/workflows/bsky_test.yml
vendored
Normal 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
|
||||
37
.github/workflows/python-package.yml
vendored
Normal file
37
.github/workflows/python-package.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
||||
|
||||
name: Lint and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -e .[dev]
|
||||
- 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
|
||||
flake8 . --count --max-line-length=79 --statistics
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest
|
||||
42
.github/workflows/pythonpublish.yml
vendored
Normal file
42
.github/workflows/pythonpublish.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: Upload Python Package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: release
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install build
|
||||
- name: Build package
|
||||
run: python -m build .
|
||||
- name: Publish to Pypi
|
||||
uses: pypa/gh-action-pypi-publish@v1.13.0
|
||||
with:
|
||||
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
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -7,3 +7,16 @@
|
||||
# Python egg metadata, regenerated from source files by setuptools.
|
||||
/*.egg-info
|
||||
/build/
|
||||
|
||||
# Vim Swapfiles
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
||||
venv/
|
||||
|
||||
.coverage
|
||||
lcov.info
|
||||
|
||||
|
||||
# venv
|
||||
venv/
|
||||
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
30
.readthedocs.yaml
Normal file
30
.readthedocs.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.13"
|
||||
jobs:
|
||||
install:
|
||||
- pip install --upgrade pip
|
||||
- pip install --group 'doc'
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
|
||||
#If using Sphinx, optionally build your docs in additional formats such as PDF
|
||||
formats:
|
||||
- pdf
|
||||
|
||||
#Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
13
CONTRIBUTING.md
Normal file
13
CONTRIBUTING.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Contributing
|
||||
|
||||
Contributions to this project are welcome!
|
||||
|
||||
The best way to contribute code to this project is to find this project on [Github][github] and submit a pull request.
|
||||
|
||||
## Call for EDLs
|
||||
|
||||
If you have EDLs you are having trouble working with becuase of unusual formatting, please send me a copy! Contact us
|
||||
through [Github].
|
||||
|
||||
|
||||
[github]: https://github.com/iluvcapra/pycmx
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018 Jamie Hardt.
|
||||
Copyright (c) 2022 Jamie Hardt.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
116
README.md
116
README.md
@@ -1,60 +1,94 @@
|
||||
[](https://pycmx.readthedocs.io/en/latest/?badge=latest)   [](https://pypi.org/project/pycmx/) 
|
||||

|
||||
[](https://github.com/iluvcapra/pycmx/actions/workflows/python-package.yml)
|
||||
|
||||
|
||||
# pycmx
|
||||
|
||||
The `pycmx` package provides a basic interface for parsing a CMX 3600 EDL and its most most common variations.
|
||||
The `pycmx` package parses a CMX 3600 EDL and its most most common variations.
|
||||
|
||||
## Features
|
||||
|
||||
* The major variations of the CMX3600, 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.
|
||||
* A more relaxed "tolerant" mode allows parsing of an EDL file where columns
|
||||
use non-standard widths.
|
||||
* 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 CDL][asc] and FRMC/VFX framecount 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
|
||||
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.
|
||||
|
||||
[asc]: https://en.wikipedia.org/wiki/ASC_CDL
|
||||
|
||||
## Usage
|
||||
|
||||
### Opening and Parsing EDL Files
|
||||
```
|
||||
import pycmx
|
||||
pycmx.parse_cmx3600("INS4_R1_010417.edl")
|
||||
print(events[5:8])
|
||||
>>> [CmxEvent(title='INS4_R1_010417', number='000006',
|
||||
clip_name='V1A-6A', source_name='A192C008_160909_R1BY',
|
||||
channels=CmxChannelMap(v=True,a1=False,a2=False,a3=False,a4=False),
|
||||
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)]
|
||||
>>> import pycmx
|
||||
>>> with open("tests/edls/TEST.edl") as f:
|
||||
... edl = pycmx.parse_cmx3600(f)
|
||||
...
|
||||
>>> edl.title
|
||||
'DC7 R1_v8.2'
|
||||
```
|
||||
|
||||
## Known Issues/Roadmap
|
||||
### Reading Events and Edits
|
||||
|
||||
To be addressed:
|
||||
* Does not decode transitions.
|
||||
* Does not decode "M2" speed changes.
|
||||
* Does not decode repair notes, audio notes or other Avid-specific notes.
|
||||
`EditList.events` is a generator...
|
||||
|
||||
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.
|
||||
* Does not parse color correction notes. For this functionality we refer you to [pycdl](https://pypi.org/project/pycdl/) or [cdl-convert](https://pypi.org/project/cdl-convert/).
|
||||
```
|
||||
>>> 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'
|
||||
```
|
||||
|
||||
## Should I Use This?
|
||||
### Acessing Transitions and Enabled Channels
|
||||
|
||||
At this time, this is (at best) alpha software and the interface will be
|
||||
changing often. It may be fun to experiment with but it is not suitable
|
||||
at this time for production code.
|
||||
|
||||
Contributions are welcome and will make this module production-ready all the
|
||||
faster! Please reach out or file a ticket!
|
||||
```
|
||||
>>> 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
|
||||
```
|
||||
|
||||
112
bin/edl2scenelist.py
Normal file
112
bin/edl2scenelist.py
Normal file
@@ -0,0 +1,112 @@
|
||||
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 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']))
|
||||
|
||||
|
||||
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()
|
||||
BIN
doc/CMX3600.pdf
BIN
doc/CMX3600.pdf
Binary file not shown.
4
docs/.buildinfo
Normal file
4
docs/.buildinfo
Normal 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
|
||||
BIN
docs/.doctrees/environment.pickle
Normal file
BIN
docs/.doctrees/environment.pickle
Normal file
Binary file not shown.
BIN
docs/.doctrees/index.doctree
Normal file
BIN
docs/.doctrees/index.doctree
Normal file
Binary file not shown.
1
docs/.gitignore
vendored
Normal file
1
docs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build/*
|
||||
19
docs/Makefile
Normal file
19
docs/Makefile
Normal 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)
|
||||
25
docs/source/classes.rst
Normal file
25
docs/source/classes.rst
Normal file
@@ -0,0 +1,25 @@
|
||||
pycmx Classes
|
||||
=============
|
||||
|
||||
.. autoclass:: pycmx.edit_list.EditList
|
||||
:members:
|
||||
|
||||
|
||||
.. autoclass:: pycmx.event.Event
|
||||
:members:
|
||||
|
||||
|
||||
.. autoclass:: pycmx.edit.Edit
|
||||
:members:
|
||||
|
||||
|
||||
.. autoclass:: pycmx.transition.Transition
|
||||
:members:
|
||||
|
||||
|
||||
.. autoclass:: pycmx.channel_map.ChannelMap
|
||||
:members:
|
||||
|
||||
.. automodule:: pycmx.cdl
|
||||
:members:
|
||||
|
||||
187
docs/source/conf.py
Normal file
187
docs/source/conf.py
Normal file
@@ -0,0 +1,187 @@
|
||||
# -*- 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 importlib
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = u'pycmx'
|
||||
copyright = u'(c) 2018-2025, Jamie Hardt'
|
||||
author = u'Jamie Hardt'
|
||||
|
||||
release = importlib.metadata.version("pycmx")
|
||||
version = release
|
||||
# The short X.Y version
|
||||
# The full version, including alpha/beta/rc tags
|
||||
|
||||
|
||||
# -- 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 = 'em'
|
||||
|
||||
# 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], "3p")
|
||||
]
|
||||
|
||||
|
||||
# -- 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
|
||||
7
docs/source/function.rst
Normal file
7
docs/source/function.rst
Normal file
@@ -0,0 +1,7 @@
|
||||
Parse Function
|
||||
==============
|
||||
|
||||
|
||||
.. autofunction:: pycmx.parse_cmx_events.parse_cmx3600
|
||||
|
||||
|
||||
87
docs/source/index.rst
Normal file
87
docs/source/index.rst
Normal file
@@ -0,0 +1,87 @@
|
||||
.. 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.
|
||||
|
||||
pycmx - A CMX EDL Parser in Python
|
||||
====================================
|
||||
|
||||
Features
|
||||
---------
|
||||
|
||||
The `pycmx` package parses a CMX 3600 EDL and its most most common variations.
|
||||
|
||||
* 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.
|
||||
* An more relaxed "tolerant" mode allows parsing of an EDL file where columns
|
||||
use non-standard widths.
|
||||
* 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 CDL`_ 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
|
||||
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.
|
||||
|
||||
.. _ASC CDL: https://en.wikipedia.org/wiki/ASC_CDL
|
||||
|
||||
Getting Started
|
||||
----------------
|
||||
|
||||
Install `pycmx` with pip, or add it with `uv` or your favorite tool.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
pip install pycmx
|
||||
|
||||
`pycmx` parses an EDL with the :func:`~pycmx.parse_cmx_events.parse_cmx3600`
|
||||
function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pycmx
|
||||
|
||||
with open("tests/edls/TEST.edl") as f:
|
||||
edl = pycmx.parse_cmx3600(f)
|
||||
|
||||
The `pycmx` parser reads each line from the input EDL and collects them into
|
||||
`~pycmx.event.Event` objects. All individual edit actions that share the same
|
||||
event number will be collected into a single Event, along with transitions and
|
||||
any remark lines, including clip names, and CDL color commands.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
for event in edl.events:
|
||||
print("- - - Event Info - - -")
|
||||
print("Event No:", event.number)
|
||||
for edit in event.edits:
|
||||
print("On Line No:", edit.line_number)
|
||||
print("Transition In:", edit.transition.kind)
|
||||
print("Source Name:", edit.source)
|
||||
print("Source In:", edit.source_in)
|
||||
print("Source Out:", edit.source_out)
|
||||
print("Rec In:", edit.record_in)
|
||||
print("Rec Out:", edit.record_out)
|
||||
print("ASC SOP:", edit.asc_sop)
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 5
|
||||
:caption: API Reference
|
||||
|
||||
function
|
||||
classes
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`search`
|
||||
@@ -1,3 +0,0 @@
|
||||
# pycmx init
|
||||
|
||||
from .parse_cmx import parse_cmx3600
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
# pycmx
|
||||
# (c) 2018 Jamie Hardt
|
||||
|
||||
from .parse_cmx_statements import parse_cmx3600_statements
|
||||
from collections import namedtuple
|
||||
|
||||
from re import compile, match
|
||||
|
||||
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:
|
||||
"""
|
||||
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 a1(self):
|
||||
return self.get_audio_channel(1)
|
||||
|
||||
@a1.setter
|
||||
def a1(self,val):
|
||||
self.set_audio_channel(1,val)
|
||||
|
||||
@property
|
||||
def a2(self):
|
||||
return self.get_audio_channel(2)
|
||||
|
||||
@a2.setter
|
||||
def a2(self,val):
|
||||
self.set_audio_channel(2,val)
|
||||
|
||||
@property
|
||||
def a3(self):
|
||||
return self.get_audio_channel(3)
|
||||
|
||||
@a3.setter
|
||||
def a3(self,val):
|
||||
self.set_audio_channel(3,val)
|
||||
|
||||
@property
|
||||
def a4(self):
|
||||
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):
|
||||
return (chan_num in self._audio_channel_set)
|
||||
|
||||
def set_audio_channel(self,chan_num,enabled):
|
||||
if enabled:
|
||||
self._audio_channel_set.add(chan_num)
|
||||
elif self.get_audio_channel(chan_num):
|
||||
self._audio_channel_set.remove(chan_num)
|
||||
|
||||
|
||||
def appendEvent(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 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
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
|
||||
# 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 sys
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
StmtTitle = namedtuple("Title",["title"])
|
||||
StmtFCM = namedtuple("FCM",["drop"])
|
||||
StmtEvent = namedtuple("Event",["event","source","channels","trans","trans_op","source_in","source_out","record_in","record_out"])
|
||||
StmtAudioExt = namedtuple("AudioExt",["audio3","audio4"])
|
||||
StmtClipName = namedtuple("ClipName",["name"])
|
||||
StmtSourceFile = namedtuple("SourceFile",["filename"])
|
||||
StmtRemark = namedtuple("Remark",["text"])
|
||||
StmtTrailer = namedtuple("Trailer",["text"])
|
||||
StmtUnrecognized = namedtuple("Unrecognized",["content"])
|
||||
|
||||
|
||||
def parse_cmx3600_statements(path):
|
||||
with open(path,'r') 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):
|
||||
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 parse_cmx3600_line(line):
|
||||
long_event_num_p = re.compile("^[0-9]{6} ")
|
||||
short_event_num_p = re.compile("^[0-9]{3} ")
|
||||
|
||||
if isinstance(line,str):
|
||||
if line.startswith("TITLE:"):
|
||||
return parse_title(line)
|
||||
elif line.startswith("FCM:"):
|
||||
return parse_fcm(line)
|
||||
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)
|
||||
else:
|
||||
return parse_long_standard_form(line, 128)
|
||||
elif short_event_num_p.match(line) != None:
|
||||
return parse_standard_form(line)
|
||||
elif line.startswith("AUD"):
|
||||
return parse_extended_audio_channels(line)
|
||||
elif line.startswith("*"):
|
||||
return parse_remark( line[1:].strip())
|
||||
elif line.startswith(">>>"):
|
||||
return parse_trailer_statement(line)
|
||||
else:
|
||||
return parse_unrecognized(line)
|
||||
|
||||
|
||||
def parse_title(line):
|
||||
title = line[6:].strip()
|
||||
return StmtTitle(title=title)
|
||||
|
||||
def parse_fcm(line):
|
||||
val = line[4:].strip()
|
||||
if val == "DROP FRAME":
|
||||
return StmtFCM(drop= True)
|
||||
else:
|
||||
return StmtFCM(drop= False)
|
||||
|
||||
def parse_long_standard_form(line,source_field_length):
|
||||
return parse_columns_for_standard_form(line, 6, source_field_length)
|
||||
|
||||
def parse_standard_form(line):
|
||||
return parse_columns_for_standard_form(line, 3, 8)
|
||||
|
||||
def parse_extended_audio_channels(line):
|
||||
content = line.strip()
|
||||
if content == "AUD 3":
|
||||
return StmtAudioExt(audio3=True, audio4=False)
|
||||
elif content == "AUD 4":
|
||||
return StmtAudioExt(audio3=False, audio4=True)
|
||||
elif content == "AUD 3 4":
|
||||
return StmtAudioExt(audio3=True, audio4=True)
|
||||
else:
|
||||
return StmtUnrecognized(content=line)
|
||||
|
||||
def parse_remark(line):
|
||||
if line.startswith("FROM CLIP NAME:"):
|
||||
return StmtClipName(name=line[15:].strip() )
|
||||
elif line.startswith("SOURCE FILE:"):
|
||||
return StmtSourceFile(filename=line[12:].strip() )
|
||||
else:
|
||||
return StmtRemark(text=line)
|
||||
|
||||
def parse_unrecognized(line):
|
||||
return StmtUnrecognized(content=line)
|
||||
|
||||
def parse_columns_for_standard_form(line, event_field_length, source_field_length):
|
||||
col_widths = edl_column_widths(event_field_length, source_field_length)
|
||||
|
||||
if sum(col_widths) > len(line):
|
||||
return StmtUnrecognized(content=line)
|
||||
|
||||
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(),
|
||||
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())
|
||||
|
||||
|
||||
def parse_trailer_statement(line):
|
||||
trimmed = line[3:].strip()
|
||||
return StmtTrailer(trimmed)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# pycmx
|
||||
# (c) 2018 Jamie Hardt
|
||||
|
||||
# Utility functions
|
||||
|
||||
def collimate(a_string, column_widths):
|
||||
'Splits a string into substrings that are column_widths length.'
|
||||
|
||||
if len(column_widths) == 0:
|
||||
return []
|
||||
|
||||
width = column_widths[0]
|
||||
element = a_string[:width]
|
||||
rest = a_string[width:]
|
||||
return [element] + collimate(rest, column_widths[1:])
|
||||
|
||||
|
||||
66
pyproject.toml
Normal file
66
pyproject.toml
Normal file
@@ -0,0 +1,66 @@
|
||||
[project]
|
||||
name = "pycmx"
|
||||
version = "1.5.0"
|
||||
description = "Python CMX 3600 Edit Decision List Parser"
|
||||
authors = [{name = "Jamie Hardt", email= "<jamiehardt@me.com>"}]
|
||||
license-files = ["LICENSE"]
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
'parser',
|
||||
'film',
|
||||
'broadcast'
|
||||
]
|
||||
requires-python = '>3.8'
|
||||
classifiers = [
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Topic :: Multimedia',
|
||||
'Topic :: Multimedia :: Video',
|
||||
'Topic :: Text Processing',
|
||||
'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.13',
|
||||
'Programming Language :: Python :: 3.14'
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
doc = [
|
||||
'sphinx >= 5.3.0',
|
||||
'sphinx_rtd_theme >= 1.1.1'
|
||||
]
|
||||
dev = [
|
||||
'flake8',
|
||||
'pytest'
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/iluvcapra/pycmx"
|
||||
Documentation = "https://pycmx.readthedocs.io/"
|
||||
Repository = "https://github.com/iluvcapra/pycmx.git"
|
||||
Tracker = "https://github.com/iluvcapra/pycmx/issues"
|
||||
|
||||
[dependency-groups]
|
||||
doc = ['sphinx', 'sphinx_rtd_theme']
|
||||
|
||||
|
||||
[tool.pyright]
|
||||
typeCheckingMode = "basic"
|
||||
|
||||
[tool.pylint]
|
||||
max-line-length = 88
|
||||
disable = [
|
||||
"C0103", # (invalid-name)
|
||||
"C0114", # (missing-module-docstring)
|
||||
"C0115", # (missing-class-docstring)
|
||||
"C0116", # (missing-function-docstring)
|
||||
"R0903", # (too-few-public-methods)
|
||||
"R0913", # (too-many-arguments)
|
||||
"W0105", # (pointless-string-statement)
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.9.18,<0.10.0"]
|
||||
build-backend = "uv_build"
|
||||
19
setup.py
19
setup.py
@@ -1,19 +0,0 @@
|
||||
from setuptools import setup
|
||||
|
||||
with open("README.md", "r") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setup(name='pycmx',
|
||||
version='0.3',
|
||||
author='Jamie Hardt',
|
||||
author_email='jamiehardt@me.com',
|
||||
description='CMX 3600 Edit Decision List Parser',
|
||||
long_description_content_type="text/markdown",
|
||||
long_description=long_description,
|
||||
url='https://github.com/iluvcapra/pycmx',
|
||||
classifiers=['Development Status :: 3 - Alpha',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Topic :: Multimedia',
|
||||
'Topic :: Multimedia :: Video',
|
||||
'Topic :: Text Processing'],
|
||||
packages=['pycmx'])
|
||||
13
src/pycmx/__init__.py
Normal file
13
src/pycmx/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
pycmx is a parser for CMX 3600-style EDLs.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from .parse_cmx_events import parse_cmx3600
|
||||
from .transition import Transition
|
||||
from .event import Event
|
||||
from .edit import Edit
|
||||
48
src/pycmx/cdl.py
Normal file
48
src/pycmx/cdl.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# pycmx
|
||||
# (c) 2025 Jamie Hardt
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Generic, NamedTuple, TypeVar
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
@dataclass
|
||||
class Rgb(Generic[T]):
|
||||
"""
|
||||
A tuple of three `T`s, where each is the respective red, green and blue
|
||||
values of interest.
|
||||
"""
|
||||
|
||||
red: T # : Red component
|
||||
green: T # : Green component
|
||||
blue: T # : Blue component
|
||||
|
||||
|
||||
@dataclass
|
||||
class AscSopComponents(Generic[T]):
|
||||
"""
|
||||
Fields in an ASC SOP (Slope-Offset-Power) color transfer function
|
||||
statement.
|
||||
|
||||
The ASC SOP is a transfer function of the form:
|
||||
|
||||
:math:`y_{color} = (ax_{color} + b)^p`
|
||||
|
||||
for each color component the source, where the `slope` is `a`, `offset`
|
||||
is `b` and `power` is `p`.
|
||||
"""
|
||||
|
||||
slope: Rgb[T] # : The linear/slope component `a`
|
||||
offset: Rgb[T] # : The constant/offset component `b`
|
||||
power: Rgb[T] # : The exponential/power component `p`
|
||||
|
||||
|
||||
class FramecountTriple(NamedTuple):
|
||||
"""
|
||||
Fields in an FRMC statement
|
||||
"""
|
||||
|
||||
start: int
|
||||
end: int
|
||||
duration: int
|
||||
113
src/pycmx/channel_map.py
Normal file
113
src/pycmx/channel_map.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# pycmx
|
||||
# (c) 2018-2025 Jamie Hardt
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
def __init__(self, v=False, audio_channels=set()):
|
||||
self._audio_channel_set = audio_channels
|
||||
self.v = v
|
||||
|
||||
@property
|
||||
def video(self) -> bool:
|
||||
'True if video is included'
|
||||
return self.v
|
||||
|
||||
@property
|
||||
def audio(self) -> bool:
|
||||
'True if an audio channel is included'
|
||||
return len(self._audio_channel_set) > 0
|
||||
|
||||
@property
|
||||
def channels(self) -> Generator[int, None, None]:
|
||||
'A generator for each audio channel'
|
||||
for c in self._audio_channel_set:
|
||||
yield c
|
||||
|
||||
@property
|
||||
def a1(self) -> bool:
|
||||
"""True if A1 is included"""
|
||||
return self.get_audio_channel(1)
|
||||
|
||||
@a1.setter
|
||||
def a1(self, val: bool):
|
||||
self.set_audio_channel(1, val)
|
||||
|
||||
@property
|
||||
def a2(self) -> bool:
|
||||
"""True if A2 is included"""
|
||||
return self.get_audio_channel(2)
|
||||
|
||||
@a2.setter
|
||||
def a2(self, val: bool):
|
||||
self.set_audio_channel(2, val)
|
||||
|
||||
@property
|
||||
def a3(self) -> bool:
|
||||
"""True if A3 is included"""
|
||||
return self.get_audio_channel(3)
|
||||
|
||||
@a3.setter
|
||||
def a3(self, val: bool):
|
||||
self.set_audio_channel(3, val)
|
||||
|
||||
@property
|
||||
def a4(self) -> bool:
|
||||
"""True if A4 is included"""
|
||||
return self.get_audio_channel(4)
|
||||
|
||||
@a4.setter
|
||||
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):
|
||||
"""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(r'^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 = audio_ext.audio3
|
||||
self.a4 = audio_ext.audio4
|
||||
|
||||
def __or__(self, other):
|
||||
"""
|
||||
the logical union of this channel map with another
|
||||
"""
|
||||
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)
|
||||
213
src/pycmx/edit.py
Normal file
213
src/pycmx/edit.py
Normal file
@@ -0,0 +1,213 @@
|
||||
# pycmx
|
||||
# (c) 2018-2025 Jamie Hardt
|
||||
|
||||
from .cdl import AscSopComponents, FramecountTriple
|
||||
from .statements import (
|
||||
StmtCdlSat,
|
||||
StmtCdlSop,
|
||||
StmtFrmc,
|
||||
StmtEvent,
|
||||
StmtAudioExt,
|
||||
StmtClipName,
|
||||
StmtSourceFile,
|
||||
StmtEffectsName,
|
||||
)
|
||||
from .transition import Transition
|
||||
from .channel_map import ChannelMap
|
||||
|
||||
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: StmtEvent,
|
||||
audio_ext_statement: Optional[StmtAudioExt],
|
||||
clip_name_statement: Optional[StmtClipName],
|
||||
source_file_statement: Optional[StmtSourceFile],
|
||||
trans_name_statement: Optional[StmtEffectsName] = None,
|
||||
asc_sop_statement: Optional[StmtCdlSop] = None,
|
||||
asc_sat_statement: Optional[StmtCdlSat] = None,
|
||||
frmc_statement: Optional[StmtFrmc] = None,
|
||||
) -> None:
|
||||
self._edit_statement: StmtEvent = edit_statement
|
||||
self._audio_ext: Optional[StmtAudioExt] = audio_ext_statement
|
||||
self._clip_name_statement: Optional[StmtClipName] = clip_name_statement
|
||||
self._source_file_statement: Optional[StmtSourceFile] = \
|
||||
source_file_statement
|
||||
self._trans_name_statement: Optional[StmtEffectsName] = \
|
||||
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:
|
||||
"""
|
||||
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) -> ChannelMap:
|
||||
"""
|
||||
Get the :obj:`ChannelMap` object associated with this Edit.
|
||||
"""
|
||||
cm = ChannelMap()
|
||||
cm._append_event(self._edit_statement.channels)
|
||||
if self._audio_ext is not None:
|
||||
cm._append_ext(self._audio_ext)
|
||||
return cm
|
||||
|
||||
@property
|
||||
def transition(self) -> Transition:
|
||||
"""
|
||||
Get the :obj:`Transition` that initiates 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) -> str:
|
||||
"""
|
||||
Get the source in timecode.
|
||||
"""
|
||||
return self._edit_statement.source_in
|
||||
|
||||
@property
|
||||
def source_out(self) -> str:
|
||||
"""
|
||||
Get the source out timecode.
|
||||
"""
|
||||
|
||||
return self._edit_statement.source_out
|
||||
|
||||
@property
|
||||
def record_in(self) -> str:
|
||||
"""
|
||||
Get the record in timecode.
|
||||
"""
|
||||
|
||||
return self._edit_statement.record_in
|
||||
|
||||
@property
|
||||
def record_out(self) -> str:
|
||||
"""
|
||||
Get the record out timecode.
|
||||
"""
|
||||
|
||||
return self._edit_statement.record_out
|
||||
|
||||
@property
|
||||
def source(self) -> str:
|
||||
"""
|
||||
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) -> bool:
|
||||
"""
|
||||
The source field for thie edit was "BL". Black video or silence should
|
||||
be used as the source for this event.
|
||||
"""
|
||||
return self.source == "BL"
|
||||
|
||||
@property
|
||||
def aux_source(self) -> bool:
|
||||
"""
|
||||
The source field for this edit was "AX". An auxiliary source is the
|
||||
source for this event.
|
||||
"""
|
||||
return self.source == "AX"
|
||||
|
||||
@property
|
||||
def source_file(self) -> Optional[str]:
|
||||
"""
|
||||
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) -> Optional[str]:
|
||||
"""
|
||||
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
|
||||
|
||||
@property
|
||||
def asc_sop(self) -> Optional[AscSopComponents[float]]:
|
||||
"""
|
||||
Get ASC CDL Slope-Offset-Power color transfer function for the edit,
|
||||
if present. The ASC SOP is a transfer function of the form:
|
||||
|
||||
:math:`y = (ax + b)^p`
|
||||
|
||||
for each color component the source, where the `slope` is `a`, `offset`
|
||||
is `b` and `power` is `p`.
|
||||
"""
|
||||
if self._asc_sop_statement is None:
|
||||
return None
|
||||
|
||||
return self._asc_sop_statement.cdl_sop
|
||||
|
||||
@property
|
||||
def asc_sop_raw(self) -> Optional[str]:
|
||||
"""
|
||||
ASC CDL Slope-Offset-Power statement raw line.
|
||||
"""
|
||||
if self._asc_sop_statement is None:
|
||||
return None
|
||||
|
||||
return self._asc_sop_statement.line
|
||||
|
||||
@property
|
||||
def asc_sat(self) -> Optional[float]:
|
||||
"""
|
||||
Get ASC CDL saturation value for clip, if present.
|
||||
"""
|
||||
if self._asc_sat_statement is None:
|
||||
return None
|
||||
|
||||
return self._asc_sat_statement.value
|
||||
|
||||
@property
|
||||
def framecounts(self) -> Optional[FramecountTriple]:
|
||||
"""
|
||||
Get frame count offset data, if it exists. If an FRMC statement exists
|
||||
in the EDL for the event it will give an integer frame count for the
|
||||
edit's source in and out times.
|
||||
"""
|
||||
if not self._frmc_statement:
|
||||
return None
|
||||
|
||||
return FramecountTriple(
|
||||
start=self._frmc_statement.start,
|
||||
end=self._frmc_statement.end,
|
||||
duration=self._frmc_statement.duration,
|
||||
)
|
||||
109
src/pycmx/edit_list.py
Normal file
109
src/pycmx/edit_list.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# pycmx
|
||||
# (c) 2018-2025 Jamie Hardt
|
||||
|
||||
from .statements import (StmtCorruptRemark, StmtTitle, StmtEvent,
|
||||
StmtUnrecognized, StmtSourceUMID)
|
||||
from .event import Event
|
||||
from .channel_map import ChannelMap
|
||||
|
||||
from typing import Any, Generator
|
||||
|
||||
|
||||
class EditList:
|
||||
"""
|
||||
Represents an entire edit decision list as returned by
|
||||
:func:`~pycmx.parse_cmx_events.parse_cmx3600()`.
|
||||
"""
|
||||
|
||||
def __init__(self, statements: list):
|
||||
self.title_statement: StmtTitle = statements[0]
|
||||
self.event_statements = statements[1:]
|
||||
|
||||
@property
|
||||
def format(self) -> str:
|
||||
"""
|
||||
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)
|
||||
|
||||
if first_event:
|
||||
if first_event.source_field_size == 8:
|
||||
return '3600'
|
||||
elif first_event.source_field_size == 32:
|
||||
return 'File32'
|
||||
elif first_event.source_field_size == 128:
|
||||
return 'File128'
|
||||
else:
|
||||
return 'unknown'
|
||||
else:
|
||||
return 'unknown'
|
||||
|
||||
@property
|
||||
def channels(self) -> ChannelMap:
|
||||
"""
|
||||
Return the union of every channel channel.
|
||||
"""
|
||||
|
||||
retval = ChannelMap()
|
||||
for event in self.events:
|
||||
for edit in event.edits:
|
||||
retval = retval | edit.channels
|
||||
|
||||
return retval
|
||||
|
||||
@property
|
||||
def title(self) -> str:
|
||||
"""
|
||||
The title of this edit list.
|
||||
"""
|
||||
return self.title_statement.title
|
||||
|
||||
@property
|
||||
def unrecognized_statements(self) -> Generator[Any, None, None]:
|
||||
"""
|
||||
A generator for all the unrecognized statements and
|
||||
corrupt remarks in the list.
|
||||
|
||||
:yields: either a :class:`StmtUnrecognized` or
|
||||
:class:`StmtCorruptRemark`
|
||||
"""
|
||||
for s in self.event_statements:
|
||||
if type(s) is StmtUnrecognized or type(s) in StmtCorruptRemark:
|
||||
yield s
|
||||
|
||||
@property
|
||||
def events(self) -> Generator[Event, None, None]:
|
||||
'A generator for all the events in the edit list'
|
||||
current_event_num = None
|
||||
event_statements = []
|
||||
for stmt in self.event_statements:
|
||||
if 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)
|
||||
|
||||
@property
|
||||
def sources(self) -> Generator[StmtSourceUMID, None, None]:
|
||||
"""
|
||||
A generator for all of the sources in the list
|
||||
"""
|
||||
|
||||
for stmt in self.event_statements:
|
||||
if type(stmt) is StmtSourceUMID:
|
||||
yield stmt
|
||||
126
src/pycmx/event.py
Normal file
126
src/pycmx/event.py
Normal file
@@ -0,0 +1,126 @@
|
||||
# pycmx
|
||||
# (c) 2023-2025 Jamie Hardt
|
||||
|
||||
from .statements import (StmtFrmc, 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. """
|
||||
|
||||
def __init__(self, statements):
|
||||
self.statements = statements
|
||||
|
||||
@property
|
||||
def number(self) -> int:
|
||||
"""
|
||||
Return the event number.
|
||||
"""
|
||||
return int(self._edit_statements()[0].event)
|
||||
|
||||
@property
|
||||
def edits(self) -> List[Edit]:
|
||||
"""
|
||||
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: List[List[Any]] = [edits_audio]
|
||||
|
||||
if len(edits_audio) == 2:
|
||||
start_name: Optional[StmtClipName] = None
|
||||
end_name: Optional[StmtClipName] = None
|
||||
|
||||
for clip_name in clip_names:
|
||||
if clip_name.affect == 'from':
|
||||
start_name = clip_name
|
||||
elif clip_name.affect == 'to':
|
||||
end_name = clip_name
|
||||
|
||||
the_zip.append([start_name, end_name])
|
||||
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: List[Optional[Any]] = [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(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]:
|
||||
"""
|
||||
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) -> List[StmtEffectsName]:
|
||||
return [s for s in self.statements if type(s) is StmtEffectsName]
|
||||
|
||||
def _edit_statements(self) -> List[StmtEvent]:
|
||||
return [s for s in self.statements if type(s) is StmtEvent]
|
||||
|
||||
def _clip_name_statements(self) -> List[StmtClipName]:
|
||||
return [s for s in self.statements if type(s) is StmtClipName]
|
||||
|
||||
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]:
|
||||
|
||||
if len(self.statements) == 1 and type(self.statements[0]) is StmtEvent:
|
||||
yield (self.statements[0], None)
|
||||
|
||||
else:
|
||||
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)
|
||||
|
||||
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)
|
||||
20
src/pycmx/parse_cmx_events.py
Normal file
20
src/pycmx/parse_cmx_events.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# pycmx
|
||||
# (c) 2018-2025 Jamie Hardt
|
||||
|
||||
from typing import TextIO
|
||||
|
||||
from .parse_cmx_statements import (parse_cmx3600_statements)
|
||||
from .edit_list import EditList
|
||||
|
||||
|
||||
def parse_cmx3600(f: TextIO, tolerant: bool = False) -> EditList:
|
||||
"""
|
||||
Parse a CMX 3600 EDL.
|
||||
|
||||
:param TextIO f: a file-like object, an opened CMX 3600 .EDL file.
|
||||
:param bool tolerant: If `True`, a relaxed event line parsing method will
|
||||
be used, in the case the default method fails.
|
||||
:returns: An :class:`pycmx.edit_list.EditList`.
|
||||
"""
|
||||
statements = parse_cmx3600_statements(f, tolerant)
|
||||
return EditList(statements)
|
||||
266
src/pycmx/parse_cmx_statements.py
Normal file
266
src/pycmx/parse_cmx_statements.py
Normal file
@@ -0,0 +1,266 @@
|
||||
# pycmx
|
||||
# (c) 2018-2025 Jamie Hardt
|
||||
|
||||
import re
|
||||
from typing import TextIO, List
|
||||
|
||||
from .cdl import AscSopComponents, Rgb
|
||||
|
||||
from .statements import (StmtCdlSat, StmtCdlSop, StmtCorruptRemark, StmtFrmc,
|
||||
StmtRemark, StmtTitle, StmtUnrecognized, StmtFCM,
|
||||
StmtAudioExt, StmtClipName, StmtEffectsName,
|
||||
StmtEvent, StmtSourceFile, StmtSplitEdit)
|
||||
from .util import collimate
|
||||
|
||||
|
||||
def parse_cmx3600_statements(file: TextIO,
|
||||
tolerant: bool = False) -> List[object]:
|
||||
"""
|
||||
Return a list of every statement in the file argument.
|
||||
"""
|
||||
lines = file.readlines()
|
||||
return [_parse_cmx3600_line(line.strip(), line_number, tolerant)
|
||||
for (line_number, line) in enumerate(lines)]
|
||||
|
||||
|
||||
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]
|
||||
|
||||
# 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: str, line_number: int,
|
||||
tolerant: bool = False) -> 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)
|
||||
if line.startswith("FCM:"):
|
||||
return _parse_fcm(line, line_number)
|
||||
if line_matcher is not None:
|
||||
event_field_len = len(line_matcher.group(1))
|
||||
|
||||
source_field_len = len(line) - (event_field_len + 65)
|
||||
|
||||
try:
|
||||
return _parse_columns_for_standard_form(
|
||||
line, event_field_len, source_field_len, line_number)
|
||||
|
||||
except EventFormError:
|
||||
if tolerant:
|
||||
return _parse_columns_tolerant(line, line_number)
|
||||
else:
|
||||
return StmtUnrecognized(line, line_number)
|
||||
|
||||
if line.startswith("AUD"):
|
||||
return _parse_extended_audio_channels(line, line_number)
|
||||
if line.startswith("*"):
|
||||
return _parse_remark(line[1:].strip(), line_number)
|
||||
if line.startswith(">>> SOURCE"):
|
||||
return _parse_source_umid_statement(line, line_number)
|
||||
if line.startswith("EFFECTS NAME IS"):
|
||||
return _parse_effects_name(line, line_number)
|
||||
if line.startswith("SPLIT:"):
|
||||
return _parse_split(line, line_number)
|
||||
if line.startswith("M2"):
|
||||
pass
|
||||
# return _parse_motion_memory(line, line_number)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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=False, line_number=line_num)
|
||||
|
||||
|
||||
def _parse_extended_audio_channels(line, line_number):
|
||||
content = line.strip()
|
||||
audio3 = "3" in content
|
||||
audio4 = "4" in content
|
||||
|
||||
if audio3 or audio4:
|
||||
return StmtAudioExt(audio3, audio4, line_number)
|
||||
else:
|
||||
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)
|
||||
elif line.startswith("TO CLIP NAME:"):
|
||||
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)
|
||||
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:
|
||||
try:
|
||||
return StmtCdlSop(line=line,
|
||||
cdl_sop=AscSopComponents(
|
||||
slope=Rgb(red=float(v[0][0]),
|
||||
green=float(v[0][1]),
|
||||
blue=float(v[0][2])),
|
||||
offset=Rgb(red=float(v[1][0]),
|
||||
green=float(v[1][1]),
|
||||
blue=float(v[1][2])),
|
||||
power=Rgb(red=float(v[2][0]),
|
||||
green=float(v[2][1]),
|
||||
blue=float(v[2][2]))
|
||||
),
|
||||
line_number=line_number)
|
||||
|
||||
except ValueError as e:
|
||||
return StmtCorruptRemark('ASC_SOP', e, line_number)
|
||||
|
||||
elif line.startswith("ASC_SAT"):
|
||||
value = re.findall(r'(-?\d+(\.\d+)?)', line)
|
||||
|
||||
if len(value) != 1:
|
||||
return StmtRemark(line, line_number)
|
||||
|
||||
else:
|
||||
try:
|
||||
return StmtCdlSat(value=float(value[0][0]),
|
||||
line_number=line_number)
|
||||
|
||||
except ValueError as e:
|
||||
return StmtCorruptRemark('ASC_SAT', e, 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 StmtCorruptRemark('FRMC', None, line_number)
|
||||
|
||||
else:
|
||||
try:
|
||||
return StmtFrmc(start=int(match.group(1)),
|
||||
end=int(match.group(2)),
|
||||
duration=int(match.group(3)),
|
||||
line_number=line_number)
|
||||
except ValueError as e:
|
||||
return StmtCorruptRemark('FRMC', e, 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: str, line_number):
|
||||
split_type = line[10:21]
|
||||
is_video = split_type.startswith("VIDEO")
|
||||
|
||||
split_delay = line[24:35]
|
||||
return StmtSplitEdit(video=is_video, delay=split_delay,
|
||||
line_number=line_number)
|
||||
|
||||
|
||||
# def _parse_motion_memory(line, line_number):
|
||||
# return StmtMotionMemory(source="", fps="")
|
||||
#
|
||||
|
||||
class EventFormError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _parse_unrecognized(line, line_number):
|
||||
return StmtUnrecognized(content=line, line_number=line_number)
|
||||
|
||||
|
||||
def _parse_columns_for_standard_form(line: str, event_field_length: int,
|
||||
source_field_length: int,
|
||||
line_number: int):
|
||||
# breakpoint()
|
||||
col_widths = _edl_column_widths(event_field_length, source_field_length)
|
||||
|
||||
if sum(col_widths) > len(line):
|
||||
raise EventFormError()
|
||||
|
||||
column_strings = collimate(line, col_widths)
|
||||
|
||||
channels = column_strings[4].strip()
|
||||
trans = column_strings[6].strip()
|
||||
|
||||
if len(channels) == 0 or len(trans) == 0:
|
||||
raise EventFormError()
|
||||
|
||||
return StmtEvent(event=column_strings[0],
|
||||
source=column_strings[2].strip(),
|
||||
channels=channels,
|
||||
trans=trans,
|
||||
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,
|
||||
source_field_size=source_field_length)
|
||||
|
||||
|
||||
def _parse_columns_tolerant(line: str, line_number: int):
|
||||
pattern = re.compile(r'^\s*(\d+)\s+(.{8,128}?)\s+'
|
||||
r'(V|A|A2|AA|NONE|AA/V|A2/V|B)\s+'
|
||||
r'(C|D|W|KB|K|KO)\s+(\d*)\s+(\d\d.\d\d.\d\d.\d\d)\s'
|
||||
r'(\d\d.\d\d.\d\d.\d\d)\s(\d\d.\d\d.\d\d.\d\d)\s'
|
||||
r'(\d\d.\d\d.\d\d.\d\d)'
|
||||
)
|
||||
|
||||
match = pattern.match(line)
|
||||
if match:
|
||||
return StmtEvent(event=int(match.group(1)), source=match.group(2),
|
||||
channels=match.group(3), trans=match.group(4),
|
||||
trans_op=match.group(5), source_in=match.group(6),
|
||||
source_out=match.group(7), record_in=match.group(8),
|
||||
record_out=match.group(9), line_number=line_number,
|
||||
source_field_size=len(match.group(2)))
|
||||
else:
|
||||
return StmtUnrecognized(line, line_number)
|
||||
|
||||
|
||||
def _parse_source_umid_statement(line, line_number):
|
||||
# trimmed = line[3:].strip()
|
||||
# return StmtSourceUMID(name=None, umid=None, line_number=line_number)
|
||||
...
|
||||
102
src/pycmx/statements.py
Normal file
102
src/pycmx/statements.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# pycmx
|
||||
# (c) 2025 Jamie Hardt
|
||||
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
from .cdl import AscSopComponents
|
||||
|
||||
|
||||
class StmtTitle(NamedTuple):
|
||||
title: str
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtFCM(NamedTuple):
|
||||
drop: bool
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtEvent(NamedTuple):
|
||||
event: int
|
||||
source: str
|
||||
channels: str
|
||||
trans: str
|
||||
trans_op: str
|
||||
source_in: str
|
||||
source_out: str
|
||||
record_in: str
|
||||
record_out: str
|
||||
source_field_size: int
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtAudioExt(NamedTuple):
|
||||
audio3: bool
|
||||
audio4: bool
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtClipName(NamedTuple):
|
||||
name: str
|
||||
affect: str
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtSourceFile(NamedTuple):
|
||||
filename: str
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtCdlSop(NamedTuple):
|
||||
line: str
|
||||
cdl_sop: AscSopComponents[float]
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtCdlSat(NamedTuple):
|
||||
value: float
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtFrmc(NamedTuple):
|
||||
start: int
|
||||
end: int
|
||||
duration: int
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtRemark(NamedTuple):
|
||||
text: str
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtEffectsName(NamedTuple):
|
||||
name: str
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtSourceUMID(NamedTuple):
|
||||
name: str
|
||||
umid: str
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtSplitEdit(NamedTuple):
|
||||
video: bool
|
||||
delay: str
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtUnrecognized(NamedTuple):
|
||||
content: str
|
||||
line_number: int
|
||||
|
||||
|
||||
class StmtCorruptRemark(NamedTuple):
|
||||
selector: str
|
||||
exception: Any
|
||||
line_number: int
|
||||
|
||||
|
||||
# StmtMotionMemory = namedtuple(
|
||||
# "MotionMemory", ["source", "fps"]) # FIXME needs more fields
|
||||
90
src/pycmx/transition.py
Normal file
90
src/pycmx/transition.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# pycmx
|
||||
# (c) 2023 Jamie Hardt
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
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) -> Optional[str]:
|
||||
"""
|
||||
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) -> bool:
|
||||
"`True` if this transition is a cut."
|
||||
return self.transition == 'C'
|
||||
|
||||
@property
|
||||
def dissolve(self) -> bool:
|
||||
"`True` if this traansition is a dissolve."
|
||||
return self.transition == 'D'
|
||||
|
||||
@property
|
||||
def wipe(self) -> bool:
|
||||
"`True` if this transition is a wipe."
|
||||
return self.transition.startswith('W')
|
||||
|
||||
@property
|
||||
def effect_duration(self) -> int:
|
||||
"""
|
||||
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) -> Optional[int]:
|
||||
"Wipes are identified by a particular number."
|
||||
if self.wipe:
|
||||
return int(self.transition[1:])
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def key_background(self) -> bool:
|
||||
"`True` if this edit is a key background."
|
||||
return self.transition == Transition.KeyBackground
|
||||
|
||||
@property
|
||||
def key_foreground(self) -> bool:
|
||||
"`True` if this edit is a key foreground."
|
||||
return self.transition == Transition.Key
|
||||
|
||||
@property
|
||||
def key_out(self) -> bool:
|
||||
"""
|
||||
`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 == Transition.KeyOut
|
||||
30
src/pycmx/util.py
Normal file
30
src/pycmx/util.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# pycmx
|
||||
# (c) 2018-2025 Jamie Hardt
|
||||
|
||||
# Utility functions
|
||||
|
||||
def collimate(a_string, column_widths):
|
||||
"""
|
||||
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:
|
||||
return []
|
||||
|
||||
width = column_widths[0]
|
||||
element = a_string[:width]
|
||||
rest = a_string[width:]
|
||||
return [element] + collimate(rest, column_widths[1:])
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
from . import test_parse
|
||||
from . import test_issue_19
|
||||
|
||||
1561
tests/edls/INS4_R1_DX_092117.edl
Normal file
1561
tests/edls/INS4_R1_DX_092117.edl
Normal file
File diff suppressed because it is too large
Load Diff
1550
tests/edls/INS4_R2_DX_092117.edl
Normal file
1550
tests/edls/INS4_R2_DX_092117.edl
Normal file
File diff suppressed because it is too large
Load Diff
1588
tests/edls/INS4_R3_DX_092117.edl
Normal file
1588
tests/edls/INS4_R3_DX_092117.edl
Normal file
File diff suppressed because it is too large
Load Diff
1872
tests/edls/INS4_R4_DX_092117.edl
Normal file
1872
tests/edls/INS4_R4_DX_092117.edl
Normal file
File diff suppressed because it is too large
Load Diff
1810
tests/edls/INS4_R5_DX_092117.edl
Normal file
1810
tests/edls/INS4_R5_DX_092117.edl
Normal file
File diff suppressed because it is too large
Load Diff
13
tests/edls/ISSUE_14_conform_edl_issue_01.edl
Normal file
13
tests/edls/ISSUE_14_conform_edl_issue_01.edl
Normal 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
|
||||
|
||||
33
tests/edls/ISSUE_14_conform_edl_issue_02.edl
Normal file
33
tests/edls/ISSUE_14_conform_edl_issue_02.edl
Normal 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
|
||||
|
||||
129
tests/edls/ISSUE_14_conform_edl_issue_03.edl
Normal file
129
tests/edls/ISSUE_14_conform_edl_issue_03.edl
Normal 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*
|
||||
|
||||
39
tests/edls/ISSUE_19_unusual01.edl
Normal file
39
tests/edls/ISSUE_19_unusual01.edl
Normal file
@@ -0,0 +1,39 @@
|
||||
TITLE: Final Master Generated by LTedlMixer...
|
||||
0001 Z125C001_220217_ROLX V C 15:51:58:10 15:52:02:16 00:00:00:00 00:00:04:06
|
||||
0002 B505C014_230224_RNBP V C 20:19:58:21 20:20:00:21 00:00:04:06 00:00:06:06
|
||||
0003 B505C014_230224_RNBP V C 20:19:59:21 20:20:01:22 00:00:06:06 00:00:08:07
|
||||
0004 B505C014_230224_RNBP V C 20:20:01:23 20:20:02:01 00:00:08:07 00:00:08:09
|
||||
0005 B505C014_230224_RNBP V C 20:20:02:01 20:20:06:10 00:00:08:09 00:00:12:18
|
||||
0006 B505C011_230224_RNBP V C 19:44:21:04 19:44:27:08 00:00:12:18 00:00:18:22
|
||||
0007 B505C016_230224_RNBP V C 20:24:54:14 20:24:58:19 00:00:18:22 00:00:23:03
|
||||
0008 Y022C029_211201_YNJI V C 12:42:37:04 12:42:39:14 00:00:23:03 00:00:25:13
|
||||
0009 A054C025_211022_R24B V C 12:30:50:11 12:30:54:16 00:00:25:13 00:00:29:18
|
||||
0010 Z040C026_211206_ROLX V C 14:42:25:21 14:42:28:17 00:00:29:18 00:00:32:14
|
||||
0011 J001_C002_20211007_R V C 12:38:48:18 12:38:51:13 00:00:32:14 00:00:35:09
|
||||
0012 C006C005_211007_RO2A V C 11:49:08:02 11:49:15:13 00:00:35:09 00:00:42:20
|
||||
0013 A021C020_211007_R24B V C 18:14:52:00 18:14:58:22 00:00:42:20 00:00:49:18
|
||||
0014 A023C013_211008_R24B V C 11:12:57:23 11:12:59:18 00:00:49:18 00:00:51:13
|
||||
0015 U001C010_211029_R268 V C 04:38:42:21 04:38:49:21 00:00:51:13 00:00:58:13
|
||||
0016 A021C009_211007_R24B V C 17:06:12:10 17:06:19:14 00:00:58:13 00:01:05:17
|
||||
0017 A055C008_211022_R24B V C 15:16:03:10 15:16:05:09 00:01:05:17 00:01:07:16
|
||||
0018 A055C008_211022_R24B V C 15:16:05:09 15:16:12:20 00:01:07:16 00:01:15:03
|
||||
0019 A055C008_211022_R24B V C 15:16:12:20 15:16:14:19 00:01:15:03 00:01:17:02
|
||||
0020 A056C011_211022_R24B V C 17:40:13:01 17:40:16:04 00:01:17:02 00:01:20:05
|
||||
0021 A024C011_211008_R24B V C 17:32:07:02 17:32:10:01 00:01:20:05 00:01:23:04
|
||||
0022 B070C001_211203_RP40 V C 17:39:20:20 17:39:22:14 00:01:23:04 00:01:24:22
|
||||
0023 A055C019_211022_R24B V C 16:31:05:06 16:31:12:10 00:01:24:22 00:01:32:02
|
||||
0024 A248C012_220224_R1Y2 V C 16:01:35:08 16:01:40:04 00:01:32:02 00:01:36:22
|
||||
0025 A127C005_211206_R24B V C 10:58:23:06 10:58:24:09 00:01:36:22 00:01:38:01
|
||||
0026 A040C006_211015_R24B V C 13:00:09:04 13:00:23:17 00:01:38:01 00:01:52:14
|
||||
0027 A041C006_211015_R24B V C 16:10:32:08 16:10:35:12 00:01:52:14 00:01:55:18
|
||||
0028 A040C006_211015_R24B V C 13:00:34:13 13:00:37:03 00:01:55:18 00:01:58:08
|
||||
0029 A041C005_211015_R24B V C 15:57:22:05 15:57:27:11 00:01:58:08 00:02:03:14
|
||||
0030 A040C008_211015_R24B V C 13:09:51:18 13:09:55:07 00:02:03:14 00:02:07:03
|
||||
0031 A040C016_211015_R24B V C 14:09:15:11 14:09:20:02 00:02:07:03 00:02:11:18
|
||||
0032 Z089C007_220122_ROLX V C 17:03:34:23 17:03:59:15 00:02:11:18 00:02:36:10
|
||||
0033 A507C008_230227_RNHZ V C 09:55:35:10 09:55:41:02 00:02:36:10 00:02:42:02
|
||||
0034 B049C021_211111_RP40 V C 17:38:55:11 17:38:56:17 00:02:42:02 00:02:43:08
|
||||
0035 Z036C012_211202_ROLX V C 17:30:23:12 17:30:25:05 00:02:43:08 00:02:45:01
|
||||
0036 A157C023_220112_R24B V C 14:13:18:04 14:13:20:06 00:02:45:01 00:02:47:03
|
||||
0037 A095C014_211110_R24B V C 19:34:35:16 19:34:37:10 00:02:47:03 00:02:48:21
|
||||
0038 Z089C010_220122_ROLX V C 17:28:55:21 17:28:58:09 00:02:48:21 00:02:51:09
|
||||
13945
tests/edls/adobe_dai109_test.txt
Normal file
13945
tests/edls/adobe_dai109_test.txt
Normal file
File diff suppressed because it is too large
Load Diff
226
tests/edls/cdl_example01.edl
Normal file
226
tests/edls/cdl_example01.edl
Normal 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
|
||||
17
tests/edls/cdl_example02.cdl
Normal file
17
tests/edls/cdl_example02.cdl
Normal 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
|
||||
7
tests/edls/cdl_frmc_example01.edl
Normal file
7
tests/edls/cdl_frmc_example01.edl
Normal 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
|
||||
123
tests/edls/cdl_frmc_example02.edl
Normal file
123
tests/edls/cdl_frmc_example02.edl
Normal 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
|
||||
60
tests/edls/test_24psf.edl
Executable file
60
tests/edls/test_24psf.edl
Executable file
@@ -0,0 +1,60 @@
|
||||
TITLE: Test EDL 24
|
||||
|
||||
001 AX V C 01:00:00:00 01:00:59:24 00:00:00:00 00:00:59:24
|
||||
* FROM CLIP NAME: clip 1
|
||||
|
||||
002 AX AA C 00:00:00:00 00:01:30:00 00:00:00:00 00:01:30:00
|
||||
* FROM CLIP NAME: clip #2
|
||||
|
||||
003 AX V C 00:00:00:00 00:00:30:01 00:00:59:24 00:01:30:00
|
||||
* FROM CLIP NAME: clip -3
|
||||
AUD 3 4
|
||||
|
||||
004 AX V C 00:00:00:00 00:00:24:17 00:01:30:00 00:01:54:17
|
||||
* FROM CLIP NAME: clip $4
|
||||
|
||||
005 AX V C 00:00:00:00 00:00:24:17 00:01:30:00 00:01:54:17
|
||||
* FROM CLIP NAME: clip &5
|
||||
|
||||
006 AX AA C 00:00:29:01 00:00:29:01 00:01:59:01 00:01:59:01
|
||||
006 BL AA W001 025 00:00:00:00 00:00:10:20 00:01:59:01 00:02:10:21
|
||||
EFFECTS NAME IS Constant Power
|
||||
* FROM CLIP NAME: Test rename
|
||||
* TO CLIP NAME: BL
|
||||
|
||||
007 AX V C 01:00:00:00 01:00:05:00 00:02:01:10 00:02:06:10
|
||||
* FROM CLIP NAME: Black Video
|
||||
|
||||
008 AX V C 01:00:10:14 01:00:15:00 00:02:06:10 00:02:10:21
|
||||
* FROM CLIP NAME: Jellyfish.jpg
|
||||
|
||||
009 AX V C 00:00:00:00 00:00:30:01 00:02:10:21 00:02:40:22
|
||||
* FROM CLIP NAME: Test rename
|
||||
M2 AX -25.0 00:00:00:00
|
||||
|
||||
010 AX AA C 00:00:00:00 00:00:30:01 00:02:10:21 00:02:40:22
|
||||
* FROM CLIP NAME: Test rename
|
||||
M2 AX -25.0 00:00:00:00
|
||||
|
||||
REM The MIT License (MIT)
|
||||
REM
|
||||
REM Copyright (c) 2013 <simon@simon-hargreaves.com>
|
||||
REM
|
||||
REM Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
REM of this software and associated documentation files (the "Software"), to deal
|
||||
REM in the Software without restriction, including without limitation the rights
|
||||
REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
REM copies of the Software, and to permit persons to whom the Software is
|
||||
REM furnished to do so, subject to the following conditions:
|
||||
REM
|
||||
REM The above copyright notice and this permission notice shall be included in
|
||||
REM all copies or substantial portions of the Software.
|
||||
REM
|
||||
REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
REM THE SOFTWARE.
|
||||
REM
|
||||
66
tests/edls/test_25.edl
Executable file
66
tests/edls/test_25.edl
Executable file
@@ -0,0 +1,66 @@
|
||||
TITLE: Sequence 01
|
||||
|
||||
001 AX V C 01:00:00:00 01:00:59:24 00:00:00:00 00:00:59:24
|
||||
* FROM CLIP NAME: Jellyfish.jpg
|
||||
|
||||
002 AX AA C 00:00:00:00 00:01:30:00 00:00:00:00 00:01:30:00
|
||||
* FROM CLIP NAME: N5_final screensaver_mp4.mov
|
||||
|
||||
003 AX V C 00:00:00:00 00:00:30:01 00:00:59:24 00:01:30:00
|
||||
* FROM CLIP NAME: Test rename
|
||||
AUD 3 4
|
||||
|
||||
004 AX V C 00:00:00:00 00:00:24:17 00:01:30:00 00:01:54:17
|
||||
* FROM CLIP NAME: Test rename
|
||||
|
||||
005 AX V C 00:00:24:17 00:00:24:17 00:01:54:17 00:01:54:17
|
||||
005 AX V D 070 00:59:58:21 01:00:05:14 00:01:54:17 00:02:01:10
|
||||
EFFECTS NAME IS CROSS DISSOLVE
|
||||
* FROM CLIP NAME: Test rename
|
||||
* TO CLIP NAME: Jellyfish.jpg
|
||||
|
||||
006 AX AA C 00:00:00:00 00:00:29:01 00:01:30:00 00:01:59:01
|
||||
* FROM CLIP NAME: Test rename
|
||||
|
||||
007 AX AA C 00:00:29:01 00:00:29:01 00:01:59:01 00:01:59:01
|
||||
007 BL AA W001 025 00:00:00:00 00:00:10:20 00:01:59:01 00:02:10:21
|
||||
EFFECTS NAME IS Constant Power
|
||||
* FROM CLIP NAME: Test rename
|
||||
* TO CLIP NAME: BL
|
||||
|
||||
008 AX V C 01:00:00:00 01:00:05:00 00:02:01:10 00:02:06:10
|
||||
* FROM CLIP NAME: Black Video
|
||||
|
||||
009 AX V C 01:00:10:14 01:00:15:00 00:02:06:10 00:02:10:21
|
||||
* FROM CLIP NAME: Jellyfish.jpg
|
||||
|
||||
010 AX V C 00:00:00:00 00:00:30:01 00:02:10:21 00:02:40:22
|
||||
* FROM CLIP NAME: Test rename
|
||||
M2 AX -25.0 00:00:00:00
|
||||
|
||||
011 AX AA C 00:00:00:00 00:00:30:01 00:02:10:21 00:02:40:22
|
||||
* FROM CLIP NAME: Test rename
|
||||
M2 AX -25.0 00:00:00:00
|
||||
|
||||
REM The MIT License (MIT)
|
||||
REM
|
||||
REM Copyright (c) 2013 <simon@simon-hargreaves.com>
|
||||
REM
|
||||
REM Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
REM of this software and associated documentation files (the "Software"), to deal
|
||||
REM in the Software without restriction, including without limitation the rights
|
||||
REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
REM copies of the Software, and to permit persons to whom the Software is
|
||||
REM furnished to do so, subject to the following conditions:
|
||||
REM
|
||||
REM The above copyright notice and this permission notice shall be included in
|
||||
REM all copies or substantial portions of the Software.
|
||||
REM
|
||||
REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
REM THE SOFTWARE.
|
||||
REM
|
||||
42
tests/edls/test_edl_cdl.edl
Normal file
42
tests/edls/test_edl_cdl.edl
Normal file
@@ -0,0 +1,42 @@
|
||||
TITLE: test_edl_cdl
|
||||
FCM: NON-DROP FRAME
|
||||
000001 BAAAAAAA_00001_ABCD V C 01:00:00:01 01:00:02:01 01:00:00:00 01:00:02:00
|
||||
*ASC_SOP (0.8111 0.8112 0.8113)(0.2111 0.2112 0.2113)(1.8111 1.8112 1.8113)
|
||||
*ASC_SAT 0.91
|
||||
*Descript:
|
||||
*FROM CLIP NAME: dra_001_0001_v0001
|
||||
*LOC: 1:00:01:00 YELLOW DRA_001_0001
|
||||
000002 BAAAAAAA_00001_ABCD V C 01:00:00:02 01:00:02:02 01:00:02:00 01:00:04:00
|
||||
*ASC_SOP (0.8121 0.8122 0.8123)(0.2121 0.2122 0.2123)(1.8121 1.8122 1.8123)
|
||||
*ASC_SAT 0.82
|
||||
*Descript:
|
||||
*FROM CLIP NAME: dra_001_0002_v0001
|
||||
*LOC: 1:00:03:00 YELLOW DRA_001_0002
|
||||
000003 BAAAAAAA_00001_ABCD V C 01:00:00:03 01:00:02:03 01:00:04:00 01:00:06:00
|
||||
*ASC_SOP (0.8131 0.8132 0.8133)(0.2131 0.2132 0.2133)(1.8131 1.8132 1.8133)
|
||||
*ASC_SAT 0.73
|
||||
*Descript:
|
||||
*FROM CLIP NAME: dra_001_0003_v0001
|
||||
*LOC: 1:00:05:00 YELLOW DRA_001_0003
|
||||
|
||||
REM The MIT License (MIT)
|
||||
REM
|
||||
REM Copyright (c) 2014 Simon Hargreaves
|
||||
REM
|
||||
REM Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
REM of this software and associated documentation files (the "Software"), to deal
|
||||
REM in the Software without restriction, including without limitation the rights
|
||||
REM to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
REM copies of the Software, and to permit persons to whom the Software is
|
||||
REM furnished to do so, subject to the following conditions:
|
||||
REM
|
||||
REM The above copyright notice and this permission notice shall be included in all
|
||||
REM copies or substantial portions of the Software.
|
||||
REM
|
||||
REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
REM IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
REM FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
REM AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
REM LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
REM OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
REM SOFTWARE.
|
||||
25
tests/test_issue_19.py
Normal file
25
tests/test_issue_19.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from unittest import TestCase
|
||||
|
||||
from pycmx import parse_cmx3600
|
||||
|
||||
|
||||
class Issue19Test(TestCase):
|
||||
def setUp(self):
|
||||
self.f = open("tests/edls/ISSUE_19_unusual01.edl")
|
||||
|
||||
def test_parse(self):
|
||||
edl = parse_cmx3600(self.f, tolerant=True)
|
||||
for event in edl.events:
|
||||
self.assertIsNotNone(event.edits)
|
||||
if event.number == 1:
|
||||
self.assertEqual(len(event.edits), 1)
|
||||
self.assertEqual(event.edits[0].source, "Z125C001_220217_ROLX")
|
||||
self.assertEqual(event.edits[0].channels.v, True)
|
||||
self.assertEqual(event.edits[0].transition.kind, "C")
|
||||
self.assertEqual(event.edits[0].transition.operand, "")
|
||||
self.assertEqual(event.edits[0].source_in, "15:51:58:10")
|
||||
self.assertEqual(event.edits[0].record_out, "00:00:04:06")
|
||||
break
|
||||
|
||||
def tearDown(self):
|
||||
self.f.close()
|
||||
@@ -2,26 +2,177 @@ from unittest import TestCase
|
||||
|
||||
import pycmx
|
||||
|
||||
|
||||
class TestParse(TestCase):
|
||||
|
||||
def test_edls(self):
|
||||
files = ["INS4_R1_010417.edl" ,
|
||||
"STP R1 v082517.edl",
|
||||
"ToD_R4_LOCK3.1_030618_Video.edl",
|
||||
"TEST.edl"
|
||||
]
|
||||
|
||||
counts = [ 287, 250 , 376, 148 ]
|
||||
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):
|
||||
|
||||
for fn, count in zip(files, counts):
|
||||
events = pycmx.parse_cmx3600(f"tests/edls/{fn}" )
|
||||
self.assertTrue( len(events) == count , f"expected {len(events)} but found {count}")
|
||||
counts = [287, 466, 250, 376, 120, 3, 466]
|
||||
|
||||
def test_audio_channels(self):
|
||||
events = pycmx.parse_cmx3600(f"tests/edls/TEST.edl" )
|
||||
self.assertTrue(events[0].channels.a2)
|
||||
self.assertFalse(events[0].channels.a1)
|
||||
self.assertTrue(events[2].channels.get_audio_channel(7))
|
||||
for fn, count in zip(type(self).files, counts):
|
||||
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))
|
||||
|
||||
def test_list_sanity(self):
|
||||
for fn in type(self).files:
|
||||
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)
|
||||
|
||||
def test_event_sanity(self):
|
||||
for fn in type(self).files:
|
||||
path = "tests/edls/" + fn
|
||||
with open(path, 'r') as f:
|
||||
edl = pycmx.parse_cmx3600(f)
|
||||
for index, event in enumerate(edl.events):
|
||||
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:
|
||||
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))
|
||||
self.assertTrue(events[2].edits[0].channels.audio)
|
||||
|
||||
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")
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
self.assertIsNotNone(sop)
|
||||
assert sop
|
||||
self.assertEqual(sop.slope.red, float("0.9405"))
|
||||
self.assertEqual(sop.offset.green, float("-0.0276"))
|
||||
|
||||
sat = event.edits[0].asc_sat
|
||||
self.assertIsNotNone(sat)
|
||||
assert sat
|
||||
self.assertEqual(sat, float('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].framecounts
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user