Move job compiler JS code into its own function
Each job compiler script now must define a `compileJob(job)` function, which will be called by Flamenco when necessary. This makes it possible to run the script without a job, and get other exported symbols from it, such as metadata about which settings its job type needs/exposes.
This commit is contained in:
parent
87826b5179
commit
289bcf6414
@ -31,17 +31,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var ErrJobTypeUnknown = errors.New("job type unknown")
|
var ErrJobTypeUnknown = errors.New("job type unknown")
|
||||||
|
var ErrScriptIncomplete = errors.New("job compiler script incomplete")
|
||||||
|
|
||||||
type GojaJobCompiler struct {
|
type GojaJobCompiler struct {
|
||||||
vm *goja.Runtime
|
jobtypes map[string]JobType // Mapping from job type name to jobType struct.
|
||||||
|
registry *require.Registry // Goja module registry.
|
||||||
|
}
|
||||||
|
|
||||||
jobtypes map[string]*goja.Program
|
type JobType struct {
|
||||||
|
program *goja.Program // Compiled JavaScript file.
|
||||||
|
filename string // The filename of that JS file.
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() (*GojaJobCompiler, error) {
|
func Load() (*GojaJobCompiler, error) {
|
||||||
compiler := GojaJobCompiler{
|
compiler := GojaJobCompiler{
|
||||||
vm: newGojaVM(),
|
jobtypes: map[string]JobType{},
|
||||||
jobtypes: map[string]*goja.Program{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := compiler.loadScripts(); err != nil {
|
if err := compiler.loadScripts(); err != nil {
|
||||||
@ -59,23 +63,19 @@ func Load() (*GojaJobCompiler, error) {
|
|||||||
return content, nil
|
return content, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
registry := require.NewRegistry(require.WithLoader(staticFileLoader))
|
compiler.registry = require.NewRegistry(require.WithLoader(staticFileLoader))
|
||||||
registry.Enable(compiler.vm)
|
compiler.registry.RegisterNativeModule("author", AuthorModule)
|
||||||
|
compiler.registry.RegisterNativeModule("path", PathModule)
|
||||||
registry.RegisterNativeModule("author", AuthorModule)
|
compiler.registry.RegisterNativeModule("process", ProcessModule)
|
||||||
registry.RegisterNativeModule("path", PathModule)
|
|
||||||
registry.RegisterNativeModule("process", ProcessModule)
|
|
||||||
compiler.vm.Set("author", require.Require(compiler.vm, "author"))
|
|
||||||
compiler.vm.Set("path", require.Require(compiler.vm, "path"))
|
|
||||||
compiler.vm.Set("process", require.Require(compiler.vm, "process"))
|
|
||||||
|
|
||||||
return &compiler, nil
|
return &compiler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGojaVM() *goja.Runtime {
|
func (c *GojaJobCompiler) newGojaVM() *goja.Runtime {
|
||||||
vm := goja.New()
|
vm := goja.New()
|
||||||
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||||
|
|
||||||
|
// Set some global functions for script debugging purposes.
|
||||||
vm.Set("print", func(call goja.FunctionCall) goja.Value {
|
vm.Set("print", func(call goja.FunctionCall) goja.Value {
|
||||||
log.Info().Interface("args", call.Arguments).Msg("print")
|
log.Info().Interface("args", call.Arguments).Msg("print")
|
||||||
return goja.Undefined()
|
return goja.Undefined()
|
||||||
@ -84,11 +84,18 @@ func newGojaVM() *goja.Runtime {
|
|||||||
log.Warn().Interface("args", call.Arguments).Msg("alert")
|
log.Warn().Interface("args", call.Arguments).Msg("alert")
|
||||||
return goja.Undefined()
|
return goja.Undefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Pre-import some useful modules.
|
||||||
|
c.registry.Enable(vm)
|
||||||
|
vm.Set("author", require.Require(vm, "author"))
|
||||||
|
vm.Set("path", require.Require(vm, "path"))
|
||||||
|
vm.Set("process", require.Require(vm, "process"))
|
||||||
|
|
||||||
return vm
|
return vm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GojaJobCompiler) Run(jobType string) error {
|
func (c *GojaJobCompiler) Run(jobTypeName string) error {
|
||||||
program, ok := c.jobtypes[jobType]
|
jobType, ok := c.jobtypes[jobTypeName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrJobTypeUnknown
|
return ErrJobTypeUnknown
|
||||||
}
|
}
|
||||||
@ -121,9 +128,25 @@ func (c *GojaJobCompiler) Run(jobType string) error {
|
|||||||
"project": "Sprøte Frøte",
|
"project": "Sprøte Frøte",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c.vm.Set("job", &job)
|
|
||||||
|
|
||||||
if _, err := c.vm.RunProgram(program); err != nil {
|
vm := c.newGojaVM()
|
||||||
|
|
||||||
|
// This should register the `compileJob()` function called below:
|
||||||
|
if _, err := vm.RunProgram(jobType.program); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
compileJob, isCallable := goja.AssertFunction(vm.Get("compileJob"))
|
||||||
|
if !isCallable {
|
||||||
|
log.Error().
|
||||||
|
Str("jobType", jobTypeName).
|
||||||
|
Str("script", jobType.filename).
|
||||||
|
Msg("script does not define a compileJob(job) function")
|
||||||
|
return ErrScriptIncomplete
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := compileJob(nil, vm.ToValue(&job)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,10 +58,13 @@ func (c *GojaJobCompiler) loadScripts() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
jobType := filenameToJobType(script.Name())
|
jobTypeName := filenameToJobType(script.Name())
|
||||||
c.jobtypes[jobType] = program
|
c.jobtypes[jobTypeName] = JobType{
|
||||||
|
program: program,
|
||||||
|
filename: script.Name(),
|
||||||
|
}
|
||||||
|
|
||||||
log.Debug().Str("script", script.Name()).Str("jobType", jobType).Msg("loaded script")
|
log.Debug().Str("script", script.Name()).Str("jobType", jobTypeName).Msg("loaded script")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -18,11 +18,6 @@
|
|||||||
*
|
*
|
||||||
* ***** END GPL LICENSE BLOCK ***** */
|
* ***** END GPL LICENSE BLOCK ***** */
|
||||||
|
|
||||||
print("Blender Render job submitted");
|
|
||||||
print("job: ", job);
|
|
||||||
|
|
||||||
const { created, settings } = job;
|
|
||||||
|
|
||||||
// Set of scene.render.image_settings.file_format values that produce
|
// Set of scene.render.image_settings.file_format values that produce
|
||||||
// files which FFmpeg is known not to handle as input.
|
// files which FFmpeg is known not to handle as input.
|
||||||
const ffmpegIncompatibleImageFormats = new Set([
|
const ffmpegIncompatibleImageFormats = new Set([
|
||||||
@ -32,18 +27,39 @@ const ffmpegIncompatibleImageFormats = new Set([
|
|||||||
"OPEN_EXR_MULTILAYER", // DNA values for these formats.
|
"OPEN_EXR_MULTILAYER", // DNA values for these formats.
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// The render path contains a filename pattern, most likely '######' or
|
function compileJob(job) {
|
||||||
// something similar. This has to be removed, so that we end up with
|
print("Blender Render job submitted");
|
||||||
// the directory that will contain the frames.
|
print("job: ", job);
|
||||||
const renderOutput = path.dirname(settings.render_output);
|
|
||||||
const finalDir = path.dirname(renderOutput);
|
const settings = job.settings;
|
||||||
const renderDir = intermediatePath(finalDir);
|
|
||||||
|
// The render path contains a filename pattern, most likely '######' or
|
||||||
|
// something similar. This has to be removed, so that we end up with
|
||||||
|
// the directory that will contain the frames.
|
||||||
|
const renderOutput = path.dirname(settings.render_output);
|
||||||
|
const finalDir = path.dirname(renderOutput);
|
||||||
|
const renderDir = intermediatePath(job, finalDir);
|
||||||
|
|
||||||
|
const renderTasks = authorRenderTasks(settings, renderDir, renderOutput);
|
||||||
|
const videoTask = authorCreateVideoTask(renderTasks, renderDir);
|
||||||
|
|
||||||
|
if (videoTask) {
|
||||||
|
// If there is a video task, all other tasks have to be done first.
|
||||||
|
for (const rt of renderTasks) {
|
||||||
|
videoTask.addDependency(rt);
|
||||||
|
}
|
||||||
|
job.addTask(videoTask);
|
||||||
|
}
|
||||||
|
for (const rt of renderTasks) {
|
||||||
|
job.addTask(rt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the intermediate render output path.
|
// Determine the intermediate render output path.
|
||||||
function intermediatePath(render_path) {
|
function intermediatePath(job, finalDir) {
|
||||||
const basename = path.basename(render_path);
|
const basename = path.basename(finalDir);
|
||||||
const name = `${basename}__intermediate-${created}`;
|
const name = `${basename}__intermediate-${job.created}`;
|
||||||
return path.join(path.dirname(render_path), name);
|
return path.join(path.dirname(finalDir), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function frameChunker(frames, callback) {
|
function frameChunker(frames, callback) {
|
||||||
@ -53,7 +69,7 @@ function frameChunker(frames, callback) {
|
|||||||
callback("21-30");
|
callback("21-30");
|
||||||
}
|
}
|
||||||
|
|
||||||
function authorRenderTasks() {
|
function authorRenderTasks(settings, renderDir, renderOutput) {
|
||||||
let renderTasks = [];
|
let renderTasks = [];
|
||||||
frameChunker(settings.frames, function(chunk) {
|
frameChunker(settings.frames, function(chunk) {
|
||||||
const task = author.Task(`render-${chunk}`);
|
const task = author.Task(`render-${chunk}`);
|
||||||
@ -70,7 +86,7 @@ function authorRenderTasks() {
|
|||||||
return renderTasks;
|
return renderTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function authorCreateVideoTask() {
|
function authorCreateVideoTask(settings, renderDir) {
|
||||||
if (ffmpegIncompatibleImageFormats.has(settings.format)) {
|
if (ffmpegIncompatibleImageFormats.has(settings.format)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -91,18 +107,4 @@ function authorCreateVideoTask() {
|
|||||||
|
|
||||||
print(`Creating output video for ${settings.format}`);
|
print(`Creating output video for ${settings.format}`);
|
||||||
return task;
|
return task;
|
||||||
}
|
|
||||||
|
|
||||||
const renderTasks = authorRenderTasks();
|
|
||||||
const videoTask = authorCreateVideoTask(renderTasks);
|
|
||||||
|
|
||||||
if (videoTask) {
|
|
||||||
// If there is a video task, all other tasks have to be done first.
|
|
||||||
for (const rt of renderTasks) {
|
|
||||||
videoTask.addDependency(rt);
|
|
||||||
}
|
|
||||||
job.addTask(videoTask);
|
|
||||||
}
|
|
||||||
for (const rt of renderTasks) {
|
|
||||||
job.addTask(rt);
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user