35 Commits
v0.8 ... v1.0.1

Author SHA1 Message Date
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
10 changed files with 110 additions and 55 deletions

View File

@@ -1,7 +1,17 @@
language: python
python:
- "3.7"
- "3.6"
- "3.5"
- "2.7"
script:
- "python3 setup.py test"
- "python setup.py test"
- "py.test tests/ -v --cov pycmx --cov-report term-missing"
install:
- "pip3 install setuptools"
- "pip install setuptools"
before_install:
- "pip install coverage"
- "pip install codecov"
- "pip install pytest-cov==2.5.0"
after_success:
- "codecov"

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)
[![Build Status](https://travis-ci.com/iluvcapra/pycmx.svg?branch=master)](https://travis-ci.com/iluvcapra/pycmx)
[![codecov](https://codecov.io/gh/iluvcapra/pycmx/branch/master/graph/badge.svg)](https://codecov.io/gh/iluvcapra/pycmx)
[![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)
# pycmx
@@ -81,33 +83,3 @@ Audio channel 7 is present
>>> events[2].edits[0].channels.video
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!

View File

@@ -1,17 +1,17 @@
# -*- 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) 2018 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'
__version__ = '1.0'
__author__ = 'Jamie Hardt'
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

View File

@@ -96,3 +96,11 @@ class ChannelMap:
self.a3 = ext.audio3
self.a4 = ext.audio4
def __or__(self, other):
"""
Return 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
# (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,6 +14,42 @@ 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):
"""
@@ -54,8 +91,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

View File

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

2
pypi_upload.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
python3 -m twine upload --repository-url https://upload.pypi.org/legacy/ dist/*

View File

@@ -4,16 +4,21 @@ with open("README.md", "r") as fh:
long_description = fh.read()
setup(name='pycmx',
version='0.8',
version='1.0.1',
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',
classifiers=['Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: MIT License',
'Topic :: Multimedia',
'Topic :: Multimedia :: Video',
'Topic :: Text Processing'],
'Topic :: Text Processing',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
],
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 ]
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")
@@ -104,3 +106,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