diff --git a/src/otio_aaf_adapter/adapters/advanced_authoring_format.py b/src/otio_aaf_adapter/adapters/advanced_authoring_format.py index 8eb14f2..eb60a8d 100644 --- a/src/otio_aaf_adapter/adapters/advanced_authoring_format.py +++ b/src/otio_aaf_adapter/adapters/advanced_authoring_format.py @@ -1255,33 +1255,21 @@ def _attach_markers(collection): slot_id = metadata.get("AttachedSlotID") track_number = metadata.get("AttachedPhysicalTrackNumber") target_track = tracks_map.get((slot_id, track_number)) - if target_track is None: - raise AAFAdapterError( - "Marker '{}' cannot be attached to an item. SlotID: '{}', " - "PhysicalTrackNumber: '{}'".format( - marker.name, slot_id, track_number - ) - ) # remove marker from current parent track current_track.markers.remove(marker) # determine new item to attach the marker to - try: - target_item = _find_child_at_time( - target_track, marker.marked_range.start_time) - - if target_item is None or not hasattr(target_item, 'markers'): - # Item found cannot have markers, for example Transition. - # See also `marker-over-transition.aaf` in test data. - # - # Leave markers on the track for now. - _transcribe_log( - 'Skip target_item `{}` cannot have markers'.format( - target_item, - ), - ) - target_item = target_track + if target_track is None: + # This can happen if you export from Avid with "Use Selected Tracks" + # where markers will not point at the correct PhysicalTrackNumber! + _transcribe_log( + f"Cannot find target track for marker: {marker}. " + "Adding to timeline." + ) + # Lets add it directly to the timeline "stack" the same way + # OTIO files generated by DaVinci Resolve does. + target_item = timeline.tracks # transform marked range into new item range marked_start_local = current_track.transformed_time( @@ -1290,25 +1278,52 @@ def _attach_markers(collection): marker.marked_range = otio.opentime.TimeRange( start_time=marked_start_local, - duration=marker.marked_range.duration + duration=marker.marked_range.duration, ) - except otio.exceptions.CannotComputeAvailableRangeError as e: - # For audio media AAF file (marker-over-audio.aaf), - # this exception would be triggered in: - # `target_item = target_track.child_at_time()` with error - # message: - # "No available_range set on media reference on clip". - # - # Leave markers on the track for now. - _transcribe_log( - 'Cannot compute availableRange from {} to {}: {}'.format( - marker, - target_track, - e, - ), - ) - target_item = target_track + else: + try: + target_item = _find_child_at_time( + target_track, marker.marked_range.start_time) + + if target_item is None or not hasattr(target_item, 'markers'): + # Item found cannot have markers, for example Transition. + # See also `marker-over-transition.aaf` in test data. + # + # Leave markers on the track for now. + _transcribe_log( + 'Skip target_item `{}` cannot have markers'.format( + target_item, + ), + ) + target_item = target_track + + # transform marked range into new item range + marked_start_local = current_track.transformed_time( + marker.marked_range.start_time, target_item + ) + + marker.marked_range = otio.opentime.TimeRange( + start_time=marked_start_local, + duration=marker.marked_range.duration + ) + + except otio.exceptions.CannotComputeAvailableRangeError as e: + # For audio media AAF file (marker-over-audio.aaf), + # this exception would be triggered in: + # `target_item = target_track.child_at_time()` with error + # message: + # "No available_range set on media reference on clip". + # + # Leave markers on the track for now. + _transcribe_log( + 'Cannot compute availableRange from {} to {}: {}'.format( + marker, + target_track, + e, + ), + ) + target_item = target_track # attach marker to target item target_item.markers.append(marker) diff --git a/tests/sample_data/bad_marker_track_from_avid.aaf b/tests/sample_data/bad_marker_track_from_avid.aaf new file mode 100644 index 0000000..9f081ad Binary files /dev/null and b/tests/sample_data/bad_marker_track_from_avid.aaf differ diff --git a/tests/test_aaf_adapter.py b/tests/test_aaf_adapter.py index a2ec59f..167170d 100644 --- a/tests/test_aaf_adapter.py +++ b/tests/test_aaf_adapter.py @@ -215,6 +215,10 @@ "avid_data_track_example.aaf" ) +BAD_TRACK_NUMBER_ON_MARKER_PATH = os.path.join( + SAMPLE_DATA_DIR, + "bad_marker_track_from_avid.aaf" +) try: lib_path = os.environ.get("OTIO_AAF_PYTHON_LIB") @@ -1230,6 +1234,30 @@ def test_aaf_marker_over_audio_file(self): self.assertEqual(label, expected_marker.get('label')) self.assertEqual(start_time, expected_marker.get('start_time')) + def test_aaf_marker_with_bad_track(self): + """ + If you export from Avid with "Use Selected Tracks" selected, Avid + will rewrite the track numbers after omitted unselected tracks. + The markers, however, may not be updated by avid to reflect the + new track numbers! + This test confirms that we don't crash when reading such a file. + """ + + timeline = None + + try: + timeline = otio.adapters.read_from_file( + BAD_TRACK_NUMBER_ON_MARKER_PATH + ) + + except Exception as e: + print('[ERROR] Transcribing test sample data `{}` caused an exception: {}'.format( # noqa + os.path.basename(BAD_TRACK_NUMBER_ON_MARKER_PATH), + e) + ) + + self.assertIsNotNone(timeline) + def _verify_user_comments(self, aaf_metadata, expected_md): self.assertTrue(aaf_metadata is not None)