Job authoring: handling of task dependencies + some bugfixes
This commit is contained in:
parent
8063a3e169
commit
fad2dc3042
@ -24,6 +24,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gitlab.com/blender/flamenco-ng-poc/pkg/api"
|
"gitlab.com/blender/flamenco-ng-poc/pkg/api"
|
||||||
)
|
)
|
||||||
@ -52,6 +53,11 @@ type JobSettings map[string]interface{}
|
|||||||
type JobMetadata map[string]string
|
type JobMetadata map[string]string
|
||||||
|
|
||||||
type AuthoredTask struct {
|
type AuthoredTask struct {
|
||||||
|
// Tasks already get their UUID in the authoring stage. This makes it simpler
|
||||||
|
// to store the dependencies, as the code doesn't have to worry about value
|
||||||
|
// vs. pointer semantics. Tasks can always be unambiguously referenced by
|
||||||
|
// their UUID.
|
||||||
|
UUID string
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
Priority int
|
Priority int
|
||||||
@ -69,6 +75,7 @@ type AuthoredCommandParameters map[string]interface{}
|
|||||||
|
|
||||||
func (a *Author) Task(name string, taskType string) (*AuthoredTask, error) {
|
func (a *Author) Task(name string, taskType string) (*AuthoredTask, error) {
|
||||||
at := AuthoredTask{
|
at := AuthoredTask{
|
||||||
|
uuid.New().String(),
|
||||||
name,
|
name,
|
||||||
taskType,
|
taskType,
|
||||||
50, // TODO: handle default priority somehow.
|
50, // TODO: handle default priority somehow.
|
||||||
|
@ -44,7 +44,7 @@ func exampleSubmittedJob() api.SubmittedJob {
|
|||||||
"frames": "1-10",
|
"frames": "1-10",
|
||||||
"images_or_video": "images",
|
"images_or_video": "images",
|
||||||
"output_file_extension": ".png",
|
"output_file_extension": ".png",
|
||||||
"render_output": "/render/sf/frames/scene123",
|
"render_output": "/render/sprites/farm_output/promo/square_ellie/square_ellie.lighting_light_breakdown2/######",
|
||||||
}}
|
}}
|
||||||
metadata := api.JobMetadata{
|
metadata := api.JobMetadata{
|
||||||
AdditionalProperties: map[string]string{
|
AdditionalProperties: map[string]string{
|
||||||
@ -101,14 +101,15 @@ func TestSimpleBlenderRenderHappy(t *testing.T) {
|
|||||||
|
|
||||||
settings := sj.Settings.AdditionalProperties
|
settings := sj.Settings.AdditionalProperties
|
||||||
|
|
||||||
// Tasks should have been created to render the frames.
|
// Tasks should have been created to render the frames: 1-3, 4-6, 7-9, 10, video-encoding
|
||||||
assert.Equal(t, 4, len(aj.Tasks))
|
assert.Equal(t, 5, len(aj.Tasks))
|
||||||
t0 := aj.Tasks[0]
|
t0 := aj.Tasks[0]
|
||||||
expectCliArgs := []interface{}{ // They are strings, but Goja doesn't know that and will produce an []interface{}.
|
expectCliArgs := []interface{}{ // They are strings, but Goja doesn't know that and will produce an []interface{}.
|
||||||
"--render-output", "/render/sf__intermediate-2006-01-02_090405/frames",
|
"--render-output", "/render/sprites/farm_output/promo/square_ellie/square_ellie.lighting_light_breakdown2__intermediate-2006-01-02_090405/######",
|
||||||
"--render-format", settings["format"].(string),
|
"--render-format", settings["format"].(string),
|
||||||
"--render-frame", "1-3",
|
"--render-frame", "1-3",
|
||||||
}
|
}
|
||||||
|
assert.NotEmpty(t, t0.UUID)
|
||||||
assert.Equal(t, "render-1-3", t0.Name)
|
assert.Equal(t, "render-1-3", t0.Name)
|
||||||
assert.Equal(t, 1, len(t0.Commands))
|
assert.Equal(t, 1, len(t0.Commands))
|
||||||
assert.Equal(t, "blender-render", t0.Commands[0].Type)
|
assert.Equal(t, "blender-render", t0.Commands[0].Type)
|
||||||
@ -117,4 +118,32 @@ func TestSimpleBlenderRenderHappy(t *testing.T) {
|
|||||||
"blendfile": settings["filepath"].(string),
|
"blendfile": settings["filepath"].(string),
|
||||||
"args": expectCliArgs,
|
"args": expectCliArgs,
|
||||||
}, t0.Commands[0].Parameters)
|
}, t0.Commands[0].Parameters)
|
||||||
|
|
||||||
|
tVideo := aj.Tasks[4] // This should be a video encoding task
|
||||||
|
assert.NotEmpty(t, tVideo.UUID)
|
||||||
|
assert.Equal(t, "create-video", tVideo.Name)
|
||||||
|
assert.Equal(t, 1, len(tVideo.Commands))
|
||||||
|
assert.Equal(t, "create-video", tVideo.Commands[0].Type)
|
||||||
|
assert.EqualValues(t, AuthoredCommandParameters{
|
||||||
|
"input_files": "/render/sprites/farm_output/promo/square_ellie/square_ellie.lighting_light_breakdown2__intermediate-2006-01-02_090405/*.png",
|
||||||
|
"output_file": "/render/sprites/farm_output/promo/square_ellie/square_ellie.lighting_light_breakdown2__intermediate-2006-01-02_090405/scene123-1-10.mp4",
|
||||||
|
"fps": int64(24),
|
||||||
|
}, tVideo.Commands[0].Parameters)
|
||||||
|
|
||||||
|
for index, task := range aj.Tasks {
|
||||||
|
if index == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
assert.NotEqual(t, t0.UUID, task.UUID, "Task UUIDs should be unique")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check dependencies
|
||||||
|
assert.Empty(t, aj.Tasks[0].Dependencies)
|
||||||
|
assert.Empty(t, aj.Tasks[1].Dependencies)
|
||||||
|
assert.Empty(t, aj.Tasks[2].Dependencies)
|
||||||
|
assert.Equal(t, 4, len(tVideo.Dependencies))
|
||||||
|
expectDeps := []*AuthoredTask{
|
||||||
|
&aj.Tasks[0], &aj.Tasks[1], &aj.Tasks[2], &aj.Tasks[3],
|
||||||
|
}
|
||||||
|
assert.Equal(t, expectDeps, tVideo.Dependencies)
|
||||||
}
|
}
|
||||||
|
@ -59,13 +59,16 @@ function compileJob(job) {
|
|||||||
// The render path contains a filename pattern, most likely '######' or
|
// The render path contains a filename pattern, most likely '######' or
|
||||||
// something similar. This has to be removed, so that we end up with
|
// something similar. This has to be removed, so that we end up with
|
||||||
// the directory that will contain the frames.
|
// the directory that will contain the frames.
|
||||||
const renderOutput = path.dirname(settings.render_output);
|
const renderOutput = settings.render_output;
|
||||||
const finalDir = path.dirname(renderOutput);
|
const finalDir = path.dirname(renderOutput);
|
||||||
const renderDir = intermediatePath(job, finalDir);
|
const renderDir = intermediatePath(job, finalDir);
|
||||||
|
|
||||||
const renderTasks = authorRenderTasks(settings, renderDir, renderOutput);
|
const renderTasks = authorRenderTasks(settings, renderDir, renderOutput);
|
||||||
const videoTask = authorCreateVideoTask(renderTasks, renderDir);
|
const videoTask = authorCreateVideoTask(settings, renderDir);
|
||||||
|
|
||||||
|
for (const rt of renderTasks) {
|
||||||
|
job.addTask(rt);
|
||||||
|
}
|
||||||
if (videoTask) {
|
if (videoTask) {
|
||||||
// If there is a video task, all other tasks have to be done first.
|
// If there is a video task, all other tasks have to be done first.
|
||||||
for (const rt of renderTasks) {
|
for (const rt of renderTasks) {
|
||||||
@ -73,9 +76,6 @@ function compileJob(job) {
|
|||||||
}
|
}
|
||||||
job.addTask(videoTask);
|
job.addTask(videoTask);
|
||||||
}
|
}
|
||||||
for (const rt of renderTasks) {
|
|
||||||
job.addTask(rt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the intermediate render output path.
|
// Determine the intermediate render output path.
|
||||||
@ -86,6 +86,7 @@ function intermediatePath(job, finalDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function authorRenderTasks(settings, renderDir, renderOutput) {
|
function authorRenderTasks(settings, renderDir, renderOutput) {
|
||||||
|
print("authorRenderTasks(", renderDir, renderOutput, ")");
|
||||||
let renderTasks = [];
|
let renderTasks = [];
|
||||||
let chunks = frameChunker(settings.frames, settings.chunk_size);
|
let chunks = frameChunker(settings.frames, settings.chunk_size);
|
||||||
for (let chunk of chunks) {
|
for (let chunk of chunks) {
|
||||||
@ -107,9 +108,11 @@ function authorRenderTasks(settings, renderDir, renderOutput) {
|
|||||||
|
|
||||||
function authorCreateVideoTask(settings, renderDir) {
|
function authorCreateVideoTask(settings, renderDir) {
|
||||||
if (ffmpegIncompatibleImageFormats.has(settings.format)) {
|
if (ffmpegIncompatibleImageFormats.has(settings.format)) {
|
||||||
|
print("Not authoring video task, FFmpeg-incompatible render output")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!settings.fps || !settings.output_file_extension) {
|
if (!settings.fps || !settings.output_file_extension) {
|
||||||
|
print("Not authoring video task, no FPS or output file extension setting:", settings)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ type StringStringMap map[string]string
|
|||||||
|
|
||||||
type Task struct {
|
type Task struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
|
UUID string `gorm:"type:char(36);not null;unique;index"`
|
||||||
|
|
||||||
Name string `gorm:"type:varchar(64);not null"`
|
Name string `gorm:"type:varchar(64);not null"`
|
||||||
Type string `gorm:"type:varchar(32);not null"`
|
Type string `gorm:"type:varchar(32);not null"`
|
||||||
@ -137,6 +138,7 @@ func (db *DB) StoreAuthoredJob(ctx context.Context, authoredJob job_compilers.Au
|
|||||||
dbTask := Task{
|
dbTask := Task{
|
||||||
Name: authoredTask.Name,
|
Name: authoredTask.Name,
|
||||||
Type: authoredTask.Type,
|
Type: authoredTask.Type,
|
||||||
|
UUID: authoredTask.UUID,
|
||||||
Job: &dbJob,
|
Job: &dbJob,
|
||||||
Priority: authoredTask.Priority,
|
Priority: authoredTask.Priority,
|
||||||
Status: string(api.TaskStatusProcessing), // TODO: is this the right place to set the default status?
|
Status: string(api.TaskStatusProcessing), // TODO: is this the right place to set the default status?
|
||||||
|
@ -40,6 +40,7 @@ func TestStoreAuthoredJob(t *testing.T) {
|
|||||||
task1 := job_compilers.AuthoredTask{
|
task1 := job_compilers.AuthoredTask{
|
||||||
Name: "render-1-3",
|
Name: "render-1-3",
|
||||||
Type: "blender",
|
Type: "blender",
|
||||||
|
UUID: "db1f5481-4ef5-4084-8571-8460c547ecaa",
|
||||||
Commands: []job_compilers.AuthoredCommand{
|
Commands: []job_compilers.AuthoredCommand{
|
||||||
{
|
{
|
||||||
Type: "blender-render",
|
Type: "blender-render",
|
||||||
@ -57,11 +58,13 @@ func TestStoreAuthoredJob(t *testing.T) {
|
|||||||
|
|
||||||
task2 := task1
|
task2 := task1
|
||||||
task2.Name = "render-4-6"
|
task2.Name = "render-4-6"
|
||||||
|
task2.UUID = "d75ac779-151b-4bc2-b8f1-d153a9c4ac69"
|
||||||
task2.Commands[0].Parameters["frames"] = "4-6"
|
task2.Commands[0].Parameters["frames"] = "4-6"
|
||||||
|
|
||||||
task3 := job_compilers.AuthoredTask{
|
task3 := job_compilers.AuthoredTask{
|
||||||
Name: "preview-video",
|
Name: "preview-video",
|
||||||
Type: "ffmpeg",
|
Type: "ffmpeg",
|
||||||
|
UUID: "4915fb05-72f5-463e-a2f4-7efdb2584a1e",
|
||||||
Commands: []job_compilers.AuthoredCommand{
|
Commands: []job_compilers.AuthoredCommand{
|
||||||
{
|
{
|
||||||
Type: "merge-frames-to-video",
|
Type: "merge-frames-to-video",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user