98 Commits
v0.8 ... v1.1.5

Author SHA1 Message Date
Jamie Hardt
4d83f81fc8 Merge pull request #5 from iluvcapra/release
Release
2022-11-18 23:03:21 -08:00
Jamie Hardt
69b6c7236d Update setup.py
Bump version number
2022-11-18 23:01:57 -08:00
Jamie Hardt
71ffe8cd0d Merge branch 'master' into release 2022-11-18 22:49:45 -08:00
Jamie Hardt
3fff6c8d2a Update gitignore 2022-11-18 22:47:44 -08:00
Jamie Hardt
e350565430 Update channel_map.py 2022-11-18 22:37:29 -08:00
Jamie Hardt
9ec30ede02 Update channel_map.py 2022-11-18 22:34:37 -08:00
Jamie Hardt
e5130b8011 Update edit_list.py 2022-11-18 22:33:33 -08:00
Jamie Hardt
00eaccabac Update index.rst
Twiddle
2022-11-18 22:27:20 -08:00
Jamie Hardt
521d86e444 Update conf.py 2022-11-16 23:24:52 -08:00
Jamie Hardt
0fdba2408b Create CONTRIBUTING.md 2022-11-16 21:26:38 -08:00
Jamie Hardt
199bba2466 Update setup.py 2022-11-16 21:18:27 -08:00
Jamie Hardt
d8f0b5694e Update setup.py 2022-11-16 21:18:14 -08:00
Jamie Hardt
742b0f96c3 Delete .idea directory 2022-11-16 21:15:21 -08:00
Jamie Hardt
d44948bdc1 Update LICENSE 2022-11-16 21:14:36 -08:00
Jamie Hardt
93cff15446 Update README.md 2022-11-16 21:04:33 -08:00
Jamie Hardt
fc914409ce Update README.md 2022-11-16 21:04:13 -08:00
Jamie Hardt
770e7f45a4 Fixed doc generation 2022-11-16 19:00:17 -08:00
Jamie Hardt
d3cf5fa5f2 Doc work 2022-11-16 18:47:16 -08:00
Jamie Hardt
67d2bd7093 Update classes.rst 2022-11-16 18:21:48 -08:00
Jamie Hardt
26a7eae437 Docs 2022-11-16 18:19:55 -08:00
Jamie Hardt
407aa1c1fd Update classes.rst 2022-11-16 18:15:58 -08:00
Jamie Hardt
13d0a80a10 Update classes.rst 2022-11-16 18:13:16 -08:00
Jamie Hardt
dcd2a22a43 Update classes.rst 2022-11-16 18:09:30 -08:00
Jamie Hardt
c586740269 Update classes.rst 2022-11-16 18:07:32 -08:00
Jamie Hardt
e28dbbbe5e Update classes.rst 2022-11-16 18:06:29 -08:00
Jamie Hardt
41df450452 Update classes.rst 2022-11-16 18:05:19 -08:00
Jamie Hardt
c37464036d Delete pycmx.rst 2022-11-16 18:04:25 -08:00
Jamie Hardt
9d89834eb3 Update index.rst 2022-11-16 18:02:15 -08:00
Jamie Hardt
8b53d2249c Docs 2022-11-16 18:00:53 -08:00
Jamie Hardt
0cdbc4e9be Docs 2022-11-16 17:59:08 -08:00
Jamie Hardt
e229e807b1 Update index.rst 2022-11-16 17:55:22 -08:00
Jamie Hardt
a7ee1f6737 Merge branch 'master' of https://github.com/iluvcapra/pycmx 2022-11-16 17:53:35 -08:00
Jamie Hardt
9097de8efa Delete modules.rst 2022-11-16 17:53:33 -08:00
Jamie Hardt
bc6d7f34c0 Update python-package.yml
Removed 3.6 from grid
2022-11-16 17:10:00 -08:00
Jamie Hardt
b642f859f3 Put it back 2022-11-16 17:09:22 -08:00
Jamie Hardt
29a9a5fba7 Removed coverage from reqs 2022-11-16 17:05:09 -08:00
Jamie Hardt
20ff7d7ee8 Added requirements.txt file 2022-11-16 17:03:44 -08:00
Jamie Hardt
183f121cfc Update channel_map.py
Added some typing
2022-11-16 16:57:21 -08:00
Jamie Hardt
e2dffcb745 Update .gitignore 2022-11-16 16:42:12 -08:00
Jamie Hardt
8c2ba3cc09 Update README.md
Added Lint and Test badge
2022-11-16 16:25:08 -08:00
Jamie Hardt
c7569045c1 Update python-package.yml
Named Package workflow
2022-11-16 16:24:16 -08:00
Jamie Hardt
50bcac23bb Update README.md
Removed travis badge.
2022-11-14 11:09:04 -08:00
Jamie Hardt
67b1631ba9 Update README.md
Removed codecov badge
2022-11-14 11:08:31 -08:00
Jamie Hardt
85cbafba8f Deleted .travis file 2022-11-13 17:57:06 -08:00
Jamie Hardt
595cf35e57 Update setup.py
Version 1.1.4
2022-11-13 17:53:51 -08:00
Jamie Hardt
7fa22d4b85 Update setup.py
Version 1.1.2
2022-11-13 17:50:26 -08:00
Jamie Hardt
42f2de54b5 Update README.md
Removed "Platform Lifecycle" section, this work is done
2022-11-13 17:42:46 -08:00
Jamie Hardt
f7d1432014 Update pythonpublish.yml 2022-11-13 17:41:55 -08:00
Jamie Hardt
db4eadb73e Update python-package.yml
Updated actions/setup-python version pin
2022-11-13 17:37:50 -08:00
Jamie Hardt
3305bc7920 Removing Python 3.5 from support 2022-11-13 17:33:57 -08:00
Jamie Hardt
6ba77b3568 Adding newer python versions
to setup.rb and test grid
2022-11-13 17:32:30 -08:00
Jamie Hardt
c68f8bca80 Fixed spelling of an re 2022-11-13 17:29:10 -08:00
Jamie Hardt
284267c9c0 Fixed some bugs picked up in flake8 2022-11-13 17:25:56 -08:00
Jamie Hardt
bd196f2dbf Create python-package.yml 2022-11-13 17:21:54 -08:00
Jamie Hardt
b14a9a6319 Update .gitignore 2022-01-16 17:16:14 -08:00
Jamie Hardt
0cbd01f418 Update README.md 2020-01-05 14:55:43 -08:00
Jamie Hardt
50d48708e9 .idea files 2020-01-04 22:38:25 -08:00
Jamie Hardt
f67c4ac2c5 Update setup.py 2020-01-04 22:38:15 -08:00
Jamie Hardt
1b8a3c3288 Create pythonpublish.yml 2020-01-04 22:36:54 -08:00
Jamie Hardt
b37b57d7c9 Removed pypi upload code 2020-01-04 22:36:24 -08:00
Jamie Hardt
a9937683e5 Update setup.py 2020-01-03 09:43:38 -08:00
Jamie Hardt
4fae65fa8d Update .travis.yml
Adding python 3.8
2020-01-03 09:42:40 -08:00
Jamie Hardt
566e6257f4 Added 'audio' method to ChannelMap
Added `audio` property to channelmap to test if it contains any audio channels.
2019-08-18 10:57:16 -07:00
Jamie Hardt
c56d2066ad Update setup.py
v1.0.1
2019-08-17 13:06:39 -07:00
Jamie Hardt
8b49a788ae Add version 3.7 support checks 2019-08-17 13:01:51 -07:00
Jamie Hardt
b31450f03d Update README.md
codecov badge
2019-01-05 12:57:16 -08:00
Jamie Hardt
5d14c3177a Update .travis.yml 2019-01-05 12:55:09 -08:00
Jamie Hardt
08dea6031d Update .travis.yml
switch to codecov
2019-01-05 12:49:52 -08:00
Jamie Hardt
d23fa33558 Update setup.py
Added version 2.7
2019-01-04 19:06:46 -08:00
Jamie Hardt
fcc4732d1a Update .travis.yml 2019-01-04 18:14:00 -08:00
Jamie Hardt
47c1ad96f0 Update .travis.yml 2019-01-04 18:01:49 -08:00
Jamie Hardt
804f649570 Configuring Coveralls 2019-01-04 18:01:23 -08:00
Jamie Hardt
58483198c3 Update .travis.yml 2019-01-04 09:12:21 -08:00
Jamie Hardt
aa01e9ad2d Update .travis.yml 2019-01-03 22:03:23 -08:00
Jamie Hardt
464052f510 Update .travis.yml
Adding 2.7 to see what happens
2019-01-03 20:09:39 -08:00
Jamie Hardt
3ba28a61dd Update README.md
Removed "should I use this?"
2019-01-03 19:47:38 -08:00
Jamie Hardt
b80339267a Version 1.0 2019-01-03 19:40:03 -08:00
Jamie Hardt
27d1073f8c Update test_parse.py 2019-01-03 19:35:20 -08:00
Jamie Hardt
0840ade312 Update test_parse.py
One more...
2019-01-03 19:33:26 -08:00
Jamie Hardt
a52e4329ce Update test_parse.py 2019-01-03 19:31:33 -08:00
Jamie Hardt
47772b21d0 Merge branch 'master' of https://github.com/iluvcapra/pycmx 2019-01-03 19:28:50 -08:00
Jamie Hardt
7b4a76448e Changed a format string in the tests so 3.5 should build 2019-01-03 19:28:48 -08:00
Jamie Hardt
c6c5d15e09 Update README.md 2019-01-03 11:13:31 -08:00
Jamie Hardt
c439ea1fbe Update README.md 2019-01-03 11:12:52 -08:00
Jamie Hardt
68f118ab6b Update README.md
Removed Travis badge
2019-01-01 12:25:36 -08:00
Jamie Hardt
a20491297e Update README.md 2018-12-31 12:11:17 -08:00
Jamie Hardt
9d57c2d374 Update README.md 2018-12-31 12:03:37 -08:00
Jamie Hardt
1882cc5308 Can't support 3.7 yet? 2018-12-31 11:59:59 -08:00
Jamie Hardt
c57fe94335 Removed versions I don't support 2018-12-31 11:58:04 -08:00
Jamie Hardt
007661ef38 Merge branch 'master' of https://github.com/iluvcapra/pycmx 2018-12-31 11:52:23 -08:00
Jamie Hardt
f34c6dd4db Update .travis.yml
Added more versions to travis
2018-12-31 11:52:08 -08:00
Jamie Hardt
eb89708bab Update README.md
badges
2018-12-31 11:49:07 -08:00
Jamie Hardt
9fde608fa0 Update setup.py
Added version classifiers
2018-12-31 11:47:34 -08:00
Jamie Hardt
4c4ca428f2 Update README.md
Badges
2018-12-31 11:43:59 -08:00
Jamie Hardt
fe4e3b9d85 Update README.md 2018-12-31 11:35:34 -08:00
Jamie Hardt
119467a884 Update README.md
Added pypi badge
2018-12-31 11:35:11 -08:00
Jamie Hardt
b5a3285e64 Nudge version
Also saved upload command to a script
2018-12-29 16:31:48 -08:00
Jamie Hardt
af1c532a67 Some SourceUMID Impl 2018-12-29 15:16:26 -08:00
25 changed files with 266 additions and 172 deletions

40
.github/workflows/python-package.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
# 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"]
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
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

26
.github/workflows/pythonpublish.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_APIKEY }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*

10
.gitignore vendored
View File

@@ -10,3 +10,13 @@
# Vim Swapfiles # Vim Swapfiles
*.swp *.swp
.DS_Store
venv/
.coverage
lcov.info
# venv
venv/

View File

@@ -1,7 +0,0 @@
language: python
python:
- "3.6"
script:
- "python3 setup.py test"
install:
- "pip3 install setuptools"

13
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,13 @@
# Contributing
Contributions to this project are welcome!
The best way to contribute code to this project is to find this project on [Github][github] and submit a pull request.
## Call for EDLs
If you have EDLs you are having trouble working with becuase of unusual formatting, please send me a copy! Contact us
through [Github].
[github]: https://github.com/iluvcapra/pycmx

View File

@@ -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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,4 +1,6 @@
[![Build Status](https://travis-ci.com/iluvcapra/pycmx.svg?branch=master)](https://travis-ci.com/iluvcapra/pycmx) [![Documentation Status](https://readthedocs.org/projects/pycmx/badge/?version=latest)](https://pycmx.readthedocs.io/en/latest/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/pycmx/badge/?version=latest)](https://pycmx.readthedocs.io/en/latest/?badge=latest) ![](https://img.shields.io/github/license/iluvcapra/pycmx.svg) ![](https://img.shields.io/pypi/pyversions/pycmx.svg) [![](https://img.shields.io/pypi/v/pycmx.svg)](https://pypi.org/project/pycmx/) ![](https://img.shields.io/pypi/wheel/pycmx.svg)
![GitHub last commit](https://img.shields.io/github/last-commit/iluvcapra/pycmx)
[![Lint and Test](https://github.com/iluvcapra/pycmx/actions/workflows/python-package.yml/badge.svg)](https://github.com/iluvcapra/pycmx/actions/workflows/python-package.yml)
# pycmx # pycmx
@@ -82,32 +84,4 @@ Audio channel 7 is present
False 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.

View File

@@ -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

24
docs/source/classes.rst Normal file
View File

@@ -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:

View File

@@ -14,13 +14,13 @@
# #
import os import os
import sys import sys
sys.path.insert(0, os.path.abspath('../../pycmx')) sys.path.insert(0, os.path.abspath('../..'))
print(sys.path)
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = u'pycmx' project = u'pycmx'
copyright = u'2018, Jamie Hardt' copyright = u'2022, Jamie Hardt'
author = u'Jamie Hardt' author = u'Jamie Hardt'
# The short X.Y version # The short X.Y version
@@ -63,7 +63,7 @@ master_doc = 'index'
# #
# This is also used if you do content translation via gettext catalogs. # This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases. # 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 # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.

7
docs/source/function.rst Normal file
View File

@@ -0,0 +1,7 @@
Parse Function
==============
.. autofunction:: pycmx.parse_cmx_events.parse_cmx3600

View File

@@ -8,13 +8,13 @@ Welcome to pycmx's documentation!
.. toctree:: .. toctree::
:maxdepth: 5 :maxdepth: 5
:caption: API Reference: :caption: API Reference
pycmx function
classes
Indices and tables Indices and tables
================== ==================
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex`
* :ref:`search` * :ref:`search`

View File

@@ -1,7 +0,0 @@
pycmx
=====
.. toctree::
:maxdepth: 4
pycmx

View File

@@ -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:

View File

@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*- # -*- 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 examples see README.md
This module (c) 2018 Jamie Hardt. For more information on your rights to 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 copy and reuse this software, refer to the LICENSE file included with the
distribution. distribution.
""" """
__version__ = '0.8' __version__ = '1.1.1'
__author__ = 'Jamie Hardt' __author__ = 'Jamie Hardt'
from .parse_cmx_events import parse_cmx3600 from .parse_cmx_events import parse_cmx3600
from .transition import Transition from .transition import Transition
from .event import Event from .event import Event
from .edit import Edit from .edit import Edit

View File

@@ -2,13 +2,14 @@
# (c) 2018 Jamie Hardt # (c) 2018 Jamie Hardt
from re import (compile, match) from re import (compile, match)
from typing import Dict, Tuple
class ChannelMap: class ChannelMap:
""" """
Represents a set of all the channels to which an event applies. Represents a set of all the channels to which an event applies.
""" """
_chan_map = { _chan_map : Dict[str, Tuple] = {
"V" : (True, False, False), "V" : (True, False, False),
"A" : (False, True, False), "A" : (False, True, False),
"A2" : (False, False, True), "A2" : (False, False, True),
@@ -27,6 +28,11 @@ class ChannelMap:
'True if video is included' 'True if video is included'
return self.v return self.v
@property
def audio(self):
'True if an audio channel is included'
return len(self._audio_channel_set) > 0
@property @property
def channels(self): def channels(self):
'A generator for each audio channel' 'A generator for each audio channel'
@@ -35,7 +41,7 @@ class ChannelMap:
@property @property
def a1(self): def a1(self):
"""True if A1 is included.""" """True if A1 is included"""
return self.get_audio_channel(1) return self.get_audio_channel(1)
@a1.setter @a1.setter
@@ -44,7 +50,7 @@ class ChannelMap:
@property @property
def a2(self): def a2(self):
"""True if A2 is included.""" """True if A2 is included"""
return self.get_audio_channel(2) return self.get_audio_channel(2)
@a2.setter @a2.setter
@@ -53,7 +59,7 @@ class ChannelMap:
@property @property
def a3(self): def a3(self):
"""True if A3 is included.""" """True if A3 is included"""
return self.get_audio_channel(3) return self.get_audio_channel(3)
@a3.setter @a3.setter
@@ -62,7 +68,7 @@ class ChannelMap:
@property @property
def a4(self): def a4(self):
"""True if A4 is included.""" """True if A4 is included"""
return self.get_audio_channel(4) return self.get_audio_channel(4)
@a4.setter @a4.setter
@@ -70,18 +76,18 @@ class ChannelMap:
self.set_audio_channel(4,val) self.set_audio_channel(4,val)
def get_audio_channel(self,chan_num): 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) return (chan_num in self._audio_channel_set)
def set_audio_channel(self,chan_num,enabled): 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: if enabled:
self._audio_channel_set.add(chan_num) self._audio_channel_set.add(chan_num)
elif self.get_audio_channel(chan_num): elif self.get_audio_channel(chan_num):
self._audio_channel_set.remove(chan_num) self._audio_channel_set.remove(chan_num)
def _append_event(self, event_str): 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: if event_str in self._chan_map:
channels = self._chan_map[event_str] channels = self._chan_map[event_str]
self.v = channels[0] self.v = channels[0]
@@ -93,6 +99,15 @@ class ChannelMap:
self.set_audio_channel(int( matchresult.group(1)), True ) self.set_audio_channel(int( matchresult.group(1)), True )
def _append_ext(self, audio_ext): def _append_ext(self, audio_ext):
self.a3 = ext.audio3 self.a3 = audio_ext.audio3
self.a4 = ext.audio4 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)

View File

@@ -1,8 +1,9 @@
# pycmx # pycmx
# (c) 2018 Jamie Hardt # (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 .event import Event
from .channel_map import ChannelMap
class EditList: class EditList:
""" """
@@ -13,13 +14,47 @@ class EditList:
self.title_statement = statements[0] self.title_statement = statements[0]
self.event_statements = statements[1:] 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 @property
def title(self): def title(self):
""" """
The title of this edit list, as attensted by the 'TITLE:' statement on The title of this edit list.
the first line.
""" """
'The title of the edit list'
return self.title_statement.title return self.title_statement.title
@@ -54,8 +89,21 @@ class EditList:
else: else:
event_statements.append(stmt) event_statements.append(stmt)
elif type(stmt) is StmtSourceUMID:
break
else: else:
event_statements.append(stmt) event_statements.append(stmt)
yield Event(statements=event_statements) 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

View File

@@ -14,7 +14,7 @@ def parse_cmx3600(f):
f : a file-like object, anything that's readlines-able. f : a file-like object, anything that's readlines-able.
Returns: Returns:
An :class:`EditList`. An :class:`pycmx.edit_list.EditList`.
""" """
statements = parse_cmx3600_statements(f) statements = parse_cmx3600_statements(f)
return EditList(statements) return EditList(statements)

View File

@@ -11,13 +11,13 @@ from .util import collimate
StmtTitle = namedtuple("Title",["title","line_number"]) StmtTitle = namedtuple("Title",["title","line_number"])
StmtFCM = namedtuple("FCM",["drop","line_number"]) StmtFCM = namedtuple("FCM",["drop","line_number"])
StmtEvent = namedtuple("Event",["event","source","channels","trans",\ 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"]) StmtAudioExt = namedtuple("AudioExt",["audio3","audio4","line_number"])
StmtClipName = namedtuple("ClipName",["name","affect","line_number"]) StmtClipName = namedtuple("ClipName",["name","affect","line_number"])
StmtSourceFile = namedtuple("SourceFile",["filename","line_number"]) StmtSourceFile = namedtuple("SourceFile",["filename","line_number"])
StmtRemark = namedtuple("Remark",["text","line_number"]) StmtRemark = namedtuple("Remark",["text","line_number"])
StmtEffectsName = namedtuple("EffectsName",["name","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"]) StmtSplitEdit = namedtuple("SplitEdit",["video","magnitue", "line_number"])
StmtMotionMemory = namedtuple("MotionMemory",["source","fps"]) # FIXME needs more fields StmtMotionMemory = namedtuple("MotionMemory",["source","fps"]) # FIXME needs more fields
StmtUnrecognized = namedtuple("Unrecognized",["content","line_number"]) 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) return _parse_extended_audio_channels(line,line_number)
elif line.startswith("*"): elif line.startswith("*"):
return _parse_remark( line[1:].strip(), line_number) return _parse_remark( line[1:].strip(), line_number)
elif line.startswith(">>>"): elif line.startswith(">>> SOURCE"):
return _parse_trailer_statement(line, line_number) return _parse_source_umid_statement(line, line_number)
elif line.startswith("EFFECTS NAME IS"): elif line.startswith("EFFECTS NAME IS"):
return _parse_effects_name(line, line_number) return _parse_effects_name(line, line_number)
elif line.startswith("SPLIT:"): 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(), source_out=column_strings[12].strip(),
record_in=column_strings[14].strip(), record_in=column_strings[14].strip(),
record_out=column_strings[16].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() trimmed = line[3:].strip()
return StmtTrailer(trimmed, line_number=line_number) return StmtSourceUMID(name=None, umid=None, line_number=line_number)

View File

@@ -70,12 +70,12 @@ class Transition:
@property @property
def key_background(self): def key_background(self):
"`True` if this edit is a key background." "`True` if this edit is a key background."
return self.transition == KeyBackground return self.transition == Transition.KeyBackground
@property @property
def key_foreground(self): def key_foreground(self):
"`True` if this edit is a key foreground." "`True` if this edit is a key foreground."
return self.transition == Key return self.transition == Transition.Key
@property @property
def key_out(self): def key_out(self):
@@ -83,4 +83,4 @@ class Transition:
`True` if this edit is a key out. This material will removed from `True` if this edit is a key out. This material will removed from
the key foreground and replaced with the key background. the key foreground and replaced with the key background.
""" """
return self.transition == KeyOut return self.transition == Transition.KeyOut

9
requirements.txt Normal file
View File

@@ -0,0 +1,9 @@
attrs==22.1.0
coverage==6.5.0
exceptiongroup==1.0.4
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
pyparsing==3.0.9
pytest==7.2.0
tomli==2.0.1

View File

@@ -4,16 +4,30 @@ with open("README.md", "r") as fh:
long_description = fh.read() long_description = fh.read()
setup(name='pycmx', setup(name='pycmx',
version='0.8', version='1.1.5',
author='Jamie Hardt', author='Jamie Hardt',
author_email='jamiehardt@me.com', author_email='jamiehardt@me.com',
description='CMX 3600 Edit Decision List Parser', description='CMX 3600 Edit Decision List Parser',
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
long_description=long_description, long_description=long_description,
project_urls={
'Source':
'https://github.com/iluvcapra/pycmx',
'Documentation':
'https://pycmx.readthedocs.io/',
'Issues':
'https://github.com/iluvcapra/pycmx/issues',
},
url='https://github.com/iluvcapra/pycmx', url='https://github.com/iluvcapra/pycmx',
classifiers=['Development Status :: 4 - Beta', classifiers=['Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
'Topic :: Multimedia', 'Topic :: Multimedia',
'Topic :: Multimedia :: Video', 'Topic :: Multimedia :: Video',
'Topic :: Text Processing'], 'Topic :: Text Processing',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10'
],
packages=['pycmx']) packages=['pycmx'])

1
tests/__init__.py Normal file
View File

@@ -0,0 +1 @@
from . import test_parse

View File

@@ -18,22 +18,24 @@ class TestParse(TestCase):
counts = [ 287, 466, 250 , 376, 120 , 3 , 466 ] counts = [ 287, 466, 250 , 376, 120 , 3 , 466 ]
for fn, count in zip(type(self).files, counts): 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) edl = pycmx.parse_cmx3600(f)
actual = len( list( edl.events )) 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): def test_list_sanity(self):
for fn in type(self).files: 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) edl = pycmx.parse_cmx3600(f)
self.assertTrue( type(edl.title) is str ) self.assertTrue( type(edl.title) is str )
self.assertTrue( len(edl.title) > 0 ) self.assertTrue( len(edl.title) > 0 )
def test_event_sanity(self): def test_event_sanity(self):
for fn in type(self).files: 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) edl = pycmx.parse_cmx3600(f)
for index, event in enumerate(edl.events): for index, event in enumerate(edl.events):
self.assertTrue( len(event.edits) > 0 ) self.assertTrue( len(event.edits) > 0 )
@@ -45,7 +47,7 @@ class TestParse(TestCase):
with open("tests/edls/TEST.edl",'r') as f: with open("tests/edls/TEST.edl",'r') as f:
edl = pycmx.parse_cmx3600(f) edl = pycmx.parse_cmx3600(f)
events = list( edl.events ) events = list( edl.events )
self.assertEqual( events[0].number , 1) self.assertEqual( events[0].number , 1)
self.assertEqual( events[0].edits[0].source , "OY_HEAD_") 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].clip_name , "HEAD LEADER MONO")
@@ -64,6 +66,7 @@ class TestParse(TestCase):
self.assertFalse( events[0].edits[0].channels.a1) self.assertFalse( events[0].edits[0].channels.a1)
self.assertTrue( events[0].edits[0].channels.a2) 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.get_audio_channel(7) )
self.assertTrue( events[2].edits[0].channels.audio)
def test_multi_edit_events(self): def test_multi_edit_events(self):
@@ -104,3 +107,7 @@ class TestParse(TestCase):
edl = pycmx.parse_cmx3600(f) edl = pycmx.parse_cmx3600(f)
events = list(edl.events) events = list(edl.events)
self.assertEqual( events[4].edits[1].transition.name , "CROSS DISSOLVE" ) self.assertEqual( events[4].edits[1].transition.name , "CROSS DISSOLVE" )
# add test for edit_list.channels