markreidvfx / pyaaf2

Read and write Advanced Authoring Format (AAF) files
http://pyaaf.readthedocs.io
MIT License
135 stars 36 forks source link

Difference between group clips and sequences - both are composition mobs #118

Open davidnorden opened 1 year ago

davidnorden commented 1 year ago

I am trying to export group clips from an avb-file, and assume it should be possible since there is already a script that exports a sequence as an aaf from a avb-file (using pyavb). Both are composition mobs, and exporting a group clip from Media Composer and importing into Premiere Pro will show that Premiere Pro interprets the aaf for a group clip as a sequence. So what is the unique identifier in the aaf-file that lets Media Composer know that the group clip is NOT a sequence (since both are composition mobs...) Does anyone know?

TrevorAyl commented 9 months ago

I think group clips are actually 'selector's - the active clip being selected and the others 'alternatives'.

Although chatgpt mentions 'essence groups' too

Still trying to get my head around it...

markreidvfx commented 9 months ago

Yes GroupClips are called selectors. EssenceGroups are different, they are use for different quality levels of the same media. Essence is what AAF calls media.

I wouldn't trust any code chatgpt gives you for using this library. There aren't enough examples in the wild for it to steal code from.

TrevorAyl commented 9 months ago

This is as far as I can get by myself!

Something wrong in how I append the selector (e.g. do I need the selector_sequence?)

Hopefully someone (@markreidvfx) can point out the errors of my ways...


import sys
import aaf2
import json
import os
from datetime import datetime

file_name = "./testSelector.aaf"
edit_rate = 50
sequence_start = 1*60*60*edit_rate
sequence_length = 20*edit_rate
dictMobID = {}

with aaf2.open(file_name, "w")  as f:
    # Composition Mob created first
    comp_mob = f.create.CompositionMob()
    comp_mob.usage = "Usage_TopLevel"
    comp_mob.name = "test"

    # Create a TimelineMobSlot with a Timecode Segment for the start timecode
    tc_segment = f.create.Timecode(edit_rate)
    tc_segment.start = sequence_start
    tc_slot = comp_mob.create_timeline_slot(edit_rate, slot_id=1)
    tc_slot.segment = tc_segment

    # Create Selector Composition Mob outside the Master MOBs loop
    selector_comp_mob = f.create.CompositionMob()
    selector_comp_mob.usage = "Usage_TopLevel"
    selector_comp_mob.name = "GRP - sequence test"

    selector_sequence = f.create.Sequence(media_kind="picture")
    selector_timeline_slot = selector_comp_mob.create_timeline_slot(edit_rate)
    selector_timeline_slot.segment = selector_sequence
    selector_timeline_slot.name = "GRP-TrackC"

    # Make the Master MOBs
    for i in range(1,5):
        tape_name= f"tapeName_{i}"

        # Make the Tape MOB
        tape_mob = f.create.SourceMob()

        tape_slot, tape_timecode_slot = tape_mob.create_tape_slots(tape_name, edit_rate, edit_rate)        

        # Set start time for clip
        tape_timecode_slot.segment.start = sequence_start
        # Reduce from default 12 hour length
        tape_slot.segment.length = sequence_length

        f.content.mobs.append(tape_mob)
        # Make a FileMob
        file_mob = f.create.SourceMob()

        file_description = f.create.CDCIDescriptor()
        file_description['ComponentWidth'].value = 8
        file_description['HorizontalSubsampling'].value = 4
        file_description['ImageAspectRatio'].value = '16/9'
        file_description['StoredWidth'].value = 1920
        file_description['StoredHeight'].value = 1080
        file_description['FrameLayout'].value = 'FullFrame'
        file_description['VideoLineMap'].value = [42, 0]
        file_description['SampleRate'].value = edit_rate
        file_description['Length'].value = sequence_length

        file_mob.descriptor = file_description
        # This length affects length of master mob and in timeline
        tape_clip = tape_mob.create_source_clip(slot_id=1, length=sequence_length)
        slot = file_mob.create_picture_slot(edit_rate)
        slot.segment.components.append(tape_clip)

        f.content.mobs.append(file_mob)

        master_mob = f.create.MasterMob()
        master_mob.name = f"MM_name_{i}"
        clip = file_mob.create_source_clip(slot_id=1)
        slot = master_mob.create_picture_slot(edit_rate)
        slot.segment.components.append(clip)

        dictMobID[i] = master_mob

        f.content.mobs.append(master_mob)

    for i in range(1,2):
        sequence = f.create.Sequence(media_kind="picture")
        timeline_slot = comp_mob.create_timeline_slot(edit_rate)
        timeline_slot.segment= sequence
        timeline_slot.name = "SRC"

        dictClipID = {}
        clip_position = 200
        for key in dictMobID:

            mm = dictMobID[key]
            # print(mm.mob_id)
            clip_start = 0
            clip_length = 2*edit_rate
            # Create a SourceClip
            clip = mm.create_source_clip(slot_id=1)
            # print(str((clip.mob_id)))
            # This is the start point of the master mob in the source clip?
            clip.start = clip_position
            # This is the length of the source clip - filled with the master mob
            clip.length = clip_length
            dictClipID[key] = clip

            sequence.components.append(clip)

    # Attempt at creating selector
    selector = f.create.Selector()
    selector.media_kind = "picture"

    # Set the first clip as selected and rest as alternates
    first_clip = True
    for clip in dictClipID.values():
        if first_clip:
            selector['Selected'].value = clip
            first_clip = False
        # else:
            selector['Alternates'].append(clip)

    selector_sequence.components.append(selector)
    sequence.components.append(selector_sequence)

    # Append your main composition mob at the end
    f.content.mobs.append(comp_mob)

errror message:

    raise AAFAttachError("cannot attached obj to %s already attached to %s" % (dir_entry.path(), self.dir.path()))
aaf2.exceptions.AAFAttachError: cannot attached obj to /Header-2/Content-3b03/Mobs-1901{c}/Slots-4403{1}/Segment-4803/Components-1001{4}/Components-1001{0}/Selected-f01 already attached to /Header-2/Content-3b03/Mobs-1901{c}/Slots-4403{1}/Segment-4803/Components-1001{0}
XDeschuyteneer commented 9 months ago

You need to create a timeline (f.create.CompositionMob). Then in this timeline, create a sequence (f.create.Sequence). Then add in this sequence components the selectors (f.create.Selector). And in this selector, you need to create “sub clips”, depending on your needs, logic, it will vary, but usually create_source_clip method on your “object” with slot id, start and length.

On 1 Mar 2024, at 13:17, TrevorAyl @.***> wrote:

This is as far as I can get by myself!

Something wrong in how I append the selector (e.g. do I need the selector_sequence?)

Hopefully someone @.*** https://github.com/markreidvfx) can point out the errors of my ways...

import sys import aaf2 import json import os import logging from datetime import datetime

file_name = "./testSelector.aaf" edit_rate = 50 sequence_start = 16060edit_rate sequence_length = 20edit_rate dictMobID = {}

with aaf2.open(file_name, "w") as f:

Composition Mob created first

comp_mob = f.create.CompositionMob()
comp_mob.usage = "Usage_TopLevel"
comp_mob.name = "test"

# Create a TimelineMobSlot with a Timecode Segment for the start timecode
tc_segment = f.create.Timecode(edit_rate)
tc_segment.start = sequence_start
tc_slot = comp_mob.create_timeline_slot(edit_rate, slot_id=1)
tc_slot.segment = tc_segment

# nested_slot = comp_mob.create_timeline_slot(edit_rate)
# nested_slot['PhysicalTrackNumber'].value = 1
# nested_scope = f.create.NestedScope()
# nested_slot.segment= nested_scope

# Create Selector Composition Mob outside the Master MOBs loop
selector_comp_mob = f.create.CompositionMob()
selector_comp_mob.usage = "Usage_TopLevel"
selector_comp_mob.name = "GRP - sequence test"

selector_sequence = f.create.Sequence(media_kind="picture")
selector_timeline_slot = selector_comp_mob.create_timeline_slot(edit_rate)
selector_timeline_slot.segment = selector_sequence
selector_timeline_slot.name = "GRP-TrackC"

# Make the Master MOBs
for i in range(1,5):
    tape_name= f"tapeName_{i}"

    # Make the Tape MOB
    tape_mob = f.create.SourceMob()

    tape_slot, tape_timecode_slot = tape_mob.create_tape_slots(tape_name, edit_rate, edit_rate)        

    # set start time for clip
    tape_timecode_slot.segment.start = sequence_start
    #Reduces from default 12 hours
    tape_slot.segment.length = sequence_length

    f.content.mobs.append(tape_mob)
    # Make a FileMob - not sure where this goes?
    file_mob = f.create.SourceMob()

    file_description = f.create.CDCIDescriptor()
    file_description['ComponentWidth'].value = 8
    file_description['HorizontalSubsampling'].value = 4
    file_description['ImageAspectRatio'].value = '16/9'
    file_description['StoredWidth'].value = 1920
    file_description['StoredHeight'].value = 1080
    file_description['FrameLayout'].value = 'FullFrame'
    file_description['VideoLineMap'].value = [42, 0]
    file_description['SampleRate'].value = edit_rate
    file_description['Length'].value = sequence_length

    file_mob.descriptor = file_description
    # This length affects length of master mob and in timeline
    tape_clip = tape_mob.create_source_clip(slot_id=1, length=sequence_length)
    slot = file_mob.create_picture_slot(edit_rate)
    slot.segment.components.append(tape_clip)

    f.content.mobs.append(file_mob)

    master_mob = f.create.MasterMob()
    master_mob.name = f"MM_name_{i}"
    clip = file_mob.create_source_clip(slot_id=1)
    slot = master_mob.create_picture_slot(edit_rate)
    slot.segment.components.append(clip)

    dictMobID[i] = master_mob

    f.content.mobs.append(master_mob)

for i in range(1,2):
    sequence = f.create.Sequence(media_kind="picture")
    timeline_slot = comp_mob.create_timeline_slot(edit_rate)
    timeline_slot.segment= sequence
    timeline_slot.name = "SRC"

    dictClipID = {}
    clip_position = 200
    for key in dictMobID:

        mm = dictMobID[key]
        # print(mm.mob_id)
        clip_start = 0
        clip_length = 2*edit_rate
        # Create a SourceClip
        clip = mm.create_source_clip(slot_id=1)
        # print(str((clip.mob_id)))
        # This is the start point of the master mob in the source clip?
        clip.start = clip_position
        # This is the length of the source clip - filled with the master mob
        clip.length = clip_length
        dictClipID[key] = clip

        sequence.components.append(clip)

# Attempt at creating selector
selector = f.create.Selector()
selector.media_kind = "picture"

# Set the first clip as selected and rest as alternates
first_clip = True
for clip in dictClipID.values():
    if first_clip:
        selector['Selected'].value = clip
        first_clip = False
    # else:
    #     # This is pseudo-code; you'll need to adjust how alternates are added based on pyaaf2's API
    #     selector.alternates.append(clip)

selector_sequence.components.append(selector)
sequence.components.append(selector_sequence)

# # Finally, append selector_comp_mob to the content
# f.content.mobs.append(selector_comp_mob)

# Append your main composition mob at the end
f.content.mobs.append(comp_mob)

— Reply to this email directly, view it on GitHub https://github.com/markreidvfx/pyaaf2/issues/118#issuecomment-1973088207, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAH6PWVLAGMHPX72LIOOINLYWBWWBAVCNFSM6AAAAAAWT66PUWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSNZTGA4DQMRQG4. You are receiving this because you are subscribed to this thread.