diff --git a/internal/manager/job_compilers/job_compilers_test.go b/internal/manager/job_compilers/job_compilers_test.go index ace90cca..9a4b71f6 100644 --- a/internal/manager/job_compilers/job_compilers_test.go +++ b/internal/manager/job_compilers/job_compilers_test.go @@ -196,3 +196,51 @@ func TestSimpleBlenderRenderWindowsPaths(t *testing.T) { "fps": int64(24), }, tVideo.Commands[0].Parameters) } + +func TestSimpleBlenderRenderOutputPathFieldReplacement(t *testing.T) { + c := mockedClock(t) + + s, err := Load(c) + assert.NoError(t, err) + + // Compiling a job should be really fast. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancel() + + sj := exampleSubmittedJob() + sj.Settings.AdditionalProperties["render_output_path"] = "/root/{timestamp}/jobname/######" + + aj, err := s.Compile(ctx, sj) + if err != nil { + t.Fatalf("job compiler failed: %v", err) + } + if aj == nil { + t.Fatalf("job compiler returned nil but no error") + } + + // The job compiler should have replaced the {timestamp} and {ext} fields. + assert.Equal(t, "/root/2006-01-02_090405/jobname/######", aj.Settings["render_output_path"]) + + // Tasks should have been created to render the frames: 1-3, 4-6, 7-9, 10, video-encoding + assert.Equal(t, 5, len(aj.Tasks)) + t0 := aj.Tasks[0] + expectCliArgs := []interface{}{ // They are strings, but Goja doesn't know that and will produce an []interface{}. + "--render-output", "/root/2006-01-02_090405/jobname__intermediate-2006-01-02_090405/######", + "--render-format", sj.Settings.AdditionalProperties["format"].(string), + "--render-frame", "1-3", + } + assert.EqualValues(t, AuthoredCommandParameters{ + "exe": "{blender}", + "blendfile": sj.Settings.AdditionalProperties["blendfile"].(string), + "args": expectCliArgs, + "argsBefore": make([]interface{}, 0), + }, t0.Commands[0].Parameters) + + tVideo := aj.Tasks[4] // This should be a video encoding task + assert.EqualValues(t, AuthoredCommandParameters{ + "input_files": "/root/2006-01-02_090405/jobname__intermediate-2006-01-02_090405/*.png", + "output_file": "/root/2006-01-02_090405/jobname__intermediate-2006-01-02_090405/scene123-1-10.mp4", + "fps": int64(24), + }, tVideo.Commands[0].Parameters) + +} diff --git a/internal/manager/job_compilers/scripts/simple_blender_render.js b/internal/manager/job_compilers/scripts/simple_blender_render.js index 71869ed7..4fee6ec6 100644 --- a/internal/manager/job_compilers/scripts/simple_blender_render.js +++ b/internal/manager/job_compilers/scripts/simple_blender_render.js @@ -14,7 +14,7 @@ const JOB_TYPE = { { key: "add_path_components", type: "int32", required: true, default: 0, propargs: {min: 0, max: 32}, description: "Number of path components of the current blend file to use in the render output path"}, { key: "render_output_path", type: "string", subtype: "file_path", editable: false, - eval: "str(Path(settings.render_output_root) / last_n_dir_parts(settings.add_path_components) / jobname / '{timestamp}' / '######.{ext}')", + eval: "str(Path(settings.render_output_root) / last_n_dir_parts(settings.add_path_components) / jobname / '{timestamp}' / '######')", description: "Final file path of where render output will be saved"}, // Automatically evaluated settings: @@ -62,12 +62,14 @@ function compileJob(job) { print("Blender Render job submitted"); print("job: ", job); - const settings = job.settings; - const renderOutput = settings.render_output_path; + const renderOutput = renderOutputPath(job); + job.settings.render_output_path = renderOutput; + const finalDir = path.dirname(renderOutput); const renderDir = intermediatePath(job, finalDir); + const settings = job.settings; const renderTasks = authorRenderTasks(settings, renderDir, renderOutput); const videoTask = authorCreateVideoTask(settings, renderDir); @@ -83,6 +85,19 @@ function compileJob(job) { } } +// Do field replacement on the render output path. +function renderOutputPath(job) { + let path = job.settings.render_output_path; + return path.replace(/{([^}]+)}/g, (match, group0) => { + switch (group0) { + case "timestamp": + return formatTimestampLocal(job.created); + default: + return match; + } + }); +} + // Determine the intermediate render output path. function intermediatePath(job, finalDir) { const basename = path.basename(finalDir);