Adding image sequence: poll() failed, context is incorrect

I’m trying to write a script to import image sequences - specifically, to take a bunch of renders in this directory structure:

renders ⌄
    scene_01 ⌄
	    image0001.png
	    image0002.png
	    ...
    scene_02 ⌄
	    image0001.png
	    image0002.png
	    ...
    scene_03 ⌄
	    image0001.png
	    image0002.png
	    ...

and import them into a sequence in the VSE, one after the other.

[scene_01 strip][scene_02 strip][scene_03 strip]

But I’m stuck at a very basic level - I can’t get the right context for the image_strip_add() operator. Here’s the current script:

import bpy
import os

renders_folder = bpy.path.abspath("//renders")
rendered_scenes = sorted(os.listdir(renders_folder))

for scene in rendered_scenes:
    folder = renders_folder + '/' + scene
    images = []
    for image in sorted(os.listdir(folder)):
        images.append({"name": image })

    bpy.ops.sequencer.image_strip_add(
        directory=folder,
        files=images,
        show_multiview=False,
        channel=1)

The error message I get is:
Operator bpy.ops.sequencer.image_strip_add.poll() failed, context is incorrect

I don’t know which aspect of the context is incorrect, or how I can fix it. Is there a way to get more detailed information?
I don’t know how to properly get a thorough view of the current context, but I can print a bunch of values:

area: None
blend_data: <bpy_struct, BlendData at 0x7f76f6315c08>
collection: <bpy_struct, Collection("Master Collection") at 0x7f76f525bf48>
engine: CYCLES
gizmo_group: None
layer_collection: <bpy_struct, LayerCollection("Master Collection") at 0x7f76f5235578>
mode: OBJECT
region: None
scene: <bpy_struct, Scene("EDIT") at 0x7f76f5248808>
screen: <bpy_struct, Screen("Default.025") at 0x7f76f632a908>
space_data: None
view_layer: <bpy_struct, ViewLayer("View Layer") at 0x7f76f525be08>
window: <bpy_struct, Window at 0x7f76f6323408>
window_manager: <bpy_struct, WindowManager("WinMan") at 0x7f771a36d008>
workspace: <bpy_struct, WorkSpace("Video Editing") at 0x7f76f632a048>
workspace.name: Video Editing

Most of the stackexchange discussions suggested setting the active area to the sequence editor, but I haven’t found a way to set the active area yet, all the old solutions from a few years ago don’t seem to work anymore.

I don’t need to run the blender GUI, I could run this once in headless mode and then save the file if that would be easier? Does that change the process of adding image sequence strips?

I am just starting out with blender scripting, so I may be missing something obvious. I’ve googled myself to exhaustion though!

For internal operators the only way to truly know the required context is to check the C code.

The poll for image_strip_add points to this function:

bool ED_operator_sequencer_active_editable(bContext *C)
{
  return ed_spacetype_test(C, SPACE_SEQ) && ED_operator_scene_editable(C);
}

The code is fairly straightforward; it checks that the operator was fired from a Sequencer editor, and that the current scene editable (i.e not liked in from a library).

If we look at the code for ed_spacetype_test, it requires that:

  • context.space_data is not None and its type is SEQUENCER
  • context.window is not None (can be any window)
  • context.screen is not None (can be any screen)
  • context.area is not None (can be any area)

… which would fail if the operator was called from a different editor, or, if it was called in headless mode (because no editor would exist without a UI).

A better option (which would let you do things without a UI) is to find a python low-level (non-operator) way of adding strips. I’m not familiar with the API for the sequencer, but one way to start exploring is having a look at:

C.scene.sequence_editor.sequences

in the python console. It lists quite a few functions:

As you can see, the sequencer data is stored on the scene itself, which generally is available without running the UI.

Oh, that’s really useful, thank you!

One difference (or maybe I’m missing something…?) is that bpy.types.SequencesTopLevel.new_image appears to be for only importing individual images, rather than a directory of images as an image sequence strip.
The name and filepath parameters both take a string, whereas with bpy.ops.sequencer.image_strip_add, files is a bpy_prop_collection of OperatorFileListElement

I can technically achieve my goal of having all the rendered frames in the sequence by doing a spectacularly dumb ‘add this frame. Ok now add this frame.’ approach:

import bpy
import os
renders_folder = bpy.path.abspath("//renders")
rendered_scenes = sorted(os.listdir(renders_folder))
current_frame = 0

for scene in rendered_scenes:
    folder = renders_folder + '/' + scene

    for image in sorted(os.listdir(folder)):
        image_path = folder + '/' + image
        bpy.context.scene.sequence_editor.sequences.new_image(name=image, filepath=image_path, channel=1, frame_start=current_frame, fit_method='ORIGINAL')
        current_frame += 1

For my 3 minute animation this took forever to complete, and my laptop warmed up my entire room, there’s no differentiation of scenes in the timeline and the sequence can’t play back smoothly, but it ‘worked’, I guess :grin:

Is there any way that I might be able to get types.SequencesTopLevel.new_image to work on multiple images, like ops.sequencer.image_strip_add does?

sequences.new_image() returns a strip object.

A strip object has a collection property called elements where you can append subsequent file names, which automatically advances one frame each time.

I haven’t tried this myself, but I hope it’s faster.

import bpy
import os
renders_folder = bpy.path.abspath("//renders")
rendered_scenes = sorted(os.listdir(renders_folder))

sed = bpy.context.scene.sequence_editor

for scene in rendered_scenes:
    folder = renders_folder + '/' + scene

    first, *rest = sorted(os.listdir(folder))

    # Create a strip with the first element of the image sequence.
    imstrip = sed.sequences.new_image(
        name=first,
        filepath=image_path,
        channel=1,
        frame_start=0,
        fit_method='ORIGINAL'
    )
    
    # Append subsequent elements.
    for image in rest:
        imstrip.elements.append(image)
1 Like

Yes!
That is way faster, and now the script does exactly what I was hoping to do:

import bpy
import os
renders_folder = bpy.path.abspath("//renders")
rendered_scenes = sorted(os.listdir(renders_folder))
current_frame = 0

for scene in rendered_scenes:
    folder = renders_folder + '/' + scene
    first, *rest = sorted(os.listdir(folder))
    image_path = folder + '/' + first

    imstrip = bpy.context.scene.sequence_editor.sequences.new_image(
        name=scene,
        filepath=image_path,
        channel=1,
        frame_start=current_frame,
        fit_method='ORIGINAL'
    )

    for image in rest:
        imstrip.elements.append(image)

    current_frame += len(os.listdir(folder))

Thanks so much for your help, I doubt that I would have found elements on my own!

1 Like