Add frame chunker and make unit test for simple blender render succeed

This commit is contained in:
Sybren A. Stüvel 2022-01-13 16:32:08 +01:00
parent 6aed4e71ff
commit 0629728ce9
13 changed files with 595 additions and 90 deletions

View File

@ -27,6 +27,7 @@ import (
"net/http"
"time"
"github.com/benbjohnson/clock"
oapi_middle "github.com/deepmap/oapi-codegen/pkg/middleware"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/labstack/echo/v4"
@ -64,7 +65,8 @@ func main() {
log.Info().Str("port", port).Msg("listening")
// Construct the services.
compiler, err := job_compilers.Load()
timeService := clock.New()
compiler, err := job_compilers.Load(timeService)
if err != nil {
log.Fatal().Err(err).Msg("error loading job compilers")
}

6
go.mod
View File

@ -3,16 +3,18 @@ module gitlab.com/blender/flamenco-goja-test
go 1.16
require (
github.com/benbjohnson/clock v1.3.0
github.com/deepmap/oapi-codegen v1.9.0
github.com/dop251/goja v0.0.0-20211217115348-3f9136fa235d
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7
github.com/getkin/kin-openapi v0.88.0
github.com/golang-migrate/migrate/v4 v4.15.1 // indirect
github.com/golang-migrate/migrate/v4 v4.15.1
github.com/google/uuid v1.3.0
github.com/labstack/echo/v4 v4.6.1
github.com/mattn/go-colorable v0.1.12
github.com/rs/zerolog v1.26.1
github.com/stretchr/testify v1.7.0
github.com/ziflex/lecho/v3 v3.1.0
modernc.org/sqlite v1.14.4 // indirect
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e
modernc.org/sqlite v1.14.4
)

13
go.sum
View File

@ -127,6 +127,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.6.1/go.mod h1:hLZ/AnkIKHLuPGjEiyghNE
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -313,6 +315,7 @@ github.com/dop251/goja v0.0.0-20211217115348-3f9136fa235d/go.mod h1:R9ET47fwRVRP
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7 h1:tYwu/z8Y0NkkzGEh3z21mSWggMg4LwLRFucLS7TjARg=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
@ -487,6 +490,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v35 v35.2.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
@ -677,10 +681,10 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
@ -712,6 +716,7 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@ -1033,6 +1038,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@ -1101,7 +1107,6 @@ golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e h1:Xj+JO91noE97IN6F/7WZxzC5QE6yENAQPrwIYhW3bsA=
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -1612,11 +1617,13 @@ modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzq
modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
modernc.org/ccgo/v3 v3.14.0 h1:Zr1Ny9+7r5yAiXpBdgp8XiXqkNA4ARrRphHGHVXeAp0=
modernc.org/ccgo/v3 v3.14.0/go.mod h1:hBrkiBlUwvr5vV/ZH9YzXIp982jKE8Ek8tR1ytoAL6Q=
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8=
modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw=
modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8=
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
@ -1683,11 +1690,13 @@ modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo=
modernc.org/tcl v1.10.0 h1:vux2MNFhSXYqD8Kq4Uc9RjWcgv2c7Atx3da3VpLPPEw=
modernc.org/tcl v1.10.0/go.mod h1:WzWapmP/7dHVhFoyPpEaNSVTL8xtewhouN/cqSJ5A2s=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
modernc.org/z v1.2.21 h1:UO0ptLQLHM2eNistCE6ofm1fMKKXk0n0IkrQly3z5HA=
modernc.org/z v1.2.21/go.mod h1:uXrObx4pGqXWIMliC5MiKuwAyMrltzwpteOFUP1PWCc=
modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View File

@ -41,6 +41,7 @@ var ErrScriptIncomplete = errors.New("job compiler script incomplete")
type Service struct {
compilers map[string]Compiler // Mapping from job type name to the job compiler of that type.
registry *require.Registry // Goja module registry.
timeService TimeService
}
type Compiler struct {
@ -57,9 +58,16 @@ type VM struct {
// jobCompileFunc is a function that fills job.Tasks.
type jobCompileFunc func(job *AuthoredJob) error
func Load() (*Service, error) {
// TimeService is a service that can tell the current time.
type TimeService interface {
Now() time.Time
}
// Load returns a job compiler service with all JS files loaded.
func Load(ts TimeService) (*Service, error) {
compiler := Service{
compilers: map[string]Compiler{},
timeService: ts,
}
if err := compiler.loadScripts(); err != nil {
@ -94,6 +102,7 @@ func (s *Service) Compile(ctx context.Context, sj api.SubmittedJob) (*AuthoredJo
// Create an AuthoredJob from this SubmittedJob.
aj := AuthoredJob{
JobID: uuid.New().String(), // Ignore the submitted ID.
Created: s.timeService.Now(),
Name: sj.Name,
JobType: sj.Type,
Priority: sj.Priority,
@ -103,14 +112,13 @@ func (s *Service) Compile(ctx context.Context, sj api.SubmittedJob) (*AuthoredJo
Metadata: make(JobMetadata),
}
if sj.Settings != nil {
for key, value := range *sj.Settings {
for key, value := range sj.Settings.AdditionalProperties {
aj.Settings[key] = value
}
}
if sj.Metadata != nil {
for key, value := range *sj.Metadata {
// TODO: make sure OpenAPI understands these keys can only be strings.
aj.Metadata[key] = value.(string)
for key, value := range sj.Metadata.AdditionalProperties {
aj.Metadata[key] = value
}
}

View File

@ -0,0 +1,115 @@
// Package job_compilers contains functionality to convert a Flamenco job
// definition into concrete tasks and commands to execute by Workers.
package job_compilers
/* ***** BEGIN GPL LICENSE BLOCK *****
*
* Original Code Copyright (C) 2022 Blender Foundation.
*
* This file is part of Flamenco.
*
* Flamenco is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
*
* ***** END GPL LICENSE BLOCK ***** */
import (
"context"
"testing"
"time"
"github.com/benbjohnson/clock"
"github.com/stretchr/testify/assert"
"gitlab.com/blender/flamenco-goja-test/pkg/api"
)
func exampleSubmittedJob() api.SubmittedJob {
settings := api.JobSettings{
AdditionalProperties: map[string]interface{}{
"blender_cmd": "{blender}",
"chunk_size": 3,
"extract_audio": true,
"filepath": "/render/sf/jobs/scene123.blend",
"format": "PNG",
"fps": 24,
"frames": "1-10",
"images_or_video": "images",
"output_file_extension": ".png",
"render_output": "/render/sf/frames/scene123",
}}
metadata := api.JobMetadata{
AdditionalProperties: map[string]string{
"project": "Sprite Fright",
"user.email": "sybren@blender.org",
"user.name": "Sybren Stüvel",
}}
sj := api.SubmittedJob{
Name: "3Д рендеринг",
Priority: 50,
Type: "simple-blender-render",
Settings: &settings,
Metadata: &metadata,
}
return sj
}
func mockedClock(t *testing.T) clock.Clock {
c := clock.NewMock()
now, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
assert.NoError(t, err)
c.Set(now)
return c
}
func TestSimpleBlenderRenderHappy(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()
aj, err := s.Compile(ctx, sj)
if err != nil {
t.Logf("job compiler failed: %v", err)
t.FailNow()
}
assert.NotNil(t, aj)
if aj == nil {
// Don't bother with the rest of the test, it'll dereference a nil pointer anyway.
return
}
// Properties should be copied as-is.
assert.Equal(t, sj.Name, aj.Name)
assert.Equal(t, sj.Type, aj.JobType)
assert.Equal(t, sj.Priority, aj.Priority)
assert.EqualValues(t, sj.Settings.AdditionalProperties, aj.Settings)
assert.EqualValues(t, sj.Metadata.AdditionalProperties, aj.Metadata)
settings := sj.Settings.AdditionalProperties
// Tasks should have been created to render the frames.
assert.Equal(t, 4, len(aj.Tasks))
t0 := aj.Tasks[0]
assert.Equal(t, "render-1-3", t0.Name)
assert.Equal(t, 1, len(t0.Commands))
assert.Equal(t, "blender-render", t0.Commands[0].Type)
assert.Equal(t, "{blender}", t0.Commands[0].Parameters["cmd"])
assert.Equal(t, settings["filepath"], t0.Commands[0].Parameters["filepath"])
assert.Equal(t, settings["format"], t0.Commands[0].Parameters["format"])
assert.Equal(t, "1-3", t0.Commands[0].Parameters["frames"])
assert.Equal(t, "/render/sf__intermediate-2006-01-02_090405/frames", t0.Commands[0].Parameters["render_output"])
}

View File

@ -21,16 +21,189 @@ package job_compilers
* ***** END GPL LICENSE BLOCK ***** */
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
"github.com/dop251/goja"
"github.com/rs/zerolog/log"
)
func jsPrint(call goja.FunctionCall) goja.Value {
// ----------------------------------------------------------
// Functions that start with `JS` are exposed to JavaScript.
// See newGojaVM() for the actual expose-as-globals code.
// ----------------------------------------------------------
func JSPrint(call goja.FunctionCall) goja.Value {
log.Info().Interface("args", call.Arguments).Msg("print")
return goja.Undefined()
}
func jsAlert(call goja.FunctionCall) goja.Value {
func JSAlert(call goja.FunctionCall) goja.Value {
log.Warn().Interface("args", call.Arguments).Msg("alert")
return goja.Undefined()
}
// JSFormatTimestampLocal returns the timestamp formatted as local time in a way that's compatible with filenames.
func JSFormatTimestampLocal(timestamp time.Time) string {
return timestamp.Local().Format("2006-01-02_150405")
}
type ErrInvalidRange struct {
Range string // The frame range that was invalid.
Message string // The error message
err error // Any wrapped error
}
func (e ErrInvalidRange) Error() string {
if e.err != nil {
return fmt.Sprintf("invalid range \"%v\": %s (%s)", e.Range, e.Message, e.Error())
}
return fmt.Sprintf("invalid range \"%v\": %s", e.Range, e.Message)
}
func (e ErrInvalidRange) Unwrap() error {
return e.err
}
func errInvalidRange(theRange, message string, errs ...error) error {
e := ErrInvalidRange{
Range: theRange,
Message: message,
}
for _, err := range errs {
if err != nil {
e.err = err
break
}
}
return e
}
const (
chunkRegular = "-"
chunkBlender = ".."
)
// JSFrameChunker takes a range like "1..10,20..25,40" and returns chunked ranges.
//
// The returned ranges will be at most `chunkSize` frames long.
//
// Supports "regular" and "blender" notation, resp. "A-Z" and "A..Z". Returned
// chunks will always be in "regular" notation because they're more compatible
// with embedding in filenames.
func JSFrameChunker(frameRange string, chunkSize int) ([]string, error) {
frameRange = strings.TrimSpace(frameRange)
if len(frameRange) == 0 {
return nil, errInvalidRange(frameRange, "empty range")
}
if chunkSize < 1 {
return nil, fmt.Errorf("invalid chunk size, must be positive number: %d", chunkSize)
}
frames, err := frameRangeExplode(frameRange)
if err != nil {
return nil, err
}
if len(frames) == 0 {
return nil, errInvalidRange(frameRange, "empty range")
}
min := func(a, b int) int {
if a < b {
return a
}
return b
}
var i int
chunks := make([]string, 0)
for i = 0; i < len(frames); i += chunkSize {
chunkFrames := frames[i:min(i+chunkSize, len(frames))]
chunkRange := frameRangeMerge(chunkFrames)
chunks = append(chunks, chunkRange)
}
return chunks, nil
}
// Given a range of frames, return an array containing each frame number.
func frameRangeExplode(frameRange string) ([]int, error) {
// Store as map to avoid duplicate frames.
frames := make(map[int]struct{}, 0)
// Convert from "blender" to "regular" range notation.
frameRange = strings.ReplaceAll(frameRange, chunkBlender, chunkRegular)
// parseInt first trims whitespace before converting to integer.
parseInt := func(s string) (int64, error) {
return strconv.ParseInt(strings.TrimSpace(s), 10, 64)
}
// Explode each comma-separated frame range.
for _, part := range strings.Split(frameRange, ",") {
startEnd := strings.Split(part, chunkRegular)
switch len(startEnd) {
case 1: // Single frame
frame, err := parseInt(startEnd[0])
if err != nil {
return nil, errInvalidRange(frameRange, part, err)
}
frames[int(frame)] = struct{}{}
case 2: // Frame range A-B
startFrame, startErr := parseInt(startEnd[0])
endFrame, endErr := parseInt(startEnd[1])
if startErr != nil || endErr != nil {
return nil, errInvalidRange(frameRange, part, startErr, endErr)
}
for frame := startFrame; frame <= endFrame; frame++ {
frames[int(frame)] = struct{}{}
}
default:
return nil, errInvalidRange(frameRange, part)
}
}
// Convert from map to sorted array.
frameList := make([]int, 0, len(frames))
for frame := range frames {
frameList = append(frameList, frame)
}
sort.Ints(frameList)
return frameList, nil
}
// frameRangeMerge merges consecutive frames into ranges like "3..8,13,15..17".
func frameRangeMerge(frames []int) string {
startFrame := frames[0]
prevFrame := frames[0]
ranges := make([]string, 0)
appendRange := func(fromFrame, toFrame int) {
switch {
case fromFrame == toFrame: // Last range was one frame only
ranges = append(ranges, strconv.FormatInt(int64(fromFrame), 10))
case fromFrame+1 == toFrame: // Last range was only two frames
ranges = append(ranges, strconv.FormatInt(int64(fromFrame), 10))
ranges = append(ranges, strconv.FormatInt(int64(toFrame), 10))
default:
ranges = append(ranges, fmt.Sprintf("%v%s%v", fromFrame, chunkRegular, toFrame))
}
}
var currentFrame int
for _, currentFrame = range frames {
if currentFrame > prevFrame+1 {
// This frame starts a new range, so append the one we now know ended.
appendRange(startFrame, prevFrame)
startFrame = currentFrame
}
prevFrame = currentFrame
}
appendRange(startFrame, currentFrame)
return strings.Join(ranges, ",")
}

View File

@ -0,0 +1,70 @@
package job_compilers
/* ***** BEGIN GPL LICENSE BLOCK *****
*
* Original Code Copyright (C) 2022 Blender Foundation.
*
* This file is part of Flamenco.
*
* Flamenco is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* Flamenco is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Flamenco. If not, see <https://www.gnu.org/licenses/>.
*
* ***** END GPL LICENSE BLOCK ***** */
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFrameChunkerHappyBlenderStyle(t *testing.T) {
chunks, err := JSFrameChunker("1..10,20..25,40,3..8", 4)
assert.Nil(t, err)
assert.Equal(t, []string{"1-4", "5-8", "9,10,20,21", "22-25", "40"}, chunks)
}
func TestFrameChunkerHappySmallInput(t *testing.T) {
// No frames, should be an error
chunks, err := JSFrameChunker(" ", 4)
assert.ErrorIs(t, err, ErrInvalidRange{Message: "empty range"})
// Just one frame.
chunks, err = JSFrameChunker("47", 4)
assert.Nil(t, err)
assert.Equal(t, []string{"47"}, chunks)
// Just one range of exactly one chunk.
chunks, err = JSFrameChunker("1-3", 3)
assert.Nil(t, err)
assert.Equal(t, []string{"1-3"}, chunks)
}
func TestFrameChunkerHappyRegularStyle(t *testing.T) {
chunks, err := JSFrameChunker("1-10,20-25,40", 4)
assert.Nil(t, err)
assert.Equal(t, []string{"1-4", "5-8", "9,10,20,21", "22-25", "40"}, chunks)
}
func TestFrameChunkerHappyExtraWhitespace(t *testing.T) {
chunks, err := JSFrameChunker(" 1 .. 10,\t20..25\n,40 ", 4)
assert.Nil(t, err)
assert.Equal(t, []string{"1-4", "5-8", "9,10,20,21", "22-25", "40"}, chunks)
}
func TestFrameRangeExplode(t *testing.T) {
frames, err := frameRangeExplode("1..10,20..25,40")
assert.Nil(t, err)
assert.Equal(t, []int{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
20, 21, 22, 23, 24, 25, 40,
}, frames)
}

View File

@ -96,9 +96,11 @@ func (s *Service) newGojaVM() *goja.Runtime {
}
}
// Set some global functions for script debugging purposes.
mustSet("print", jsPrint)
mustSet("alert", jsAlert)
// Set some global functions.
mustSet("print", JSPrint)
mustSet("alert", JSAlert)
mustSet("frameChunker", JSFrameChunker)
mustSet("formatTimestampLocal", JSFormatTimestampLocal)
// Pre-import some useful modules.
s.registry.Enable(vm)

View File

@ -81,20 +81,14 @@ function compileJob(job) {
// Determine the intermediate render output path.
function intermediatePath(job, finalDir) {
const basename = path.basename(finalDir);
const name = `${basename}__intermediate-${job.created}`;
const name = `${basename}__intermediate-${formatTimestampLocal(job.created)}`;
return path.join(path.dirname(finalDir), name);
}
function frameChunker(frames, callback) {
// TODO: actually implement.
callback("1-10");
callback("11-20");
callback("21-30");
}
function authorRenderTasks(settings, renderDir, renderOutput) {
let renderTasks = [];
frameChunker(settings.frames, function(chunk) {
let chunks = frameChunker(settings.frames, settings.chunk_size);
for (let chunk of chunks) {
const task = author.Task(`render-${chunk}`);
const command = author.Command("blender-render", {
cmd: settings.blender_cmd,
@ -105,7 +99,7 @@ function authorRenderTasks(settings, renderDir, renderOutput) {
});
task.addCommand(command);
renderTasks.push(task);
});
}
return renderTasks;
}

View File

@ -114,10 +114,7 @@ func (db *DB) FetchJob(ctx context.Context, jobID string) (*api.Job, error) {
return nil, err
}
// TODO: make settings and metadata an explicit type.
settings := make(map[string]interface{})
metadata := make(map[string]interface{})
var settings api.JobSettings
rows, err := db.sqldb.QueryContext(ctx, "SELECT key, value FROM job_settings WHERE job_id=?", jobID)
if err != nil {
return nil, err
@ -128,7 +125,7 @@ func (db *DB) FetchJob(ctx context.Context, jobID string) (*api.Job, error) {
if err := rows.Scan(&key, &value); err != nil {
return nil, err
}
settings[key] = value
settings.AdditionalProperties[key] = value
}
if err := rows.Close(); err != nil {
return nil, err
@ -137,6 +134,7 @@ func (db *DB) FetchJob(ctx context.Context, jobID string) (*api.Job, error) {
return nil, err
}
var metadata api.JobMetadata
rows, err = db.sqldb.QueryContext(ctx, "SELECT key, value FROM job_metadata WHERE job_id=?", jobID)
if err != nil {
return nil, err
@ -147,7 +145,7 @@ func (db *DB) FetchJob(ctx context.Context, jobID string) (*api.Job, error) {
if err := rows.Scan(&key, &value); err != nil {
return nil, err
}
metadata[key] = value
metadata.AdditionalProperties[key] = value
}
if err := rows.Close(); err != nil {
return nil, err

View File

@ -261,12 +261,9 @@ components:
properties:
"name": {type: string}
"type": {type: string}
"status": {$ref: "#/components/schemas/JobStatus"}
"priority": {type: integer, default: 50}
"settings":
type: object
"metadata":
type: object
"settings": {$ref: "#/components/schemas/JobSettings"}
"metadata": {$ref: "#/components/schemas/JobMetadata"}
required: [name, type, priority]
example:
type: "simple-blender-render"
@ -286,7 +283,7 @@ components:
metadata:
"user.name": "Sybren Stüvel"
"user.email": "sybren@blender.org"
project: "Sprite Fright"
"project": "Sprite Fright"
Job:
allOf:
- $ref: '#/components/schemas/SubmittedJob'
@ -303,8 +300,22 @@ components:
type: string
format: date-time
description: Creation timestamp
required: [id, created, updated]
status: {$ref: "#/components/schemas/JobStatus"}
required: [id, created, updated, status]
JobSettings:
type: object
additionalProperties: true
JobMetadata:
type: object
description: Arbitrary metadata strings. More complex structures can be modeled by using `a.b.c` notation for the key.
additionalProperties:
type: string
example:
"user.name": Sybren Stüvel
"user.email": sybren@blender.org
"project": "Sprite Fright"
Error:
type: object

View File

@ -18,48 +18,50 @@ import (
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"H4sIAAAAAAAC/7xZ3Y4btxV+FYIp0AQdSWtveqOrOnHsrJEmi2gNX9gLLWd4pOEuhxyTHMmqISAP0Tdp",
"A/SiueoLbN6oOCTnTzPaXbf+gbEYDcnzf75zDuc9zXRRagXKWTp/T22WQ8H84xNrxVoBv2D2Bn9zsJkR",
"pRNa0XlvlQhLGHH4xCwRDn8byEBsgJN0R1wO5JU2N2CmNKGl0SUYJ8BzyXRRMMX9s3BQ+Ic/GFjROf1i",
"1go3i5LNvg0H6D6hblcCnVNmDNvhb8Hx8Eqbgjk6p1UlOG12WWeEWuO2a53ivrH3y9IIbYTbdTYI5WAN",
"pt4R3o4cV6wYX7ibpnXMVfdqjWZehJ2oOLM3xwWpLJiRhX1CDbythAFO56+ptw2aIh6ICjQCdeQ+ME3H",
"Dl1RktaXl43VdXoNmUOpnmyYkCyV8EKnC3AOZRpE1UKotQRiwzrRK8LIC50SpGZHgifXIguPfTqvclBk",
"LTagEiJFIZyPwQ2TguPfCixxGt9ZIJHIlPyk5I5UFmUkW+FyEiznmSPvJjwHFj8MRA4rVkk3lOsiBxIX",
"gxzE5nqrojAEHUG2KDsHB6YQyvPPha1NMkXywIVDKQP9yGrFpIVkaAeXg0H6TEq9JXj0kCZhK4d7ciDX",
"OiU5syQFUMRWaSGcAz4lr3QlORFFKXeEg4RwTEoC74QNBJm9sWSlTSB9rdOEMMURB3RRCol7hJu+UW1G",
"plpLYAo1uoHd0FhnHJQTKwEm0m0CIyFFZR1JgVRKvK2Cu4RqVKg9NnBUmwAfYDlRFMAFcyB3xADGM2Ge",
"DYeVUAIPJBiqXnFkmXh5dOXCq5IZJ7JKMtN48YgZbJXWWX0XGIyk0iKebILxgylcxOMbYcVhbDlT3WUg",
"jOF+REVfvDwLKYzGqqPJkC+luAHCyDcSFAdDGOcTrb6akgU4JHflHXIVEiFUE6YIQqZRTDY8XM4csq4k",
"V3/0wdDkEijuc8mOG/oACTH44qYHAtei9dMBflXpBFdCOIRgrH1Ovq2MAeXkjmhEGlbT9dHdwRo7JVff",
"P1l8/93T5bOzH75bnj+5+P4q1FguDGROmx0pmcvJn8jVGzr7wv97Q68IK0s0KQ9qg6oK1G8lJCxxP00o",
"F6Z+9K8j5ufM5sCX7c7LkeQ5FjRDlIsW6GjfydgAsMySs6fnAc13Xm0MmhgSU/KjJgqsA46GqTJXGbDk",
"Sw+wNiFcZMiKGQH2K8IMEFuVpTbuUPUofIIF9/QxKi01czTxsXCvkuPa1fWo5Rl6HGHJX5liazAB+YTz",
"qc8KhPKR4iVZCvLDOolozIc3S2NFd1CvDtIhhkQQr8PzvtxAa42U4h+EdXUw+Og+brehjepG43/T+KKH",
"iEfUbVmMKVh3mwO14gIxUBqwKAJhxIb2JfZBHoneQVY5uK8LfpDHD4Qbd9ud7vrOGO1bw8MenEOvc66z",
"ZditFmAtW8P97aWn2e4fk+ZF6MOZlD+t6Pz13X5d1M0IntonAxUMMAdjfsIFoRVxogDrWFEiCtSKcuZg",
"gitjzYIYIffy5dnTGtxf+Ob53nGjKvlHFm2sk68N0PK73F8GIy+aCaOGRZY5sfEdO1MZSH8MbS7BxWcV",
"YFdoNVkxEXY0DyWrrH94W0HlH5jJcpz2mseAuoH8BIX1YB6J9F7450ClQuyfdJnThG6Z71gnK20mWJ/s",
"KGz/DGthHRjgIcWGQc44N2DH+/cHDo6SWbf0tutPcx3kFtnN8TlQMoc8xhNdr9yWmSMo0HhwuFSD6bKZ",
"xPpgec+wMhZLjRZJY7XuWFirkdAsdDSeNT20T0enI2KOgcICsgqnzCNQ9WD8uQt4elgyWt/bzr6dghDO",
"n0lWgMo0Iji8Y5gxQSrHOHMsyuv5zOmiNMIBeWbEOndxzJ5CwYREsXepAfWXNDY82qzrHSF+6MJvIAv3",
"n39vQLZtAT29/Tv5/ZfbX29/u/3n7a+//3L7r9vfbv/Rndfnfz7pV47IZZkVnM7p+/hzjx7MK3WztOJv",
"QOenqJMzLHNLVnGh674fs843hXM6M/7kzK5m1zpFdAYFjx6fTj3JLnyd//gcf5aWzh9/ndAV9kGWzumj",
"yaMT7McKtga71Ga5ERw0Fhn/hiZUV66sXOhF4Z0DZYNfpqXHlCDBMuzqixSYNEJ1gt3i6AqTqPgkHAl3",
"M/3oav04iJoH3e80IxO6YOSy53g5f+hNUAvonVnv7lyIuRxvaRpxx1Kjc8/0AfWiqQwNlGPqt5XjIXWg",
"KSql0RlYbKVGkT7ge8B7w0LOHqLE/wHDkBlwnwNpI6ceno6y6ODx0GNBZI+YCwyQINLW22jJKkzaQQtj",
"cY4uACdKbGPCZnL2NCEls3arDa+XgozhIo0wV281HeMjEvrQ9BM2syJrK2fuXEn3KKNQKx06TeVY5tqW",
"l9aISi6AoQkqI+NJO5/NVjXeCj0LF2tdTX4O9wfPmClIEUYI8uT8DCuRyEBZ6PB5fv7D5nRAf7vdTteq",
"QvidxTN2ti7l5HR6MgU1zV3hodcJJ3vSRnY0oRswEaAeTU+mJ7hbl6BYKRCr/SsMbpd7z8xYKTx0+qDV",
"1psCQ9cb84yHO4RCuNBcxgT5RvNdbT5Q/gwrSykyf2p2bUMSBIi4D0D6nfR+YFU/3+pY92g3aLEc+Ci2",
"pUZLIafHJyefVbItwwEyy8CuKil3JNwuAidCOU2E4mIjeMVkuJCcHtzGfhQxQ2syIp9fIHXn4XOzKgpm",
"do1XCSMKtn4WXmnTtBP1ANyZGP31JcNK4UdUSy975F7UN2oWg4+A4qUWynl9mxibNRi1hpFAew6uGds/",
"oVeHdwQjpms2tfcEBwZ8Do7IwV2CH7NzEObgquUO07WsGvNft58YevZ7f63TpeD7oyZ8Bi7LQ6q2/P0s",
"K1CreNMWISgQG2RU0rHjPSPI/vIT+umOpPPw3XeH19wvEJaGq27vuwfEbTikeATRAiWvzR4qzMzEcW6y",
"bae5UbCs57449X0axBxpOUYMFXZhCtfSf1bwHEzAIyIqDC9Jahk+KzhWCt6VkOEgBXFPNzBq8SNCbmt/",
"1rEUX1yOHAouQVxoT9rDiHLxK/aRmpvlwCsJF2GQ/XRY2P2mPmIk/zW9WwT2CX188vWwiftRxy9u/a8I",
"/ja1vmTcJ/Trk9OPV517k/mI8Odg6nr0FJQA3mtPPSr2GtPXl4hnrTd/Sh0TKgaA61vivkjwhrPRi6Zb",
"D70IZlPjcuj/ZhRZR4qHtv0G7Yn/vUn9dySUZA3OF4p6SCQpk6lkPXy3/mryoLSdn/WLffCPp5npoqgU",
"+iN+cj7sCKYt+aj3/nL/3wAAAP//aU95WLUhAAA=",
"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",
"///WNQTcViMAAA==",
}
// GetSwagger returns the content of the embedded swagger specification file

View File

@ -4,6 +4,8 @@
package api
import (
"encoding/json"
"fmt"
"time"
)
@ -168,11 +170,22 @@ type Job struct {
// UUID of the Job
Id string `json:"id"`
Status JobStatus `json:"status"`
// Creation timestamp
Updated time.Time `json:"updated"`
}
// Arbitrary metadata strings. More complex structures can be modeled by using `a.b.c` notation for the key.
type JobMetadata struct {
AdditionalProperties map[string]string `json:"-"`
}
// JobSettings defines model for JobSettings.
type JobSettings struct {
AdditionalProperties map[string]interface{} `json:"-"`
}
// JobStatus defines model for JobStatus.
type JobStatus string
@ -195,11 +208,11 @@ type SecurityError struct {
// Job definition submitted to Flamenco.
type SubmittedJob struct {
Metadata *map[string]interface{} `json:"metadata,omitempty"`
// Arbitrary metadata strings. More complex structures can be modeled by using `a.b.c` notation for the key.
Metadata *JobMetadata `json:"metadata,omitempty"`
Name string `json:"name"`
Priority int `json:"priority"`
Settings *map[string]interface{} `json:"settings,omitempty"`
Status *JobStatus `json:"status,omitempty"`
Settings *JobSettings `json:"settings,omitempty"`
Type string `json:"type"`
}
@ -225,3 +238,109 @@ type SubmitJobJSONRequestBody SubmitJobJSONBody
// RegisterWorkerJSONRequestBody defines body for RegisterWorker for application/json ContentType.
type RegisterWorkerJSONRequestBody RegisterWorkerJSONBody
// Getter for additional properties for JobMetadata. Returns the specified
// element and whether it was found
func (a JobMetadata) Get(fieldName string) (value string, found bool) {
if a.AdditionalProperties != nil {
value, found = a.AdditionalProperties[fieldName]
}
return
}
// Setter for additional properties for JobMetadata
func (a *JobMetadata) Set(fieldName string, value string) {
if a.AdditionalProperties == nil {
a.AdditionalProperties = make(map[string]string)
}
a.AdditionalProperties[fieldName] = value
}
// Override default JSON handling for JobMetadata to handle AdditionalProperties
func (a *JobMetadata) UnmarshalJSON(b []byte) error {
object := make(map[string]json.RawMessage)
err := json.Unmarshal(b, &object)
if err != nil {
return err
}
if len(object) != 0 {
a.AdditionalProperties = make(map[string]string)
for fieldName, fieldBuf := range object {
var fieldVal string
err := json.Unmarshal(fieldBuf, &fieldVal)
if err != nil {
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
}
a.AdditionalProperties[fieldName] = fieldVal
}
}
return nil
}
// Override default JSON handling for JobMetadata to handle AdditionalProperties
func (a JobMetadata) MarshalJSON() ([]byte, error) {
var err error
object := make(map[string]json.RawMessage)
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
if err != nil {
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
}
}
return json.Marshal(object)
}
// Getter for additional properties for JobSettings. Returns the specified
// element and whether it was found
func (a JobSettings) Get(fieldName string) (value interface{}, found bool) {
if a.AdditionalProperties != nil {
value, found = a.AdditionalProperties[fieldName]
}
return
}
// Setter for additional properties for JobSettings
func (a *JobSettings) Set(fieldName string, value interface{}) {
if a.AdditionalProperties == nil {
a.AdditionalProperties = make(map[string]interface{})
}
a.AdditionalProperties[fieldName] = value
}
// Override default JSON handling for JobSettings to handle AdditionalProperties
func (a *JobSettings) UnmarshalJSON(b []byte) error {
object := make(map[string]json.RawMessage)
err := json.Unmarshal(b, &object)
if err != nil {
return err
}
if len(object) != 0 {
a.AdditionalProperties = make(map[string]interface{})
for fieldName, fieldBuf := range object {
var fieldVal interface{}
err := json.Unmarshal(fieldBuf, &fieldVal)
if err != nil {
return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err)
}
a.AdditionalProperties[fieldName] = fieldVal
}
}
return nil
}
// Override default JSON handling for JobSettings to handle AdditionalProperties
func (a JobSettings) MarshalJSON() ([]byte, error) {
var err error
object := make(map[string]json.RawMessage)
for fieldName, field := range a.AdditionalProperties {
object[fieldName], err = json.Marshal(field)
if err != nil {
return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err)
}
}
return json.Marshal(object)
}