26 Commits

Author SHA1 Message Date
Jamie Hardt
98e21e7a09 Update CONTRIBUTING.md 2026-05-03 12:17:15 -07:00
Jamie Hardt
694b99d081 Update CONTRIBUTING.md
Updated Agents policy
2026-05-03 12:16:59 -07:00
Jamie Hardt
2e4a0439c1 Update .readthedocs.yaml
Updated rtd build to ubuntu-lts-latest
2026-03-30 11:41:05 -07:00
688402d195 some notes to myslf 2025-12-20 13:44:29 -08:00
4e81810584 ruff configuration 2025-12-18 22:07:08 -08:00
faf2596a57 ruff for linting 2025-12-18 21:45:24 -08:00
1e9fbe339c ruff for linting 2025-12-18 21:44:28 -08:00
a8d00470d4 ruff for linting 2025-12-18 21:43:12 -08:00
fe1e59e731 fix __all__ for module 2025-12-18 21:36:11 -08:00
ec8a08074d doc twiddles 2025-12-18 20:50:06 -08:00
Jamie Hardt
ef683a7683 Update pip install command for documentation 2025-12-18 20:46:33 -08:00
Jamie Hardt
d778f64230 Merge pull request #21 from iluvcapra/uv-build
PEP 621 compliance
2025-12-18 20:43:40 -08:00
1b0ccd4ef7 doc twiddles 2025-12-18 20:41:44 -08:00
498d5c8fea doc tiwddles 2025-12-18 17:24:26 -08:00
af5d937aeb doc tiwddles 2025-12-18 17:19:20 -08:00
637f4ab9a4 tweaking rtd config 2025-12-18 16:00:25 -08:00
4cd635ff17 nudging docs python version 2025-12-18 15:54:48 -08:00
26e1e38320 nudging docs python version 2025-12-18 15:46:01 -08:00
0e97742336 added 3.14 to matrix, some dependency work 2025-12-18 13:30:52 -08:00
67ea12042f updated .flake8 path 2025-12-18 12:35:53 -08:00
6b910a0920 pyproject.toml now PEP 621 compliant
Update build system to uv
2025-12-18 12:27:21 -08:00
a8f35a9ffc twiddle, nudge version number 2025-12-18 10:18:04 -08:00
Jamie Hardt
242f2e08d5 Merge pull request #20 from iluvcapra/19-unusual-edl
Tolerant Parsing Mode
2025-12-18 10:06:07 -08:00
610d406e97 flake8 2025-12-17 15:13:49 -08:00
28307608fc Added raw ASC_SOP line method 2025-12-17 15:12:34 -08:00
9ab3804c89 Removed explicit imports of pycmx 2025-12-17 15:09:05 -08:00
19 changed files with 162 additions and 83 deletions

View File

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

View File

@@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
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 }}
@@ -26,13 +26,10 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest
python -m pip install -e .
- name: Lint with flake8
python -m pip install -e .[dev]
- name: Lint with ruff
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 . --count --max-line-length=79 --statistics
ruff check src/
- name: Test with pytest
run: |
pytest

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.13

View File

@@ -7,13 +7,13 @@ version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
os: ubuntu-lts-latest
tools:
python: "3.10"
# You can also specify other tool versions:
# nodejs: "16"
# rust: "1.55"
# golang: "1.17"
python: "3.13"
jobs:
install:
- pip install --upgrade pip
- pip install --group 'doc' -e .
# Build documentation in the docs/ directory with Sphinx
sphinx:
@@ -28,5 +28,3 @@ python:
install:
- method: pip
path: .
extra_requirements:
- doc

View File

@@ -9,5 +9,36 @@ The best way to contribute code to this project is to find this project on [Gith
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
## Regarding use of Agents
`pycmx` is an open-source project that is offered free for no commerical gain, and
is developed and maintained for educational and creative reasons.
If you use an agent or LLM to produce code for it you are missing out on the benefits
of contributing to an open-source project, particularly community, collaboration with
other developers and designers, and being able to learn and experiment without the
burden of deadlines or worrying about business cases or profits.
This project is supposed to be fun, do not let machines have fun for you.
We can't prevent you from using LLMs to contribute to this project but we ask you
abide by the following eitiquette when doing so:
* All communication with the maintainers must be written by a human in their own
voice. Never use an LLM to craft thread comments, discussion posts, issues, emails
or other correspondence with other developers or the maintainers.
* PRs must be submitted by a person. Do not allow an agent to submit its own PRs to
this project.
* Especially if you are a new contributor to this project, please submit only one PR
at a time and please restrict the subject matter of the PR to a specific unit,
module or tool. All submissions have to be reviewed and understood by the
maintainers before they can be merged.
Obviously we can't verify if you follow all of these rules but certain telltale
traits of LLM-predicted text or code will raise a flag: lack of brevity in
descriptions or code comments, large amounts of text describing your process or
steps that add little to understanding the changes you've made, use of an
obsequious tone or being excessively accomodating, immediately doing requests
without further discussion or clarifications.

View File

@@ -14,7 +14,7 @@ The `pycmx` package parses a CMX 3600 EDL and its most most common variations.
read. Event number field and source name field sizes are determined
dynamically for each statement for a high level of compliance at the expense
of strictness.
* An more relaxed "tolerant" mode allows parsing of an EDL file where columns
* 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
@@ -37,7 +37,7 @@ The `pycmx` package parses a CMX 3600 EDL and its most most common variations.
### Opening and Parsing EDL Files
```
>>> import pycmx
>>> with open("tests/edls/TEST.edl") as f
>>> with open("tests/edls/TEST.edl") as f:
... edl = pycmx.parse_cmx3600(f)
...
>>> edl.title

View File

@@ -16,7 +16,7 @@ The `pycmx` package parses a CMX 3600 EDL and its most most common variations.
read. Event number field and source name field sizes are determined
dynamically for each statement for a high level of compliance at the expense
of strictness.
* An more relaxed "tolerant" mode allows parsing of an EDL file where columns
* 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

View File

@@ -1,15 +1,16 @@
[tool.poetry]
[project]
name = "pycmx"
version = "1.4.0"
version = "1.5.0"
description = "Python CMX 3600 Edit Decision List Parser"
authors = ["Jamie Hardt <jamiehardt@me.com>"]
license = "MIT"
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',
@@ -21,36 +22,59 @@ classifiers = [
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13'
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14'
]
homepage = "https://github.com/iluvcapra/pycmx"
documentation = "https://pycmx.readthedocs.io/"
repository = "https://github.com/iluvcapra/pycmx.git"
urls.Tracker = "https://github.com/iluvcapra/pycmx/issues"
[tool.poetry.extras]
[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.poetry.dependencies]
python = "^3.8"
sphinx = { version='>= 5.3.0', optional=true}
sphinx_rtd_theme = {version ='>= 1.1.1', optional=true}
[tool.pyright]
typeCheckingMode = "basic"
[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)
]
[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 = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requires = ["uv_build>=0.9.18,<0.10.0"]
build-backend = "uv_build"

View File

@@ -11,3 +11,5 @@ 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")

View File

@@ -1,8 +1,8 @@
# pycmx
# (c) 2018-2025 Jamie Hardt
from pycmx.cdl import AscSopComponents, FramecountTriple
from pycmx.statements import (
from .cdl import AscSopComponents, FramecountTriple
from .statements import (
StmtCdlSat,
StmtCdlSop,
StmtFrmc,
@@ -35,7 +35,6 @@ class Edit:
asc_sat_statement: Optional[StmtCdlSat] = None,
frmc_statement: Optional[StmtFrmc] = None,
) -> None:
# Assigning types for the attributes explicitly
self._edit_statement: StmtEvent = edit_statement
self._audio_ext: Optional[StmtAudioExt] = audio_ext_statement
self._clip_name_statement: Optional[StmtClipName] = clip_name_statement
@@ -51,8 +50,8 @@ class Edit:
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.
this edit. Line numbers a zero-indexed, such that the "TITLE:" record
is line zero.
"""
return self._edit_statement.line_number
@@ -177,10 +176,20 @@ class Edit:
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
Get ASC CDL saturation value for clip, if present.
"""
if self._asc_sat_statement is None:
return None

View File

@@ -1,8 +1,8 @@
# pycmx
# (c) 2018-2025 Jamie Hardt
from pycmx.statements import (StmtCorruptRemark, StmtTitle, StmtEvent,
StmtUnrecognized, StmtSourceUMID)
from .statements import (StmtCorruptRemark, StmtTitle, StmtEvent,
StmtUnrecognized, StmtSourceUMID)
from .event import Event
from .channel_map import ChannelMap
@@ -12,7 +12,7 @@ from typing import Any, Generator
class EditList:
"""
Represents an entire edit decision list as returned by
:func:`~pycmx.parse_cmx3600()`.
:func:`~pycmx.parse_cmx_events.parse_cmx3600()`.
"""
def __init__(self, statements: list):

View File

@@ -1,10 +1,9 @@
# pycmx
# (c) 2023-2025 Jamie Hardt
from pycmx.statements import StmtFrmc
from .parse_cmx_statements import (
StmtEvent, StmtClipName, StmtSourceFile, StmtAudioExt, StmtUnrecognized,
StmtEffectsName, StmtCdlSop, StmtCdlSat)
from .statements import (StmtFrmc, StmtEvent, StmtClipName, StmtSourceFile,
StmtAudioExt, StmtUnrecognized, StmtEffectsName,
StmtCdlSop, StmtCdlSat)
from .edit import Edit
from typing import List, Generator, Optional, Tuple, Any
@@ -32,12 +31,27 @@ class Event:
will have multiple edits when a dissolve, wipe or key transition needs
to be performed.
"""
# FTR this is a totall bonkers way of doing this, I wrote this when
# I was still learning Python and I'm sure there's easier ways to do
# it. The job is complicated because multiple edits can occur in one
# event and then other statements can modify the event in different
# ways.
edits_audio = list(self._statements_with_audio_ext())
clip_names = self._clip_name_statements()
source_files = self._source_file_statements()
# We first get the edit events combined with their extra audio
# channel statements, if any.
# The list the_zip contains one element for each initialization
# parameter in Edit()
the_zip: List[List[Any]] = [edits_audio]
# If there are two Clip Name statements and two edits, we look for
# "FROM" and "TO" clip name lines. Otherwise we just look for on
# each per edit.
if len(edits_audio) == 2:
start_name: Optional[StmtClipName] = None
end_name: Optional[StmtClipName] = None
@@ -55,6 +69,10 @@ class Event:
else:
the_zip.append([None] * len(edits_audio))
# if there's one source file statemnent per clip, we allocate them to
# each edit in order. Otherwise if there's only one, we assign the one
# to all the edits. If there's no source_file statements, we provide
# None.
if len(edits_audio) == len(source_files):
the_zip.append(source_files)
elif len(source_files) == 1:
@@ -62,7 +80,7 @@ class Event:
else:
the_zip.append([None] * len(edits_audio))
# attach trans name to last event
# attach effects name to last event
try:
trans_statement = self._trans_name_statements()[0]
trans_names: List[Optional[Any]] = [None] * (len(edits_audio) - 1)
@@ -70,6 +88,7 @@ class Event:
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,

View File

@@ -4,7 +4,7 @@
import re
from typing import TextIO, List
from pycmx.cdl import AscSopComponents, Rgb
from .cdl import AscSopComponents, Rgb
from .statements import (StmtCdlSat, StmtCdlSop, StmtCorruptRemark, StmtFrmc,
StmtRemark, StmtTitle, StmtUnrecognized, StmtFCM,
@@ -60,9 +60,8 @@ def _parse_cmx3600_line(line: str, line_number: int,
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)
return _parse_columns_for_standard_form(
line, event_field_len, source_field_len, line_number)
except EventFormError:
if tolerant:
@@ -134,15 +133,19 @@ def _parse_remark(line, line_number) -> object:
else:
try:
return StmtCdlSop(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)
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)

View File

@@ -3,9 +3,7 @@
from typing import Any, NamedTuple
from pycmx.cdl import AscSopComponents
# type str = str
from .cdl import AscSopComponents
class StmtTitle(NamedTuple):
@@ -50,6 +48,7 @@ class StmtSourceFile(NamedTuple):
class StmtCdlSop(NamedTuple):
line: str
cdl_sop: AscSopComponents[float]
line_number: int

View File

@@ -24,7 +24,7 @@ class Transition:
@property
def kind(self) -> Optional[str]:
"""
Return the kind of transition: Cut, Wipe, etc
Return the kind of transition: Cut, Wipe, etc.
"""
if self.cut:
return Transition.Cut
@@ -56,7 +56,8 @@ class Transition:
@property
def effect_duration(self) -> int:
"""The duration of this transition, in frames of the record target.
"""
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.
"""