mirror of
https://github.com/iluvcapra/pycmx.git
synced 2026-07-02 04:10:59 +00:00
Compare commits
372 Commits
v0.4
...
4e81810584
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e81810584 | |||
| faf2596a57 | |||
| 1e9fbe339c | |||
| a8d00470d4 | |||
| fe1e59e731 | |||
| ec8a08074d | |||
| ef683a7683 | |||
| d778f64230 | |||
| 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 |
@@ -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
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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 ruff
|
||||
run: |
|
||||
ruff check src/
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest
|
||||
@@ -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
@@ -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/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
3.13
|
||||
@@ -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' -e .
|
||||
|
||||
# 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: .
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
```
|
||||
>>> 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'
|
||||
|
||||
Contributions are welcome and will make this module production-ready all the
|
||||
faster! Please reach out or file a ticket!
|
||||
# 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
|
||||
```
|
||||
|
||||
@@ -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()
|
||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: 9f647f525db0c82aadec132928a40ec5
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
build/*
|
||||
@@ -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)
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
Parse Function
|
||||
==============
|
||||
|
||||
|
||||
.. autofunction:: pycmx.parse_cmx_events.parse_cmx3600
|
||||
|
||||
|
||||
@@ -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.
|
||||
* 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`_ 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,179 +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 f"CmxChannelMap(v={self.v}, audio_channels={self._audio_channel_set})"
|
||||
|
||||
|
||||
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(v=False, audio_channels=set([]))
|
||||
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:])
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
[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 = [
|
||||
'pytest',
|
||||
'ruff>=0.14.10'
|
||||
]
|
||||
|
||||
[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]
|
||||
dev = ['ruff', 'pytest']
|
||||
doc = ['sphinx', 'sphinx_rtd_theme']
|
||||
|
||||
|
||||
[tool.pyright]
|
||||
typeCheckingMode = "basic"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "W"]
|
||||
|
||||
[tool.ruff.format]
|
||||
docstring-code-line-length = 88
|
||||
|
||||
# [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"
|
||||
@@ -1,19 +0,0 @@
|
||||
from setuptools import setup
|
||||
|
||||
with open("README.md", "r") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setup(name='pycmx',
|
||||
version='0.4',
|
||||
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'])
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- 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
|
||||
|
||||
__all__ = ("parse_cmx3600", "Transition", "Event", "Edit")
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
...
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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:])
|
||||
@@ -0,0 +1,2 @@
|
||||
from . import test_parse
|
||||
from . import test_issue_19
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Executable
+60
@@ -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
|
||||
Executable
+66
@@ -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
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
+167
-23
@@ -2,33 +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"
|
||||
]
|
||||
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"
|
||||
]
|
||||
|
||||
counts = [ 287, 250 , 376, 148 ]
|
||||
def test_event_counts(self):
|
||||
|
||||
counts = [287, 466, 250, 376, 120, 3, 466]
|
||||
|
||||
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}")
|
||||
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_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))
|
||||
self.assertFalse(events[2].channels.get_audio_channel(1))
|
||||
self.assertFalse(events[2].channels.get_audio_channel(2))
|
||||
self.assertFalse(events[2].channels.get_audio_channel(3))
|
||||
self.assertFalse(events[2].channels.get_audio_channel(4))
|
||||
self.assertFalse(events[2].channels.get_audio_channel(10))
|
||||
self.assertFalse(events[2].channels.get_audio_channel(11))
|
||||
self.assertFalse(events[2].channels.get_audio_channel(12))
|
||||
self.assertFalse(events[2].channels.get_audio_channel(13))
|
||||
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