mirror of
https://github.com/iluvcapra/pycmx.git
synced 2025-12-31 08:50:54 +00:00
Compare commits
23 Commits
19-unusual
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 688402d195 | |||
| 4e81810584 | |||
| faf2596a57 | |||
| 1e9fbe339c | |||
| a8d00470d4 | |||
| fe1e59e731 | |||
| ec8a08074d | |||
|
|
ef683a7683 | ||
|
|
d778f64230 | ||
| 1b0ccd4ef7 | |||
| 498d5c8fea | |||
| af5d937aeb | |||
| 637f4ab9a4 | |||
| 4cd635ff17 | |||
| 26e1e38320 | |||
| 0e97742336 | |||
| 67ea12042f | |||
| 6b910a0920 | |||
| a8f35a9ffc | |||
|
|
242f2e08d5 | ||
| 610d406e97 | |||
| 28307608fc | |||
| 9ab3804c89 |
5
.flake8
5
.flake8
@@ -1,5 +0,0 @@
|
||||
[flake8]
|
||||
per-file-ignores =
|
||||
pycmx/__init__.py: F401
|
||||
tests/__init__.py: F401
|
||||
|
||||
11
.github/workflows/python-package.yml
vendored
11
.github/workflows/python-package.yml
vendored
@@ -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
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
@@ -9,11 +9,11 @@ version: 2
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -1,7 +1,7 @@
|
||||
# pycmx
|
||||
# (c) 2018-2025 Jamie Hardt
|
||||
|
||||
from pycmx.statements import (StmtCorruptRemark, StmtTitle, StmtEvent,
|
||||
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):
|
||||
@@ -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,
|
||||
@@ -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,12 +133,16 @@ 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]),
|
||||
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]),
|
||||
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]),
|
||||
power=Rgb(red=float(v[2][0]),
|
||||
green=float(v[2][1]),
|
||||
blue=float(v[2][2]))
|
||||
),
|
||||
line_number=line_number)
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
Reference in New Issue
Block a user