Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example showing simple timeline creation #82

Open
maptz opened this issue Aug 15, 2020 · 4 comments
Open

Add an example showing simple timeline creation #82

maptz opened this issue Aug 15, 2020 · 4 comments

Comments

@maptz
Copy link

maptz commented Aug 15, 2020

Hi @markreidvfx ,

Amazing work. Thanks so much.

I'm fighting trying to programatically create an AAF audio timeline that I can import into Avid with an AAF.

Essentially, I want to create an AAF with a timeline that references a single clip (perhaps with a couple of cuts). Once imported I want to relink the master clip in the Avid, so the AAF generated clip has got to be imported with a specific name, Tape Id, have 4 audio tracks starting at a specific timecode. I don't want to include the audio binary itself in the AAF though.

Are you able to show an example that does this?

I think it would be a great example for your examples folder because it would be a good starting point for building projects.

@maptz
Copy link
Author

maptz commented Aug 15, 2020

FYI, I think the code in Issue 42 here must be pretty close.

I'm struggling a bit though to adapt this for when there's no essence to import. IE file_mob.import_dnxhd_essence(j['name']+".dnxhd", edit_rate, tm_clip) isn't right for my use case as I don't want to embed an essence.

If I don't attach the essence itself, I get a No Slot Id: 1 error. Any help?

import aaf2

# mocked up dictionary from Layout
maya_seq = {
        'aaf_name': 'Layout_Seq_AAF',
        'seq_name': 'Layout Send to Edit',
        'tracks': [
            {
                'v_track': 1,
                'v_track_name': 'Track 1',
                'clips': [
                    {'name': 'filler', 'frames': 50},
                    {'name': '960_010', 'frames': 100, 'start': 20, 'tape': '960_010_tape'}
                ]
            },
            {
                'v_track': 2,
                'v_track_name': 'Track 2',
                'clips': [
                    {'name': 'filler', 'frames': 130},
                    {'name': '960_020a', 'frames': 96, 'start': 1, 'tape': '960_020a_tape'}
                ]
            },
            {
                'v_track': 3,
                'v_track_name': 'Track 3',
                'clips': [
                    {'name': 'filler', 'frames': 130},
                    {'name': '960_020b', 'frames': 96, 'start': 1, 'tape': '960_020b_tape'}
                ]
            }

        ]
    }

with aaf2.open(maya_seq['aaf_name']+'.aaf', 'w') as f:

    edit_rate = 23.976
    timecode_fps = 24

    comp_mob = f.create.CompositionMob()
    comp_mob.usage = "Usage_TopLevel"
    comp_mob.name = maya_seq['seq_name']
    f.content.mobs.append(comp_mob)

    tc_slot = comp_mob.create_empty_sequence_slot(edit_rate, media_kind='timecode')
    tc = f.create.Timecode(24, True)
    tc.start = 86400
    tc_slot.segment.components.append(tc)

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

    for i in maya_seq['tracks']:
        sequence = f.create.Sequence(media_kind="picture")
        nested_scope.slots.append(sequence)

        for j in i['clips']:
            if j['name'] == 'filler':
                if i['v_track'] <= 1:
                    comp_fill = f.create.Filler("picture", j['frames'])
                else:
                    comp_fill = f.create.ScopeReference("picture", j['frames'])
                    comp_fill['RelativeSlot'].value = 1
                    comp_fill['RelativeScope'].value = 0

                sequence.components.append(comp_fill)

            else:

                tape_mob = f.create.SourceMob()
                tape_name = j['tape']
                tape_slot, tape_timecode_slot = tape_mob.create_tape_slots(tape_name, edit_rate, timecode_fps, media_kind='picture')
                tape_slot.segment.length = (j['frames'] + 86400)
                f.content.mobs.append(tape_mob)

                file_mob = f.create.SourceMob()
                tm_clip = tape_mob.create_source_clip(slot_id=1, start=86400, length=j['frames'])
                # NOTE: if you use the import_dnxhd_essence on master_mob you can skip the file_mobs
                file_mob.import_dnxhd_essence(j['name']+".dnxhd", edit_rate, tm_clip)

                file_mob.name = j['name']
                f.content.mobs.append(file_mob)

                master_mob = f.create.MasterMob()
                master_mob.name = j['name']
                f.content.mobs.append(master_mob)

                fm_clip = file_mob.create_source_clip(slot_id=1, length=j['frames'])
                print fm_clip.start, fm_clip.length

                slot = master_mob.create_picture_slot(edit_rate)
                slot.segment.components.append(fm_clip)

                mm_clip = master_mob.create_source_clip(slot_id=1, start=(j['start']-1), length=j['frames'])
                sequence.components.append(mm_clip)

    # comp_mob.dump()

@TrevorAyl
Copy link

I'm struggling getting off the ground here too.

I have data that is tape_name, start, length that I'm trying to create a (v1 picture only) AAF from.

I get stuck on where the 'start' should be - and get errors when start > length.

Commented code below on what I think is occurring - happy for any comments, corrections, clarifications ... (Including pointers as to best approach to find these things in the code)

import aaf2

# mocked up dictionary from Layout
maya_seq = {
        'aaf_name': 'Layout_Seq_AAF',
        'seq_name': 'Layout Send to Edit',
        'tracks': [
            {
                'v_track': 1,
                'v_track_name': 'Track 1',
                'clips': [
                    {'name': 'filler', 'frames': 50},
                    {'name': '960_010', 'frames': 100, 'start': 20, 'tape': '960_010_tape'}
                ]
            },
            {
                'v_track': 2,
                'v_track_name': 'Track 2',
                'clips': [
                    {'name': 'filler', 'frames': 130},
                    {'name': '960_020a', 'frames': 96, 'start': 1, 'tape': '960_020a_tape'}
                ]
            },
            {
                'v_track': 3,
                'v_track_name': 'Track 3',
                'clips': [
                    {'name': 'filler', 'frames': 130},
                    {'name': '960_020b', 'frames': 96, 'start': 1, 'tape': '960_020b_tape'}
                ]
            }

        ]
    }

with aaf2.open(maya_seq['aaf_name']+'.aaf', 'w') as f:

    edit_rate = 23.976
    timecode_fps = 24

    comp_mob = f.create.CompositionMob()
    comp_mob.usage = "Usage_TopLevel"
    comp_mob.name = maya_seq['seq_name']
    f.content.mobs.append(comp_mob)

comp_mob will be the container for the composition that will hold the sequence(s)?

tc_slot = comp_mob.create_empty_sequence_slot(edit_rate, media_kind='timecode')
tc = f.create.Timecode(24, True)
tc.start = 86400
tc_slot.segment.components.append(tc)

comp_mob now has a 24fps time code track

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

I think nested_scope is going to be a container for multiple tracks or a submaster sequence?

for i in maya_seq['tracks']:
    sequence = f.create.Sequence(media_kind="picture")
    nested_scope.slots.append(sequence)

    for j in i['clips']:
        if j['name'] == 'filler':
            if i['v_track'] <= 1:
                comp_fill = f.create.Filler("picture", j['frames'])

comp_fill, as yet unused, contains filler

            else:
                comp_fill = f.create.ScopeReference("picture", j['frames'])
                comp_fill['RelativeSlot'].value = 1
                comp_fill['RelativeScope'].value = 0

Not sure what happens to comp_fill above track 1

            sequence.components.append(comp_fill)

comp_fill added to the sequence

        else:

            tape_mob = f.create.SourceMob()
            tape_name = j['tape']
            tape_slot, tape_timecode_slot = tape_mob.create_tape_slots(tape_name, edit_rate, timecode_fps, media_kind='picture')
            tape_slot.segment.length = (j['frames'] + 86400)
            f.content.mobs.append(tape_mob)

I think the tape_mob identifies a tape source, with a tape_name for relink purposes (as opposed to a 'file' source, that would have a file path for relink purposes)

            file_mob = f.create.SourceMob()
            tm_clip = tape_mob.create_source_clip(slot_id=1, start=86400, length=j['frames'])

And tm_clip is a clip based on the tape_mob - with start time code set to 01.00.00.00 @24 fps

            # NOTE: if you use the import_dnxhd_essence on master_mob you can skip the file_mobs
            file_mob.import_dnxhd_essence(j['name']+".dnxhd", edit_rate, tm_clip)

            file_mob.name = j['name']
            f.content.mobs.append(file_mob)

But then tm_clip is used in a file_mob so I get confused...

            master_mob = f.create.MasterMob()
            master_mob.name = j['name']
            f.content.mobs.append(master_mob)

And the master_mob is also necessary?

            fm_clip = file_mob.create_source_clip(slot_id=1, length=j['frames'])

The fm_clip doesn't have a start time?

            print fm_clip.start, fm_clip.length

            slot = master_mob.create_picture_slot(edit_rate)
            slot.segment.components.append(fm_clip)

But the fm_clip does get appended to the master_mob

            mm_clip = master_mob.create_source_clip(slot_id=1, start=(j['start']-1), length=j['frames'])

and then an mm_clip gets created (why is start time 1 frame early?) and appended to the sequence.

            sequence.components.append(mm_clip)

# comp_mob.dump()

@XDeschuyteneer
Copy link

XDeschuyteneer commented Feb 11, 2024 via email

@TrevorAyl
Copy link

Thanks Xavier
I've got it sorted now, for the most part.

How are you reviewing the Avid aaf? Is it with aafdump.py?

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

No branches or pull requests

3 participants