Skip to content

Commit

Permalink
Add support for Premiere styled EDLs to the CMX 3600 adapter (#1199)
Browse files Browse the repository at this point in the history
* Converted VALID_EDL_STYLES into a dict to consolidate how a style is applied and support a null value for the added Premiere style
* Update style spec and added fallback OTIO comment if style does not have a spec
* Added sample edl for avid and premiere

Signed-off-by: Doug Halley <douglascomet@gmail.com>
  • Loading branch information
douglascomet committed Nov 14, 2022
1 parent 5e27e78 commit 0c80e91
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 58 deletions.
53 changes: 40 additions & 13 deletions src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,16 @@ class EDLParseError(exceptions.OTIOError):
# the comment string for the media reference:
# 'avid': '* FROM CLIP:' (default)
# 'nucoda': '* FROM FILE:'
# 'premiere': None (If Adobe Premiere imports an EDL that uses
# a "FROM" comment will result in the clips
# being named UNKNOWN instead of using the reel or file name)
# When adding a new style, please be sure to add sufficient tests
# to verify both the new and existing styles.
VALID_EDL_STYLES = ['avid', 'nucoda']
VALID_EDL_STYLES = {
'avid': 'CLIP',
'nucoda': 'FILE',
'premiere': None,
}


def _extend_source_range_duration(obj, duration):
Expand Down Expand Up @@ -719,6 +726,7 @@ class CommentHandler:
('ASC_SAT', 'asc_sat'),
('M2', 'motion_effect'),
('\\* FREEZE FRAME', 'freeze_frame'),
('\\* OTIO REFERENCE [a-zA-Z]+', 'media_reference'),
])

def __init__(self, comments):
Expand Down Expand Up @@ -972,7 +980,13 @@ def __init__(
reelname_len
):

line = EventLine(kind, rate, reel=_reel_from_clip(clip, reelname_len))
# Premiere style uses AX for the reel name
if style == 'premiere':
reel = 'AX'
else:
reel = _reel_from_clip(clip, reelname_len)

line = EventLine(kind, rate, reel=reel)
line.source_in = clip.source_range.start_time
line.source_out = clip.source_range.end_time_exclusive()

Expand Down Expand Up @@ -1212,6 +1226,10 @@ def _generate_comment_lines(
elif hasattr(clip.media_reference, 'abstract_target_url'):
url = _get_image_sequence_url(clip)

if url:
# Premiere style uses the base name of the media reference
if style == 'premiere':
clip.name = os.path.basename(clip.media_reference.target_url)
else:
url = clip.name

Expand Down Expand Up @@ -1240,28 +1258,37 @@ def _generate_comment_lines(
lines.append(
"* {from_or_to} CLIP NAME: {name}{suffix}".format(
from_or_to=from_or_to,
name=clip.name,
name=os.path.basename(url) if style == 'premiere' else clip.name,
suffix=suffix
)
)
if timing_effect and timing_effect.effect_name == "FreezeFrame":
lines.append('* * FREEZE FRAME')
if url and style == 'avid':
lines.append("* {from_or_to} CLIP: {url}".format(
from_or_to=from_or_to,
url=url
))
if url and style == 'nucoda':
lines.append("* {from_or_to} FILE: {url}".format(
from_or_to=from_or_to,
url=url
))

# If the style has a spec, apply it and add it as a comment
style_spec = VALID_EDL_STYLES.get(style)
if url:
if style_spec:
lines.append("* {from_or_to} {style_spec}: {url}".format(
from_or_to=from_or_to,
style_spec=style_spec,
url=_flip_windows_slashes(url)
))
else:
lines.append("* OTIO REFERENCE {from_or_to}: {url}".format(
from_or_to=from_or_to,
url=_flip_windows_slashes(url)
))

if reelname_len and not clip.metadata.get('cmx_3600', {}).get('reel'):
lines.append("* OTIO TRUNCATED REEL NAME FROM: {url}".format(
url=os.path.basename(_flip_windows_slashes(url or clip.name))
))

if style == 'premiere':
clip.metadata.setdefault('cmx_3600', {})
clip.metadata['cmx_3600'].update({'reel': 'AX'})

cdl = clip.metadata.get('cdl')
if cdl:
asc_sop = cdl.get('asc_sop')
Expand Down
7 changes: 7 additions & 0 deletions tests/sample_data/avid_example.edl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
TITLE: Avid_Example.01
001 ZZ100_50 V C 01:00:04:05 01:00:05:12 00:59:53:11 00:59:54:18
* FROM CLIP NAME: take_1
* FROM CLIP: S:\path\to\ZZ100_501.take_1.0001.exr
002 ZZ100_50 V C 01:00:06:13 01:00:08:15 00:59:54:18 00:59:56:20
* FROM CLIP NAME: take_2
* FROM CLIP: S:\path\to\ZZ100_502A.take_2.0101.exr
5 changes: 5 additions & 0 deletions tests/sample_data/premiere_example.edl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
TITLE: Premiere_Example.01
001 AX V C 01:00:04:05 01:00:05:12 00:59:53:11 00:59:54:18
* FROM CLIP NAME: ZZ100_501.take_1.0001.exr
002 AX V C 01:00:06:13 01:00:08:15 00:59:54:18 00:59:56:20
* FROM CLIP NAME: ZZ100_502A.take_2.0101.exr
165 changes: 120 additions & 45 deletions tests/test_cmx_3600_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
from tempfile import TemporaryDirectory # noqa: F401
import tempfile


SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data")
SCREENING_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "screening_example.edl")
AVID_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "avid_example.edl")
NUCODA_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "nucoda_example.edl")
PREMIERE_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "premiere_example.edl")
EXEMPLE_25_FPS_PATH = os.path.join(SAMPLE_DATA_DIR, "25fps.edl")
NO_SPACES_PATH = os.path.join(SAMPLE_DATA_DIR, "no_spaces_test.edl")
DISSOLVE_TEST = os.path.join(SAMPLE_DATA_DIR, "dissolve_test.edl")
Expand All @@ -30,10 +31,7 @@
WIPE_TEST = os.path.join(SAMPLE_DATA_DIR, "wipe_test.edl")
TIMECODE_MISMATCH_TEST = os.path.join(SAMPLE_DATA_DIR, "timecode_mismatch.edl")
SPEED_EFFECTS_TEST = os.path.join(SAMPLE_DATA_DIR, "speed_effects.edl")
SPEED_EFFECTS_TEST_SMALL = os.path.join(
SAMPLE_DATA_DIR,
"speed_effects_small.edl"
)
SPEED_EFFECTS_TEST_SMALL = os.path.join(SAMPLE_DATA_DIR, "speed_effects_small.edl")
MULTIPLE_TARGET_AUDIO_PATH = os.path.join(SAMPLE_DATA_DIR, "multi_audio.edl")
TRANSITION_DURATION_TEST = os.path.join(SAMPLE_DATA_DIR, "transition_duration.edl")
ENABLED_TEST = os.path.join(SAMPLE_DATA_DIR, "enabled.otio")
Expand Down Expand Up @@ -714,47 +712,81 @@ def test_read_generators(self):
'SMPTEBars'
)

def test_nucoda_edl_read(self):
edl_path = NUCODA_EXAMPLE_PATH
fps = 24
timeline = otio.adapters.read_from_file(edl_path)
self.assertTrue(timeline is not None)
self.assertEqual(len(timeline.tracks), 1)
self.assertEqual(len(timeline.tracks[0]), 2)
self.assertEqual(
timeline.tracks[0][0].name,
"take_1"
)
self.assertEqual(
timeline.tracks[0][0].source_range.duration,
otio.opentime.from_timecode("00:00:01:07", fps)
)
self.assertIsOTIOEquivalentTo(
timeline.tracks[0][0].media_reference,
otio.schema.ExternalReference(
target_url=r"S:\path\to\ZZ100_501.take_1.0001.exr"
def test_style_edl_read(self):
edl_paths = [AVID_EXAMPLE_PATH, NUCODA_EXAMPLE_PATH, PREMIERE_EXAMPLE_PATH]
for edl_path in edl_paths:
fps = 24
timeline = otio.adapters.read_from_file(edl_path)
self.assertTrue(timeline is not None)
self.assertEqual(len(timeline.tracks), 1)
self.assertEqual(len(timeline.tracks[0]), 2)
print(edl_path)

# If cannot assertEqual fails with clip name
# Attempt to assertEqual with
try:
self.assertEqual(
timeline.tracks[0][0].name,
"take_1"
)
except AssertionError:
self.assertEqual(
timeline.tracks[0][0].name,
"ZZ100_501.take_1.0001.exr"
)
self.assertEqual(
timeline.tracks[0][0].source_range.duration,
otio.opentime.from_timecode("00:00:01:07", fps)
)
)
self.assertEqual(
timeline.tracks[0][1].name,
"take_2"
)
self.assertEqual(
timeline.tracks[0][1].source_range.duration,
otio.opentime.from_timecode("00:00:02:02", fps)
)
self.assertIsOTIOEquivalentTo(
timeline.tracks[0][1].media_reference,
otio.schema.ExternalReference(
target_url=r"S:\path\to\ZZ100_502A.take_2.0101.exr"
print(timeline.tracks[0][0].media_reference)

try:
self.assertIsOTIOEquivalentTo(
timeline.tracks[0][0].media_reference,
otio.schema.ExternalReference(
target_url=r"S:\path\to\ZZ100_501.take_1.0001.exr"
)
)
except AssertionError:
self.assertIsOTIOEquivalentTo(
timeline.tracks[0][0].media_reference,
otio.schema.MissingReference()
)

try:
self.assertEqual(
timeline.tracks[0][1].name,
"take_2"
)
except AssertionError:
self.assertEqual(
timeline.tracks[0][1].name,
"ZZ100_502A.take_2.0101.exr"
)

self.assertEqual(
timeline.tracks[0][1].source_range.duration,
otio.opentime.from_timecode("00:00:02:02", fps)
)
)

def test_nucoda_edl_write(self):
try:
self.assertIsOTIOEquivalentTo(
timeline.tracks[0][1].media_reference,
otio.schema.ExternalReference(
target_url=r"S:\path\to\ZZ100_502A.take_2.0101.exr"
)
)
except AssertionError:
self.assertIsOTIOEquivalentTo(
timeline.tracks[0][1].media_reference,
otio.schema.MissingReference()
)

def test_style_edl_write(self):
track = otio.schema.Track()
tl = otio.schema.Timeline("test_nucoda_timeline", tracks=[track])
tl = otio.schema.Timeline("temp", tracks=[track])
rt = otio.opentime.RationalTime(5.0, 24.0)
mr = otio.schema.ExternalReference(target_url=r"S:\var\tmp\test.exr")
mr = otio.schema.ExternalReference(target_url=r"S:/var/tmp/test.exr")

tr = otio.opentime.TimeRange(
start_time=otio.opentime.RationalTime(0.0, 24.0),
Expand All @@ -781,6 +813,7 @@ def test_nucoda_edl_write(self):
tl.tracks[0].append(gap)
tl.tracks[0].append(cl2)

tl.name = 'test_nucoda_timeline'
result = otio.adapters.write_to_string(
tl,
adapter_name='cmx_3600',
Expand All @@ -791,11 +824,53 @@ def test_nucoda_edl_write(self):
001 test V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
* FROM CLIP NAME: test clip1
* FROM FILE: S:\var\tmp\test.exr
* FROM FILE: S:/var/tmp/test.exr
* OTIO TRUNCATED REEL NAME FROM: test.exr
002 test V C 00:00:00:00 00:00:00:05 00:00:01:05 00:00:01:10
* FROM CLIP NAME: test clip2
* FROM FILE: S:/var/tmp/test.exr
* OTIO TRUNCATED REEL NAME FROM: test.exr
'''

self.assertMultiLineEqual(result, expected)

tl.name = 'test_avid_timeline'
result = otio.adapters.write_to_string(
tl,
adapter_name='cmx_3600',
style='avid'
)

expected = r'''TITLE: test_avid_timeline
001 test V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
* FROM CLIP NAME: test clip1
* FROM CLIP: S:/var/tmp/test.exr
* OTIO TRUNCATED REEL NAME FROM: test.exr
002 test V C 00:00:00:00 00:00:00:05 00:00:01:05 00:00:01:10
* FROM CLIP NAME: test clip2
* FROM FILE: S:\var\tmp\test.exr
* FROM CLIP: S:/var/tmp/test.exr
* OTIO TRUNCATED REEL NAME FROM: test.exr
'''

self.assertMultiLineEqual(result, expected)

tl.name = 'test_premiere_timeline'
result = otio.adapters.write_to_string(
tl,
adapter_name='cmx_3600',
style='premiere'
)

expected = r'''TITLE: test_premiere_timeline
001 AX V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
* FROM CLIP NAME: test.exr
* OTIO REFERENCE FROM: S:/var/tmp/test.exr
* OTIO TRUNCATED REEL NAME FROM: test.exr
002 AX V C 00:00:00:00 00:00:00:05 00:00:01:05 00:00:01:10
* FROM CLIP NAME: test.exr
* OTIO REFERENCE FROM: S:/var/tmp/test.exr
* OTIO TRUNCATED REEL NAME FROM: test.exr
'''

Expand All @@ -807,10 +882,10 @@ def test_reels_edl_round_trip_string2mem2string(self):
001 ZZ100_50 V C 01:00:04:05 01:00:05:12 00:59:53:11 00:59:54:18
* FROM CLIP NAME: take_1
* FROM FILE: S:\path\to\ZZ100_501.take_1.0001.exr
* FROM FILE: S:/path/to/ZZ100_501.take_1.0001.exr
002 ZZ100_50 V C 01:00:06:13 01:00:08:15 00:59:54:18 00:59:56:20
* FROM CLIP NAME: take_2
* FROM FILE: S:\path\to\ZZ100_502A.take_2.0101.exr
* FROM FILE: S:/path/to/ZZ100_502A.take_2.0101.exr
'''

timeline = otio.adapters.read_from_string(sample_data, adapter_name="cmx_3600")
Expand Down

0 comments on commit 0c80e91

Please sign in to comment.