Skip to content

MasterMob Transcribing Changes#44

Merged
timlehr merged 7 commits intoOpenTimelineIO:mainfrom
markreidvfx:mastermob_refactor_v1
Aug 26, 2025
Merged

MasterMob Transcribing Changes#44
timlehr merged 7 commits intoOpenTimelineIO:mainfrom
markreidvfx:mastermob_refactor_v1

Conversation

@markreidvfx
Copy link
Copy Markdown
Member

This PR proposes an overhaul to how the adapter transcribes AAF files. This CHANGES the structure of the OTIO file being generated in a few ways. This WILL most likely break tools rely on the old metadata structure and therefore should be a major version increase. I believe this new structure is a better mapping of the AAF to OTIO.

Improvements included:

  • OTIO Clip start timecode should be correct and based on their tape SourceMobs
  • Removed use pyaaf walk method. Files that previously failed due to this should work now
  • MasterMob parsing is based on AAF specification.
  • SourceMob become MediaReferences and more metadata including EssenceDescriptors are preserved
  • MasterMobs/CompositionMobs previously converted to OTIO timelines are cached
  • Leans more into the simplifying stage with more aggressive timeline flattening

This rework follows a few guiding principals. ONLY SourceClips on MasterMobs become OTIO Clips. SourceClips on SourceMobs become MediaReferences.

Here a Diagram of transcribing a MasterMob hierarchy. It shows what AAF objects get converted to what OTIO objects.

image

Only SourceClips on MasterMobs become OTIO Clips

ONLY SourceClips on MasterMobs become OTIO Clips.

There are strict rules in the AAF Spec that define what MasterMob slots can contain. They are only allowed to contain one or more EssenceGroups or SourceClips. These components are often inside a Sequence object. I've seen some case where MasterMobs contain Filler even though not in the spec. This PR should handle all these cases.

A SourceClip in CompositionsMob is only allowed to reference slots on MasterMobs (or another CompositionsMob in some cases). They aren't allowed to reference SourceMobs. When transcribing a CompositionMob and a SourceClip is encountered, the referenced MasterMob is completely converted into a separate OTIO Timeline. Then track they are referencing by SourceClip's slot_id is the cloned into the timeline being built. The clip(s) on the cloned track will contain all the MasterMob's metadata.

With the previous behaviour, it is a little messy on what becomes a OTIO Clip. Typically the MasterMob metadata would end up on a MediaReference. Now MasterMob metadata will ALWAYS be on the OTIO Clip. This makes it very simple and consistent to find UserComments and other bin metadata.

SourceClips on SourceMobs become MediaReferences

A SourceMob referenced by a MasterMob now becomes its own media reference. More SourceMob metadata is persevered including EssenceDescriptor objects. A NetworkLocator will get converted to a target_url. If the SourceMobs EssenceDescriptor doesn't have a NetworkLocator a OTIO MissingReference is created.

Each MediaReference has a available_range base on the timecode found on the SourceMobs. This timecode propagated back to the OTIO Clip, so it too should have the correct start timecode.

I'm also converting the MasterMob's UNC Path UserComment into a MediaReference too because this was the old behaviour, I'm considering removing that behaviour or changing it to an option. I've seen people use other bin metadata to specify media reference urls, it might be more useful for this to a user supplied list of keys to look for additional urls.

SubClip CompositionMobs become stacks

A SourceClip in a CompositionMob can also reference another CompositionMob. This is often referred to as a SubClip. The Mob these SourceClips reference are converted to a OTIO Timelines and the referenced Track gets cloned in a similar fashion to MasterMobs.

A SubClip can contain useful user metadata. To preserve this data, SourceClips that reference other CompostionMobs become OTIO Stacks. Special care is also taken to preserve any Markers found SubClips.

If the SubClip has only have 1 track they could be flatten to Track instead of Stack, but I choose not to do this. I was thinking it was better keep SubClips consistent.

Changes to OperationGroup

The other structural change that might break things is all OperationGroup metadata is now stored in the OTIO Effect. This allows flattening to preserve all effect data. OperationGroup are only flattened if their effect has one input. If the effect requires multiple inputs they remain a OTIO Stack.

Changes to Simplify

More attempts are made to simplify the file after transcribing is complete. This includes some additional flattening of OTIO tracks and combining effects if possible. Simplify also makes sure all stacks have tracks. I still think this code is pretty complex since it involves a lot of recursion. More testing and documenting is required here.

Still TODO

  • Any SourceClip metadata we want to preserve on the clip, we might want know the Clips/MediaReference slotID, and some data in ComponentAttributeList
  • I'm not entirely clear on what types of SubClip to preserve and what ones can be flattened, right now I preseve them if they have UserComments, should probably be using the UsageCode or AppCode instead.
  • SourceMobs are flatten into a list of MediaReferences losing generational linking. Should we preserve how they are linked together, this might be useful for the AAFWriter.
  • Should we flatten SubClip stacks to Tracks?
  • Make sure Effect flattening checks operationdef for number of inputs required
  • Verify SubMasters maintain Nested structure.
  • More testing on how this change effects the AAFWriter is there anything else we could change to make writing easier.
  • Write synthetic MasterMob test suit, I have code that can generate all the various MasterMob structures but it needs to be cleaned up
  • Add more comments to sections of code people might not understand.
  • Rework parsing properties and keyframes
  • Rework parsing transitions

Clip Example

Here is a small condensed example from ALab of what the changes look like to an OTIO Clip.

Old Structure

{
    "OTIO_SCHEMA": "Clip.2",
    "metadata": {
        "AAF": {
            "ClassName": "SourceClip",
            "ComponentAttributeList": {
                "_IMAGE_BOUNDS_OVERRIDE": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?><Bounds>  <Framing>-800/1 -450/1 1600/1 900/1</Framing>  <Valid>-800/1 -450/1 1600/1 900/1</Valid>  <Essence>-800/1 -450/1 1600/1 900/1</Essence>  <Source>-800/1 -450/1 1600/1 900/1</Source></Bounds>"
            },
            "DataDefinition": {
                "Description": "Picture data",
                "Identification": "01030202-0100-0000-060e-2b3404010101",
                "Name": "Picture"
            },
            "Length": 34,
            "Name": "mk020_0060",
            "SourceID": "urn:smpte:umid:060a2b34.01010105.01010f10.13000000.f5e701d0.98459605.de43f493.9ff4a1bd",
            "SourceMobSlotID": 1,
            "SourceMobUsage": "",
            "StartTime": 0
        }
    },
    "name": "mk020_0060",
    "source_range": {
        "OTIO_SCHEMA": "TimeRange.1",
        "duration": {
            "OTIO_SCHEMA": "RationalTime.1",
            "rate": 24.0,
            "value": 34.0
        },
        "start_time": {
            "OTIO_SCHEMA": "RationalTime.1",
            "rate": 24.0,
            "value": 1002.0
        }
    },
    "effects": [],
    "markers": [],
    "enabled": true,
    "media_references": {
        "DEFAULT_MEDIA": {
            "OTIO_SCHEMA": "ExternalReference.1",
            "metadata": {
                "AAF": {
                    "ClassName": "MasterMob",
                    "ConvertFrameRate": false,
                    "CreationTime": "2022-09-23 05:23:43",
                    "LastModified": "2022-09-23 07:30:31",
                    "MobAttributeList": {
                        "_COLOR_B": 38144,
                        "_COLOR_G": 11776,
                        "_COLOR_R": 14592,
                        "_GEN": 1663918231,
                        "_IMPORTSETTING": "__AttributeList",
                        "_USER_POS": 0
                    },
                    "MobID": "urn:smpte:umid:060a2b34.01010105.01010f10.13000000.f5e701d0.98459605.de43f493.9ff4a1bd",
                    "Name": "mk020_0060",
                    "Slots": {},
                    "UserComments": {
                        "UNC Path": "C:\\Users\\nathanla\\Desktop\\mk020_shots\\mk020_0060\\mk020_minicut_comp_scene_cut_intentcut_v001.0561.png"
                    }
                }
            },
            "name": "",
            "available_range": {
                "OTIO_SCHEMA": "TimeRange.1",
                "duration": {
                    "OTIO_SCHEMA": "RationalTime.1",
                    "rate": 24.0,
                    "value": 34.0
                },
                "start_time": {
                    "OTIO_SCHEMA": "RationalTime.1",
                    "rate": 24.0,
                    "value": 1002.0
                }
            },
            "available_image_bounds": null,
            "target_url": "file://C:/Users/nathanla/Desktop/mk020_shots/mk020_0060/mk020_minicut_comp_scene_cut_intentcut_v001.0561.png"
        }
    },
    "active_media_reference_key": "DEFAULT_MEDIA"
}

New Structure

{
    "OTIO_SCHEMA": "Clip.2",
    "metadata": {
        "AAF": {
            "ClassName": "MasterMob",
            "ConvertFrameRate": false,
            "CreationTime": "2022-09-23 05:23:43",
            "LastModified": "2022-09-23 07:30:31",
            "MediaKind": "Picture",
            "MobAttributeList": {
                "_COLOR_B": 38144,
                "_COLOR_G": 11776,
                "_COLOR_R": 14592,
                "_GEN": 1663918231,
                "_IMPORTSETTING": "__AttributeList",
                "_USER_POS": 0
            },
            "MobID": "urn:smpte:umid:060a2b34.01010105.01010f10.13000000.f5e701d0.98459605.de43f493.9ff4a1bd",
            "Name": "mk020_0060",
            "Slots": {},
            "UserComments": {
                "UNC Path": "C:\\Users\\nathanla\\Desktop\\mk020_shots\\mk020_0060\\mk020_minicut_comp_scene_cut_intentcut_v001.0561.png"
            }
        }
    },
    "name": "mk020_0060",
    "source_range": {
        "OTIO_SCHEMA": "TimeRange.1",
        "duration": {
            "OTIO_SCHEMA": "RationalTime.1",
            "rate": 24.0,
            "value": 34.0
        },
        "start_time": {
            "OTIO_SCHEMA": "RationalTime.1",
            "rate": 24.0,
            "value": 1002.0
        }
    },
    "effects": [],
    "markers": [],
    "enabled": true,
    "media_references": {
        "DEFAULT_MEDIA": {
            "OTIO_SCHEMA": "ExternalReference.1",
            "metadata": {},
            "name": "UNC Path",
            "available_range": {
                "OTIO_SCHEMA": "TimeRange.1",
                "duration": {
                    "OTIO_SCHEMA": "RationalTime.1",
                    "rate": 24.0,
                    "value": 34.0
                },
                "start_time": {
                    "OTIO_SCHEMA": "RationalTime.1",
                    "rate": 24.0,
                    "value": 1002.0
                }
            },
            "available_image_bounds": null,
            "target_url": "file://C:/Users/nathanla/Desktop/mk020_shots/mk020_0060/mk020_minicut_comp_scene_cut_intentcut_v001.0561.png"
        },
        "mk020_minicut_comp_scene_cut_intentcut_v001.0561.png": {
            "OTIO_SCHEMA": "ExternalReference.1",
            "metadata": {
                "AAF": {
                    "ClassName": "SourceMob",
                    "CreationTime": "2022-09-23 05:40:44",
                    "EssenceDescription": {
                        "ClassName": "ImportDescriptor",
                        "Locator": {}
                    },
                    "LastModified": "2022-09-23 05:40:44",
                    "MobAttributeList": {
                        "_PJ": "Polaris"
                    },
                    "MobID": "urn:smpte:umid:060a2b34.01010105.01010f10.13000000.f8ecb58d.98459605.c930f493.9ff4a1bd",
                    "Name": "mk020_minicut_comp_scene_cut_intentcut_v001.0561.png",
                    "Slots": {}
                }
            },
            "name": "mk020_minicut_comp_scene_cut_intentcut_v001.0561.png",
            "available_range": {
                "OTIO_SCHEMA": "TimeRange.1",
                "duration": {
                    "OTIO_SCHEMA": "RationalTime.1",
                    "rate": 24.0,
                    "value": 34.0
                },
                "start_time": {
                    "OTIO_SCHEMA": "RationalTime.1",
                    "rate": 24.0,
                    "value": 1002.0
                }
            },
            "available_image_bounds": null,
            "target_url": "file:////C/Users/nathanla/Desktop/mk020_shots/mk020_0060/mk020_minicut_comp_scene_cut_intentcut_v001.0561.png"
        },
        "urn:smpte:umid:060a2b34.01010105.01010f10.13000000.01c3e84a.98459605.b414f493.9ff4a1bd": {
            "OTIO_SCHEMA": "ExternalReference.1",
            "metadata": {
                "AAF": {
                    "ClassName": "SourceMob",
                    "CreationTime": "2022-09-23 07:30:31",
                    "EssenceDescription": {
                        "BlackReferenceLevel": 16,
                        "ClassName": "CDCIDescriptor",
                        "CodingEquations": "CodingEquations_ITU709",
                        "ColorPrimaries": "ColorPrimaries_ITU709",
                        "ColorRange": 225,
                        "ColorSiting": "CoSiting",
                        "ComponentWidth": 8,
                        "Compression": "04010202-7113-0000-060e-2b340401010a",
                        "DataOffset": 393216,
                        "DisplayHeight": 1080,
                        "DisplayWidth": 1920,
                        "DisplayXOffset": 0,
                        "DisplayYOffset": 0,
                        "EssenceBox": {
                            "Height": "900",
                            "PositionX": "-800",
                            "PositionY": "-450",
                            "Width": "1600"
                        },
                        "FrameIndexByteOrder": 18761,
                        "FrameLayout": "FullFrame",
                        "FrameSampleSize": 0,
                        "HorizontalSubsampling": 2,
                        "ImageAlignmentFactor": 4096,
                        "ImageAspectRatio": "16/9",
                        "ImageSize": 6406144,
                        "Length": 34,
                        "Locator": {},
                        "OffsetToFrameIndexes64": 6541799,
                        "ResolutionID": 1253,
                        "SampleRate": "24",
                        "SampledHeight": 1080,
                        "SampledWidth": 1920,
                        "SampledXOffset": 0,
                        "SampledYOffset": 0,
                        "SourceBox": {
                            "Height": "900",
                            "PositionX": "-800",
                            "PositionY": "-450",
                            "Width": "1600"
                        },
                        "StoredHeight": 1080,
                        "StoredWidth": 1920,
                        "TransferCharacteristic": "TransferCharacteristic_ITU709",
                        "ValidBox": {
                            "Height": "900",
                            "PositionX": "-800",
                            "PositionY": "-450",
                            "Width": "1600"
                        },
                        "VerticalSubsampling": 1,
                        "VideoLineMap": {},
                        "WhiteReferenceLevel": 235
                    },
                    "LastModified": "2022-09-23 07:30:31",
                    "MobAttributeList": {
                        "_ORIGINAL_FILEMOB_ID": "060a2b340101010501010f1013-000000-f5e7025598459605-a27cf4939ff4-a1bd",
                        "_PJ": "ALAB_project"
                    },
                    "MobID": "urn:smpte:umid:060a2b34.01010105.01010f10.13000000.01c3e84a.98459605.b414f493.9ff4a1bd",
                    "Name": "",
                    "Slots": {}
                }
            },
            "name": "urn:smpte:umid:060a2b34.01010105.01010f10.13000000.01c3e84a.98459605.b414f493.9ff4a1bd",
            "available_range": {
                "OTIO_SCHEMA": "TimeRange.1",
                "duration": {
                    "OTIO_SCHEMA": "RationalTime.1",
                    "rate": 24.0,
                    "value": 34.0
                },
                "start_time": {
                    "OTIO_SCHEMA": "RationalTime.1",
                    "rate": 24.0,
                    "value": 1002.0
                }
            },
            "available_image_bounds": null,
            "target_url": "file://Pixar-Nexis/Tools_Media/Avid%20MediaFiles/MXF/octave.2/V01.632D60B2_1C3E801C3E84AV.mxf"
        }
    },
    "active_media_reference_key": "DEFAULT_MEDIA"
}

@jminor
Copy link
Copy Markdown
Member

jminor commented Apr 11, 2024

@markreidvfx does this PR supersede this old one? AcademySoftwareFoundation/OpenTimelineIO#1063

@markreidvfx
Copy link
Copy Markdown
Member Author

@jminor yes it supersedes 1063. This is also a different approach.

@jminor
Copy link
Copy Markdown
Member

jminor commented Dec 7, 2024

FYI, we spent some time comparing new/old results for the time warp test suite, and some simple production examples. The changes made to the timecode look great, and the changes to the media references and metadata seem easy to accommodate in our (Pixar) pipeline.

We need to run some more complex examples to understand the nesting changes, and we plan to run a large set of older production example AAFs through to look for any problems, but in general this new approach looks great.

In this week's OTIO TSC meeting, I believe that @reinecke said Netflix is already using this updated code in their pipeline with good results.

Thanks @markreidvfx!

@k3vinpark
Copy link
Copy Markdown

k3vinpark commented Apr 10, 2025

Heyo! Kevin from Netflix here. Just wanted to say hello and chime in to give some context on how we are utilizing this updated adapter since it was mentioned earlier that we've been using it.

It's been giving us excellent results and has resolved several major bugs for us. However, we noticed a change in how markers are gathered. With the code in this PR, markers on source clips in Avid that fall within the visible range of the clip on the timeline are collected and attached to the clip in the OTIO timeline, even if they're not visible in the Avid timeline.

edit: after chatting with mark, it's because of how markers are gathered from the mastermobs now.

I understand this approach is quite opinionated and may not suit everyone's needs. I also don't want to assume that the marker behavior is not intended either, but I thought it was worth sharing our experience.

Thanks for the great work on the adapter! 🤗

mob_timeline.tracks.append(slot_track)

# master mobs don't have transitions so its safe to do this
_attach_markers(mob_timeline, indent)
Copy link
Copy Markdown

@k3vinpark k3vinpark Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this line will attach markers that are on the source clip but not visible in the timeline. It would be great to get an argument to turn this behavior on or off. Thanks!

p.s. however, it doesn't seem to resolve the issue for markers that are on a source subclip.

result.source_range = source_range

track_number = slot_track.metadata.get("AAF", {}).get("PhysicalTrackNumber")
marker_track = None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm that removing this block below will not attach markers that are on source subclips but not visible in the aaf timeline. If this can be excluded when using the flag as well, that would be awesome. Thanks! 🙏

@roger-limewoodsw
Copy link
Copy Markdown

Nice work, Mark!

@jminor
Copy link
Copy Markdown
Member

jminor commented Apr 14, 2025

FYI, it appears that this PR is included in Avid Media Composer 2024.12's OpenTimelineIO export feature.

Signed-off-by: Mark Reid <markreid@netflixanimation.com>
Signed-off-by: Mark Reid <markreid@netflixanimation.com>
- Only MasterMob tracks can become otio clips
- CompositionMob referenced by SourceClip become Stacks
- SourceMobs become MediaReferences
- More aggressively simpify
- Cache prevously processed Mobs

Signed-off-by: Mark Reid <markreid@netflixanimation.com>
- OperationGroup metadata only stored on effect
- Improved naming of OperationsGroups

Signed-off-by: Mark Reid <markreid@netflixanimation.com>

more flake crap
Signed-off-by: Mark Reid <markreid@netflixanimation.com>
Signed-off-by: Mark Reid <markreid@netflixanimation.com>
Signed-off-by: Mark Reid <markreid@netflixanimation.com>
@markreidvfx markreidvfx force-pushed the mastermob_refactor_v1 branch from ac19f50 to f09a971 Compare May 16, 2025 01:03
@markreidvfx markreidvfx marked this pull request as ready for review May 16, 2025 06:18
@markreidvfx
Copy link
Copy Markdown
Member Author

This is now rebased on top of the current main branch!

@jminor
Copy link
Copy Markdown
Member

jminor commented May 16, 2025

Yay! Thanks @markreidvfx - we'll give it a try. Do you plan any further changes before this is ready to land?

Copy link
Copy Markdown
Collaborator

@timlehr timlehr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great @markreidvfx! Thanks so much for all the work on this. I added some comments, but I approve it anyway, because those are more nice-to-haves. I think bumping the version and dropping Python versions could be a separate PR.

dependencies = [
"opentimelineio >= 0.17.0",
"pyaaf2>=1.4.0"
"pyaaf2>=1.7.0"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be in a separate PR?
Might as well require pyaaf2>=1.7.1

os: [ubuntu-latest, macos-13, windows-latest, macos-latest]
otio-version: ["main", "0.17.0"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: [ "3.9", "3.10", "3.11", "3.12"]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be in a separate PR?

for c, child in enumerate(thing):
thing[c] = _simplify(child)

if isinstance(thing, otio.schema.Track):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can break this into a separate function, we're getting pretty nested here.

):
raise AAFAdapterError(
"Wrong duration? {} should be {} in {}".format(
"Wrong duration? {} should be {} {} {} in {}".format(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're all Python 3 now, we could use f-strings for these, make it more legible.


for essence_group in _iter_slot_essence_groups(slot):
essence_group_items = []
for component in essence_group:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can break this loop out into another function. Pretty deep nesting.

@timlehr
Copy link
Copy Markdown
Collaborator

timlehr commented Jul 17, 2025

@markreidvfx lets merge this for now to get a 2.0.0 out of the door - my comments are all really minor and we can address them later. This MR has been lingering for too long and most users already leverage it in their own builds it seems.

Copy link
Copy Markdown

@lbarral lbarral left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a somewhat high abstraction/indirection between the AAF model and the code as written here, which makes it complex to review from our perspective.
Nevertheless, there are limitations concerning what can be represented in AAF and what OTIO permits. One such limitation is the marker concept, which in the AAF data model, allows for event based and timeline based markes. The former will not have a duration/length specified (they occur in an instant) but the OTIO mapping makes them have length set to 1, therefore changing their nature. This is seen in https://github.com/OpenTimelineIO/otio-aaf-adapter/pull/44/files#diff-76feb0a6a0d1a0ee6f77159db124c3ef8956a95b1a667138f54d5f65caa8a813R910
We also see some abbreviations on how the start time code is being interpreted, which do not fully translate the logic that avid employs in the AAFs it produces.
Because the mob hierarchy that can be established in the AAF data model cannot be fully reproduced in the OTIO space, some interpretation will occur in both directions, which might mean that a roundtrip isn't always possible and/or producing the same result.

if start_tc:
start = start + start_tc.start_time.rescaled_to(edit_rate)
duration_tc = start_tc.duration.rescaled_to(edit_rate)
duration = max(duration, duration_tc)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we take longest duration between SourceClip and TC?

target_urls = target_urls or [None]

if isinstance(mob.descriptor, aaf2.essence.MultipleDescriptor):
# stream protocol here
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is meant by 'stream protocol'?


# This is custom behavior we probably shouldn't be doing this
# keeping it here to maintain some compatibly
unc_path = metadata.get("UserComments", {}).get("UNC Path", None)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an unreliable source of information - tagged values are specific to certain workflows and the Network Locator element from the FileDescriptor is a better source of information to retrieve the path to the essence file.

continue

return False
if slot['PhysicalTrackNumber'].value == 1:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the AAF specification, any Mob can have a timecode slot/track - however, for Avid, we will always derive the timecode from the lower-level FileSourceMob (a.k.a. Physical Mob, i.e. the Mob that carries a PhysicalDescriptor). Then all the offsets between the MasterMob SourceClip - which defines the output /playback position of the master clip under analysis - and the referred PhysicalSource, via the FileSourceMob (top level essence mob, with a File Descriptor) are taken into account.
The PhysicalMob, for Avid generated media, shall only have a single Timecode slot, which in most cases have a PhysicalTrackNumber == 1, but there's no restriction, so we believe this validation might be too much.

This function seems to be used generically, and not only applied to Physical Mobs, which means the interpretation of the logic above has to be done in the context where it is being used from.


# if the user manually sets the timecode the mastermob
# will have an timecode track that acts as a global offset
global_start_time = _get_mob_start_tc(mob)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in other comments, the start time code seems to be taken from the MasterMob in this case, which is not expected in an "Avid file" - we expect the start TC will be computed from the relationship between the MasterMob source clip reference position relative to the FileSource and then Physical Source they are referring to.
Of course the above applies to the logic Avid uses in its master clip concept.

# this track and use it for the Timeline's global_start_time`
timecode = _get_mob_start_tc(item)
if timecode:
result.global_start_time = timecode.start_time
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works here, because we're getting the start timecode from the composition, which is 'new material' so it will be separate from the master clips it uses.

@jminor
Copy link
Copy Markdown
Member

jminor commented Jul 23, 2025

@lbarral would it be better if the adapter mapped an instantaneous event based marker to an OTIO marker with 0 duration (instead of 1)? That is allowed in OTIO and could retain the distinction. That could maybe be addressed as part of #47

@k3vinpark
Copy link
Copy Markdown

@lbarral would it be better if the adapter mapped an instantaneous event based marker to an OTIO marker with 0 duration (instead of 1)? That is allowed in OTIO and could retain the distinction. That could maybe be addressed as part of #47

I'll defer to @lbarral for their opinion, but I personally think it would be better to have the OTIO marker be 0 duration for an event based marker, with spanned markers in Avid mapped to OTIO markers with durations > 0. It would be great to maintain that distinction.

@timlehr
Copy link
Copy Markdown
Collaborator

timlehr commented Aug 26, 2025

After discussing with @markreidvfx and @reinecke, we decided to land this PR as is, since it's already In use successfully at multiple studios. To address comments and issues with this rewrite, we should aim for smaller more targeted PRs that have less of a review / maintenance overhead.

Since this is a breaking change, we will coordinate a 2.0 release for the adapter in the coming months. Thank you @markreidvfx for all the amazing work on this.

@timlehr timlehr merged commit 1f57821 into OpenTimelineIO:main Aug 26, 2025
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants