From 0860fe797c49f33a25582b9ddc3d12a1711385da Mon Sep 17 00:00:00 2001 From: Tim Lehr Date: Tue, 16 Jul 2024 16:10:40 -0700 Subject: [PATCH 1/2] Writer: added support for mob attribute list Signed-off-by: Tim Lehr --- .../adapters/aaf_adapter/aaf_writer.py | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/otio_aaf_adapter/adapters/aaf_adapter/aaf_writer.py b/src/otio_aaf_adapter/adapters/aaf_adapter/aaf_writer.py index e4986e8..65343a6 100644 --- a/src/otio_aaf_adapter/adapters/aaf_adapter/aaf_writer.py +++ b/src/otio_aaf_adapter/adapters/aaf_adapter/aaf_writer.py @@ -114,6 +114,7 @@ def __init__(self, input_otio, aaf_file, **kwargs): # transcribe timeline comments onto composition mob self._transcribe_user_comments(input_otio, self.compositionmob) + self._transcribe_mob_attributes(input_otio, self.compositionmob) def _unique_mastermob(self, otio_clip): """Get a unique mastermob, identified by clip metadata mob id.""" @@ -126,12 +127,14 @@ def _unique_mastermob(self, otio_clip): self.aaf_file.content.mobs.append(mastermob) self._unique_mastermobs[mob_id] = mastermob - # transcribe clip comments onto master mob + # transcribe clip comments / mob attributes onto master mob self._transcribe_user_comments(otio_clip, mastermob) + self._transcribe_mob_attributes(otio_clip, mastermob) - # transcribe media reference comments onto master mob. - # this might overwrite clip comments. + # transcribe media reference comments / mob attributes onto master mob. + # this might overwrite clip comments / attributes. self._transcribe_user_comments(otio_clip.media_reference, mastermob) + self._transcribe_mob_attributes(otio_clip.media_reference, mastermob) return mastermob @@ -226,6 +229,22 @@ def _transcribe_user_comments(self, otio_item, target_mob): f"'{type(val)}' for key '{key}'." ) + def _transcribe_mob_attributes(self, otio_item, target_mob): + """Transcribes mob attribute list onto the `target_mob`. + This can be used to roundtrip specific mob config values, like audio channel + settings. + """ + mob_attr_map = otio_item.metadata.get("AAF", {}).get("MobAttributeList", {}) + mob_attr_list = aaf2.misc.TaggedValueHelper(target_mob['MobAttributeList']) + for key, val in mob_attr_map.items(): + if isinstance(val, (int, str)): + mob_attr_list[key] = val + elif isinstance(val, (float, Rational)): + mob_attr_list[key] = aaf2.rational.AAFRational(val) + else: + raise ValueError(f"Unsupported mob attribute type '{type(val)}' for " + f"key '{key}'.") + def validate_metadata(timeline): """Print a check of necessary metadata requirements for an otio timeline.""" From 319bd04ec0138fcffcc01e32fb500852fb882f62 Mon Sep 17 00:00:00 2001 From: Tim Lehr Date: Fri, 27 Sep 2024 14:17:11 -0700 Subject: [PATCH 2/2] Writer: added test Signed-off-by: Tim Lehr --- tests/test_aaf_adapter.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_aaf_adapter.py b/tests/test_aaf_adapter.py index 3192a5e..19a5bef 100644 --- a/tests/test_aaf_adapter.py +++ b/tests/test_aaf_adapter.py @@ -1906,6 +1906,30 @@ def test_aaf_writer_user_comments(self): self.assertEqual(dict(master_mob.comments.items()), expected_comments) self.assertEqual(dict(comp_mob.comments.items()), expected_comments) + def test_aaf_writer_metadata_roundtrip(self): + """Tries to roundtrip metadata through AAF and `MobAttributeList`.""" + og_aaf_tl = otio.adapters.read_from_file(ONE_AUDIO_CLIP_PATH) + clip = og_aaf_tl.find_clips()[0] + + # change a value to test roundtrip + clip.media_reference.metadata["AAF"]["MobAttributeList"]["_USER_POS"] = 2 + _, tmp_aaf_path = tempfile.mkstemp(suffix='.aaf') + otio.adapters.write_to_file(og_aaf_tl, tmp_aaf_path) + + roundtripped_tl = otio.adapters.read_from_file(tmp_aaf_path) + + clip = roundtripped_tl.find_clips()[0] + expected = { + "_IMPORTSETTING": "__AttributeList", + "_SAVED_AAF_AUDIO_LENGTH": 0, + "_SAVED_AAF_AUDIO_RATE_DEN": 1, + "_SAVED_AAF_AUDIO_RATE_NUM": 24, + "_USER_POS": 2, + "_VERSION": 2 + } + self.assertEqual(clip.media_reference.metadata["AAF"]["MobAttributeList"], + expected) + def test_aaf_writer_global_start_time(self): for tc, rate in [("01:00:00:00", 23.97), ("01:00:00:00", 24),