Skip to content

Commit

Permalink
AAFWriter: added support for AAF user comments (#22)
Browse files Browse the repository at this point in the history
Signed-off-by: Tim Lehr <[email protected]>
  • Loading branch information
timlehr committed Mar 25, 2024
1 parent a354a81 commit d9c5c87
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 0 deletions.
30 changes: 30 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 All @@ -13,6 +14,7 @@
import os
import copy
import re
import logging


AAF_PARAMETERDEF_PAN = aaf2.auid.AUID("e4962322-2267-11d3-8a4c-0050040ef7d2")
Expand All @@ -27,6 +29,8 @@
AAF_VVAL_EXTRAPOLATION_ID = uuid.UUID("0e24dd54-66cd-4f1a-b0a0-670ac3a7a0b3")
AAF_OPERATIONDEF_SUBMASTER = uuid.UUID("f1db0f3d-8d64-11d3-80df-006008143e6f")

logger = logging.getLogger(__name__)


def _is_considered_gap(thing):
"""Returns whether or not thiing can be considered gap.
Expand Down Expand Up @@ -87,6 +91,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 +104,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 +155,21 @@ 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, str)):
target_mob.comments[key] = val
elif isinstance(val, (float, Rational)):
target_mob.comments[key] = aaf2.rational.AAFRational(val)
else:
logger.warning(
f"Skip transcribing unsupported comment value of type "
f"'{type(val)}' for key '{key}'."
)


def validate_metadata(timeline):
"""Print a check of necessary metadata requirements for an otio timeline."""
Expand Down
44 changes: 44 additions & 0 deletions tests/test_aaf_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,50 @@ 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_Unicode": "ラーメン",
"Test_Int": 1337,
"Test_Float": 13.37,
"Test_Bool": True,
"Test_Unsupported_List": ["test1", "test2", "test3"],
"Test_Unsupported_Dict": {"test_key": "test_value"},
"Test_Unsupported_Schema": otio.schema.Marker(name="SomeMarker")
}

expected_comments = {
"Test_String": "Test_Value",
"Test_Unicode": "ラーメン",
"Test_Int": 1337,
"Test_Float": aaf2.rational.AAFRational(13.37),
"Test_Bool": 1,
}

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:
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 d9c5c87

Please sign in to comment.