From 344198bc6682efc8cbcb3875d69de1e8af27b673 Mon Sep 17 00:00:00 2001 From: Eric Reinecke Date: Tue, 4 Feb 2020 12:33:08 -0800 Subject: [PATCH] Fixed issue where CMX3600 adapter would try to add the same clip to multiple tracks. Also moved some code out of a loop it didn't need to be in. --- .../opentimelineio/adapters/cmx_3600.py | 114 +++++++++--------- tests/sample_data/multi_audio.edl | 6 + tests/test_cmx_3600_adapter.py | 19 +++ 3 files changed, 85 insertions(+), 54 deletions(-) create mode 100644 tests/sample_data/multi_audio.edl diff --git a/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py b/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py index b606447139..475c8c0951 100644 --- a/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py +++ b/src/py-opentimelineio/opentimelineio/adapters/cmx_3600.py @@ -33,6 +33,7 @@ # TODO: currently tracks with linked audio/video will lose their linkage when # read into OTIO. +import copy import os import re import math @@ -137,62 +138,67 @@ def add_clip(self, line, comments, rate=24): clip_handler.transition_data ) - tracks = self.tracks_for_channel(clip_handler.channel_code) - for track in tracks: + edl_rate = clip_handler.edl_rate + record_in = opentime.from_timecode( + clip_handler.record_tc_in, + edl_rate + ) + record_out = opentime.from_timecode( + clip_handler.record_tc_out, + edl_rate + ) - edl_rate = clip_handler.edl_rate - record_in = opentime.from_timecode( - clip_handler.record_tc_in, - edl_rate - ) - record_out = opentime.from_timecode( - clip_handler.record_tc_out, - edl_rate - ) + src_duration = clip.duration() + rec_duration = record_out - record_in + if rec_duration != src_duration: + motion = comment_handler.handled.get('motion_effect') + freeze = comment_handler.handled.get('freeze_frame') + if motion is not None or freeze is not None: + # Adjust the clip to match the record duration + clip.source_range = opentime.TimeRange( + start_time=clip.source_range.start_time, + duration=rec_duration + ) - src_duration = clip.duration() - rec_duration = record_out - record_in - if rec_duration != src_duration: - motion = comment_handler.handled.get('motion_effect') - freeze = comment_handler.handled.get('freeze_frame') - if motion is not None or freeze is not None: - # Adjust the clip to match the record duration - clip.source_range = opentime.TimeRange( - start_time=clip.source_range.start_time, - duration=rec_duration + if freeze is not None: + clip.effects.append(schema.FreezeFrame()) + # XXX remove 'FF' suffix (writing edl will add it back) + if clip.name.endswith(' FF'): + clip.name = clip.name[:-3] + elif motion is not None: + fps = float( + SPEED_EFFECT_RE.match(motion).group("speed") + ) + time_scalar = fps / rate + clip.effects.append( + schema.LinearTimeWarp(time_scalar=time_scalar) ) - if freeze is not None: - clip.effects.append(schema.FreezeFrame()) - # XXX remove 'FF' suffix (writing edl will add it back) - if clip.name.endswith(' FF'): - clip.name = clip.name[:-3] - elif motion is not None: - fps = float( - SPEED_EFFECT_RE.match(motion).group("speed") - ) - time_scalar = fps / rate - clip.effects.append( - schema.LinearTimeWarp(time_scalar=time_scalar) - ) - - elif self.ignore_timecode_mismatch: - # Pretend there was no problem by adjusting the record_out. - # Note that we don't actually use record_out after this - # point in the code, since all of the subsequent math uses - # the clip's source_range. Adjusting the record_out is - # just to document what the implications of ignoring the - # mismatch here entails. - record_out = record_in + src_duration + elif self.ignore_timecode_mismatch: + # Pretend there was no problem by adjusting the record_out. + # Note that we don't actually use record_out after this + # point in the code, since all of the subsequent math uses + # the clip's source_range. Adjusting the record_out is + # just to document what the implications of ignoring the + # mismatch here entails. + record_out = record_in + src_duration - else: - raise EDLParseError( - "Source and record duration don't match: {} != {}" - " for clip {}".format( - src_duration, - rec_duration, - clip.name - )) + else: + raise EDLParseError( + "Source and record duration don't match: {} != {}" + " for clip {}".format( + src_duration, + rec_duration, + clip.name + )) + + # Add clip instances to the tracks + tracks = self.tracks_for_channel(clip_handler.channel_code) + for track in tracks: + if len(tracks) > 1: + track_clip = copy.deepcopy(clip) + else: + track_clip = clip if track.source_range is None: zero = opentime.RationalTime(0, edl_rate) @@ -211,7 +217,7 @@ def add_clip(self, line, comments, rate=24): raise EDLParseError( "Overlapping record in value: {} for clip {}".format( clip_handler.record_tc_in, - clip.name + track_clip.name )) # If the next clip is supposed to start beyond the end of the @@ -228,8 +234,8 @@ def add_clip(self, line, comments, rate=24): track.append(gap) _extend_source_range_duration(track, gap.duration()) - track.append(clip) - _extend_source_range_duration(track, clip.duration()) + track.append(track_clip) + _extend_source_range_duration(track, track_clip.duration()) def guess_kind_for_track_name(self, name): if name.startswith("V"): diff --git a/tests/sample_data/multi_audio.edl b/tests/sample_data/multi_audio.edl new file mode 100644 index 0000000000..bb7c17cf1c --- /dev/null +++ b/tests/sample_data/multi_audio.edl @@ -0,0 +1,6 @@ +TITLE: MultiAudio +FCM: NON-DROP FRAME + +001 AX AA C 00:00:00:00 00:56:55:22 00:00:00:00 00:56:55:22 +* FROM CLIP NAME: AX +AUD 3 diff --git a/tests/test_cmx_3600_adapter.py b/tests/test_cmx_3600_adapter.py index ff3fbbcccd..cf0b07cc9b 100755 --- a/tests/test_cmx_3600_adapter.py +++ b/tests/test_cmx_3600_adapter.py @@ -48,6 +48,7 @@ SAMPLE_DATA_DIR, "speed_effects_small.edl" ) +MULTIPLE_TARGET_AUDIO_PATH = os.path.join(SAMPLE_DATA_DIR, "multi_audio.edl") class EDLAdapterTest(unittest.TestCase, otio_test_utils.OTIOAssertions): @@ -812,6 +813,24 @@ def test_nucoda_edl_write_with_double_transition(self): self.assertMultiLineEqual(result, expected) + def test_read_edl_with_multiple_target_audio_tracks(self): + tl = otio.adapters.read_from_file(MULTIPLE_TARGET_AUDIO_PATH) + + self.assertEqual(len(tl.audio_tracks()), 2) + + first_track, second_track = tl.audio_tracks() + self.assertEqual(first_track.name, "A1") + self.assertEqual(second_track.name, "A2") + + self.assertEqual(first_track[0].name, "AX") + self.assertEqual(second_track[0].name, "AX") + + expected_range = otio.opentime.TimeRange( + duration=otio.opentime.from_timecode("00:56:55:22", rate=24) + ) + self.assertEqual(first_track[0].source_range, expected_range) + self.assertEqual(second_track[0].source_range, expected_range) + def test_custom_reel_names(self): track = otio.schema.Track() tl = otio.schema.Timeline(tracks=[track])