Skip to content

Commit

Permalink
AAFWriter: added support for AAF user comments (OpenTimelineIO#22)
Browse files Browse the repository at this point in the history
Signed-off-by: Tim Lehr <[email protected]>
  • Loading branch information
timlehr committed Jun 28, 2023
1 parent 4b2d658 commit 99b6b62
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/otio_aaf_adapter/adapters/aaf_adapter/aaf_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Specifies how to transcribe an OpenTimelineIO file into an AAF file.
"""
from numbers import Rational

import aaf2
import abc
Expand Down Expand Up @@ -87,6 +88,9 @@ def __init__(self, input_otio, aaf_file, **kwargs):
self._unique_tapemobs = {}
self._clip_mob_ids_map = _gather_clip_mob_ids(input_otio, **kwargs)

# transcribe timeline comments onto composition mob
self._transcribe_user_comments(input_otio, self.compositionmob)

def _unique_mastermob(self, otio_clip):
"""Get a unique mastermob, identified by clip metadata mob id."""
mob_id = self._clip_mob_ids_map.get(otio_clip)
Expand All @@ -97,6 +101,14 @@ def _unique_mastermob(self, otio_clip):
mastermob.mob_id = aaf2.mobid.MobID(mob_id)
self.aaf_file.content.mobs.append(mastermob)
self._unique_mastermobs[mob_id] = mastermob

# transcribe clip comments onto master mob
self._transcribe_user_comments(otio_clip, mastermob)

# transcribe media reference comments onto master mob.
# this might overwrite clip comments.
self._transcribe_user_comments(otio_clip.media_reference, mastermob)

return mastermob

def _unique_tapemob(self, otio_clip):
Expand Down Expand Up @@ -140,6 +152,19 @@ def track_transcriber(self, otio_track):
f"Unsupported track kind: {otio_track.kind}")
return transcriber

def _transcribe_user_comments(self, otio_item, target_mob):
"""Transcribes user comments on `otio_item` onto `target_mob` in AAF."""

user_comments = otio_item.metadata.get("AAF", {}).get("UserComments", {})
for key, val in user_comments.items():
if isinstance(val, int):
target_mob.comments[key] = val
elif isinstance(val, (float, Rational)):
target_mob.comments[key] = aaf2.rational.AAFRational(val)
else:
# ensure we can store comment value by converting it to unicode string
target_mob.comments[key] = str(val)


def validate_metadata(timeline):
"""Print a check of necessary metadata requirements for an otio timeline."""
Expand Down
40 changes: 40 additions & 0 deletions tests/test_aaf_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,46 @@ def test_generator_reference(self):
cl.media_reference.generator_kind = "not slug"
otio.adapters.write_to_file(tl, tmp_aaf_path)

def test_aaf_writer_user_comments(self):
# construct simple timeline
timeline = otio.schema.Timeline()
range = otio.opentime.TimeRange(
otio.opentime.RationalTime(0, 24),
otio.opentime.RationalTime(100, 24),
)
media_ref = otio.schema.ExternalReference(available_range=range)
clip = otio.schema.Clip(source_range=range)
clip.media_reference = media_ref
timeline.tracks.append(otio.schema.Track(children=[clip]))

# add comments to clip + timeline
original_comments = {
"Test_String": "Test_Value",
"Test_Int": 1337,
"Test_Float": 13.37,
"Test_Bool": True,
}

expected_comments = {
"Test_String": "Test_Value",
"Test_Int": "1337",
"Test_Float": aaf2.rational.AAFRational(13.37),
"Test_Bool": "True",
}

timeline.metadata["AAF"] = {"UserComments": original_comments}
media_ref.metadata["AAF"] = {"UserComments": original_comments}

_, tmp_aaf_path = tempfile.mkstemp(suffix='.aaf')
otio.adapters.write_to_file(timeline, tmp_aaf_path, use_empty_mob_ids=True)

with aaf2.open(tmp_aaf_path) as aaf_file:
print(dict(next(aaf_file.content.mastermobs()).comments.items()))
master_mob = next(aaf_file.content.mastermobs())
comp_mob = next(aaf_file.content.compositionmobs())
self.assertEqual(dict(master_mob.comments.items()), expected_comments)
self.assertEqual(dict(comp_mob).comments.items(), expected_comments)

def _verify_aaf(self, aaf_path):
otio_timeline = otio.adapters.read_from_file(aaf_path, simplify=True)
fd, tmp_aaf_path = tempfile.mkstemp(suffix='.aaf')
Expand Down

0 comments on commit 99b6b62

Please sign in to comment.