mirror of
https://github.com/iluvcapra/pycmx.git
synced 2026-07-02 04:10:59 +00:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -0,0 +1,41 @@
|
||||
# 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.7", "3.8", "3.9", "3.10", "3.11"]
|
||||
|
||||
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 flake8 pytest
|
||||
python3 -m pip install -e .
|
||||
# if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Upload Python Package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4.6.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools build wheel twine
|
||||
- name: Build and publish
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_APIKEY }}
|
||||
run: |
|
||||
python -m build .
|
||||
twine upload dist/*
|
||||
- name: Report to Mastodon
|
||||
uses: cbrgm/mastodon-github-action@v1.0.1
|
||||
with:
|
||||
message: |
|
||||
I just released a new version of pycmx, my library for reading CMX EDLs!
|
||||
#sounddesign #filmmaking #python
|
||||
${{ github.server_url }}/${{ github.repository }}
|
||||
env:
|
||||
MASTODON_URL: ${{ secrets.MASTODON_URL }}
|
||||
MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }}
|
||||
+10
@@ -10,3 +10,13 @@
|
||||
|
||||
# Vim Swapfiles
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
||||
venv/
|
||||
|
||||
.coverage
|
||||
lcov.info
|
||||
|
||||
|
||||
# venv
|
||||
venv/
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
language: python
|
||||
python:
|
||||
- "3.6"
|
||||
script:
|
||||
- "python3 setup.py test"
|
||||
install:
|
||||
- "pip3 install setuptools"
|
||||
@@ -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,4 +1,6 @@
|
||||
[](https://travis-ci.com/iluvcapra/pycmx) [](https://pycmx.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://pycmx.readthedocs.io/en/latest/?badge=latest)   [](https://pypi.org/project/pycmx/) 
|
||||

|
||||
[](https://github.com/iluvcapra/pycmx/actions/workflows/python-package.yml)
|
||||
|
||||
|
||||
# pycmx
|
||||
@@ -82,32 +84,4 @@ Audio channel 7 is present
|
||||
False
|
||||
```
|
||||
|
||||
## How is this different from `python-edl`?
|
||||
|
||||
There are two important differences between `import edl` and `import pycmx`
|
||||
and motivated my development of this module.
|
||||
|
||||
1. The `pycmx` parser doesn't take timecode or framerates into account,
|
||||
and strictly treats timecodes like opaque values. As far as `pycmx` is
|
||||
concerend, they're just strings. This was done because in my experience,
|
||||
the frame rate of an EDL is often difficult to precisely determine and
|
||||
often the frame rate of various sources is different from the frame rate
|
||||
of the target track.
|
||||
|
||||
In any event, timecodes in an EDL are a kind of *address* and are not
|
||||
exactly scalar, they're meant to point to a particular block of video or
|
||||
audio data on a medium and presuming that they refer to a real time, or
|
||||
duration, or are convertible, etc. isn't always safe.
|
||||
|
||||
2. The `pycmx` parser reads event numbers and keeps track of which EDL rows
|
||||
are meant to happen "at the same time," with two decks. This makes it
|
||||
easier to reconstruct transition A/B clips, and read clip names from
|
||||
such events appropriately.
|
||||
|
||||
## Should I Use This?
|
||||
|
||||
At this time, this is (at best) beta software. I feel like the interface is
|
||||
about where where I'd like it to be but more testing is required.
|
||||
|
||||
Contributions are welcome and will make this module production-ready all the
|
||||
faster! Please reach out or file a ticket!
|
||||
|
||||
Binary file not shown.
@@ -1,35 +0,0 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
||||
@@ -0,0 +1,24 @@
|
||||
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:
|
||||
|
||||
|
||||
|
||||
+4
-4
@@ -14,13 +14,13 @@
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath('../../pycmx'))
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
print(sys.path)
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = u'pycmx'
|
||||
copyright = u'2018, Jamie Hardt'
|
||||
copyright = u'2022, Jamie Hardt'
|
||||
author = u'Jamie Hardt'
|
||||
|
||||
# The short X.Y version
|
||||
@@ -63,7 +63,7 @@ master_doc = 'index'
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
language = 'em'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Parse Function
|
||||
==============
|
||||
|
||||
|
||||
.. autofunction:: pycmx.parse_cmx_events.parse_cmx3600
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ Welcome to pycmx's documentation!
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 5
|
||||
:caption: API Reference:
|
||||
|
||||
pycmx
|
||||
:caption: API Reference
|
||||
|
||||
function
|
||||
classes
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
pycmx
|
||||
=====
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
pycmx
|
||||
@@ -1,46 +0,0 @@
|
||||
pycmx package
|
||||
=============
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
pycmx.channel\_map module
|
||||
-------------------------
|
||||
|
||||
.. automodule:: pycmx.channel_map
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
pycmx.parse\_cmx\_events module
|
||||
-------------------------------
|
||||
|
||||
.. automodule:: pycmx.parse_cmx_events
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
pycmx.parse\_cmx\_statements module
|
||||
-----------------------------------
|
||||
|
||||
.. automodule:: pycmx.parse_cmx_statements
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
pycmx.util module
|
||||
-----------------
|
||||
|
||||
.. automodule:: pycmx.util
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: pycmx
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
+5
-6
@@ -1,17 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
pycmx is a module for parsing CMX 3600-style EDLs. For more information and
|
||||
pycmx is a module for parsing CMX 3600-style EDLs. For more information and
|
||||
examples see README.md
|
||||
|
||||
This module (c) 2018 Jamie Hardt. For more information on your rights to
|
||||
copy and reuse this software, refer to the LICENSE file included with the
|
||||
This module (c) 2022 Jamie Hardt. For more information on your rights to
|
||||
copy and reuse this software, refer to the LICENSE file included with the
|
||||
distribution.
|
||||
"""
|
||||
|
||||
__version__ = '0.8'
|
||||
__author__ = 'Jamie Hardt'
|
||||
__version__ = '1.2.0'
|
||||
|
||||
from .parse_cmx_events import parse_cmx3600
|
||||
from .parse_cmx_events import parse_cmx3600
|
||||
from .transition import Transition
|
||||
from .event import Event
|
||||
from .edit import Edit
|
||||
|
||||
+25
-10
@@ -2,13 +2,14 @@
|
||||
# (c) 2018 Jamie Hardt
|
||||
|
||||
from re import (compile, match)
|
||||
from typing import Dict, Tuple
|
||||
|
||||
class ChannelMap:
|
||||
"""
|
||||
Represents a set of all the channels to which an event applies.
|
||||
"""
|
||||
|
||||
_chan_map = {
|
||||
_chan_map : Dict[str, Tuple] = {
|
||||
"V" : (True, False, False),
|
||||
"A" : (False, True, False),
|
||||
"A2" : (False, False, True),
|
||||
@@ -27,6 +28,11 @@ class ChannelMap:
|
||||
'True if video is included'
|
||||
return self.v
|
||||
|
||||
@property
|
||||
def audio(self):
|
||||
'True if an audio channel is included'
|
||||
return len(self._audio_channel_set) > 0
|
||||
|
||||
@property
|
||||
def channels(self):
|
||||
'A generator for each audio channel'
|
||||
@@ -35,7 +41,7 @@ class ChannelMap:
|
||||
|
||||
@property
|
||||
def a1(self):
|
||||
"""True if A1 is included."""
|
||||
"""True if A1 is included"""
|
||||
return self.get_audio_channel(1)
|
||||
|
||||
@a1.setter
|
||||
@@ -44,7 +50,7 @@ class ChannelMap:
|
||||
|
||||
@property
|
||||
def a2(self):
|
||||
"""True if A2 is included."""
|
||||
"""True if A2 is included"""
|
||||
return self.get_audio_channel(2)
|
||||
|
||||
@a2.setter
|
||||
@@ -53,7 +59,7 @@ class ChannelMap:
|
||||
|
||||
@property
|
||||
def a3(self):
|
||||
"""True if A3 is included."""
|
||||
"""True if A3 is included"""
|
||||
return self.get_audio_channel(3)
|
||||
|
||||
@a3.setter
|
||||
@@ -62,7 +68,7 @@ class ChannelMap:
|
||||
|
||||
@property
|
||||
def a4(self):
|
||||
"""True if A4 is included."""
|
||||
"""True if A4 is included"""
|
||||
return self.get_audio_channel(4)
|
||||
|
||||
@a4.setter
|
||||
@@ -70,18 +76,18 @@ class ChannelMap:
|
||||
self.set_audio_channel(4,val)
|
||||
|
||||
def get_audio_channel(self,chan_num):
|
||||
"""True if chan_num is included."""
|
||||
"""True if chan_num is included"""
|
||||
return (chan_num in self._audio_channel_set)
|
||||
|
||||
def set_audio_channel(self,chan_num,enabled):
|
||||
"""If enabled is true, chan_num will be included."""
|
||||
"""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('^A(\d+)')
|
||||
alt_channel_re = compile(r'^A(\d+)')
|
||||
if event_str in self._chan_map:
|
||||
channels = self._chan_map[event_str]
|
||||
self.v = channels[0]
|
||||
@@ -93,6 +99,15 @@ class ChannelMap:
|
||||
self.set_audio_channel(int( matchresult.group(1)), True )
|
||||
|
||||
def _append_ext(self, audio_ext):
|
||||
self.a3 = ext.audio3
|
||||
self.a4 = ext.audio4
|
||||
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)
|
||||
|
||||
|
||||
+52
-4
@@ -1,8 +1,9 @@
|
||||
# pycmx
|
||||
# (c) 2018 Jamie Hardt
|
||||
|
||||
from .parse_cmx_statements import (StmtUnrecognized, StmtFCM, StmtEvent)
|
||||
from .parse_cmx_statements import (StmtUnrecognized, StmtFCM, StmtEvent, StmtSourceUMID)
|
||||
from .event import Event
|
||||
from .channel_map import ChannelMap
|
||||
|
||||
class EditList:
|
||||
"""
|
||||
@@ -13,13 +14,47 @@ class EditList:
|
||||
self.title_statement = statements[0]
|
||||
self.event_statements = statements[1:]
|
||||
|
||||
|
||||
@property
|
||||
def format(self):
|
||||
"""
|
||||
The detected format of the EDL. Possible values are: `3600`,`File32`,
|
||||
`File128`, and `unknown`
|
||||
"""
|
||||
first_event = next( (s for s in self.event_statements if type(s) is StmtEvent), None)
|
||||
|
||||
if first_event:
|
||||
if first_event.format == 8:
|
||||
return '3600'
|
||||
elif first_event.format == 32:
|
||||
return 'File32'
|
||||
elif first_event.format == 128:
|
||||
return 'File128'
|
||||
else:
|
||||
return 'unknown'
|
||||
else:
|
||||
return 'unknown'
|
||||
|
||||
|
||||
@property
|
||||
def channels(self):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
The title of this edit list, as attensted by the 'TITLE:' statement on
|
||||
the first line.
|
||||
The title of this edit list.
|
||||
"""
|
||||
'The title of the edit list'
|
||||
return self.title_statement.title
|
||||
|
||||
|
||||
@@ -54,8 +89,21 @@ class EditList:
|
||||
else:
|
||||
event_statements.append(stmt)
|
||||
|
||||
elif type(stmt) is StmtSourceUMID:
|
||||
break
|
||||
else:
|
||||
event_statements.append(stmt)
|
||||
|
||||
yield Event(statements=event_statements)
|
||||
|
||||
@property
|
||||
def sources(self):
|
||||
"""
|
||||
A generator for all of the sources in the list
|
||||
"""
|
||||
|
||||
for stmt in self.event_statements:
|
||||
if type(stmt) is StmtSourceUMID:
|
||||
yield stmt
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ def parse_cmx3600(f):
|
||||
f : a file-like object, anything that's readlines-able.
|
||||
|
||||
Returns:
|
||||
An :class:`EditList`.
|
||||
An :class:`pycmx.edit_list.EditList`.
|
||||
"""
|
||||
statements = parse_cmx3600_statements(f)
|
||||
return EditList(statements)
|
||||
|
||||
@@ -11,13 +11,13 @@ from .util import collimate
|
||||
StmtTitle = namedtuple("Title",["title","line_number"])
|
||||
StmtFCM = namedtuple("FCM",["drop","line_number"])
|
||||
StmtEvent = namedtuple("Event",["event","source","channels","trans",\
|
||||
"trans_op","source_in","source_out","record_in","record_out","line_number"])
|
||||
"trans_op","source_in","source_out","record_in","record_out","format","line_number"])
|
||||
StmtAudioExt = namedtuple("AudioExt",["audio3","audio4","line_number"])
|
||||
StmtClipName = namedtuple("ClipName",["name","affect","line_number"])
|
||||
StmtSourceFile = namedtuple("SourceFile",["filename","line_number"])
|
||||
StmtRemark = namedtuple("Remark",["text","line_number"])
|
||||
StmtEffectsName = namedtuple("EffectsName",["name","line_number"])
|
||||
StmtTrailer = namedtuple("Trailer",["text","line_number"])
|
||||
StmtSourceUMID = namedtuple("Source",["name","umid","line_number"])
|
||||
StmtSplitEdit = namedtuple("SplitEdit",["video","magnitue", "line_number"])
|
||||
StmtMotionMemory = namedtuple("MotionMemory",["source","fps"]) # FIXME needs more fields
|
||||
StmtUnrecognized = namedtuple("Unrecognized",["content","line_number"])
|
||||
@@ -69,8 +69,8 @@ def _parse_cmx3600_line(line, line_number):
|
||||
return _parse_extended_audio_channels(line,line_number)
|
||||
elif line.startswith("*"):
|
||||
return _parse_remark( line[1:].strip(), line_number)
|
||||
elif line.startswith(">>>"):
|
||||
return _parse_trailer_statement(line, line_number)
|
||||
elif line.startswith(">>> SOURCE"):
|
||||
return _parse_source_umid_statement(line, line_number)
|
||||
elif line.startswith("EFFECTS NAME IS"):
|
||||
return _parse_effects_name(line, line_number)
|
||||
elif line.startswith("SPLIT:"):
|
||||
@@ -157,10 +157,11 @@ def _parse_columns_for_standard_form(line, event_field_length, source_field_leng
|
||||
source_out=column_strings[12].strip(),
|
||||
record_in=column_strings[14].strip(),
|
||||
record_out=column_strings[16].strip(),
|
||||
line_number=line_number)
|
||||
line_number=line_number,
|
||||
format=source_field_length)
|
||||
|
||||
|
||||
def _parse_trailer_statement(line, line_number):
|
||||
def _parse_source_umid_statement(line, line_number):
|
||||
trimmed = line[3:].strip()
|
||||
return StmtTrailer(trimmed, line_number=line_number)
|
||||
return StmtSourceUMID(name=None, umid=None, line_number=line_number)
|
||||
|
||||
|
||||
+3
-3
@@ -70,12 +70,12 @@ class Transition:
|
||||
@property
|
||||
def key_background(self):
|
||||
"`True` if this edit is a key background."
|
||||
return self.transition == KeyBackground
|
||||
return self.transition == Transition.KeyBackground
|
||||
|
||||
@property
|
||||
def key_foreground(self):
|
||||
"`True` if this edit is a key foreground."
|
||||
return self.transition == Key
|
||||
return self.transition == Transition.Key
|
||||
|
||||
@property
|
||||
def key_out(self):
|
||||
@@ -83,4 +83,4 @@ class Transition:
|
||||
`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 == KeyOut
|
||||
return self.transition == Transition.KeyOut
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
[build-system]
|
||||
requires = ["flit_core >=3.2,<4"]
|
||||
build-backend = "flit_core.buildapi"
|
||||
|
||||
[project]
|
||||
name = "pycmx"
|
||||
authors = [{name = "Jamie Hardt", email = "jamiehardt@me.com"}]
|
||||
readme = "README.md"
|
||||
dynamic = ["version", "description"]
|
||||
requires-python = "~=3.7"
|
||||
classifiers = [
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Topic :: Multimedia',
|
||||
'Topic :: Multimedia :: Video',
|
||||
'Topic :: Text Processing',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11'
|
||||
]
|
||||
dependencies = [
|
||||
|
||||
]
|
||||
keywords = [
|
||||
'parser',
|
||||
'film',
|
||||
'broadcast'
|
||||
]
|
||||
|
||||
[tool.flit.module]
|
||||
name = "pycmx"
|
||||
|
||||
[project.optional-dependencies]
|
||||
doc = [
|
||||
'sphinx >= 5.3.0',
|
||||
'sphinx_rtd_theme >= 1.1.1',
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Home = "https://github.com/iluvcapra/pycmx"
|
||||
Documentation = "https://pycmx.readthedocs.io/"
|
||||
Source = "https://github.com/iluvcapra/pycmx.git"
|
||||
Issues = "https://github.com/iluvcapra/pycmx/issues"
|
||||
|
||||
[tool.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)
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
from setuptools import setup
|
||||
|
||||
with open("README.md", "r") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setup(name='pycmx',
|
||||
version='0.8',
|
||||
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 :: 4 - Beta',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Topic :: Multimedia',
|
||||
'Topic :: Multimedia :: Video',
|
||||
'Topic :: Text Processing'],
|
||||
packages=['pycmx'])
|
||||
@@ -0,0 +1 @@
|
||||
from . import test_parse
|
||||
+13
-6
@@ -18,22 +18,24 @@ class TestParse(TestCase):
|
||||
counts = [ 287, 466, 250 , 376, 120 , 3 , 466 ]
|
||||
|
||||
for fn, count in zip(type(self).files, counts):
|
||||
with open(f"tests/edls/{fn}" ,'r') as f:
|
||||
with open("tests/edls/" + fn ,'r') as f:
|
||||
edl = pycmx.parse_cmx3600(f)
|
||||
actual = len( list( edl.events ))
|
||||
self.assertTrue( actual == count , f"expected {count} in file {fn} but found {actual}")
|
||||
self.assertTrue( actual == count ,
|
||||
"expected %i in file %s but found %i" % (count, fn, actual))
|
||||
|
||||
def test_list_sanity(self):
|
||||
for fn in type(self).files:
|
||||
with open(f"tests/edls/{fn}" ,'r') as f:
|
||||
with open("tests/edls/" + fn ,'r') as f:
|
||||
edl = pycmx.parse_cmx3600(f)
|
||||
self.assertTrue( type(edl.title) is str )
|
||||
self.assertTrue( len(edl.title) > 0 )
|
||||
|
||||
|
||||
|
||||
def test_event_sanity(self):
|
||||
for fn in type(self).files:
|
||||
with open(f"tests/edls/{fn}" ,'r') as f:
|
||||
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 )
|
||||
@@ -45,7 +47,7 @@ class TestParse(TestCase):
|
||||
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")
|
||||
@@ -64,6 +66,7 @@ class TestParse(TestCase):
|
||||
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):
|
||||
@@ -104,3 +107,7 @@ class TestParse(TestCase):
|
||||
edl = pycmx.parse_cmx3600(f)
|
||||
events = list(edl.events)
|
||||
self.assertEqual( events[4].edits[1].transition.name , "CROSS DISSOLVE" )
|
||||
|
||||
|
||||
# add test for edit_list.channels
|
||||
|
||||
|
||||
Reference in New Issue
Block a user