Skip to content

Commit 6ef21be

Browse files
douglascometMichaelPlug
authored andcommitted
Add support for Premiere styled EDLs to the CMX 3600 adapter (AcademySoftwareFoundation#1199)
* 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 <[email protected]> Signed-off-by: Michele Spina <[email protected]>
1 parent f859ca9 commit 6ef21be

File tree

4 files changed

+172
-58
lines changed

4 files changed

+172
-58
lines changed

src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,16 @@ class EDLParseError(exceptions.OTIOError):
6464
# the comment string for the media reference:
6565
# 'avid': '* FROM CLIP:' (default)
6666
# 'nucoda': '* FROM FILE:'
67+
# 'premiere': None (If Adobe Premiere imports an EDL that uses
68+
# a "FROM" comment will result in the clips
69+
# being named UNKNOWN instead of using the reel or file name)
6770
# When adding a new style, please be sure to add sufficient tests
6871
# to verify both the new and existing styles.
69-
VALID_EDL_STYLES = ['avid', 'nucoda']
72+
VALID_EDL_STYLES = {
73+
'avid': 'CLIP',
74+
'nucoda': 'FILE',
75+
'premiere': None,
76+
}
7077

7178

7279
def _extend_source_range_duration(obj, duration):
@@ -719,6 +726,7 @@ class CommentHandler:
719726
('ASC_SAT', 'asc_sat'),
720727
('M2', 'motion_effect'),
721728
('\\* FREEZE FRAME', 'freeze_frame'),
729+
('\\* OTIO REFERENCE [a-zA-Z]+', 'media_reference'),
722730
])
723731

724732
def __init__(self, comments):
@@ -972,7 +980,13 @@ def __init__(
972980
reelname_len
973981
):
974982

975-
line = EventLine(kind, rate, reel=_reel_from_clip(clip, reelname_len))
983+
# Premiere style uses AX for the reel name
984+
if style == 'premiere':
985+
reel = 'AX'
986+
else:
987+
reel = _reel_from_clip(clip, reelname_len)
988+
989+
line = EventLine(kind, rate, reel=reel)
976990
line.source_in = clip.source_range.start_time
977991
line.source_out = clip.source_range.end_time_exclusive()
978992

@@ -1212,6 +1226,10 @@ def _generate_comment_lines(
12121226
elif hasattr(clip.media_reference, 'abstract_target_url'):
12131227
url = _get_image_sequence_url(clip)
12141228

1229+
if url:
1230+
# Premiere style uses the base name of the media reference
1231+
if style == 'premiere':
1232+
clip.name = os.path.basename(clip.media_reference.target_url)
12151233
else:
12161234
url = clip.name
12171235

@@ -1240,28 +1258,37 @@ def _generate_comment_lines(
12401258
lines.append(
12411259
"* {from_or_to} CLIP NAME: {name}{suffix}".format(
12421260
from_or_to=from_or_to,
1243-
name=clip.name,
1261+
name=os.path.basename(url) if style == 'premiere' else clip.name,
12441262
suffix=suffix
12451263
)
12461264
)
12471265
if timing_effect and timing_effect.effect_name == "FreezeFrame":
12481266
lines.append('* * FREEZE FRAME')
1249-
if url and style == 'avid':
1250-
lines.append("* {from_or_to} CLIP: {url}".format(
1251-
from_or_to=from_or_to,
1252-
url=url
1253-
))
1254-
if url and style == 'nucoda':
1255-
lines.append("* {from_or_to} FILE: {url}".format(
1256-
from_or_to=from_or_to,
1257-
url=url
1258-
))
1267+
1268+
# If the style has a spec, apply it and add it as a comment
1269+
style_spec = VALID_EDL_STYLES.get(style)
1270+
if url:
1271+
if style_spec:
1272+
lines.append("* {from_or_to} {style_spec}: {url}".format(
1273+
from_or_to=from_or_to,
1274+
style_spec=style_spec,
1275+
url=_flip_windows_slashes(url)
1276+
))
1277+
else:
1278+
lines.append("* OTIO REFERENCE {from_or_to}: {url}".format(
1279+
from_or_to=from_or_to,
1280+
url=_flip_windows_slashes(url)
1281+
))
12591282

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

1288+
if style == 'premiere':
1289+
clip.metadata.setdefault('cmx_3600', {})
1290+
clip.metadata['cmx_3600'].update({'reel': 'AX'})
1291+
12651292
cdl = clip.metadata.get('cdl')
12661293
if cdl:
12671294
asc_sop = cdl.get('asc_sop')

tests/sample_data/avid_example.edl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
TITLE: Avid_Example.01
2+
001 ZZ100_50 V C 01:00:04:05 01:00:05:12 00:59:53:11 00:59:54:18
3+
* FROM CLIP NAME: take_1
4+
* FROM CLIP: S:\path\to\ZZ100_501.take_1.0001.exr
5+
002 ZZ100_50 V C 01:00:06:13 01:00:08:15 00:59:54:18 00:59:56:20
6+
* FROM CLIP NAME: take_2
7+
* FROM CLIP: S:\path\to\ZZ100_502A.take_2.0101.exr
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
TITLE: Premiere_Example.01
2+
001 AX V C 01:00:04:05 01:00:05:12 00:59:53:11 00:59:54:18
3+
* FROM CLIP NAME: ZZ100_501.take_1.0001.exr
4+
002 AX V C 01:00:06:13 01:00:08:15 00:59:54:18 00:59:56:20
5+
* FROM CLIP NAME: ZZ100_502A.take_2.0101.exr

tests/test_cmx_3600_adapter.py

Lines changed: 120 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616
from tempfile import TemporaryDirectory # noqa: F401
1717
import tempfile
1818

19-
2019
SAMPLE_DATA_DIR = os.path.join(os.path.dirname(__file__), "sample_data")
2120
SCREENING_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "screening_example.edl")
21+
AVID_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "avid_example.edl")
2222
NUCODA_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "nucoda_example.edl")
23+
PREMIERE_EXAMPLE_PATH = os.path.join(SAMPLE_DATA_DIR, "premiere_example.edl")
2324
EXEMPLE_25_FPS_PATH = os.path.join(SAMPLE_DATA_DIR, "25fps.edl")
2425
NO_SPACES_PATH = os.path.join(SAMPLE_DATA_DIR, "no_spaces_test.edl")
2526
DISSOLVE_TEST = os.path.join(SAMPLE_DATA_DIR, "dissolve_test.edl")
@@ -30,10 +31,7 @@
3031
WIPE_TEST = os.path.join(SAMPLE_DATA_DIR, "wipe_test.edl")
3132
TIMECODE_MISMATCH_TEST = os.path.join(SAMPLE_DATA_DIR, "timecode_mismatch.edl")
3233
SPEED_EFFECTS_TEST = os.path.join(SAMPLE_DATA_DIR, "speed_effects.edl")
33-
SPEED_EFFECTS_TEST_SMALL = os.path.join(
34-
SAMPLE_DATA_DIR,
35-
"speed_effects_small.edl"
36-
)
34+
SPEED_EFFECTS_TEST_SMALL = os.path.join(SAMPLE_DATA_DIR, "speed_effects_small.edl")
3735
MULTIPLE_TARGET_AUDIO_PATH = os.path.join(SAMPLE_DATA_DIR, "multi_audio.edl")
3836
TRANSITION_DURATION_TEST = os.path.join(SAMPLE_DATA_DIR, "transition_duration.edl")
3937
ENABLED_TEST = os.path.join(SAMPLE_DATA_DIR, "enabled.otio")
@@ -714,47 +712,81 @@ def test_read_generators(self):
714712
'SMPTEBars'
715713
)
716714

717-
def test_nucoda_edl_read(self):
718-
edl_path = NUCODA_EXAMPLE_PATH
719-
fps = 24
720-
timeline = otio.adapters.read_from_file(edl_path)
721-
self.assertTrue(timeline is not None)
722-
self.assertEqual(len(timeline.tracks), 1)
723-
self.assertEqual(len(timeline.tracks[0]), 2)
724-
self.assertEqual(
725-
timeline.tracks[0][0].name,
726-
"take_1"
727-
)
728-
self.assertEqual(
729-
timeline.tracks[0][0].source_range.duration,
730-
otio.opentime.from_timecode("00:00:01:07", fps)
731-
)
732-
self.assertIsOTIOEquivalentTo(
733-
timeline.tracks[0][0].media_reference,
734-
otio.schema.ExternalReference(
735-
target_url=r"S:\path\to\ZZ100_501.take_1.0001.exr"
715+
def test_style_edl_read(self):
716+
edl_paths = [AVID_EXAMPLE_PATH, NUCODA_EXAMPLE_PATH, PREMIERE_EXAMPLE_PATH]
717+
for edl_path in edl_paths:
718+
fps = 24
719+
timeline = otio.adapters.read_from_file(edl_path)
720+
self.assertTrue(timeline is not None)
721+
self.assertEqual(len(timeline.tracks), 1)
722+
self.assertEqual(len(timeline.tracks[0]), 2)
723+
print(edl_path)
724+
725+
# If cannot assertEqual fails with clip name
726+
# Attempt to assertEqual with
727+
try:
728+
self.assertEqual(
729+
timeline.tracks[0][0].name,
730+
"take_1"
731+
)
732+
except AssertionError:
733+
self.assertEqual(
734+
timeline.tracks[0][0].name,
735+
"ZZ100_501.take_1.0001.exr"
736+
)
737+
self.assertEqual(
738+
timeline.tracks[0][0].source_range.duration,
739+
otio.opentime.from_timecode("00:00:01:07", fps)
736740
)
737-
)
738-
self.assertEqual(
739-
timeline.tracks[0][1].name,
740-
"take_2"
741-
)
742-
self.assertEqual(
743-
timeline.tracks[0][1].source_range.duration,
744-
otio.opentime.from_timecode("00:00:02:02", fps)
745-
)
746-
self.assertIsOTIOEquivalentTo(
747-
timeline.tracks[0][1].media_reference,
748-
otio.schema.ExternalReference(
749-
target_url=r"S:\path\to\ZZ100_502A.take_2.0101.exr"
741+
print(timeline.tracks[0][0].media_reference)
742+
743+
try:
744+
self.assertIsOTIOEquivalentTo(
745+
timeline.tracks[0][0].media_reference,
746+
otio.schema.ExternalReference(
747+
target_url=r"S:\path\to\ZZ100_501.take_1.0001.exr"
748+
)
749+
)
750+
except AssertionError:
751+
self.assertIsOTIOEquivalentTo(
752+
timeline.tracks[0][0].media_reference,
753+
otio.schema.MissingReference()
754+
)
755+
756+
try:
757+
self.assertEqual(
758+
timeline.tracks[0][1].name,
759+
"take_2"
760+
)
761+
except AssertionError:
762+
self.assertEqual(
763+
timeline.tracks[0][1].name,
764+
"ZZ100_502A.take_2.0101.exr"
765+
)
766+
767+
self.assertEqual(
768+
timeline.tracks[0][1].source_range.duration,
769+
otio.opentime.from_timecode("00:00:02:02", fps)
750770
)
751-
)
752771

753-
def test_nucoda_edl_write(self):
772+
try:
773+
self.assertIsOTIOEquivalentTo(
774+
timeline.tracks[0][1].media_reference,
775+
otio.schema.ExternalReference(
776+
target_url=r"S:\path\to\ZZ100_502A.take_2.0101.exr"
777+
)
778+
)
779+
except AssertionError:
780+
self.assertIsOTIOEquivalentTo(
781+
timeline.tracks[0][1].media_reference,
782+
otio.schema.MissingReference()
783+
)
784+
785+
def test_style_edl_write(self):
754786
track = otio.schema.Track()
755-
tl = otio.schema.Timeline("test_nucoda_timeline", tracks=[track])
787+
tl = otio.schema.Timeline("temp", tracks=[track])
756788
rt = otio.opentime.RationalTime(5.0, 24.0)
757-
mr = otio.schema.ExternalReference(target_url=r"S:\var\tmp\test.exr")
789+
mr = otio.schema.ExternalReference(target_url=r"S:/var/tmp/test.exr")
758790

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

816+
tl.name = 'test_nucoda_timeline'
784817
result = otio.adapters.write_to_string(
785818
tl,
786819
adapter_name='cmx_3600',
@@ -791,11 +824,53 @@ def test_nucoda_edl_write(self):
791824
792825
001 test V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
793826
* FROM CLIP NAME: test clip1
794-
* FROM FILE: S:\var\tmp\test.exr
827+
* FROM FILE: S:/var/tmp/test.exr
828+
* OTIO TRUNCATED REEL NAME FROM: test.exr
829+
002 test V C 00:00:00:00 00:00:00:05 00:00:01:05 00:00:01:10
830+
* FROM CLIP NAME: test clip2
831+
* FROM FILE: S:/var/tmp/test.exr
832+
* OTIO TRUNCATED REEL NAME FROM: test.exr
833+
'''
834+
835+
self.assertMultiLineEqual(result, expected)
836+
837+
tl.name = 'test_avid_timeline'
838+
result = otio.adapters.write_to_string(
839+
tl,
840+
adapter_name='cmx_3600',
841+
style='avid'
842+
)
843+
844+
expected = r'''TITLE: test_avid_timeline
845+
846+
001 test V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
847+
* FROM CLIP NAME: test clip1
848+
* FROM CLIP: S:/var/tmp/test.exr
795849
* OTIO TRUNCATED REEL NAME FROM: test.exr
796850
002 test V C 00:00:00:00 00:00:00:05 00:00:01:05 00:00:01:10
797851
* FROM CLIP NAME: test clip2
798-
* FROM FILE: S:\var\tmp\test.exr
852+
* FROM CLIP: S:/var/tmp/test.exr
853+
* OTIO TRUNCATED REEL NAME FROM: test.exr
854+
'''
855+
856+
self.assertMultiLineEqual(result, expected)
857+
858+
tl.name = 'test_premiere_timeline'
859+
result = otio.adapters.write_to_string(
860+
tl,
861+
adapter_name='cmx_3600',
862+
style='premiere'
863+
)
864+
865+
expected = r'''TITLE: test_premiere_timeline
866+
867+
001 AX V C 00:00:00:00 00:00:00:05 00:00:00:00 00:00:00:05
868+
* FROM CLIP NAME: test.exr
869+
* OTIO REFERENCE FROM: S:/var/tmp/test.exr
870+
* OTIO TRUNCATED REEL NAME FROM: test.exr
871+
002 AX V C 00:00:00:00 00:00:00:05 00:00:01:05 00:00:01:10
872+
* FROM CLIP NAME: test.exr
873+
* OTIO REFERENCE FROM: S:/var/tmp/test.exr
799874
* OTIO TRUNCATED REEL NAME FROM: test.exr
800875
'''
801876

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

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

0 commit comments

Comments
 (0)