Unable to execute MoviePipelineQueue from Python

I am interested in generating a movie pipeline queue dynamically from python. I was able to figure out all the necessary pieces. However, when using the executor on the queue it will not execute. I’ve tried 3 different executors.

This executor Doesn’t have any error. However, it never actually renders the movie.
executor=unreal.MoviePipelineInProcessExecutor()

The following two complain that the map associated with the job has not been saved. However, made sure it is saved. I have also tried with the target map loaded and not loaded.

executor=unreal.MoviePipelineNewProcessExecutor()

executor=unreal.MoviePipelinePIEExecutor()

ERROR Message:
Message dialog closed, result: Ok, title: Message, text: One or more jobs in the queue have an unsaved map as their target map. Maps must be saved at least once before rendering.

To confirm, I am able to successfully add the sequence to the MovieRenderQueue through the UI. And and render from UI.

Below is a sample script to test.

import unreal

umap = '/Game/NewMap'
level_sequence='/Game/NewLevelSequence'
outdir='out'
fps=60
frame_count = 120

q = unreal.MoviePipelineQueue()
#executor=unreal.MoviePipelineInProcessExecutor()
#executor=unreal.MoviePipelineNewProcessExecutor()
executor=unreal.MoviePipelinePIEExecutor()

#Create new movie pipeline job
job = q.allocate_new_job()
#job.set_editor_property('map',unreal.SoftObjectPath(umap))
#job.set_editor_property('sequence',unreal.SoftObjectPath(level_sequence))
job.map=unreal.SoftObjectPath(umap)
job.sequence=unreal.SoftObjectPath(level_sequence)

c=job.get_configuration()
#c=unreal.MoviePipelineMasterConfig()
render_pass_settings=c.find_or_add_setting_by_class(unreal.MoviePipelineDeferredPassBase)
output_setting=c.find_or_add_setting_by_class(unreal.MoviePipelineOutputSetting)
output_setting.output_directory=unreal.DirectoryPath(outdir)
png_setting=c.find_or_add_setting_by_class(unreal.MoviePipelineImageSequenceOutput_PNG)
#job.set_configuration(c)

error_callback=unreal.OnMoviePipelineExecutorErrored()
def movie_error(pipeline_executor,pipeline_with_error,is_fatal,error_text):
    print(pipeline_executor)
    print(pipeline_with_error)
    print(is_fatal)
    print(error_text)
error_callback.add_callable(movie_error)
def movie_finished(pipeline_executor,success):
    print(pipeline_executor)
    print(success)
finished_callback=unreal.OnMoviePipelineExecutorFinished()
finished_callback.add_callable(movie_finished)

executor.set_editor_property('on_executor_errored_delegate',error_callback)
executor.set_editor_property('on_executor_finished_delegate',finished_callback)
executor.execute(q)

Regards

2 Likes

I was able to finally figure out the problem. The main issue was that specifying a map reference requires you to specify the world in the map as well.

umap = ‘/Game/NewMap’

Should be

umap = ‘/Game/NewMap.NewMap’

I also found out how to interface with the editor queue as well through the subsystem. This is useful if you want to inspect the generated queue from the GUI.

Here is a complete script.

import unreal
import os

umap = '/Game/NewMap.NewMap'
level_sequence='/Game/NewLevelSequence'
outdir=os.path.abspath(os.path.join(unreal.Paths().project_dir(),'out'))
fps=60
frame_count = 120

#Get movie queue subsystem for editor.
subsystem=unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
q=subsystem.get_queue()
executor=unreal.MoviePipelinePIEExecutor()

#Optional: empty queue first.
for j in q.get_jobs():
    q.delete_job(j)

#Create new movie pipeline job
job = q.allocate_new_job()
job.set_editor_property('map',unreal.SoftObjectPath(umap))
job.set_editor_property('sequence',unreal.SoftObjectPath(level_sequence))

c=job.get_configuration()
render_pass_settings=c.find_or_add_setting_by_class(unreal.MoviePipelineDeferredPassBase)
output_setting=c.find_or_add_setting_by_class(unreal.MoviePipelineOutputSetting)
output_setting.output_directory=unreal.DirectoryPath(outdir)
png_setting=c.find_or_add_setting_by_class(unreal.MoviePipelineImageSequenceOutput_PNG)

error_callback=unreal.OnMoviePipelineExecutorErrored()
def movie_error(pipeline_executor,pipeline_with_error,is_fatal,error_text):
    print(pipeline_executor)
    print(pipeline_with_error)
    print(is_fatal)
    print(error_text)
error_callback.add_callable(movie_error)
def movie_finished(pipeline_executor,success):
    print(pipeline_executor)
    print(success)
finished_callback=unreal.OnMoviePipelineExecutorFinished()
finished_callback.add_callable(movie_finished)

executor = subsystem.render_queue_with_executor(unreal.MoviePipelinePIEExecutor)
if executor:
    executor.set_editor_property('on_executor_errored_delegate',error_callback)
    executor.set_editor_property('on_executor_finished_delegate',finished_callback)
4 Likes

It runs good, all images render out

But have you callback success?

And…

I have test Burn In setting , The function looks not finish yet , so nothing appear in image

The callback finished works fine for me.
The above example prints:

LogPython: <Object '/Engine/Transient.UnrealEdEngine_0:MoviePipelineQueueSubsystem_0.MoviePipelinePIEExecutor_1' (0x0000018BB1F5DD00) Class 'MoviePipelinePIEExecutor'>
LogPython: True

I have not tried burn-in settings.

ok, I move the call back into main function, it’s worked.

Thank you so much

And burn in setting is not show when preview, but it appears on images, I’m looking how to control it now

Have you test start this render function by using command line?

I have got

[2020.07.15-14.05.40:522][ 0]LogWindows: Error: Assertion failed: IsValid() [File:D:\Build++UE4+Licensee\Sync\Engine\Source\Runtime\Core\Public\Templates/SharedPointer.h] [Line: 879]

I have not had much luck when executing from the command line.

UE4Editor-Cmd.exe "%cd%/MyProject.uproject" -ExecutePythonScript="%cd%/Python/test_render.py"

The problem is that the python script finishes immediately after launching the queue. Then the editor just exits without waiting for queue to finish. Any attempt to stall in the python script just hangs the editor.

I cannot figure out how to make the editor stay open and wait on the finish callback.

As a workaround, I was planning to add the movie script as a startup script for the editor and then make the editor exit when the finish callback is called. (unreal.SystemLibrary.quit_editor())

Hi, i get error with this script with unreal 4.27

job = q.allocate_new_job() returning error. it need a parameters, but i cant find wich one. the documention is not detailed on this one.

allocate_new_job(job_type) → MoviePipelineExecutorJob

Parameters
job_type (type(Class)) – Specify the specific Job type that should be created. Custom Executors can use custom Job types to allow the user to provide more information.

Do you have a solution?

Thank you!

Hello,
Did you find a solution for this? Do you remenber ?

Thanks

Use this
job = q.allocate_new_job(unreal.load_class(None, “/Script/MovieRenderPipelineCore.MoviePipelineExecutorJob”))

1 Like

Excuse me, I imitate the same code with Mentln. But in my code all images render out without running the callback. I just want to ask what your ‘move the call back into main function’ means.
Thank you very much.

You need to make sure finished_callback is still defined when the executor needs it. Making it global instead of in the function creating the executor is a way to do so.

1 Like

Thanks a lot for this thread, every works well for me now :slight_smile:

Mentin Did you ever find a way to render it with a .bat file? I’m having the same issue…

Or if its possible to save the MovieRenderQueue as an asset?

How can I apply in unreal engine 5
I can’t understand

I found a workaround, if you run the script as a startup script (Project Settings - Plugin -Python - Startup Scripts) it executes and renders the queue as expected.

So what I did is to make a .bat file wich copies my .py script into the startup script location, then it starts unreal. Unreal runs the script on startup and renders the shot. After the rendering is done the .py startup file gets deleted.

Had some issues quitting unreal after the rendering is done so I run a parallel script lisning to the CPU usage and quitting unreal when its done rendering.

Some hassle to setup but it works for now untill I figure out a better way to do it.

I found another workaround. I start UE in headless mode and inside Python script run subprocess.call(args) to call UE in game mode and render MoviePipelineQueue. subprocess.call(args) will start new process and Python script will wait until its finished.
Basically, the work flow in Python script is following: load map, make changes, save changes and run subprocess.call(args).

or try this
“q.allocate_new_job(unreal.MoviePipelineExecutorJob)”

I’m trying to use the callback method to post the render file when it’s ready, but it seems to be running the callback function before the render is finished, any ideas? about how to wait the render finish to execute a function ?

def render(level_map, master_sequence, sequence, start_frame, end_frame, output_path, file_name, project_check_in ):

    level_sequencer = unreal.LevelSequence( unreal.load_asset(sequence) )

    # Get movie queue subsystem for editor.
    subsystem=unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
    q=subsystem.get_queue()
    executor=unreal.MoviePipelinePIEExecutor()

    #Get movie queue subsystem for editor.
    mpqSubsystem=unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
    queue=mpqSubsystem.get_queue()

    job = queue.allocate_new_job(unreal.MoviePipelineExecutorJob)

    # #Create new movie pipeline job
    job.map=unreal.SoftObjectPath(level_map)
    job.sequence=unreal.SoftObjectPath(master_sequence)
    job.job_name=file_name#job_name

    # Create shot and add it to the job
    job_shot = unreal.MoviePipelineExecutorShot(level_sequencer)
    job_shot.outer_name = level_sequencer.get_name()

    job_shot.inner_name = sequence

    job_shot.enabled = True
    job.shot_info = [job_shot]

    c=unreal.MoviePipelineMasterConfig()
    # Job output settings
    output_setting = c.find_or_add_setting_by_class(unreal.MoviePipelineOutputSetting)
    output_setting.output_directory = unreal.DirectoryPath(output_path)
    output_setting.file_name_format = file_name
    output_setting.output_resolution= 480, 640#1920, 1080
    output_setting.use_custom_playback_range = True
    output_setting.custom_start_frame = start_frame
    output_setting.custom_end_frame = end_frame
    
    # Job encoding settings
    proRes = c.find_or_add_setting_by_class(unreal.MoviePipelineAppleProResOutput)
    proRes.codec = unreal.AppleProResEncoderCodec.PRO_RES_422_PROXY

    # Ensure deferred rendering on job so lights are properly rendered
    c.find_or_add_setting_by_class(unreal.MoviePipelineDeferredPassBase)

    # Use high res settings
    highResSettings = c.find_or_add_setting_by_class(unreal.MoviePipelineHighResSetting)
    highResSettings.texture_sharpness_bias = -0.1

    job.set_configuration(c)

    error_callback=unreal.OnMoviePipelineExecutorErrored()
    def movie_error(pipeline_executor,pipeline_with_error,is_fatal,error_text):
        print(pipeline_executor)
        print(pipeline_with_error)
        print(is_fatal)
        print(error_text)
    error_callback.add_callable(movie_error)

    def movie_finished(pipeline_executor,success):
        print(pipeline_executor)
        print(success)
        project_check_in() # Publish function, passed on arguments 

    finished_callback=unreal.OnMoviePipelineExecutorFinished()
    finished_callback.add_callable(movie_finished)


    executor = mpqSubsystem.render_queue_with_executor(unreal.MoviePipelinePIEExecutor)
    

    if executor:
        executor.set_editor_property('on_executor_errored_delegate',error_callback)
        executor.set_editor_property('on_executor_finished_delegate',finished_callback)


I’ve done something similar with take_high_res_screenshot, in case that’s helpful. This code will take a screenshot from the point of view of “CameraOne”. It does fine on all the frames except the first one, so I added a bogus frame at the start of the level sequence (frame -1).

Note that the settings for high res screenshots are very persistent. If the quality is too low, you might look at the options (hidden under the three-bar menu in the upper left of the viewport).

I’m sure this is inelegant – I don’t really do this sort of event-based programming – but it works for me.

import unreal
import time


class Capture(object):
    """ Register a function and run it on tick, """

    def __init__(self, fileprefix="test_", frames=range(-1, 50)):
        self.fileprefix = fileprefix
        LES = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
        LES.editor_set_game_view(True)
        EAS = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
        actors = EAS.get_all_level_actors()

        self.cameraActor = unreal.EditorFilterLibrary.by_actor_label(actors, "CameraOne")[0]
        # make a generator function so we can call next on it,
        self.frames = frames
        self.frameIter = iter(self.frames)
        self.tickcount = 0
        self.shotName = "warmup.png"
        self.theFrame = next(self.frameIter)

        print("setting frame")
        print("frame is " + str(self.theFrame))
        unreal.LevelSequenceEditorBlueprintLibrary.set_current_time(self.theFrame)
        print("capture, self.tickcount=" + str(self.tickcount) + "  :t=" + str(self.theFrame))
        time.sleep(4)

        # make a file name with a 4 digit frame number
        self.shotName = self.fileprefix + str(self.theFrame).zfill(4) + ".png"
        self.captureTask = unreal.AutomationLibrary.take_high_res_screenshot(
            1920,
            1080,
            self.shotName,
            camera=self.cameraActor,
            capture_hdr=True,
            delay=0.25
        )

        # register a callback on pretick
        self.on_pre_tick = unreal.register_slate_pre_tick_callback(self.__pretick__)

    def __pretick__(self, deltatime):
        """ Function we will call every pretick, """

        if self.captureTask.is_task_done():
            try:
                # Count
                self.tickcount = self.tickcount + 1

                # Get the next frame from our generator function
                self.theFrame = next(self.frameIter)
                print("setting frame")
                print("frame is " + str(self.theFrame))
                self.shotName = self.fileprefix + str(self.theFrame).zfill(4) + ".png"

                unreal.LevelSequenceEditorBlueprintLibrary.set_current_time(self.theFrame)
                time.sleep(2)

                print("capture, self.tickcount=" + str(self.tickcount) + "  :t=" + str(self.theFrame))

                self.captureTask = unreal.AutomationLibrary.take_high_res_screenshot(
                        1920,
                        1080,
                        # actor.get_name() + ".hdr",
                        self.shotName,
                        camera=self.cameraActor,
                        capture_hdr=True
                    )

            except Exception as error:
                print(error)
                unreal.unregister_slate_pre_tick_callback(self.on_pre_tick)


if __name__ == '__main__':
        instance = Capture()