1414
1515import collections
1616import fractions
17+ from typing import List
18+
1719import opentimelineio as otio
1820
1921lib_path = os .environ .get ("OTIO_AAF_PYTHON_LIB" )
2729import aaf2 .core # noqa: E402
2830import aaf2 .misc # noqa: E402
2931from otio_aaf_adapter .adapters .aaf_adapter import aaf_writer # noqa: E402
32+ from otio_aaf_adapter .adapters .aaf_adapter import hooks # noqa: E402
3033
3134
3235debug = False
@@ -268,35 +271,36 @@ def _convert_rgb_to_marker_color(rgb_dict):
268271
269272def _find_timecode_mobs (item ):
270273 mobs = [item .mob ]
271-
272- for c in item .walk ():
273- if isinstance (c , aaf2 .components .SourceClip ):
274- mob = c .mob
275- if mob :
276- mobs .append (mob )
274+ try :
275+ for c in item .walk ():
276+ if isinstance (c , aaf2 .components .SourceClip ):
277+ mob = c .mob
278+ if mob :
279+ mobs .append (mob )
280+ else :
281+ continue
277282 else :
283+ # This could be 'EssenceGroup', 'Pulldown' or other segment
284+ # subclasses
285+ # For example:
286+ # An EssenceGroup is a Segment that has one or more
287+ # alternate choices, each of which represent different variations
288+ # of one actual piece of content.
289+ # According to the AAF Object Specification and Edit Protocol
290+ # documents:
291+ # "Typically the different representations vary in essence format,
292+ # compression, or frame size. The application is responsible for
293+ # choosing the appropriate implementation of the essence."
294+ # It also says they should all have the same length, but
295+ # there might be nested Sequences inside which we're not attempting
296+ # to handle here (yet). We'll need a concrete example to ensure
297+ # we're doing the right thing.
298+ # TODO: Is the Timecode for an EssenceGroup correct?
299+ # TODO: Try CountChoices() and ChoiceAt(i)
300+ # For now, lets just skip it.
278301 continue
279- else :
280- # This could be 'EssenceGroup', 'Pulldown' or other segment
281- # subclasses
282- # For example:
283- # An EssenceGroup is a Segment that has one or more
284- # alternate choices, each of which represent different variations
285- # of one actual piece of content.
286- # According to the AAF Object Specification and Edit Protocol
287- # documents:
288- # "Typically the different representations vary in essence format,
289- # compression, or frame size. The application is responsible for
290- # choosing the appropriate implementation of the essence."
291- # It also says they should all have the same length, but
292- # there might be nested Sequences inside which we're not attempting
293- # to handle here (yet). We'll need a concrete example to ensure
294- # we're doing the right thing.
295- # TODO: Is the Timecode for an EssenceGroup correct?
296- # TODO: Try CountChoices() and ChoiceAt(i)
297- # For now, lets just skip it.
298- continue
299-
302+ except NotImplementedError as err :
303+ _transcribe_log (f"Couldn't walk component. Skip:\n { repr (err )} " )
300304 return mobs
301305
302306
@@ -1580,25 +1584,23 @@ def _get_mobs_for_transcription(storage):
15801584
15811585
15821586def read_from_file (
1583- filepath ,
1584- simplify = True ,
1585- transcribe_log = False ,
1586- attach_markers = True ,
1587- bake_keyframed_properties = False
1588- ):
1587+ filepath : str ,
1588+ simplify : bool = True ,
1589+ transcribe_log : bool = False ,
1590+ attach_markers : bool = True ,
1591+ bake_keyframed_properties : bool = False ,
1592+ ** kwargs
1593+ ) -> otio .schema .Timeline :
15891594 """Reads AAF content from `filepath` and outputs an OTIO timeline object.
15901595
15911596 Args:
1592- filepath (str) : AAF filepath
1593- simplify (bool, optional) : simplify timeline structure by stripping empty items
1594- transcribe_log (bool, optional) : log activity as items are getting transcribed
1595- attach_markers (bool, optional) : attaches markers to their appropriate items
1597+ filepath: AAF filepath
1598+ simplify: simplify timeline structure by stripping empty items
1599+ transcribe_log: log activity as items are getting transcribed
1600+ attach_markers: attaches markers to their appropriate items
15961601 like clip, gap. etc on the track
1597- bake_keyframed_properties (bool, optional): bakes animated property values
1598- for each frame in a source clip
1599- Returns:
1600- otio.schema.Timeline
1601-
1602+ bake_keyframed_properties: bakes animated property values
1603+ for each frame in a source clip
16021604 """
16031605 # 'activate' transcribe logging if adapter argument is provided.
16041606 # Note that a global 'switch' is used in order to avoid
@@ -1612,41 +1614,86 @@ def read_from_file(
16121614 # Note: We're skipping: aaf_file.header
16131615 # Is there something valuable in there?
16141616
1617+ # trigger adapter specific pre-transcribe read hook
1618+ hooks .run_pre_read_transcribe_hook (
1619+ read_filepath = filepath ,
1620+ aaf_handle = aaf_file ,
1621+ extra_kwargs = kwargs .get (
1622+ "hook_function_argument_map" , {}
1623+ )
1624+ )
1625+
16151626 storage = aaf_file .content
16161627 mobs_to_transcribe = _get_mobs_for_transcription (storage )
16171628
1618- result = _transcribe (mobs_to_transcribe , parents = list (), edit_rate = None )
1629+ timeline = _transcribe (mobs_to_transcribe , parents = list (), edit_rate = None )
1630+
1631+ # trigger adapter specific post-transcribe read hook
1632+ hooks .run_post_read_transcribe_hook (
1633+ timeline = timeline ,
1634+ read_filepath = filepath ,
1635+ aaf_handle = aaf_file ,
1636+ extra_kwargs = kwargs .get (
1637+ "hook_function_argument_map" , {}
1638+ )
1639+ )
16191640
16201641 # OTIO represents transitions a bit different than AAF, so
16211642 # we need to iterate over them and modify the items on either side.
16221643 # Note this needs to be done before attaching markers, marker
16231644 # positions are not stored with transition length offsets
1624- _fix_transitions (result )
1645+ _fix_transitions (timeline )
16251646
16261647 # Attach marker to the appropriate clip, gap etc.
16271648 if attach_markers :
1628- result = _attach_markers (result )
1649+ timeline = _attach_markers (timeline )
16291650
16301651 # AAF is typically more deeply nested than OTIO.
16311652 # Let's try to simplify the structure by collapsing or removing
16321653 # unnecessary stuff.
16331654 if simplify :
1634- result = _simplify (result )
1655+ timeline = _simplify (timeline )
16351656
16361657 # Reset transcribe_log debugging
16371658 _TRANSCRIBE_DEBUG = False
1638- return result
1659+ return timeline
16391660
16401661
1641- def write_to_file (input_otio , filepath , ** kwargs ):
1662+ def write_to_file (input_otio , filepath , embed_essence = False ,
1663+ create_edgecode = True , ** kwargs ):
1664+ """Serialize `input_otio` to an AAF file at `filepath`.
1665+
1666+ Args:
1667+ input_otio(otio.schema.Timeline): input timeline
1668+ filepath(str): output filepath
1669+ embed_essence(Optional[bool]): if `True`, media essence will be included in AAF
1670+ create_edgecode(bool): if `True` each clip will get an EdgeCode slot
1671+ assigned that defines the Avid Frame Count Start / End.
1672+ **kwargs: extra adapter arguments
1673+
1674+ """
16421675
16431676 with aaf2 .open (filepath , "w" ) as f :
1677+ # trigger adapter specific pre-transcribe write hook
1678+ hook_tl = hooks .run_pre_write_transcribe_hook (
1679+ timeline = input_otio ,
1680+ write_filepath = filepath ,
1681+ aaf_handle = f ,
1682+ embed_essence = embed_essence ,
1683+ extra_kwargs = kwargs .get (
1684+ "hook_function_argument_map" , {}
1685+ )
1686+ )
16441687
1645- timeline = aaf_writer ._stackify_nested_groups (input_otio )
1688+ timeline = aaf_writer ._stackify_nested_groups (hook_tl )
16461689
16471690 aaf_writer .validate_metadata (timeline )
16481691
1649- otio2aaf = aaf_writer .AAFFileTranscriber (timeline , f , ** kwargs )
1692+ otio2aaf = aaf_writer .AAFFileTranscriber (input_otio = timeline ,
1693+ aaf_file = f ,
1694+ embed_essence = embed_essence ,
1695+ create_edgecode = create_edgecode ,
1696+ ** kwargs )
16501697
16511698 if not isinstance (timeline , otio .schema .Timeline ):
16521699 raise otio .exceptions .NotSupportedError (
@@ -1671,3 +1718,24 @@ def write_to_file(input_otio, filepath, **kwargs):
16711718 # This is required for compatibility with DaVinci Resolve.
16721719 if default_edit_rate or input_otio .global_start_time :
16731720 otio2aaf .add_timecode (input_otio , default_edit_rate )
1721+
1722+ # trigger adapter specific post-transcribe write hook
1723+ hooks .run_post_write_transcribe_hook (
1724+ timeline = timeline ,
1725+ write_filepath = filepath ,
1726+ aaf_handle = f ,
1727+ embed_essence = embed_essence ,
1728+ extra_kwargs = kwargs .get (
1729+ "hook_function_argument_map" , {}
1730+ )
1731+ )
1732+
1733+
1734+ def adapter_hook_names () -> List [str ]:
1735+ """Returns names of custom hooks implemented by this adapter."""
1736+ return [
1737+ hooks .HOOK_POST_READ_TRANSCRIBE ,
1738+ hooks .HOOK_POST_WRITE_TRANSCRIBE ,
1739+ hooks .HOOK_PRE_READ_TRANSCRIBE ,
1740+ hooks .HOOK_PRE_WRITE_TRANSCRIBE
1741+ ]
0 commit comments