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 ErrScriptIncomplete = errors.New("job compiler script incomplete")
|
||||
|
||||
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) {
|
||||
compiler := GojaJobCompiler{
|
||||
vm: newGojaVM(),
|
||||
jobtypes: map[string]*goja.Program{},
|
||||
jobtypes: map[string]JobType{},
|
||||
}
|
||||
|
||||
if err := compiler.loadScripts(); err != nil {
|
||||
@ -59,23 +63,19 @@ func Load() (*GojaJobCompiler, error) {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
registry := require.NewRegistry(require.WithLoader(staticFileLoader))
|
||||
registry.Enable(compiler.vm)
|
||||
|
||||
registry.RegisterNativeModule("author", AuthorModule)
|
||||
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"))
|
||||
compiler.registry = require.NewRegistry(require.WithLoader(staticFileLoader))
|
||||
compiler.registry.RegisterNativeModule("author", AuthorModule)
|
||||
compiler.registry.RegisterNativeModule("path", PathModule)
|
||||
compiler.registry.RegisterNativeModule("process", ProcessModule)
|
||||
|
||||
return &compiler, nil
|
||||
}
|
||||
|
||||
func newGojaVM() *goja.Runtime {
|
||||
func (c *GojaJobCompiler) newGojaVM() *goja.Runtime {
|
||||
vm := goja.New()
|
||||
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||
|
||||
// Set some global functions for script debugging purposes.
|
||||
vm.Set("print", func(call goja.FunctionCall) goja.Value {
|
||||
log.Info().Interface("args", call.Arguments).Msg("print")
|
||||
return goja.Undefined()
|
||||
@ -84,11 +84,18 @@ func newGojaVM() *goja.Runtime {
|
||||
log.Warn().Interface("args", call.Arguments).Msg("alert")
|
||||
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
|
||||
}
|
||||
|
||||
func (c *GojaJobCompiler) Run(jobType string) error {
|
||||
program, ok := c.jobtypes[jobType]
|
||||
func (c *GojaJobCompiler) Run(jobTypeName string) error {
|
||||
jobType, ok := c.jobtypes[jobTypeName]
|
||||
if !ok {
|
||||
return ErrJobTypeUnknown
|
||||
}
|
||||
@ -121,9 +128,25 @@ func (c *GojaJobCompiler) Run(jobType string) error {
|
||||
"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
|
||||
}
|
||||
|
||||
|
@ -58,10 +58,13 @@ func (c *GojaJobCompiler) loadScripts() error {
|
||||
continue
|
||||
}
|
||||
|
||||
jobType := filenameToJobType(script.Name())
|
||||
c.jobtypes[jobType] = program
|
||||
jobTypeName := filenameToJobType(script.Name())
|
||||
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
|
||||
|
@ -18,11 +18,6 @@
|
||||
*
|
||||
* ***** 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
|
||||
// files which FFmpeg is known not to handle as input.
|
||||
const ffmpegIncompatibleImageFormats = new Set([
|
||||
@ -32,18 +27,39 @@ const ffmpegIncompatibleImageFormats = new Set([
|
||||
"OPEN_EXR_MULTILAYER", // DNA values for these formats.
|
||||
]);
|
||||
|
||||
// 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(finalDir);
|
||||
function compileJob(job) {
|
||||
print("Blender Render job submitted");
|
||||
print("job: ", job);
|
||||
|
||||
const settings = job.settings;
|
||||
|
||||
// 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.
|
||||
function intermediatePath(render_path) {
|
||||
const basename = path.basename(render_path);
|
||||
const name = `${basename}__intermediate-${created}`;
|
||||
return path.join(path.dirname(render_path), name);
|
||||
function intermediatePath(job, finalDir) {
|
||||
const basename = path.basename(finalDir);
|
||||
const name = `${basename}__intermediate-${job.created}`;
|
||||
return path.join(path.dirname(finalDir), name);
|
||||
}
|
||||
|
||||
function frameChunker(frames, callback) {
|
||||
@ -53,7 +69,7 @@ function frameChunker(frames, callback) {
|
||||
callback("21-30");
|
||||
}
|
||||
|
||||
function authorRenderTasks() {
|
||||
function authorRenderTasks(settings, renderDir, renderOutput) {
|
||||
let renderTasks = [];
|
||||
frameChunker(settings.frames, function(chunk) {
|
||||
const task = author.Task(`render-${chunk}`);
|
||||
@ -70,7 +86,7 @@ function authorRenderTasks() {
|
||||
return renderTasks;
|
||||
}
|
||||
|
||||
function authorCreateVideoTask() {
|
||||
function authorCreateVideoTask(settings, renderDir) {
|
||||
if (ffmpegIncompatibleImageFormats.has(settings.format)) {
|
||||
return;
|
||||
}
|
||||
@ -91,18 +107,4 @@ function authorCreateVideoTask() {
|
||||
|
||||
print(`Creating output video for ${settings.format}`);
|
||||
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…
Reference in New Issue
Block a user