Skip to content

Commit 3b27039

Browse files
committed
Reworked timecode handling all ported aaf tests pass
1 parent 2282d77 commit 3b27039

File tree

2 files changed

+51
-49
lines changed

2 files changed

+51
-49
lines changed

src/otio_avb_adapter/adapters/avb_adapter.py

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
import collections.abc as collections_abc
2626
except ImportError:
2727
import collections as collections_abc
28-
import fractions
2928
import opentimelineio as otio
29+
from opentimelineio.opentime import RationalTime
3030

3131
lib_path = os.environ.get("OTIO_AVB_PYTHON_LIB")
3232
if lib_path and lib_path not in sys.path:
@@ -348,16 +348,17 @@ def _walk_reference_chain(item, time, results):
348348
if item is None:
349349
return results
350350

351-
results.append([time, item])
351+
results.append([RationalTime(time, item.edit_rate), item])
352352

353353
if isinstance(item, avb.components.SourceClip):
354354

355355
mob = item.mob
356356
track = item.track
357357

358358
if track:
359-
results.append([item.start_time + time, mob])
360-
results.append([item.start_time + time, track])
359+
r_time = RationalTime(item.start_time + time, item.edit_rate)
360+
results.append([r_time, mob])
361+
results.append([r_time, track])
361362
# TODO: check for this affects anything
362363
if hasattr(track, 'start_pos'):
363364
raise AVBAdapterError("start_pos not handles, sample please")
@@ -404,15 +405,15 @@ def _walk_reference_chain(item, time, results):
404405

405406
r = _walk_reference_chain(track.component, time, [])
406407
if r and isinstance(r[-1][1], avb.components.SourceClip):
407-
results.append([time, track])
408+
results.append([RationalTime(time, item.edit_rate), track])
408409
results.extend(r)
409410
return results
410411

411412
# give up and return first track
412413
for track in item.tracks:
413414
r = _walk_reference_chain(track.component, time, [])
414415
if r:
415-
results.append([time, track])
416+
results.append([RationalTime(time, item.edit_rate), track])
416417
results.extend(r)
417418
return r
418419

@@ -428,7 +429,16 @@ def _walk_reference_chain(item, time, results):
428429

429430
def _extract_timecode_info(source_mob, start_time):
430431

432+
# Please note, timecode and frame rate are not the same thing!
433+
# Timecode is a way to label frames in a recording and
434+
# frame rate is the speed at which images have been
435+
# recorded or are played back. Timecode should NOT be treated as
436+
# a unit of time but rather a frame index. Fortunately AVB stores
437+
# timecode as a frame offset.
438+
431439
# TODO: need to find sample with multiple timecode samples
440+
441+
assert isinstance(start_time, RationalTime)
432442
for track in source_mob.tracks:
433443
if track.component.media_kind == 'timecode':
434444

@@ -441,22 +451,27 @@ def _extract_timecode_info(source_mob, start_time):
441451

442452
# start timecode is the first timecode sample
443453
timecode, _ = component.nearest_component_at_time(0)
444-
track_tc = timecode.start
454+
track_tc = RationalTime(timecode.start, timecode.edit_rate)
445455

446456
# source timecode is nearest timecode sample
447-
tc, tc_offset = component.nearest_component_at_time(start_time)
448-
source_tc = tc.start + start_time - tc_offset
457+
scale_time = start_time.value_rescaled_to(component.edit_rate)
458+
tc, tc_offset = component.nearest_component_at_time(scale_time)
459+
460+
tc_offset = RationalTime(tc_offset, tc.edit_rate)
461+
tc_start = RationalTime(tc.start, tc.edit_rate)
449462

450-
return track_tc, source_tc, timecode.fps
463+
source_tc = tc_start + start_time - tc_offset
464+
465+
return track_tc, source_tc
451466

452467
elif isinstance(track.component, avb.components.Timecode):
453468
timecode = track.component
454-
track_tc = timecode.start
469+
track_tc = RationalTime(timecode.start, timecode.edit_rate)
455470
source_tc = track_tc + start_time
456471

457-
return track_tc, source_tc, timecode.fps
472+
return track_tc, source_tc
458473

459-
return None, None, None
474+
return None, None
460475

461476

462477
def _add_child(parent, child, source):
@@ -579,12 +594,11 @@ def _transcribe(item, parents, edit_rate, indent=0):
579594
mobs = [mob for start, mob in ref_chain
580595
if isinstance(mob, avb.trackgroups.Composition)]
581596

582-
source_start = item.start_time
583-
source_length = item.length
597+
source_start = RationalTime(item.start_time, item.edit_rate)
598+
source_length = RationalTime(item.length, item.edit_rate)
584599

585-
media_start = 0
586-
media_length = item.length
587-
media_edit_rate = edit_rate
600+
media_start = RationalTime(0, item.edit_rate)
601+
media_length = source_length
588602

589603
source_mob = None
590604
for start_time, comp in ref_chain:
@@ -594,24 +608,19 @@ def _transcribe(item, parents, edit_rate, indent=0):
594608

595609
if isinstance(comp, avb.components.SourceClip):
596610
source_start = start_time
597-
media_start = comp.start_time
598-
media_length = comp.length
599-
media_edit_rate = comp.edit_rate
611+
media_start = RationalTime(comp.start_time, comp.edit_rate)
612+
media_length = RationalTime(comp.length, comp.edit_rate)
600613

601614
if source_mob:
602-
track_tc, source_tc, tc_rate = _extract_timecode_info(source_mob,
603-
start_time)
604-
if track_tc is not None:
605-
source_start = source_tc
606-
media_start = track_tc
607-
media_edit_rate = tc_rate
608-
609-
# NOTE: duration is in the edit rate of the source clip
610-
# and start_time is in edit rate of the source media
611-
# need to further test this
615+
track_tc_start, source_tc_start = _extract_timecode_info(source_mob,
616+
start_time)
617+
if track_tc_start is not None:
618+
source_start = source_tc_start
619+
media_start = track_tc_start
620+
612621
result.source_range = otio.opentime.TimeRange(
613-
otio.opentime.RationalTime(source_start, media_edit_rate),
614-
otio.opentime.RationalTime(source_length, edit_rate)
622+
source_start,
623+
source_length,
615624
)
616625

617626
mastermobs = []
@@ -676,8 +685,8 @@ def _transcribe(item, parents, edit_rate, indent=0):
676685
media = otio.schema.MissingReference()
677686

678687
media.available_range = otio.opentime.TimeRange(
679-
otio.opentime.RationalTime(media_start, media_edit_rate),
680-
otio.opentime.RationalTime(media_length, media_edit_rate)
688+
media_start,
689+
media_length,
681690
)
682691

683692
# Copy the metadata from the master into the media_reference
@@ -890,17 +899,12 @@ def _find_timecode_track_start(track):
890899
if not isinstance(track.component, avb.components.Timecode):
891900
return
892901

893-
edit_rate = fractions.Fraction(track.component.fps)
902+
edit_rate = track.component.edit_rate
894903
start = track.component.start
895904

896-
if edit_rate.denominator == 1:
897-
rate = edit_rate.numerator
898-
else:
899-
rate = float(edit_rate)
900-
901905
return otio.opentime.RationalTime(
902-
value=int(start),
903-
rate=rate,
906+
value=start,
907+
rate=edit_rate,
904908
)
905909

906910

tests/test_avb_adapter.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,10 @@ def test_avb_global_start_time(self):
298298
timeline.global_start_time
299299
)
300300

301-
def skip_test_avb_global_start_time_NTSC_DFTC(self):
301+
def test_avb_global_start_time_NTSC_DFTC(self):
302302
timeline = otio.adapters.read_from_file(FPS2997_DFTC_PATH)
303-
# TODO : I don't understand this test
304303
self.assertEqual(
305-
otio.opentime.from_timecode("05:00:00;00", rate=(30000.0 / 1001)),
304+
otio.opentime.from_timecode("05:00:00;00", rate=29.97),
306305
timeline.global_start_time
307306
)
308307

@@ -861,10 +860,9 @@ def test_30fps(self):
861860
tl = otio.adapters.read_from_file(FPS30_CLIP_PATH)
862861
self.assertEqual(tl.duration().rate, 30)
863862

864-
def skip_test_2997fps(self):
865-
# TODO not sure whats up with this
863+
def test_2997fps(self):
866864
tl = otio.adapters.read_from_file(FPS2997_CLIP_PATH)
867-
self.assertEqual(tl.duration().rate, 30000 / 1001.0)
865+
self.assertEqual(tl.duration().rate, 29.97)
868866

869867
def test_utf8_names(self):
870868
# NOTE: AAF to AVB convert in MC resulted in different for name

0 commit comments

Comments
 (0)