unexpected cross fade addition in VSE python script

I trying to write a blender (2.74) python script (not plugin) for the Video Sequence Editor (VSE) which adds cross fades (audio and video) on adjacent sequences (located on different channels).

Here is the current script:


# this blender python script adds cross transition effect strips on adjacent strips which are on different channels
import bpy

# number of frames for the crossing
CROSS_FRAMES = 20

# find the right context
screen = bpy.context.window.screen
for area in screen.areas:
    if area.type == 'SEQUENCE_EDITOR':
        for region in area.regions:
            if region.type == 'WINDOW':
                override = {'window': bpy.context.window, 'screen': screen, 'area': area, 'region': region, 'scene': bpy.context.scene}
                # for two following sequences in the current scene
                for sequence_first in bpy.context.scene.sequence_editor.sequences_all:
                    for sequence_second in bpy.context.scene.sequence_editor.sequences_all:
                        if sequence_first.frame_final_end==sequence_second.frame_final_start and sequence_first.channel!=sequence_second.channel:
                            # visual sequences
                            if ((sequence_first.type=='MOVIE' or sequence_first.type=='IMAGE') and (sequence_second.type=='MOVIE' or sequence_second.type=='IMAGE')) or (sequence_first.type=='SOUND' and sequence_second.type=='SOUND'):
                                print("crossing "+sequence_first.name+" with "+sequence_second.name)
                                # add cross margin to first sequence
                                if sequence_first.type=='IMAGE':
                                    sequence_first.frame_final_duration += CROSS_FRAMES/2
                                else: # MOVIE or SOUND
                                    if sequence_first.frame_offset_end<CROSS_FRAMES/2:
                                        sequence_first.frame_offset_end = 0
                                    else:
                                        sequence_first.frame_offset_end -= CROSS_FRAMES/2
                                # add cross margin to second sequence
                                if sequence_second.type=='IMAGE':
                                    sequence_second.frame_start -= CROSS_FRAMES/2
                                    sequence_second.frame_final_duration += CROSS_FRAMES/2
                                else: # MOVIE or SOUND
                                    if sequence_second.frame_offset_start<CROSS_FRAMES/2:
                                        sequence_second.frame_offset_start = 0
                                    else:
                                        sequence_second.frame_offset_start -= CROSS_FRAMES/2
                                # select sequences
                                bpy.ops.sequencer.select_all(action='DESELECT')
                                sequence_first.select = True
                                bpy.context.scene.sequence_editor.active_strip = sequence_first
                                sequence_second.select = True
                                bpy.context.scene.sequence_editor.active_strip = sequence_second
                                #bpy.ops.sequencer.select()
                                # add cross fade
                                if sequence_first.type=='SOUND' and sequence_second.type=='SOUND':
                                    bpy.ops.sequencer.crossfade_sounds()
                                    print("audio")
                                else: # MOVIE/IMAGE
                                    bpy.ops.sequencer.effect_strip_add(override, type='CROSS', frame_start=sequence_second.frame_final_start, frame_end=sequence_first.frame_final_end)

                break

bpy.ops.sequencer.select_all(action='DESELECT')

I’m running the script using

blender montage.blend --python BlenderAddCross.py

Here some aspects I don’t really understand:

  • I have to use override (from the beginning of the script) in effect_strip_add else the context is wrong and I can’t add the effect strip
  • I have to set bpy.context.scene.sequence_editor.active_strip = sequence_second else crossfade_sounds() complains the context is wrong because no sound is selected (even it is selected just before)
  • I have to set bpy.context.scene.sequence_editor.active_strip = sequence_second else effect_strip_add() will often add the effect a unexpected locations (not the the sequences overlap)
  • bpy.ops.sequencer.select() complains I’m in the wrong context, but I couldn’t find if the python source code which one it expects.

And the most important issue right now it that crossfade_sounds() will often increase/decrease the volume over the whole sequence, instead of the overlapping pieces.
My guess is that there is something wrong with the selection, but I didn’t manage to use select() because of a context issue I couldn’t understand.
Could someone explain this strange behaviour where the effect is not applied on the overlapping piece, or point to a context/select tutorial?

edit 2015-05-16:
I found a solution by myself for this problem.
See the third post on this thread for the description (once the moderators approved it)

If you are executing a script from the command line, you have virtually no “context” at all. Naturally, the operators almost never document what kind of context they use when. Because that would make it too easy.

Essentially, the context is adding further, undocumented parameters to an operator. It’s like passing the entire program state into a function call and say “Here you got the entire thing, pick what you need yourself”. Also the add_effect_strip operator already does several completely different things depending on what parameters are used.

Anyway, the video sequence operators all need a sequence editor area in their context. Don’t ask me why, the polling function is in C code and tests for it, but the window/area/gui is - so far as I can determine - almost never used.

The active_sequence and selected_sequences is also in the context, and are used as “pseudo-parameters” in the operators. So you can either select them in your script like you just did, or pass them in the override context too (it took me weeks to figure that one out!).

If you are having trouble with the sound crossfade, maybe you should write your own function to do it.

In situations like this, I iterate over every area there is and just use the first which looks like a sequence editor in the context override.

step 1:

to figure out were bpy.ops.sequencer.crossfade_sounds() made a mistake I added debug print messages (the python source is in sequencer.py).


print("start,stop:",sequence_second.frame_final_start,sequence_first.frame_final_end)
bpy.context.scene.frame_current    = sequence_second.frame_final_start
sequence_first.keyframe_insert("volume")
print(bpy.context.scene.frame_current,sequence_first.volume,sequence_second.volume)
bpy.context.scene.frame_current = sequence_first.frame_final_end
sequence_first.volume = 0
sequence_first.keyframe_insert("volume")
sequence_second.keyframe_insert("volume")
print(bpy.context.scene.frame_current,sequence_first.volume,sequence_second.volume)
bpy.context.scene.frame_current = sequence_second.frame_final_start
sequence_second.volume = 0
sequence_second.keyframe_insert("volume")
print(bpy.context.scene.frame_current,sequence_first.volume,sequence_second.volume)

first the script goes through sound sequences of channel 2 and crosses them with following sound sequence on channel 4


crossing 00673.mkv with 00682.001
start,stop: 140617,140637
140617 1.0 1.0
140637 0.0 1.0
140617 0.0 0.0

the volume on the start of the cross is wrong in the end. it should be 1.0 instead of 0.0

when I verify in the GUI in blender I get:
140617 1.0 0.0
140637 0.0 1.0

and that are the right values

then the script goes through sound sequences of channel 4 and crosses them with following sound sequence on channel 2


crossing 00671.mkv with 00673.mkv
start,stop: 128652,128672
128652 0.0 0.0
128672 0.0 0.0
128652 0.0 0.0

the volume is not at the expected 1.0 anymore, but always at 0.0

this is also the case in the GUI:
128652 0.0 0.0
128672 0.0 0.0

as if the wrong volume of the sequence first found 140617 0.0 is in fact used for the next cross

step 2:

I played with setting the value on a single sequence


import bpy

for sequence in bpy.context.scene.sequence_editor.sequences_all:
    if sequence.name == "00673.mkv":
        bpy.context.scene.frame_current = sequence.frame_final_start+20
        print(bpy.context.scene.frame_current,sequence.volume)
        tmp_vol = sequence.volume
        sequence.keyframe_insert("volume")
        bpy.context.scene.frame_current = sequence.frame_final_start
        print(bpy.context.scene.frame_current,sequence.volume)
        sequence.volume = 0
        sequence.keyframe_insert("volume")
        print(bpy.context.scene.frame_current,sequence.volume)
        bpy.context.scene.frame_current = sequence.frame_final_start+20
        print(bpy.context.scene.frame_current,sequence.volume)

this produces


128682 1.0
128662 1.0
128662 0.0
128682 0.0

we see the same error: 128662 0.0 should be at 1.0

in the GUI in blender I read
128662 1.0
128682 0.0

conclusion:

although the volume set should be only the volume on this keyframe, it is as if it was set for the whole sequence.
and this wrong value is read and used when adding keyframes elsewhere, where the volume should have a different value.

solution:

my solution was to remember the volume and set it back afterwards so the right value would be used.


                                    # bpy.ops.sequencer.crossfade_sounds() is buggy because of the wrong sequence volume
                                    # this is a reimplementation with volume correction (and it does not use the selection)
                                    tmp_frame = bpy.context.scene.frame_current
                                    bpy.context.scene.frame_current    = sequence_second.frame_final_start
                                    tmp_volume_first = sequence_first.volume
                                    sequence_first.keyframe_insert("volume")
                                    bpy.context.scene.frame_current = sequence_first.frame_final_end
                                    sequence_first.volume = 0
                                    sequence_first.keyframe_insert("volume")
                                    tmp_volume_second = sequence_second.volume
                                    sequence_second.keyframe_insert("volume")
                                    bpy.context.scene.frame_current = sequence_second.frame_final_start
                                    sequence_second.volume = 0
                                    sequence_first.volume = tmp_volume_first # correct the volume, else it will think it's the previously set value
                                    sequence_second.keyframe_insert("volume")
                                    bpy.context.scene.frame_current = sequence_first.frame_final_end
                                    sequence_second.volume = tmp_volume_second # correct the volume, else it will think it's the previously set value
                                    bpy.context.scene.frame_current = tmp_frame

this is not a perfect solution because the volume gets applied to the rest of the strip.
for example is there is already a volume increase before and I read an intermediate value, then this intermediate values gets “applied” to the rest of the strip.
thus you should only use this script on sequences with constant volume, then it works just fine.

I would have to spend more time with the keyframe inner working to have the perfect working solution.