diff --git a/cmd/flamenco-manager-poc/main.go b/cmd/flamenco-manager-poc/main.go
index 28083f68..6d7dabb2 100644
--- a/cmd/flamenco-manager-poc/main.go
+++ b/cmd/flamenco-manager-poc/main.go
@@ -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")
}
diff --git a/go.mod b/go.mod
index c8861490..3f0669ac 100644
--- a/go.mod
+++ b/go.mod
@@ -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
)
diff --git a/go.sum b/go.sum
index 7e946199..800547e9 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
diff --git a/internal/manager/job_compilers/job_compilers.go b/internal/manager/job_compilers/job_compilers.go
index 0ce38fdb..94f91c6b 100644
--- a/internal/manager/job_compilers/job_compilers.go
+++ b/internal/manager/job_compilers/job_compilers.go
@@ -39,8 +39,9 @@ var ErrScriptIncomplete = errors.New("job compiler script incomplete")
// Service contains job compilers defined in JavaScript.
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.
+ 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{},
+ 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
}
}
diff --git a/internal/manager/job_compilers/job_compilers_test.go b/internal/manager/job_compilers/job_compilers_test.go
new file mode 100644
index 00000000..db8cfa90
--- /dev/null
+++ b/internal/manager/job_compilers/job_compilers_test.go
@@ -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 .
+ *
+ * ***** 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"])
+}
diff --git a/internal/manager/job_compilers/js_globals.go b/internal/manager/job_compilers/js_globals.go
index 834425d2..60653eff 100644
--- a/internal/manager/job_compilers/js_globals.go
+++ b/internal/manager/job_compilers/js_globals.go
@@ -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, ",")
+}
diff --git a/internal/manager/job_compilers/js_globals_test.go b/internal/manager/job_compilers/js_globals_test.go
new file mode 100644
index 00000000..a235ddd5
--- /dev/null
+++ b/internal/manager/job_compilers/js_globals_test.go
@@ -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 .
+ *
+ * ***** 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)
+}
diff --git a/internal/manager/job_compilers/scripts.go b/internal/manager/job_compilers/scripts.go
index b16bc374..4b467c39 100644
--- a/internal/manager/job_compilers/scripts.go
+++ b/internal/manager/job_compilers/scripts.go
@@ -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)
diff --git a/internal/manager/job_compilers/scripts/simple_blender_render.js b/internal/manager/job_compilers/scripts/simple_blender_render.js
index 6c9d60be..3c118b4f 100644
--- a/internal/manager/job_compilers/scripts/simple_blender_render.js
+++ b/internal/manager/job_compilers/scripts/simple_blender_render.js
@@ -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;
}
diff --git a/internal/manager/persistence/db.go b/internal/manager/persistence/db.go
index 885044c3..b4f36a39 100644
--- a/internal/manager/persistence/db.go
+++ b/internal/manager/persistence/db.go
@@ -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
diff --git a/pkg/api/flamenco-manager.yaml b/pkg/api/flamenco-manager.yaml
index 4dfbd330..de6ec446 100644
--- a/pkg/api/flamenco-manager.yaml
+++ b/pkg/api/flamenco-manager.yaml
@@ -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
diff --git a/pkg/api/openapi_spec.gen.go b/pkg/api/openapi_spec.gen.go
index 593e2680..7d4de50d 100644
--- a/pkg/api/openapi_spec.gen.go
+++ b/pkg/api/openapi_spec.gen.go
@@ -18,48 +18,50 @@ import (
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
- "+iN+cj7sCKYt+aj3/nL/3wAAAP//aU95WLUhAAA=",
+ "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",
+ "///WNQTcViMAAA==",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/pkg/api/openapi_types.gen.go b/pkg/api/openapi_types.gen.go
index c47e1da4..53c8eb02 100644
--- a/pkg/api/openapi_types.gen.go
+++ b/pkg/api/openapi_types.gen.go
@@ -4,6 +4,8 @@
package api
import (
+ "encoding/json"
+ "fmt"
"time"
)
@@ -167,12 +169,23 @@ type Job struct {
Created time.Time `json:"created"`
// UUID of the Job
- Id string `json:"id"`
+ 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,12 +208,12 @@ type SecurityError struct {
// Job definition submitted to Flamenco.
type SubmittedJob struct {
- Metadata *map[string]interface{} `json:"metadata,omitempty"`
- Name string `json:"name"`
- Priority int `json:"priority"`
- Settings *map[string]interface{} `json:"settings,omitempty"`
- Status *JobStatus `json:"status,omitempty"`
- Type string `json:"type"`
+ // 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 *JobSettings `json:"settings,omitempty"`
+ Type string `json:"type"`
}
// TaskStatus defines model for TaskStatus.
@@ -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)
+}